Add requirements support, query param, copy link, and automatic tab selection for stlite.
Browse files- app.py +102 -93
- templates.py +108 -11
- templates/stlite/stlite-snippet-template.html +1 -1
- templates/stlite/stlite-template.html +1 -1
app.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import logging
|
| 2 |
import os
|
| 3 |
import re
|
|
|
|
| 4 |
import warnings
|
| 5 |
from pathlib import Path
|
| 6 |
|
|
@@ -98,9 +99,14 @@ def add_hotkeys() -> str:
|
|
| 98 |
return Path("hotkeys.js").read_text()
|
| 99 |
|
| 100 |
|
| 101 |
-
def apply_query_params(
|
| 102 |
params = dict(request.query_params)
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
|
| 106 |
def update_state(requirements: [str], error: str):
|
|
@@ -112,101 +118,104 @@ with gr.Blocks(title="KiteWind") as demo:
|
|
| 112 |
gr.Markdown(
|
| 113 |
'<h4 align="center">Chat-assisted web app creator by <a href="https://huggingface.co/gstaff">@gstaff</a></h4>')
|
| 114 |
selectedTab = gr.State(value='gradio-lite')
|
| 115 |
-
with gr.
|
| 116 |
-
with gr.
|
| 117 |
-
with gr.
|
| 118 |
-
gr.
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
with gr.
|
| 123 |
-
with gr.
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
with gr.
|
| 148 |
-
gr.
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
with gr.
|
| 157 |
-
gr.
|
| 158 |
-
|
| 159 |
-
gr.
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
with gr.
|
| 163 |
-
with gr.
|
| 164 |
-
gr.
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
with gr.
|
| 169 |
-
with gr.
|
| 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 |
gradio_lite_tab.select(lambda: "gradio-lite", None, selectedTab).then(None, None, None,
|
| 204 |
_js=load_js(DemoType.GRADIO))
|
| 205 |
stlite_tab.select(lambda: "stlite", None, selectedTab).then(None, None, None, _js=load_js(DemoType.STREAMLIT))
|
| 206 |
-
demo.load(None, None, None, _js=load_js(DemoType.GRADIO))
|
| 207 |
demo.load(None, None, None, _js=add_hotkeys())
|
| 208 |
-
|
| 209 |
-
demo.load(apply_query_params, gradio_code_area, [gradio_code_area, gradio_requirements_area])
|
| 210 |
demo.css = "footer {visibility: hidden}"
|
| 211 |
|
| 212 |
if __name__ == "__main__":
|
|
|
|
| 1 |
import logging
|
| 2 |
import os
|
| 3 |
import re
|
| 4 |
+
import typing
|
| 5 |
import warnings
|
| 6 |
from pathlib import Path
|
| 7 |
|
|
|
|
| 99 |
return Path("hotkeys.js").read_text()
|
| 100 |
|
| 101 |
|
| 102 |
+
def apply_query_params(gradio_code: str, stlite_code: str, request: gr.Request) -> (str, str, str, str, typing.Any):
|
| 103 |
params = dict(request.query_params)
|
| 104 |
+
demo_type = params.get('type')
|
| 105 |
+
if demo_type == 'gradio':
|
| 106 |
+
return params.get('code') or gradio_code, params.get('requirements') or '', stlite_code, '', gr.Tabs(selected=0)
|
| 107 |
+
if demo_type == 'streamlit':
|
| 108 |
+
return gradio_code, '', params.get('code') or stlite_code, params.get('requirements') or '', gr.Tabs(selected=1)
|
| 109 |
+
return gradio_code, '', stlite_code, '', gr.Tabs(selected=0)
|
| 110 |
|
| 111 |
|
| 112 |
def update_state(requirements: [str], error: str):
|
|
|
|
| 118 |
gr.Markdown(
|
| 119 |
'<h4 align="center">Chat-assisted web app creator by <a href="https://huggingface.co/gstaff">@gstaff</a></h4>')
|
| 120 |
selectedTab = gr.State(value='gradio-lite')
|
| 121 |
+
with gr.Tabs() as tabs:
|
| 122 |
+
with gr.Tab('Gradio (gradio-lite)', id=0) as gradio_lite_tab:
|
| 123 |
+
with gr.Row():
|
| 124 |
+
with gr.Column():
|
| 125 |
+
gr.Markdown("## 1. Run your app in the browser!")
|
| 126 |
+
gr.HTML(value='<div id="gradioDemoDiv"></div>')
|
| 127 |
+
gr.Markdown("## 2. Customize using voice requests!")
|
| 128 |
+
with gr.Row():
|
| 129 |
+
with gr.Column():
|
| 130 |
+
with gr.Group():
|
| 131 |
+
in_audio = gr.Audio(label="Record a voice request (click or press ctrl + ` to start/stop)",
|
| 132 |
+
source='microphone', type='filepath', elem_classes=["record-btn"])
|
| 133 |
+
in_prompt = gr.Textbox(label="Or type a text request and press Enter",
|
| 134 |
+
placeholder="Need an idea? Try one of these:\n- Add a button to reverse the name\n- Change the greeting to Spanish\n- Put the reversed name output into a separate textbox")
|
| 135 |
+
out_text = gr.TextArea(label="π€ Chat Assistant Response")
|
| 136 |
+
clear = gr.ClearButton([in_prompt, in_audio, out_text])
|
| 137 |
+
with gr.Column():
|
| 138 |
+
gradio_code_area = gr.Code(
|
| 139 |
+
label="App Code - You can also edit directly and then click Update App or ctrl + space",
|
| 140 |
+
language='python', value=starting_app_code(DemoType.GRADIO))
|
| 141 |
+
gradio_requirements_area = gr.Code(label="App Requirements (additional modules pip installed for pyodide)")
|
| 142 |
+
update_btn = gr.Button("Update App (Ctrl + Space)", variant="primary", elem_classes=["update-btn"])
|
| 143 |
+
last_error = gr.State()
|
| 144 |
+
code_update_params = {'fn': update_state, 'inputs': [gradio_code_area, gradio_requirements_area],
|
| 145 |
+
'outputs': [gradio_requirements_area, last_error],
|
| 146 |
+
'_js': update_iframe_js(DemoType.GRADIO)}
|
| 147 |
+
gen_text_params = {'fn': generate_text, 'inputs': [gradio_code_area, in_prompt],
|
| 148 |
+
'outputs': [out_text, gradio_code_area]}
|
| 149 |
+
transcribe_params = {'fn': transcribe, 'inputs': [in_audio], 'outputs': [in_prompt, in_audio]}
|
| 150 |
+
update_btn.click(**code_update_params)
|
| 151 |
+
in_prompt.submit(**gen_text_params).then(**code_update_params)
|
| 152 |
+
in_audio.stop_recording(**transcribe_params).then(**gen_text_params).then(**code_update_params)
|
| 153 |
+
with gr.Row():
|
| 154 |
+
with gr.Column():
|
| 155 |
+
gr.Markdown("## 3. Export your app to share!")
|
| 156 |
+
share_link_btn = gr.Button("π Copy share link to clipboard")
|
| 157 |
+
share_link_btn.click(link_copy_notify, [gradio_code_area, gradio_requirements_area], None, _js=copy_share_link_js(DemoType.GRADIO))
|
| 158 |
+
copy_snippet_btn = gr.Button("βοΈ Copy app snippet to paste into another page")
|
| 159 |
+
copy_snippet_btn.click(copy_notify, [gradio_code_area, gradio_requirements_area], None, _js=copy_snippet_js(DemoType.GRADIO))
|
| 160 |
+
download_btn = gr.Button("π Download app as a standalone file")
|
| 161 |
+
download_btn.click(None, [gradio_code_area, gradio_requirements_area], None, _js=download_code_js(DemoType.GRADIO))
|
| 162 |
+
with gr.Row():
|
| 163 |
+
with gr.Column():
|
| 164 |
+
gr.Markdown("## Current limitations")
|
| 165 |
+
with gr.Accordion("Click to view", open=False):
|
| 166 |
+
gr.Markdown(
|
| 167 |
+
"- Only gradio-lite apps using the libraries available in pyodide are supported\n- The chat hasn't been tuned on gradio library data; it may make mistakes")
|
| 168 |
+
with gr.Tab('Streamlit (stlite)', id=1) as stlite_tab:
|
| 169 |
+
with gr.Row():
|
| 170 |
+
with gr.Column():
|
| 171 |
+
gr.Markdown("## 1. Run your app in the browser!")
|
| 172 |
+
gr.HTML(value='<div id="stliteDemoDiv"></div>')
|
| 173 |
+
gr.Markdown("## 2. Customize using voice requests!")
|
| 174 |
+
with gr.Row():
|
| 175 |
+
with gr.Column():
|
| 176 |
+
with gr.Group():
|
| 177 |
+
in_audio = gr.Audio(label="Record a voice request (click or press ctrl + ` to start/stop)",
|
| 178 |
+
source='microphone', type='filepath', elem_classes=["record-btn"])
|
| 179 |
+
in_prompt = gr.Textbox(label="Or type a text request and press Enter",
|
| 180 |
+
placeholder="Need an idea? Try one of these:\n- Add a button to reverse the name\n- Change the greeting to Spanish\n- Change the theme to soft")
|
| 181 |
+
out_text = gr.TextArea(label="π€ Chat Assistant Response")
|
| 182 |
+
clear_btn = gr.ClearButton([in_prompt, in_audio, out_text])
|
| 183 |
+
with gr.Column():
|
| 184 |
+
stlite_code_area = gr.Code(
|
| 185 |
+
label="App Code - You can also edit directly and then click Update App or ctrl + space",
|
| 186 |
+
language='python', value=starting_app_code(DemoType.STREAMLIT))
|
| 187 |
+
stlite_requirements_area = gr.Code(label="App Requirements (additional modules pip installed for pyodide)")
|
| 188 |
+
update_btn = gr.Button("Update App (Ctrl + Space)", variant="primary", elem_classes=["update-btn"])
|
| 189 |
+
last_error = gr.State()
|
| 190 |
+
code_update_params = {'fn': None, 'inputs': [stlite_code_area, stlite_requirements_area], 'outputs': [stlite_requirements_area, last_error],
|
| 191 |
+
'_js': update_iframe_js(DemoType.STREAMLIT)}
|
| 192 |
+
gen_text_params = {'fn': generate_text, 'inputs': [stlite_code_area, in_prompt],
|
| 193 |
+
'outputs': [out_text, stlite_code_area]}
|
| 194 |
+
transcribe_params = {'fn': transcribe, 'inputs': [in_audio], 'outputs': [in_prompt, in_audio]}
|
| 195 |
+
update_btn.click(**code_update_params)
|
| 196 |
+
in_prompt.submit(**gen_text_params).then(**code_update_params)
|
| 197 |
+
in_audio.stop_recording(**transcribe_params).then(**gen_text_params).then(**code_update_params)
|
| 198 |
+
with gr.Row():
|
| 199 |
+
with gr.Column():
|
| 200 |
+
gr.Markdown("## 3. Export your app to share!")
|
| 201 |
+
share_link_btn = gr.Button("π Copy share link to clipboard")
|
| 202 |
+
share_link_btn.click(link_copy_notify, [stlite_code_area, stlite_requirements_area], None,
|
| 203 |
+
_js=copy_share_link_js(DemoType.STREAMLIT))
|
| 204 |
+
copy_snippet_btn = gr.Button("βοΈ Copy app snippet into paste in another page")
|
| 205 |
+
copy_snippet_btn.click(copy_notify, [stlite_code_area, stlite_requirements_area], None, _js=copy_snippet_js(DemoType.STREAMLIT))
|
| 206 |
+
download_btn = gr.Button("π Download app as a standalone file")
|
| 207 |
+
download_btn.click(None, [stlite_code_area, stlite_requirements_area], None, _js=download_code_js(DemoType.STREAMLIT))
|
| 208 |
+
with gr.Row():
|
| 209 |
+
with gr.Column():
|
| 210 |
+
gr.Markdown("## Current limitations")
|
| 211 |
+
with gr.Accordion("Click to view", open=False):
|
| 212 |
+
gr.Markdown(
|
| 213 |
+
"- Only Streamlit apps using libraries available in pyodide are supported\n- The chat hasn't been tuned on Streamlit library data; it may make mistakes")
|
| 214 |
gradio_lite_tab.select(lambda: "gradio-lite", None, selectedTab).then(None, None, None,
|
| 215 |
_js=load_js(DemoType.GRADIO))
|
| 216 |
stlite_tab.select(lambda: "stlite", None, selectedTab).then(None, None, None, _js=load_js(DemoType.STREAMLIT))
|
|
|
|
| 217 |
demo.load(None, None, None, _js=add_hotkeys())
|
| 218 |
+
demo.load(apply_query_params, [gradio_code_area, stlite_code_area], [gradio_code_area, gradio_requirements_area, stlite_code_area, stlite_requirements_area, tabs])
|
|
|
|
| 219 |
demo.css = "footer {visibility: hidden}"
|
| 220 |
|
| 221 |
if __name__ == "__main__":
|
templates.py
CHANGED
|
@@ -48,8 +48,13 @@ def load_js(demo_type: DemoType) -> str:
|
|
| 48 |
// Parse the query string into an object
|
| 49 |
const queryParams = parseQueryString(queryString);
|
| 50 |
// Access individual parameters
|
| 51 |
-
const
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
const htmlString = '<iframe id="gradio-iframe" width="100%" height="512px" src="about:blank"></iframe>';
|
| 55 |
const parser = new DOMParser();
|
|
@@ -74,6 +79,35 @@ def load_js(demo_type: DemoType) -> str:
|
|
| 74 |
if (window.stliteLoaded) {{
|
| 75 |
return
|
| 76 |
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
const htmlString = '<iframe id="stlite-iframe" width="100%" height="512px" src="about:blank"></iframe>';
|
| 78 |
const parser = new DOMParser();
|
| 79 |
const doc = parser.parseFromString(htmlString, 'text/html');
|
|
@@ -81,7 +115,12 @@ def load_js(demo_type: DemoType) -> str:
|
|
| 81 |
const div = document.getElementById('stliteDemoDiv');
|
| 82 |
div.appendChild(iframe);
|
| 83 |
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
const frame = document.getElementById('stlite-iframe');
|
| 86 |
frame.contentWindow.document.open();
|
| 87 |
frame.contentWindow.document.write(template);
|
|
@@ -150,6 +189,7 @@ def update_iframe_js(demo_type: DemoType) -> str:
|
|
| 150 |
const allRequirements = formattedRequirements.concat(installedRequirements);
|
| 151 |
// Update URL query params to include the current demo code state
|
| 152 |
const currentUrl = new URL(window.location.href);
|
|
|
|
| 153 |
currentUrl.searchParams.set('requirements', allRequirements.join('\\n'));
|
| 154 |
currentUrl.searchParams.set('code', code);
|
| 155 |
// Replace the current URL with the updated one
|
|
@@ -158,14 +198,56 @@ def update_iframe_js(demo_type: DemoType) -> str:
|
|
| 158 |
return [allRequirements, errorResult];
|
| 159 |
}}"""
|
| 160 |
elif demo_type == DemoType.STREAMLIT:
|
| 161 |
-
return f"""async (code, requirements) => {{
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
async function update() {{
|
| 163 |
const appController = document.getElementById('stlite-iframe').contentWindow.window.appController;
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
}};
|
| 168 |
await update();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
}}"""
|
| 170 |
raise NotImplementedError(f'{demo_type} is not a supported demo type')
|
| 171 |
|
|
@@ -174,6 +256,18 @@ def copy_share_link_js(demo_type: DemoType) -> str:
|
|
| 174 |
if demo_type == DemoType.GRADIO:
|
| 175 |
return f"""async (code, requirements) => {{
|
| 176 |
const url = new URL(window.location.href);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
url.searchParams.set('requirements', requirements);
|
| 178 |
url.searchParams.set('code', code);
|
| 179 |
// TODO: Figure out why link doesn't load as expected in Spaces.
|
|
@@ -196,14 +290,16 @@ def copy_snippet_js(demo_type: DemoType) -> str:
|
|
| 196 |
return [code, requirements];
|
| 197 |
}}"""
|
| 198 |
elif demo_type == DemoType.STREAMLIT:
|
| 199 |
-
return f"""async (code) => {{
|
| 200 |
const escapedCode = code.replaceAll(String.fromCharCode(92), String.fromCharCode(92) + String.fromCharCode(92) + String.fromCharCode(92)).replaceAll('`', String.fromCharCode(92) + '`');
|
| 201 |
const template = `{stlite_snippet_template}`;
|
| 202 |
// Step 1: Generate the HTML content
|
| 203 |
-
const
|
|
|
|
| 204 |
|
| 205 |
const snippet = completedTemplate;
|
| 206 |
await navigator.clipboard.writeText(snippet);
|
|
|
|
| 207 |
}}"""
|
| 208 |
raise NotImplementedError(f'{demo_type} is not a supported demo type')
|
| 209 |
|
|
@@ -233,10 +329,11 @@ def download_code_js(demo_type: DemoType) -> str:
|
|
| 233 |
URL.revokeObjectURL(url);
|
| 234 |
}}"""
|
| 235 |
elif demo_type == demo_type.STREAMLIT:
|
| 236 |
-
return f"""(code) => {{
|
| 237 |
const escapedCode = code.replaceAll(String.fromCharCode(92), String.fromCharCode(92) + String.fromCharCode(92)).replaceAll('`', String.fromCharCode(92) + '`');
|
| 238 |
// Step 1: Generate the HTML content
|
| 239 |
-
const
|
|
|
|
| 240 |
|
| 241 |
// Step 2: Create a Blob from the HTML content
|
| 242 |
const blob = new Blob([completedTemplate], {{ type: "text/html" }});
|
|
|
|
| 48 |
// Parse the query string into an object
|
| 49 |
const queryParams = parseQueryString(queryString);
|
| 50 |
// Access individual parameters
|
| 51 |
+
const typeValue = queryParams.type;
|
| 52 |
+
let codeValue = null;
|
| 53 |
+
let requirementsValue = null;
|
| 54 |
+
if (typeValue === 'gradio') {{
|
| 55 |
+
codeValue = queryParams.code;
|
| 56 |
+
requirementsValue = queryParams.requirements;
|
| 57 |
+
}}
|
| 58 |
|
| 59 |
const htmlString = '<iframe id="gradio-iframe" width="100%" height="512px" src="about:blank"></iframe>';
|
| 60 |
const parser = new DOMParser();
|
|
|
|
| 79 |
if (window.stliteLoaded) {{
|
| 80 |
return
|
| 81 |
}}
|
| 82 |
+
|
| 83 |
+
// Get the query string from the URL
|
| 84 |
+
const queryString = window.location.search;
|
| 85 |
+
// Use a function to parse the query string into an object
|
| 86 |
+
function parseQueryString(queryString) {{
|
| 87 |
+
const params = {{}};
|
| 88 |
+
const queryStringWithoutQuestionMark = queryString.substring(1); // Remove the leading question mark
|
| 89 |
+
const keyValuePairs = queryStringWithoutQuestionMark.split('&');
|
| 90 |
+
|
| 91 |
+
keyValuePairs.forEach(keyValue => {{
|
| 92 |
+
const [key, value] = keyValue.split('=');
|
| 93 |
+
if (value) {{
|
| 94 |
+
params[key] = decodeURIComponent(value.replace(/\+/g, ' '));
|
| 95 |
+
}}
|
| 96 |
+
}});
|
| 97 |
+
|
| 98 |
+
return params;
|
| 99 |
+
}}
|
| 100 |
+
// Parse the query string into an object
|
| 101 |
+
const queryParams = parseQueryString(queryString);
|
| 102 |
+
// Access individual parameters
|
| 103 |
+
const typeValue = queryParams.type;
|
| 104 |
+
let codeValue = null;
|
| 105 |
+
let requirementsValue = null;
|
| 106 |
+
if (typeValue === 'streamlit') {{
|
| 107 |
+
codeValue = queryParams.code;
|
| 108 |
+
requirementsValue = queryParams.requirements;
|
| 109 |
+
}}
|
| 110 |
+
|
| 111 |
const htmlString = '<iframe id="stlite-iframe" width="100%" height="512px" src="about:blank"></iframe>';
|
| 112 |
const parser = new DOMParser();
|
| 113 |
const doc = parser.parseFromString(htmlString, 'text/html');
|
|
|
|
| 115 |
const div = document.getElementById('stliteDemoDiv');
|
| 116 |
div.appendChild(iframe);
|
| 117 |
|
| 118 |
+
let template = `{stlite_html_template.replace('STARTING_CODE', starting_app_code(demo_type))}`;
|
| 119 |
+
if (codeValue) {{
|
| 120 |
+
template = `{stlite_html_template}`.replace('STARTING_CODE', codeValue.replaceAll(String.fromCharCode(92), String.fromCharCode(92) + String.fromCharCode(92)).replaceAll('`', String.fromCharCode(92) + '`'));
|
| 121 |
+
}}
|
| 122 |
+
const formattedRequirements = (requirementsValue || '').split('\\n').filter(x => x && !x.startsWith('#')).map(x => x.trim());
|
| 123 |
+
template = template.replace('STARTING_REQUIREMENTS', formattedRequirements.map(x => `"${{x}}"`).join(', ') || '');
|
| 124 |
const frame = document.getElementById('stlite-iframe');
|
| 125 |
frame.contentWindow.document.open();
|
| 126 |
frame.contentWindow.document.write(template);
|
|
|
|
| 189 |
const allRequirements = formattedRequirements.concat(installedRequirements);
|
| 190 |
// Update URL query params to include the current demo code state
|
| 191 |
const currentUrl = new URL(window.location.href);
|
| 192 |
+
currentUrl.searchParams.set('type', 'gradio');
|
| 193 |
currentUrl.searchParams.set('requirements', allRequirements.join('\\n'));
|
| 194 |
currentUrl.searchParams.set('code', code);
|
| 195 |
// Replace the current URL with the updated one
|
|
|
|
| 198 |
return [allRequirements, errorResult];
|
| 199 |
}}"""
|
| 200 |
elif demo_type == DemoType.STREAMLIT:
|
| 201 |
+
return f"""async (code, requirements) => {{
|
| 202 |
+
const formattedRequirements = requirements.split('\\n').filter(x => x && !x.startsWith('#')).map(x => x.trim());
|
| 203 |
+
let errorResult = null;
|
| 204 |
+
const attemptedRequirements = new Set();
|
| 205 |
+
const installedRequirements = [];
|
| 206 |
async function update() {{
|
| 207 |
const appController = document.getElementById('stlite-iframe').contentWindow.window.appController;
|
| 208 |
+
try {{
|
| 209 |
+
await appController.install(formattedRequirements);
|
| 210 |
+
const newCode = code + ` # Update tag ${{Math.random()}}`;
|
| 211 |
+
const entrypointFile = "streamlit_app.py";
|
| 212 |
+
// TODO: As code rerun happens inside streamlit this won't throw an error for self-healing imports
|
| 213 |
+
await appController.writeFile(entrypointFile, newCode);
|
| 214 |
+
}}
|
| 215 |
+
catch (e) {{
|
| 216 |
+
// If the error is caused by a missing module try once to install it and update again.
|
| 217 |
+
if (e.toString().includes('ModuleNotFoundError')) {{
|
| 218 |
+
try {{
|
| 219 |
+
const guessedModuleName = e.toString().split("'")[1].replaceAll('_', '-');
|
| 220 |
+
if (attemptedRequirements.has(guessedModuleName)) {{
|
| 221 |
+
throw Error(`Could not install pyodide module ${{guessedModuleName}}`);
|
| 222 |
+
}}
|
| 223 |
+
console.log(`Attempting to install missing pyodide module "${{guessedModuleName}}"`);
|
| 224 |
+
attemptedRequirements.add(guessedModuleName);
|
| 225 |
+
await appController.install([guessedModuleName]);
|
| 226 |
+
installedRequirements.push(guessedModuleName);
|
| 227 |
+
return await update();
|
| 228 |
+
}}
|
| 229 |
+
catch (err) {{
|
| 230 |
+
console.log(err);
|
| 231 |
+
}}
|
| 232 |
+
}}
|
| 233 |
+
|
| 234 |
+
errorResult = e.toString();
|
| 235 |
+
const allRequirements = formattedRequirements.concat(installedRequirements);
|
| 236 |
+
return [allRequirements, errorResult];
|
| 237 |
+
}}
|
| 238 |
}};
|
| 239 |
await update();
|
| 240 |
+
|
| 241 |
+
const allRequirements = formattedRequirements.concat(installedRequirements);
|
| 242 |
+
// Update URL query params to include the current demo code state
|
| 243 |
+
const currentUrl = new URL(window.location.href);
|
| 244 |
+
currentUrl.searchParams.set('type', 'streamlit');
|
| 245 |
+
currentUrl.searchParams.set('requirements', allRequirements.join('\\n'));
|
| 246 |
+
currentUrl.searchParams.set('code', code);
|
| 247 |
+
// Replace the current URL with the updated one
|
| 248 |
+
history.replaceState({{}}, '', currentUrl.href);
|
| 249 |
+
|
| 250 |
+
return [allRequirements.join('\\n'), errorResult];
|
| 251 |
}}"""
|
| 252 |
raise NotImplementedError(f'{demo_type} is not a supported demo type')
|
| 253 |
|
|
|
|
| 256 |
if demo_type == DemoType.GRADIO:
|
| 257 |
return f"""async (code, requirements) => {{
|
| 258 |
const url = new URL(window.location.href);
|
| 259 |
+
url.searchParams.set('type', 'gradio');
|
| 260 |
+
url.searchParams.set('requirements', requirements);
|
| 261 |
+
url.searchParams.set('code', code);
|
| 262 |
+
// TODO: Figure out why link doesn't load as expected in Spaces.
|
| 263 |
+
const shareLink = url.toString().replace('gstaff-kitewind.hf.space', 'huggingface.co/spaces/gstaff/KiteWind');
|
| 264 |
+
await navigator.clipboard.writeText(shareLink);
|
| 265 |
+
return [code, requirements];
|
| 266 |
+
}}"""
|
| 267 |
+
if demo_type == DemoType.STREAMLIT:
|
| 268 |
+
return f"""async (code, requirements) => {{
|
| 269 |
+
const url = new URL(window.location.href);
|
| 270 |
+
url.searchParams.set('type', 'streamlit');
|
| 271 |
url.searchParams.set('requirements', requirements);
|
| 272 |
url.searchParams.set('code', code);
|
| 273 |
// TODO: Figure out why link doesn't load as expected in Spaces.
|
|
|
|
| 290 |
return [code, requirements];
|
| 291 |
}}"""
|
| 292 |
elif demo_type == DemoType.STREAMLIT:
|
| 293 |
+
return f"""async (code, requirements) => {{
|
| 294 |
const escapedCode = code.replaceAll(String.fromCharCode(92), String.fromCharCode(92) + String.fromCharCode(92) + String.fromCharCode(92)).replaceAll('`', String.fromCharCode(92) + '`');
|
| 295 |
const template = `{stlite_snippet_template}`;
|
| 296 |
// Step 1: Generate the HTML content
|
| 297 |
+
const formattedRequirements = (requirements || '').split('\\n').filter(x => x && !x.startsWith('#')).map(x => x.trim());
|
| 298 |
+
const completedTemplate = template.replace('STARTING_CODE', code).replace('STARTING_REQUIREMENTS', formattedRequirements.map(x => `"${{x}}"`).join(', ') || '');
|
| 299 |
|
| 300 |
const snippet = completedTemplate;
|
| 301 |
await navigator.clipboard.writeText(snippet);
|
| 302 |
+
return [code, requirements];
|
| 303 |
}}"""
|
| 304 |
raise NotImplementedError(f'{demo_type} is not a supported demo type')
|
| 305 |
|
|
|
|
| 329 |
URL.revokeObjectURL(url);
|
| 330 |
}}"""
|
| 331 |
elif demo_type == demo_type.STREAMLIT:
|
| 332 |
+
return f"""(code, requirements) => {{
|
| 333 |
const escapedCode = code.replaceAll(String.fromCharCode(92), String.fromCharCode(92) + String.fromCharCode(92)).replaceAll('`', String.fromCharCode(92) + '`');
|
| 334 |
// Step 1: Generate the HTML content
|
| 335 |
+
const formattedRequirements = (requirements || '').split('\\n').filter(x => x && !x.startsWith('#')).map(x => x.trim());
|
| 336 |
+
const completedTemplate = `{stlite_html_template}`.replace('STARTING_CODE', escapedCode).replace('STARTING_REQUIREMENTS', formattedRequirements.map(x => `"${{x}}"`).join(', ') || '');
|
| 337 |
|
| 338 |
// Step 2: Create a Blob from the HTML content
|
| 339 |
const blob = new Blob([completedTemplate], {{ type: "text/html" }});
|
templates/stlite/stlite-snippet-template.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
| 9 |
const streamlitConfig = "[server]\\\\nrunOnSave = true";
|
| 10 |
const code = \\\`STARTING_CODE\\\`;
|
| 11 |
const appController = stlite.mount({
|
| 12 |
-
requirements: [
|
| 13 |
entrypoint: "streamlit_app.py", // The target file of the streamlit run command
|
| 14 |
files: {
|
| 15 |
".streamlit/config.toml": streamlitConfig,
|
|
|
|
| 9 |
const streamlitConfig = "[server]\\\\nrunOnSave = true";
|
| 10 |
const code = \\\`STARTING_CODE\\\`;
|
| 11 |
const appController = stlite.mount({
|
| 12 |
+
requirements: [STARTING_REQUIREMENTS], // Packages to install
|
| 13 |
entrypoint: "streamlit_app.py", // The target file of the streamlit run command
|
| 14 |
files: {
|
| 15 |
".streamlit/config.toml": streamlitConfig,
|
templates/stlite/stlite-template.html
CHANGED
|
@@ -16,7 +16,7 @@
|
|
| 16 |
const code = \`STARTING_CODE\`;
|
| 17 |
// Mount options defined here: https://github.com/whitphx/stlite/blob/main/packages/mountable/src/options.ts#L7
|
| 18 |
const appController = stlite.mount({
|
| 19 |
-
requirements: [
|
| 20 |
entrypoint: "streamlit_app.py", // The target file of the streamlit run command
|
| 21 |
files: {
|
| 22 |
".streamlit/config.toml": streamlitConfig,
|
|
|
|
| 16 |
const code = \`STARTING_CODE\`;
|
| 17 |
// Mount options defined here: https://github.com/whitphx/stlite/blob/main/packages/mountable/src/options.ts#L7
|
| 18 |
const appController = stlite.mount({
|
| 19 |
+
requirements: [STARTING_REQUIREMENTS], // Packages to install
|
| 20 |
entrypoint: "streamlit_app.py", // The target file of the streamlit run command
|
| 21 |
files: {
|
| 22 |
".streamlit/config.toml": streamlitConfig,
|