gstaff commited on
Commit
3df3aa7
β€’
1 Parent(s): 3b6c4d9

Add undo and redo buttons for gradio and streamlit. Also rename components to eliminate overlapping names between gradio and streamlit tabs.

Browse files
Files changed (2) hide show
  1. app.py +118 -55
  2. 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) -> (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):
113
- return '\n'.join(sorted(requirements)), error
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- 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")
@@ -174,37 +216,56 @@ with gr.Blocks(title="KiteWind") as demo:
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")
@@ -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], [gradio_code_area, gradio_requirements_area, stlite_code_area, stlite_requirements_area, tabs])
 
 
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
- console.log(allRequirements, errorResult);
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
- currentUrl.searchParams.set('requirements', allRequirements.join('\\n'));
194
- currentUrl.searchParams.set('code', code);
 
 
 
 
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
- 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
@@ -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
- 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
 
 
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