KingNish commited on
Commit
71623d4
·
1 Parent(s): a6837f3

Added js based history

Browse files
Files changed (1) hide show
  1. app.py +85 -28
app.py CHANGED
@@ -13,6 +13,51 @@ client = OpenAI(
13
  )
14
  model = os.getenv("OPENAI_MODEL")
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  def _conv_choices(state_value):
18
  return gr.update(
@@ -43,7 +88,7 @@ class GradioEvents:
43
  }
44
  else:
45
  conv_id = state_value["conversation_id"]
46
- ctx = state_value["conversation_contexts"].setdefault(
47
  conv_id, {"history": []})
48
 
49
  ctx = state_value["conversation_contexts"][conv_id]
@@ -194,14 +239,11 @@ class GradioEvents:
194
  return (
195
  _conv_choices(state_value),
196
  gr.update(value=None),
197
- gr.update(value=True),
198
  gr.update(value=state_value),
199
  )
200
  return (
201
  _conv_choices(state_value),
202
  gr.skip(),
203
- gr.skip(),
204
- gr.skip(),
205
  gr.update(value=state_value),
206
  )
207
 
@@ -224,24 +266,32 @@ class GradioEvents:
224
  ctx["history"][-1]["metadata"] = ctx["history"][-1].get(
225
  "metadata", {})
226
  ctx["history"][-1]["metadata"]["footer"] = "Chat completion paused"
227
- return (gr.update(value=ctx.get("history", [])),
228
- gr.update(value=state_value), gr.update(visible=True),
229
- gr.update(visible=False))
230
-
231
- @staticmethod
232
- def save_browser_state(state_value):
233
- return gr.update(value=dict(
234
- conversations=state_value["conversations"],
235
- conversation_contexts=state_value["conversation_contexts"]))
236
 
237
  @staticmethod
238
- def load_browser_state(browser_state_value, state_value):
239
- if not browser_state_value:
 
 
240
  return gr.skip(), gr.skip()
241
- state_value["conversations"] = browser_state_value.get("conversations", [])
242
- state_value["conversation_contexts"] = browser_state_value.get("conversation_contexts", {})
 
 
 
 
 
243
  return _conv_choices(state_value), gr.update(value=state_value)
244
 
 
 
 
 
 
 
 
 
 
245
  css = open("./app.css", "r").read()
246
 
247
  with gr.Blocks(fill_width=True, title="Demo Chat") as demo:
@@ -251,6 +301,9 @@ with gr.Blocks(fill_width=True, title="Demo Chat") as demo:
251
  "conversation_id": "",
252
  })
253
 
 
 
 
254
  with gr.Row(elem_id="main-row"):
255
  with gr.Column(scale=0, min_width=260, elem_id="sidebar"):
256
  new_chat_btn = gr.Button(
@@ -346,21 +399,25 @@ with gr.Blocks(fill_width=True, title="Demo Chat") as demo:
346
  cancels=[submit_event],
347
  )
348
 
349
- browser_state = gr.BrowserState(
350
- {
351
- "conversation_contexts": {},
352
- "conversations": [],
353
- },
354
- storage_key="chat_app_state"
355
- )
356
  state.change(
357
- fn=GradioEvents.save_browser_state,
358
  inputs=[state],
359
- outputs=[browser_state],
 
 
 
 
 
360
  )
 
361
  demo.load(
362
- fn=GradioEvents.load_browser_state,
363
- inputs=[browser_state, state],
 
 
 
 
 
364
  outputs=[conv_choice, state],
365
  )
366
 
 
13
  )
14
  model = os.getenv("OPENAI_MODEL")
15
 
16
+ JS_LOAD_STATE = """(_) => {
17
+ const titles = JSON.parse(localStorage.getItem("titles") || "{}");
18
+ const conversations = [];
19
+ const conversation_contexts = {};
20
+
21
+ for (const [id, label] of Object.entries(titles)) {
22
+ const raw = localStorage.getItem("chat_id_" + id);
23
+ if (raw) {
24
+ conversations.push({ key: id, label: label });
25
+ conversation_contexts[id] = JSON.parse(raw);
26
+ }
27
+ }
28
+
29
+ return JSON.stringify({ conversations, conversation_contexts });
30
+ }"""
31
+
32
+ JS_SAVE_STATE = """(stateJson) => {
33
+ const state = JSON.parse(stateJson);
34
+ const titles = {};
35
+
36
+ for (const conv of (state.conversations || [])) {
37
+ titles[conv.key] = conv.label;
38
+ const ctx = (state.conversation_contexts || {})[conv.key];
39
+ if (ctx !== undefined) {
40
+ localStorage.setItem("chat_id_" + conv.key, JSON.stringify(ctx));
41
+ }
42
+ }
43
+
44
+ // Remove stale chat_id_* keys that are no longer in conversations
45
+ const validIds = new Set(Object.keys(titles));
46
+ for (let i = 0; i < localStorage.length; i++) {
47
+ const k = localStorage.key(i);
48
+ if (k && k.startsWith("chat_id_")) {
49
+ const id = k.slice("chat_id_".length);
50
+ if (!validIds.has(id)) {
51
+ localStorage.removeItem(k);
52
+ i--; // adjust index after removal
53
+ }
54
+ }
55
+ }
56
+
57
+ localStorage.setItem("titles", JSON.stringify(titles));
58
+ return stateJson; // pass-through so Gradio output still works
59
+ }"""
60
+
61
 
62
  def _conv_choices(state_value):
63
  return gr.update(
 
88
  }
89
  else:
90
  conv_id = state_value["conversation_id"]
91
+ state_value["conversation_contexts"].setdefault(
92
  conv_id, {"history": []})
93
 
94
  ctx = state_value["conversation_contexts"][conv_id]
 
239
  return (
240
  _conv_choices(state_value),
241
  gr.update(value=None),
 
242
  gr.update(value=state_value),
243
  )
244
  return (
245
  _conv_choices(state_value),
246
  gr.skip(),
 
 
247
  gr.update(value=state_value),
248
  )
249
 
 
266
  ctx["history"][-1]["metadata"] = ctx["history"][-1].get(
267
  "metadata", {})
268
  ctx["history"][-1]["metadata"]["footer"] = "Chat completion paused"
269
+ return (gr.update(value=ctx.get("history", [])), gr.update(value=state_value), gr.update(visible=True), gr.update(visible=False))
 
 
 
 
 
 
 
 
270
 
271
  @staticmethod
272
+ def load_from_js(serialised_json, state_value):
273
+ """Receive the JSON string that JS_LOAD_STATE returned, merge into state."""
274
+ import json
275
+ if not serialised_json:
276
  return gr.skip(), gr.skip()
277
+ try:
278
+ loaded = json.loads(serialised_json)
279
+ except Exception:
280
+ return gr.skip(), gr.skip()
281
+
282
+ state_value["conversations"] = loaded.get("conversations", [])
283
+ state_value["conversation_contexts"] = loaded.get("conversation_contexts", {})
284
  return _conv_choices(state_value), gr.update(value=state_value)
285
 
286
+ @staticmethod
287
+ def prepare_save(state_value):
288
+ """Serialise state to JSON so JS_SAVE_STATE can write it to localStorage."""
289
+ import json
290
+ return json.dumps({
291
+ "conversations": state_value.get("conversations", []),
292
+ "conversation_contexts": state_value.get("conversation_contexts", {}),
293
+ })
294
+
295
  css = open("./app.css", "r").read()
296
 
297
  with gr.Blocks(fill_width=True, title="Demo Chat") as demo:
 
301
  "conversation_id": "",
302
  })
303
 
304
+ js_load_output = gr.Textbox(visible=False, elem_id="js-load-output")
305
+ js_save_input = gr.Textbox(visible=False, elem_id="js-save-input")
306
+
307
  with gr.Row(elem_id="main-row"):
308
  with gr.Column(scale=0, min_width=260, elem_id="sidebar"):
309
  new_chat_btn = gr.Button(
 
399
  cancels=[submit_event],
400
  )
401
 
 
 
 
 
 
 
 
402
  state.change(
403
+ fn=GradioEvents.prepare_save,
404
  inputs=[state],
405
+ outputs=[js_save_input],
406
+ ).then(
407
+ fn=lambda x: x,
408
+ inputs=[js_save_input],
409
+ outputs=[js_save_input],
410
+ js=JS_SAVE_STATE,
411
  )
412
+
413
  demo.load(
414
+ fn=lambda x: x,
415
+ inputs=[js_load_output],
416
+ outputs=[js_load_output],
417
+ js=JS_LOAD_STATE,
418
+ ).then(
419
+ fn=GradioEvents.load_from_js,
420
+ inputs=[js_load_output, state],
421
  outputs=[conv_choice, state],
422
  )
423