File size: 20,543 Bytes
1ba22b4 4b286f3 1ba22b4 4b286f3 82502f1 4b286f3 1ba22b4 82502f1 1ba22b4 4b286f3 1ba22b4 4b286f3 149da6f 73c77f1 3b6c4d9 73c77f1 82502f1 4b286f3 82502f1 4b286f3 73c77f1 7563e81 73c77f1 a8e9c78 82502f1 4b286f3 82502f1 4b286f3 149da6f 4b286f3 1ba22b4 4b286f3 149da6f 3b6c4d9 82502f1 4b286f3 82502f1 4b286f3 3b6c4d9 82502f1 4b286f3 149da6f 4b286f3 1ba22b4 3df3aa7 a8e9c78 82502f1 3aacece 713a4fb 3aacece 82502f1 713a4fb a8e9c78 713a4fb a8e9c78 713a4fb a8e9c78 713a4fb a8e9c78 3df3aa7 713a4fb 82502f1 73c77f1 a8e9c78 73c77f1 3b6c4d9 3df3aa7 73c77f1 a8e9c78 3df3aa7 82502f1 1ba22b4 3df3aa7 3b6c4d9 4b286f3 82502f1 3b6c4d9 3df3aa7 3b6c4d9 fd3511e 3b6c4d9 fd3511e 3b6c4d9 3df3aa7 3b6c4d9 4b286f3 3b6c4d9 3df3aa7 3b6c4d9 3df3aa7 4b286f3 73c77f1 4d17237 3b6c4d9 4d17237 7df958b 73c77f1 1ba22b4 a8e9c78 7563e81 82502f1 4b286f3 7563e81 4b286f3 a8e9c78 4b286f3 1ba22b4 3b6c4d9 7563e81 82502f1 4b286f3 3b6c4d9 4b286f3 3b6c4d9 4b286f3 1ba22b4 a8e9c78 7563e81 4b286f3 a8e9c78 82502f1 4b286f3 82502f1 4b286f3 82502f1 4b286f3 82502f1 4b286f3 82502f1 4b286f3 1ba22b4 3b6c4d9 7563e81 149da6f 3b6c4d9 149da6f 4b286f3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 |
from enum import Enum
from pathlib import Path
class DemoType(Enum):
GRADIO = 1
STREAMLIT = 2
gradio_lite_html_template = Path('templates/gradio-lite/gradio-lite-template.html').read_text()
stlite_html_template = Path('templates/stlite/stlite-template.html').read_text()
gradio_lite_snippet_template = Path('templates/gradio-lite/gradio-lite-snippet-template.html').read_text()
stlite_snippet_template = Path('templates/stlite/stlite-snippet-template.html').read_text()
def starting_app_code(demo_type: DemoType) -> str:
if demo_type == DemoType.GRADIO:
return Path('templates/gradio-lite/gradio_lite_starting_code.py').read_text().replace('`', r'\`')
elif demo_type == DemoType.STREAMLIT:
return Path('templates/stlite/stlite_starting_code.py').read_text().replace('`', r'\`')
raise NotImplementedError(f'{demo_type} is not a supported demo type')
def load_js(demo_type: DemoType) -> str:
if demo_type == DemoType.GRADIO:
return f"""() => {{
if (window.gradioLiteLoaded) {{
return
}}
// Get the query string from the URL
const queryString = window.location.search;
// Use a function to parse the query string into an object
function parseQueryString(queryString) {{
const params = {{}};
const queryStringWithoutQuestionMark = queryString.substring(1); // Remove the leading question mark
const keyValuePairs = queryStringWithoutQuestionMark.split('&');
keyValuePairs.forEach(keyValue => {{
const [key, value] = keyValue.split('=');
if (value) {{
params[key] = decodeURIComponent(value.replace(/\+/g, ' '));
}}
}});
return params;
}}
// Parse the query string into an object
const queryParams = parseQueryString(queryString);
// Access individual parameters
const typeValue = queryParams.type;
let codeValue = null;
let requirementsValue = null;
if (typeValue === 'gradio') {{
codeValue = queryParams.code;
requirementsValue = queryParams.requirements;
}}
const htmlString = '<iframe id="gradio-iframe" width="100%" height="512px" src="about:blank"></iframe>';
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/html');
const iframe = doc.getElementById('gradio-iframe');
const div = document.getElementById('gradioDemoDiv');
div.appendChild(iframe);
let template = `{gradio_lite_html_template.replace('STARTING_CODE', starting_app_code(demo_type))}`;
if (codeValue) {{
template = `{gradio_lite_html_template}`.replace('STARTING_CODE', codeValue.replaceAll(String.fromCharCode(92), String.fromCharCode(92) + String.fromCharCode(92)).replaceAll('`', String.fromCharCode(92) + '`'));
}}
template = template.replace('STARTING_REQUIREMENTS', requirementsValue || '');
const frame = document.getElementById('gradio-iframe');
frame.contentWindow.document.open('text/html', 'replace');
frame.contentWindow.document.write(template);
frame.contentWindow.document.close();
window.gradioLiteLoaded = true;
}}"""
elif demo_type == DemoType.STREAMLIT:
return f"""() => {{
if (window.stliteLoaded) {{
return
}}
// Get the query string from the URL
const queryString = window.location.search;
// Use a function to parse the query string into an object
function parseQueryString(queryString) {{
const params = {{}};
const queryStringWithoutQuestionMark = queryString.substring(1); // Remove the leading question mark
const keyValuePairs = queryStringWithoutQuestionMark.split('&');
keyValuePairs.forEach(keyValue => {{
const [key, value] = keyValue.split('=');
if (value) {{
params[key] = decodeURIComponent(value.replace(/\+/g, ' '));
}}
}});
return params;
}}
// Parse the query string into an object
const queryParams = parseQueryString(queryString);
// Access individual parameters
const typeValue = queryParams.type;
let codeValue = null;
let requirementsValue = null;
if (typeValue === 'streamlit') {{
codeValue = queryParams.code;
requirementsValue = queryParams.requirements;
}}
const htmlString = '<iframe id="stlite-iframe" width="100%" height="512px" src="about:blank"></iframe>';
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/html');
const iframe = doc.getElementById('stlite-iframe');
const div = document.getElementById('stliteDemoDiv');
div.appendChild(iframe);
let template = `{stlite_html_template.replace('STARTING_CODE', starting_app_code(demo_type))}`;
if (codeValue) {{
template = `{stlite_html_template}`.replace('STARTING_CODE', codeValue.replaceAll(String.fromCharCode(92), String.fromCharCode(92) + String.fromCharCode(92)).replaceAll('`', String.fromCharCode(92) + '`'));
}}
const formattedRequirements = (requirementsValue || '').split('\\n').filter(x => x && !x.startsWith('#')).map(x => x.trim());
template = template.replace('STARTING_REQUIREMENTS', formattedRequirements.map(x => `"${{x}}"`).join(', ') || '');
const frame = document.getElementById('stlite-iframe');
frame.contentWindow.document.open();
frame.contentWindow.document.write(template);
frame.contentWindow.document.close();
window.stliteLoaded = true;
}}"""
raise NotImplementedError(f'{demo_type} is not a supported demo type')
def update_iframe_js(demo_type: DemoType) -> str:
if demo_type == DemoType.GRADIO:
return f"""async (code, requirements, lastError, codeHistory, codeHistoryIndex) => {{
const formattedRequirements = requirements.split('\\n').filter(x => x && !x.startsWith('#')).map(x => x.trim());
let errorResult = null;
const attemptedRequirements = new Set();
const installedRequirements = [];
async function update() {{
// Remove existing stylesheet so it will be reloaded;
// see https://github.com/gradio-app/gradio/blob/200237d73c169f39514465efc163db756969d3ac/js/app/src/lite/css.ts#L41
const demoFrameWindow = document.getElementById('gradio-iframe').contentWindow;
const oldStyle = demoFrameWindow.document.querySelector("head style");
oldStyle.remove();
const appController = demoFrameWindow.window.appController;
const newCode = code + ` # Update tag ${{Math.random()}}`;
try {{
await appController.install(formattedRequirements);
await appController.run_code(newCode);
}}
catch (e) {{
// Replace old style if code error prevented new style from loading.
const newStyle = demoFrameWindow.document.querySelector("head style");
if (!newStyle) {{
demoFrameWindow.document.head.appendChild(oldStyle);
}}
// If the error is caused by a missing module try once to install it and update again.
if (e.toString().includes('ModuleNotFoundError')) {{
try {{
const guessedModuleName = e.toString().split("'")[1].replaceAll('_', '-');
if (attemptedRequirements.has(guessedModuleName)) {{
throw Error(`Could not install pyodide module ${{guessedModuleName}}`);
}}
console.log(`Attempting to install missing pyodide module "${{guessedModuleName}}"`);
attemptedRequirements.add(guessedModuleName);
await appController.install([guessedModuleName]);
installedRequirements.push(guessedModuleName);
return await update();
}}
catch (err) {{
console.log(err);
}}
}}
// Hide app so the error traceback is visible.
// First div in main is the error traceback, second is the app.
const appBody = demoFrameWindow.document.querySelectorAll("div.main > div")[1];
appBody.style.visibility = "hidden";
errorResult = e.toString();
const allRequirements = formattedRequirements.concat(installedRequirements);
return [code, allRequirements, errorResult, codeHistory, codeHistoryIndex];
}}
}};
await update();
const allRequirements = formattedRequirements.concat(installedRequirements);
// Update URL query params to include the current demo code state
const currentUrl = new URL(window.location.href);
currentUrl.searchParams.set('type', 'gradio');
if (requirements) {{
currentUrl.searchParams.set('requirements', allRequirements.join('\\n'));
}}
if (code) {{
currentUrl.searchParams.set('code', code);
}}
// Replace the current URL with the updated one
history.replaceState({{}}, '', currentUrl.href);
return [code, allRequirements, errorResult, codeHistory, codeHistoryIndex];
}}"""
elif demo_type == DemoType.STREAMLIT:
return f"""async (code, requirements, lastError, codeHistory, codeHistoryIndex) => {{
const formattedRequirements = (requirements || '').split('\\n').filter(x => x && !x.startsWith('#')).map(x => x.trim());
let errorResult = null;
const attemptedRequirements = new Set();
const installedRequirements = [];
async function update() {{
const appController = document.getElementById('stlite-iframe').contentWindow.window.appController;
try {{
if (formattedRequirements) {{
await appController.install(formattedRequirements);
}}
const newCode = code + ` # Update tag ${{Math.random()}}`;
const entrypointFile = "streamlit_app.py";
// As code rerun happens inside streamlit this won't throw an error for self-healing imports.
await appController.writeFile(entrypointFile, newCode);
// So instead wait 500 milliseconds to see if the streamlit error banner appeared with an error.
// TODO: Consider a way to make this not rely on streamlit refresh timing; otherwise user can just re-update and this will trigger.
await new Promise(r => setTimeout(r, 500));
const messageDiv = document.getElementById('stlite-iframe').contentWindow.document.querySelector('.message');
if (messageDiv) {{
throw Error(messageDiv.innerHTML);
}}
}}
catch (e) {{
// If the error is caused by a missing module try once to install it and update again.
if (e.toString().includes('ModuleNotFoundError')) {{
try {{
const guessedModuleName = e.toString().split("'")[1].replaceAll('_', '-');
if (attemptedRequirements.has(guessedModuleName)) {{
throw Error(`Could not install pyodide module ${{guessedModuleName}}`);
}}
console.log(`Attempting to install missing pyodide module "${{guessedModuleName}}"`);
attemptedRequirements.add(guessedModuleName);
await appController.install([guessedModuleName]);
installedRequirements.push(guessedModuleName);
return await update();
}}
catch (err) {{
console.log(err);
}}
}}
errorResult = e.toString();
const allRequirements = formattedRequirements.concat(installedRequirements);
return [code, allRequirements, errorResult, codeHistory, codeHistoryIndex];
}}
}};
await update();
const allRequirements = formattedRequirements.concat(installedRequirements);
// Update URL query params to include the current demo code state
const currentUrl = new URL(window.location.href);
currentUrl.searchParams.set('type', 'streamlit');
if (requirements) {{
currentUrl.searchParams.set('requirements', allRequirements.join('\\n'));
}}
if (code) {{
currentUrl.searchParams.set('code', code);
}}
// Replace the current URL with the updated one
history.replaceState({{}}, '', currentUrl.href);
return [code, allRequirements, errorResult, codeHistory, codeHistoryIndex];
}}"""
raise NotImplementedError(f'{demo_type} is not a supported demo type')
def copy_share_link_js(demo_type: DemoType) -> str:
if demo_type == DemoType.GRADIO:
return f"""async (code, requirements) => {{
const url = new URL(window.location.href);
url.searchParams.set('type', 'gradio');
url.searchParams.set('requirements', requirements);
url.searchParams.set('code', code);
// TODO: Figure out why link doesn't load as expected in Spaces.
const shareLink = url.toString().replace('gstaff-kitewind.hf.space', 'huggingface.co/spaces/gstaff/KiteWind');
await navigator.clipboard.writeText(shareLink);
return [code, requirements];
}}"""
if demo_type == DemoType.STREAMLIT:
return f"""async (code, requirements) => {{
const url = new URL(window.location.href);
url.searchParams.set('type', 'streamlit');
url.searchParams.set('requirements', requirements);
url.searchParams.set('code', code);
// TODO: Figure out why link doesn't load as expected in Spaces.
const shareLink = url.toString().replace('gstaff-kitewind.hf.space', 'huggingface.co/spaces/gstaff/KiteWind');
await navigator.clipboard.writeText(shareLink);
return [code, requirements];
}}"""
raise NotImplementedError(f'{demo_type} is not a supported demo type')
def copy_snippet_js(demo_type: DemoType) -> str:
if demo_type == DemoType.GRADIO:
return f"""async (code, requirements) => {{
const escapedCode = code.replaceAll(String.fromCharCode(92), String.fromCharCode(92) + String.fromCharCode(92) + String.fromCharCode(92) + String.fromCharCode(92)).replaceAll('`', String.fromCharCode(92) + '`');
const template = `{gradio_lite_snippet_template}`;
// Step 1: Generate the HTML content
const completedTemplate = template.replace('STARTING_CODE', escapedCode).replace('STARTING_REQUIREMENTS', requirements);
const snippet = completedTemplate;
await navigator.clipboard.writeText(snippet);
return [code, requirements];
}}"""
elif demo_type == DemoType.STREAMLIT:
return f"""async (code, requirements) => {{
const escapedCode = code.replaceAll(String.fromCharCode(92), String.fromCharCode(92) + String.fromCharCode(92) + String.fromCharCode(92)).replaceAll('`', String.fromCharCode(92) + '`');
const template = `{stlite_snippet_template}`;
// Step 1: Generate the HTML content
const formattedRequirements = (requirements || '').split('\\n').filter(x => x && !x.startsWith('#')).map(x => x.trim());
const completedTemplate = template.replace('STARTING_CODE', code).replace('STARTING_REQUIREMENTS', formattedRequirements.map(x => `"${{x}}"`).join(', ') || '');
const snippet = completedTemplate;
await navigator.clipboard.writeText(snippet);
return [code, requirements];
}}"""
raise NotImplementedError(f'{demo_type} is not a supported demo type')
def download_code_js(demo_type: DemoType) -> str:
if demo_type == demo_type.GRADIO:
return f"""(code, requirements) => {{
const escapedCode = code.replaceAll(String.fromCharCode(92), String.fromCharCode(92) + String.fromCharCode(92)).replaceAll('`', String.fromCharCode(92) + '`');
// Step 1: Generate the HTML content
const completedTemplate = `{gradio_lite_html_template}`.replace('STARTING_CODE', escapedCode).replace('STARTING_REQUIREMENTS', requirements);
// Step 2: Create a Blob from the HTML content
const blob = new Blob([completedTemplate], {{ type: "text/html" }});
// Step 3: Create a URL for the Blob
const url = URL.createObjectURL(blob);
// Step 4: Create a download link
const downloadLink = document.createElement("a");
downloadLink.href = url;
downloadLink.download = "gradio-lite-app.html"; // Specify the filename for the download
// Step 5: Trigger a click event on the download link
downloadLink.click();
// Clean up by revoking the URL
URL.revokeObjectURL(url);
}}"""
elif demo_type == demo_type.STREAMLIT:
return f"""(code, requirements) => {{
const escapedCode = code.replaceAll(String.fromCharCode(92), String.fromCharCode(92) + String.fromCharCode(92)).replaceAll('`', String.fromCharCode(92) + '`');
// Step 1: Generate the HTML content
const formattedRequirements = (requirements || '').split('\\n').filter(x => x && !x.startsWith('#')).map(x => x.trim());
const completedTemplate = `{stlite_html_template}`.replace('STARTING_CODE', escapedCode).replace('STARTING_REQUIREMENTS', formattedRequirements.map(x => `"${{x}}"`).join(', ') || '');
// Step 2: Create a Blob from the HTML content
const blob = new Blob([completedTemplate], {{ type: "text/html" }});
// Step 3: Create a URL for the Blob
const url = URL.createObjectURL(blob);
// Step 4: Create a download link
const downloadLink = document.createElement("a");
downloadLink.href = url;
downloadLink.download = "stlite-app.html"; // Specify the filename for the download
// Step 5: Trigger a click event on the download link
downloadLink.click();
// Clean up by revoking the URL
URL.revokeObjectURL(url);
}}"""
raise NotImplementedError(f'{demo_type} is not a supported demo type')
|