Add undo and redo buttons for gradio and streamlit. Also rename components to eliminate overlapping names between gradio and streamlit tabs.
Browse files- app.py +118 -55
- templates.py +22 -13
app.py
CHANGED
@@ -99,18 +99,41 @@ def add_hotkeys() -> str:
|
|
99 |
return Path("hotkeys.js").read_text()
|
100 |
|
101 |
|
102 |
-
def apply_query_params(gradio_code: str, stlite_code: str, request: gr.Request) -> (
|
|
|
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 '',
|
|
|
107 |
if demo_type == 'streamlit':
|
108 |
-
return gradio_code, '', params.get('code') or stlite_code, params.get('requirements') or '',
|
109 |
-
|
|
|
110 |
|
111 |
|
112 |
-
def update_state(requirements: [str], error: str)
|
113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
|
115 |
|
116 |
with gr.Blocks(title="KiteWind") as demo:
|
@@ -128,37 +151,56 @@ with gr.Blocks(title="KiteWind") as demo:
|
|
128 |
with gr.Row():
|
129 |
with gr.Column():
|
130 |
with gr.Group():
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
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(
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
153 |
with gr.Row():
|
154 |
with gr.Column():
|
155 |
gr.Markdown("## 3. Export your app to share!")
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
|
|
|
|
|
|
162 |
with gr.Row():
|
163 |
with gr.Column():
|
164 |
gr.Markdown("## Current limitations")
|
@@ -174,37 +216,56 @@ with gr.Blocks(title="KiteWind") as demo:
|
|
174 |
with gr.Row():
|
175 |
with gr.Column():
|
176 |
with gr.Group():
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
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(
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
198 |
with gr.Row():
|
199 |
with gr.Column():
|
200 |
gr.Markdown("## 3. Export your app to share!")
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
|
|
|
|
208 |
with gr.Row():
|
209 |
with gr.Column():
|
210 |
gr.Markdown("## Current limitations")
|
@@ -215,7 +276,9 @@ with gr.Blocks(title="KiteWind") as demo:
|
|
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],
|
|
|
|
|
219 |
demo.css = "footer {visibility: hidden}"
|
220 |
|
221 |
if __name__ == "__main__":
|
|
|
99 |
return Path("hotkeys.js").read_text()
|
100 |
|
101 |
|
102 |
+
def apply_query_params(gradio_code: str, stlite_code: str, request: gr.Request) -> (
|
103 |
+
str, str, [str], str, str, [str], typing.Any):
|
104 |
params = dict(request.query_params)
|
105 |
demo_type = params.get('type')
|
106 |
if demo_type == 'gradio':
|
107 |
+
return params.get('code') or gradio_code, params.get('requirements') or '', [
|
108 |
+
params.get('code') or gradio_code], stlite_code, '', [stlite_code_area], gr.Tabs(selected=0)
|
109 |
if demo_type == 'streamlit':
|
110 |
+
return gradio_code, '', [gradio_code], params.get('code') or stlite_code, params.get('requirements') or '', [
|
111 |
+
params.get('code') or stlite_code], gr.Tabs(selected=1)
|
112 |
+
return gradio_code, '', [gradio_code], stlite_code, '', [stlite_code], gr.Tabs(selected=0)
|
113 |
|
114 |
|
115 |
+
def update_state(code: str, requirements: [str], error: str, history: [str], current_index: int) -> (
|
116 |
+
str, str, [str], int):
|
117 |
+
# Only modify undo history if new code was added.
|
118 |
+
if history[current_index] != code:
|
119 |
+
history = history[:current_index + 1]
|
120 |
+
history.append(code)
|
121 |
+
current_index = len(history) - 1
|
122 |
+
return '\n'.join(sorted(requirements)), error, history, current_index
|
123 |
+
|
124 |
+
|
125 |
+
def undo(code: str, history: [str], current_index: int) -> (str, int):
|
126 |
+
if current_index > 0:
|
127 |
+
current_index -= 1
|
128 |
+
return history[current_index], current_index
|
129 |
+
return code, current_index
|
130 |
+
|
131 |
+
|
132 |
+
def redo(code: str, history: [str], current_index: int) -> (str, int):
|
133 |
+
if current_index < len(history) - 1:
|
134 |
+
current_index += 1
|
135 |
+
return history[current_index], current_index
|
136 |
+
return code, current_index
|
137 |
|
138 |
|
139 |
with gr.Blocks(title="KiteWind") as demo:
|
|
|
151 |
with gr.Row():
|
152 |
with gr.Column():
|
153 |
with gr.Group():
|
154 |
+
gradio_audio = gr.Audio(label="Record a voice request (click or press ctrl + ` to start/stop)",
|
155 |
+
source='microphone', type='filepath', elem_classes=["record-btn"])
|
156 |
+
gradio_prompt = gr.Textbox(label="Or type a text request and press Enter",
|
157 |
+
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")
|
158 |
+
gradio_bot_text = gr.TextArea(label="π€ Chat Assistant Response")
|
159 |
+
gradio_clear = gr.ClearButton([gradio_prompt, gradio_audio, gradio_bot_text])
|
160 |
with gr.Column():
|
161 |
gradio_code_area = gr.Code(
|
162 |
label="App Code - You can also edit directly and then click Update App or ctrl + space",
|
163 |
language='python', value=starting_app_code(DemoType.GRADIO))
|
164 |
+
gradio_requirements_area = gr.Code(
|
165 |
+
label="App Requirements (additional modules pip installed for pyodide)")
|
166 |
+
with gr.Group():
|
167 |
+
gradio_update_btn = gr.Button("Update App (Ctrl + Space)", variant="primary",
|
168 |
+
elem_classes=["update-btn"])
|
169 |
+
gradio_undo_btn = gr.Button("Undo")
|
170 |
+
gradio_redo_btn = gr.Button("Redo")
|
171 |
+
gradio_error = gr.State()
|
172 |
+
gradio_history = gr.State(value=[])
|
173 |
+
gradio_index = gr.State(value=0)
|
174 |
+
gradio_code_update_params = {'fn': update_state,
|
175 |
+
'inputs': [gradio_code_area, gradio_requirements_area, gradio_error,
|
176 |
+
gradio_history, gradio_index],
|
177 |
+
'outputs': [gradio_requirements_area, gradio_error, gradio_history,
|
178 |
+
gradio_index],
|
179 |
+
'_js': update_iframe_js(DemoType.GRADIO)}
|
180 |
+
gradio_gen_text_params = {'fn': generate_text, 'inputs': [gradio_code_area, gradio_prompt],
|
181 |
+
'outputs': [gradio_bot_text, gradio_code_area]}
|
182 |
+
gradio_transcribe_params = {'fn': transcribe, 'inputs': [gradio_audio],
|
183 |
+
'outputs': [gradio_prompt, gradio_audio]}
|
184 |
+
gradio_update_btn.click(**gradio_code_update_params)
|
185 |
+
gradio_undo_btn.click(undo, [gradio_code_area, gradio_history, gradio_index],
|
186 |
+
[gradio_code_area, gradio_index]).then(**gradio_code_update_params)
|
187 |
+
gradio_redo_btn.click(redo, [gradio_code_area, gradio_history, gradio_index],
|
188 |
+
[gradio_code_area, gradio_index]).then(**gradio_code_update_params)
|
189 |
+
gradio_prompt.submit(**gradio_gen_text_params).then(**gradio_code_update_params)
|
190 |
+
gradio_audio.stop_recording(**gradio_transcribe_params).then(**gradio_gen_text_params).then(
|
191 |
+
**gradio_code_update_params)
|
192 |
with gr.Row():
|
193 |
with gr.Column():
|
194 |
gr.Markdown("## 3. Export your app to share!")
|
195 |
+
gradio_share_link_btn = gr.Button("π Copy share link to clipboard")
|
196 |
+
gradio_share_link_btn.click(link_copy_notify, [gradio_code_area, gradio_requirements_area], None,
|
197 |
+
_js=copy_share_link_js(DemoType.GRADIO))
|
198 |
+
gradio_copy_snippet_btn = gr.Button("βοΈ Copy app snippet to paste into another page")
|
199 |
+
gradio_copy_snippet_btn.click(copy_notify, [gradio_code_area, gradio_requirements_area], None,
|
200 |
+
_js=copy_snippet_js(DemoType.GRADIO))
|
201 |
+
gradio_download_btn = gr.Button("π Download app as a standalone file")
|
202 |
+
gradio_download_btn.click(None, [gradio_code_area, gradio_requirements_area], None,
|
203 |
+
_js=download_code_js(DemoType.GRADIO))
|
204 |
with gr.Row():
|
205 |
with gr.Column():
|
206 |
gr.Markdown("## Current limitations")
|
|
|
216 |
with gr.Row():
|
217 |
with gr.Column():
|
218 |
with gr.Group():
|
219 |
+
stlite_audio = gr.Audio(label="Record a voice request (click or press ctrl + ` to start/stop)",
|
220 |
+
source='microphone', type='filepath', elem_classes=["record-btn"])
|
221 |
+
stlite_prompt = gr.Textbox(label="Or type a text request and press Enter",
|
222 |
+
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")
|
223 |
+
stlite_bot_text = gr.TextArea(label="π€ Chat Assistant Response")
|
224 |
+
stlite_clear_btn = gr.ClearButton([stlite_prompt, stlite_audio, stlite_bot_text])
|
225 |
with gr.Column():
|
226 |
stlite_code_area = gr.Code(
|
227 |
label="App Code - You can also edit directly and then click Update App or ctrl + space",
|
228 |
language='python', value=starting_app_code(DemoType.STREAMLIT))
|
229 |
+
stlite_requirements_area = gr.Code(
|
230 |
+
label="App Requirements (additional modules pip installed for pyodide)")
|
231 |
+
with gr.Group():
|
232 |
+
stlite_update_btn = gr.Button("Update App (Ctrl + Space)", variant="primary",
|
233 |
+
elem_classes=["update-btn"])
|
234 |
+
stlite_undo_btn = gr.Button("Undo")
|
235 |
+
stlite_redo_btn = gr.Button("Redo")
|
236 |
+
stlite_error = gr.State()
|
237 |
+
stlite_history = gr.State(value=[])
|
238 |
+
stlite_index = gr.State(value=0)
|
239 |
+
stlite_code_update_params = {'fn': update_state,
|
240 |
+
'inputs': [stlite_code_area, stlite_requirements_area, stlite_error,
|
241 |
+
stlite_history, stlite_index],
|
242 |
+
'outputs': [stlite_requirements_area, stlite_error, stlite_history,
|
243 |
+
stlite_index],
|
244 |
+
'_js': update_iframe_js(DemoType.STREAMLIT)}
|
245 |
+
stlite_gen_text_params = {'fn': generate_text, 'inputs': [stlite_code_area, stlite_prompt],
|
246 |
+
'outputs': [stlite_bot_text, stlite_code_area]}
|
247 |
+
stlite_transcribe_params = {'fn': transcribe, 'inputs': [stlite_audio],
|
248 |
+
'outputs': [stlite_prompt, stlite_audio]}
|
249 |
+
stlite_update_btn.click(**stlite_code_update_params)
|
250 |
+
stlite_undo_btn.click(undo, [stlite_code_area, stlite_history, stlite_index],
|
251 |
+
[stlite_code_area, stlite_index]).then(**stlite_code_update_params)
|
252 |
+
stlite_redo_btn.click(redo, [stlite_code_area, stlite_history, stlite_index],
|
253 |
+
[stlite_code_area, stlite_index]).then(**stlite_code_update_params)
|
254 |
+
stlite_prompt.submit(**stlite_gen_text_params).then(**stlite_code_update_params)
|
255 |
+
stlite_audio.stop_recording(**stlite_transcribe_params).then(**stlite_gen_text_params).then(
|
256 |
+
**stlite_code_update_params)
|
257 |
with gr.Row():
|
258 |
with gr.Column():
|
259 |
gr.Markdown("## 3. Export your app to share!")
|
260 |
+
stlite_share_link_btn = gr.Button("π Copy share link to clipboard")
|
261 |
+
stlite_share_link_btn.click(link_copy_notify, [stlite_code_area, stlite_requirements_area], None,
|
262 |
+
_js=copy_share_link_js(DemoType.STREAMLIT))
|
263 |
+
stlite_copy_snippet_btn = gr.Button("βοΈ Copy app snippet into paste in another page")
|
264 |
+
stlite_copy_snippet_btn.click(copy_notify, [stlite_code_area, stlite_requirements_area], None,
|
265 |
+
_js=copy_snippet_js(DemoType.STREAMLIT))
|
266 |
+
stlite_download_btn = gr.Button("π Download app as a standalone file")
|
267 |
+
stlite_download_btn.click(None, [stlite_code_area, stlite_requirements_area], None,
|
268 |
+
_js=download_code_js(DemoType.STREAMLIT))
|
269 |
with gr.Row():
|
270 |
with gr.Column():
|
271 |
gr.Markdown("## Current limitations")
|
|
|
276 |
_js=load_js(DemoType.GRADIO))
|
277 |
stlite_tab.select(lambda: "stlite", None, selectedTab).then(None, None, None, _js=load_js(DemoType.STREAMLIT))
|
278 |
demo.load(None, None, None, _js=add_hotkeys())
|
279 |
+
demo.load(apply_query_params, [gradio_code_area, stlite_code_area],
|
280 |
+
[gradio_code_area, gradio_requirements_area, gradio_history, stlite_code_area, stlite_requirements_area,
|
281 |
+
stlite_history, tabs])
|
282 |
demo.css = "footer {visibility: hidden}"
|
283 |
|
284 |
if __name__ == "__main__":
|
templates.py
CHANGED
@@ -132,7 +132,7 @@ def load_js(demo_type: DemoType) -> str:
|
|
132 |
|
133 |
def update_iframe_js(demo_type: DemoType) -> str:
|
134 |
if demo_type == DemoType.GRADIO:
|
135 |
-
return f"""async (code, requirements) => {{
|
136 |
const formattedRequirements = requirements.split('\\n').filter(x => x && !x.startsWith('#')).map(x => x.trim());
|
137 |
let errorResult = null;
|
138 |
const attemptedRequirements = new Set();
|
@@ -180,8 +180,7 @@ def update_iframe_js(demo_type: DemoType) -> str:
|
|
180 |
appBody.style.visibility = "hidden";
|
181 |
errorResult = e.toString();
|
182 |
const allRequirements = formattedRequirements.concat(installedRequirements);
|
183 |
-
|
184 |
-
return [allRequirements, errorResult];
|
185 |
}}
|
186 |
}};
|
187 |
await update();
|
@@ -190,23 +189,29 @@ def update_iframe_js(demo_type: DemoType) -> str:
|
|
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 |
-
|
194 |
-
|
|
|
|
|
|
|
|
|
195 |
// Replace the current URL with the updated one
|
196 |
history.replaceState({{}}, '', currentUrl.href);
|
197 |
|
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 |
-
|
|
|
|
|
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
|
@@ -233,7 +238,7 @@ def update_iframe_js(demo_type: DemoType) -> str:
|
|
233 |
|
234 |
errorResult = e.toString();
|
235 |
const allRequirements = formattedRequirements.concat(installedRequirements);
|
236 |
-
return [allRequirements, errorResult];
|
237 |
}}
|
238 |
}};
|
239 |
await update();
|
@@ -242,12 +247,16 @@ def update_iframe_js(demo_type: DemoType) -> str:
|
|
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 |
-
|
246 |
-
|
|
|
|
|
|
|
|
|
247 |
// Replace the current URL with the updated one
|
248 |
history.replaceState({{}}, '', currentUrl.href);
|
249 |
|
250 |
-
return [allRequirements
|
251 |
}}"""
|
252 |
raise NotImplementedError(f'{demo_type} is not a supported demo type')
|
253 |
|
|
|
132 |
|
133 |
def update_iframe_js(demo_type: DemoType) -> str:
|
134 |
if demo_type == DemoType.GRADIO:
|
135 |
+
return f"""async (code, requirements, lastError, codeHistory, codeHistoryIndex) => {{
|
136 |
const formattedRequirements = requirements.split('\\n').filter(x => x && !x.startsWith('#')).map(x => x.trim());
|
137 |
let errorResult = null;
|
138 |
const attemptedRequirements = new Set();
|
|
|
180 |
appBody.style.visibility = "hidden";
|
181 |
errorResult = e.toString();
|
182 |
const allRequirements = formattedRequirements.concat(installedRequirements);
|
183 |
+
return [code, allRequirements, errorResult, codeHistory, codeHistoryIndex];
|
|
|
184 |
}}
|
185 |
}};
|
186 |
await update();
|
|
|
189 |
// Update URL query params to include the current demo code state
|
190 |
const currentUrl = new URL(window.location.href);
|
191 |
currentUrl.searchParams.set('type', 'gradio');
|
192 |
+
if (requirements) {{
|
193 |
+
currentUrl.searchParams.set('requirements', allRequirements.join('\\n'));
|
194 |
+
}}
|
195 |
+
if (code) {{
|
196 |
+
currentUrl.searchParams.set('code', code);
|
197 |
+
}}
|
198 |
// Replace the current URL with the updated one
|
199 |
history.replaceState({{}}, '', currentUrl.href);
|
200 |
|
201 |
+
return [code, allRequirements, errorResult, codeHistory, codeHistoryIndex];
|
202 |
}}"""
|
203 |
elif demo_type == DemoType.STREAMLIT:
|
204 |
+
return f"""async (code, requirements, lastError, codeHistory, codeHistoryIndex) => {{
|
205 |
+
const formattedRequirements = (requirements || '').split('\\n').filter(x => x && !x.startsWith('#')).map(x => x.trim());
|
206 |
let errorResult = null;
|
207 |
const attemptedRequirements = new Set();
|
208 |
const installedRequirements = [];
|
209 |
async function update() {{
|
210 |
const appController = document.getElementById('stlite-iframe').contentWindow.window.appController;
|
211 |
try {{
|
212 |
+
if (formattedRequirements) {{
|
213 |
+
await appController.install(formattedRequirements);
|
214 |
+
}}
|
215 |
const newCode = code + ` # Update tag ${{Math.random()}}`;
|
216 |
const entrypointFile = "streamlit_app.py";
|
217 |
// TODO: As code rerun happens inside streamlit this won't throw an error for self-healing imports
|
|
|
238 |
|
239 |
errorResult = e.toString();
|
240 |
const allRequirements = formattedRequirements.concat(installedRequirements);
|
241 |
+
return [code, allRequirements, errorResult, codeHistory, codeHistoryIndex];
|
242 |
}}
|
243 |
}};
|
244 |
await update();
|
|
|
247 |
// Update URL query params to include the current demo code state
|
248 |
const currentUrl = new URL(window.location.href);
|
249 |
currentUrl.searchParams.set('type', 'streamlit');
|
250 |
+
if (requirements) {{
|
251 |
+
currentUrl.searchParams.set('requirements', allRequirements.join('\\n'));
|
252 |
+
}}
|
253 |
+
if (code) {{
|
254 |
+
currentUrl.searchParams.set('code', code);
|
255 |
+
}}
|
256 |
// Replace the current URL with the updated one
|
257 |
history.replaceState({{}}, '', currentUrl.href);
|
258 |
|
259 |
+
return [code, allRequirements, errorResult, codeHistory, codeHistoryIndex];
|
260 |
}}"""
|
261 |
raise NotImplementedError(f'{demo_type} is not a supported demo type')
|
262 |
|