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,
|