Seth0330 commited on
Commit
71d489c
·
verified ·
1 Parent(s): aa866f2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +63 -23
app.py CHANGED
@@ -5,10 +5,11 @@ import requests
5
  import traceback
6
  import difflib
7
 
8
- # ---- BASIC NAME GUESS FOR FEMALE NAMES (expand as needed)
9
- COMMON_FEMALE_NAMES = {"alice", "mary", "lisa", "jane", "emily", "sophia", "emma", "olivia", "ava", "mia", "isabella", "charlotte", "amelia", "harper", "abigail"}
 
 
10
 
11
- # ---- SESSION STATE ----
12
  if "json_data" not in st.session_state:
13
  st.session_state.json_data = {}
14
  if "messages" not in st.session_state:
@@ -17,11 +18,16 @@ if "files_loaded" not in st.session_state:
17
  st.session_state.files_loaded = False
18
  if "temp_input" not in st.session_state:
19
  st.session_state.temp_input = ""
 
 
 
 
 
 
20
 
21
  st.set_page_config(page_title="Chat with Your JSONs", layout="wide")
22
  st.title("Chat with Your JSON Files (OpenAI function-calling, No LangChain)")
23
 
24
- # ---- UPLOAD FILES ----
25
  uploaded_files = st.sidebar.file_uploader(
26
  "Choose one or more JSON files", type="json", accept_multiple_files=True
27
  )
@@ -40,7 +46,6 @@ elif not uploaded_files:
40
  st.session_state.json_data.clear()
41
  st.session_state.files_loaded = False
42
 
43
- # ---- UTILS ----
44
  def normalize(s):
45
  return ' '.join(str(s).lower().replace("_", " ").replace("-", " ").replace(".", " ").split())
46
 
@@ -48,7 +53,6 @@ def is_fuzzy_match(a, b, threshold=0.7):
48
  ratio = difflib.SequenceMatcher(None, a, b).ratio()
49
  return ratio >= threshold or a in b or b in a
50
 
51
- # ---- SEARCH ALL KEYS FOR KEY/VALUE MATCH ----
52
  def search_all_jsons(key, value):
53
  matches = []
54
  value_norm = normalize(value)
@@ -71,7 +75,6 @@ def search_all_jsons(key, value):
71
  recursive_search(data)
72
  return matches
73
 
74
- # ---- FUZZY VALUE SEARCH (returns WHOLE RECORD) ----
75
  def fuzzy_value_search(value):
76
  matches = []
77
  value_norm = normalize(value)
@@ -93,7 +96,6 @@ def fuzzy_value_search(value):
93
  recursive_search(data)
94
  return matches
95
 
96
- # ---- LIST KEYS ----
97
  def list_keys(file_name):
98
  try:
99
  data = st.session_state.json_data[file_name]
@@ -106,7 +108,6 @@ def list_keys(file_name):
106
  except Exception as e:
107
  return {"error": str(e)}
108
 
109
- # ---- COUNT KEY OCCURRENCES ----
110
  def count_key_occurrences(file_name, key):
111
  try:
112
  data = st.session_state.json_data[file_name]
@@ -126,7 +127,6 @@ def count_key_occurrences(file_name, key):
126
  except Exception as e:
127
  return {"error": str(e)}
128
 
129
- # ---- FIND/COUNT IN ARRAYS (e.g., COMPLETED TASKS) ----
130
  def find_in_arrays(key, value, return_count=True):
131
  matches = []
132
  count = 0
@@ -141,7 +141,8 @@ def find_in_arrays(key, value, return_count=True):
141
  matches.append({
142
  "file": file_name,
143
  "item": item,
144
- "array_path": key
 
145
  })
146
  count += 1
147
  recursive(item)
@@ -151,7 +152,6 @@ def find_in_arrays(key, value, return_count=True):
151
  recursive(data)
152
  return count if return_count else matches
153
 
154
- # ---- SUM FIELD BY NAME (e.g., TOTAL AMOUNT FOR JOHNNY) ----
155
  def sum_field_by_name(name, field="amount"):
156
  total = 0
157
  details = []
@@ -162,7 +162,6 @@ def sum_field_by_name(name, field="amount"):
162
  if isinstance(obj, dict):
163
  for k, v in obj.items():
164
  if isinstance(v, (str, int, float, bool)) and is_fuzzy_match(name_norm, normalize(v)):
165
- # Look for field in this or sibling dict
166
  if field in obj:
167
  try:
168
  amt = float(obj[field])
@@ -177,7 +176,6 @@ def sum_field_by_name(name, field="amount"):
177
  recursive(data)
178
  return {"total": total, "matches": details}
179
 
180
- # ---- COUNT FEMALE NAMES (guess from common names) ----
181
  def count_female_names():
182
  count = 0
183
  names = []
@@ -198,7 +196,6 @@ def count_female_names():
198
  recursive(data)
199
  return {"count": count, "names": names}
200
 
201
- # ---- FUNCTION SCHEMA for OpenAI ----
202
  function_schema = [
203
  {
204
  "name": "search_all_jsons",
@@ -281,7 +278,6 @@ function_schema = [
281
  }
282
  ]
283
 
284
- # ---- SYSTEM PROMPT ----
285
  system_message = {
286
  "role": "system",
287
  "content": (
@@ -296,7 +292,6 @@ system_message = {
296
  )
297
  }
298
 
299
- # ---- CHAT UI (with OpenAI function-calling!) ----
300
  st.markdown("### Ask any question about your data, just like ChatGPT.")
301
 
302
  for msg in st.session_state.messages:
@@ -307,6 +302,55 @@ for msg in st.session_state.messages:
307
  elif msg["role"] == "function":
308
  st.markdown(f"<details><summary><b>Function '{msg['name']}' output:</b></summary><pre>{msg['content']}</pre></details>", unsafe_allow_html=True)
309
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
311
  HEADERS = {
312
  "Authorization": f"Bearer {OPENAI_API_KEY}",
@@ -323,7 +367,7 @@ def send_message():
323
  "https://api.openai.com/v1/chat/completions",
324
  headers=HEADERS,
325
  json={
326
- "model": "gpt-4.1",
327
  "messages": chat_messages,
328
  "functions": function_schema,
329
  "function_call": "auto",
@@ -341,7 +385,6 @@ def send_message():
341
  args_json = msg["function_call"]["arguments"]
342
  args = json.loads(args_json)
343
 
344
- # Call the right function
345
  if func_name == "search_all_jsons":
346
  result = search_all_jsons(args.get("key"), args.get("value"))
347
  elif func_name == "fuzzy_value_search":
@@ -372,7 +415,6 @@ def send_message():
372
  "content": json.dumps(result, indent=2),
373
  })
374
 
375
- # Let LLM observe function output and craft final answer
376
  followup_messages = chat_messages + [
377
  {"role": "function", "name": func_name, "content": json.dumps(result, indent=2)}
378
  ]
@@ -380,7 +422,7 @@ def send_message():
380
  "https://api.openai.com/v1/chat/completions",
381
  headers=HEADERS,
382
  json={
383
- "model": "gpt-4.1",
384
  "messages": followup_messages,
385
  "temperature": 0,
386
  "max_tokens": 1200,
@@ -391,8 +433,6 @@ def send_message():
391
  final_json = final_resp.json()
392
  answer = final_json["choices"][0]["message"]["content"]
393
  st.session_state.messages.append({"role": "assistant", "content": answer})
394
- else:
395
- st.session_state.messages.append({"role": "assistant", "content": msg["content"]})
396
  st.session_state.temp_input = ""
397
  except Exception as e:
398
  st.error("Exception: " + str(e))
 
5
  import traceback
6
  import difflib
7
 
8
+ COMMON_FEMALE_NAMES = {
9
+ "alice", "mary", "lisa", "jane", "emily", "sophia", "emma", "olivia",
10
+ "ava", "mia", "isabella", "charlotte", "amelia", "harper", "abigail"
11
+ }
12
 
 
13
  if "json_data" not in st.session_state:
14
  st.session_state.json_data = {}
15
  if "messages" not in st.session_state:
 
18
  st.session_state.files_loaded = False
19
  if "temp_input" not in st.session_state:
20
  st.session_state.temp_input = ""
21
+ if "modal_open" not in st.session_state:
22
+ st.session_state.modal_open = False
23
+ if "modal_content" not in st.session_state:
24
+ st.session_state.modal_content = ""
25
+ if "modal_title" not in st.session_state:
26
+ st.session_state.modal_title = ""
27
 
28
  st.set_page_config(page_title="Chat with Your JSONs", layout="wide")
29
  st.title("Chat with Your JSON Files (OpenAI function-calling, No LangChain)")
30
 
 
31
  uploaded_files = st.sidebar.file_uploader(
32
  "Choose one or more JSON files", type="json", accept_multiple_files=True
33
  )
 
46
  st.session_state.json_data.clear()
47
  st.session_state.files_loaded = False
48
 
 
49
  def normalize(s):
50
  return ' '.join(str(s).lower().replace("_", " ").replace("-", " ").replace(".", " ").split())
51
 
 
53
  ratio = difflib.SequenceMatcher(None, a, b).ratio()
54
  return ratio >= threshold or a in b or b in a
55
 
 
56
  def search_all_jsons(key, value):
57
  matches = []
58
  value_norm = normalize(value)
 
75
  recursive_search(data)
76
  return matches
77
 
 
78
  def fuzzy_value_search(value):
79
  matches = []
80
  value_norm = normalize(value)
 
96
  recursive_search(data)
97
  return matches
98
 
 
99
  def list_keys(file_name):
100
  try:
101
  data = st.session_state.json_data[file_name]
 
108
  except Exception as e:
109
  return {"error": str(e)}
110
 
 
111
  def count_key_occurrences(file_name, key):
112
  try:
113
  data = st.session_state.json_data[file_name]
 
127
  except Exception as e:
128
  return {"error": str(e)}
129
 
 
130
  def find_in_arrays(key, value, return_count=True):
131
  matches = []
132
  count = 0
 
141
  matches.append({
142
  "file": file_name,
143
  "item": item,
144
+ "array_path": key,
145
+ "record": item # show the item dict itself for popup
146
  })
147
  count += 1
148
  recursive(item)
 
152
  recursive(data)
153
  return count if return_count else matches
154
 
 
155
  def sum_field_by_name(name, field="amount"):
156
  total = 0
157
  details = []
 
162
  if isinstance(obj, dict):
163
  for k, v in obj.items():
164
  if isinstance(v, (str, int, float, bool)) and is_fuzzy_match(name_norm, normalize(v)):
 
165
  if field in obj:
166
  try:
167
  amt = float(obj[field])
 
176
  recursive(data)
177
  return {"total": total, "matches": details}
178
 
 
179
  def count_female_names():
180
  count = 0
181
  names = []
 
196
  recursive(data)
197
  return {"count": count, "names": names}
198
 
 
199
  function_schema = [
200
  {
201
  "name": "search_all_jsons",
 
278
  }
279
  ]
280
 
 
281
  system_message = {
282
  "role": "system",
283
  "content": (
 
292
  )
293
  }
294
 
 
295
  st.markdown("### Ask any question about your data, just like ChatGPT.")
296
 
297
  for msg in st.session_state.messages:
 
302
  elif msg["role"] == "function":
303
  st.markdown(f"<details><summary><b>Function '{msg['name']}' output:</b></summary><pre>{msg['content']}</pre></details>", unsafe_allow_html=True)
304
 
305
+ # --- JSON MODAL POPUP ---
306
+ def show_json_links_and_modal():
307
+ # Find last function message
308
+ for msg in reversed(st.session_state.messages):
309
+ if msg.get("role") == "function":
310
+ func_name = msg.get("name")
311
+ try:
312
+ content = json.loads(msg["content"])
313
+ except Exception:
314
+ content = None
315
+ links_shown = False
316
+ if isinstance(content, list):
317
+ for idx, match in enumerate(content):
318
+ if isinstance(match, dict) and "record" in match:
319
+ if st.button(f"View JSON: {match.get('file', 'unknown')} record #{idx+1}", key=f"modal_{func_name}_{idx}"):
320
+ st.session_state.modal_open = True
321
+ st.session_state.modal_content = json.dumps(match["record"], indent=2)
322
+ st.session_state.modal_title = f"{match.get('file', 'unknown')} record #{idx+1}"
323
+ links_shown = True
324
+ elif isinstance(content, dict):
325
+ # For dicts with matches
326
+ if "matches" in content and isinstance(content["matches"], list):
327
+ for idx, match in enumerate(content["matches"]):
328
+ if isinstance(match, dict) and "record" in match:
329
+ if st.button(f"View JSON: {match.get('file', 'unknown')} record #{idx+1}", key=f"modal_{func_name}_matches_{idx}"):
330
+ st.session_state.modal_open = True
331
+ st.session_state.modal_content = json.dumps(match["record"], indent=2)
332
+ st.session_state.modal_title = f"{match.get('file', 'unknown')} record #{idx+1}"
333
+ links_shown = True
334
+ if "names" in content and isinstance(content["names"], list):
335
+ for idx, match in enumerate(content["names"]):
336
+ if isinstance(match, dict) and "record" in match:
337
+ if st.button(f"View JSON: {match.get('file', 'unknown')} record #{idx+1}", key=f"modal_{func_name}_names_{idx}"):
338
+ st.session_state.modal_open = True
339
+ st.session_state.modal_content = json.dumps(match["record"], indent=2)
340
+ st.session_state.modal_title = f"{match.get('file', 'unknown')} record #{idx+1}"
341
+ links_shown = True
342
+ if links_shown:
343
+ break
344
+
345
+ # Modal popup UI using st.expander as a modal hack
346
+ if st.session_state.modal_open:
347
+ with st.expander(f"JSON Record: {st.session_state.modal_title}", expanded=True):
348
+ st.code(st.session_state.modal_content, language="json")
349
+ if st.button("Close", key="close_modal"):
350
+ st.session_state.modal_open = False
351
+
352
+ show_json_links_and_modal()
353
+
354
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
355
  HEADERS = {
356
  "Authorization": f"Bearer {OPENAI_API_KEY}",
 
367
  "https://api.openai.com/v1/chat/completions",
368
  headers=HEADERS,
369
  json={
370
+ "model": "gpt-4o",
371
  "messages": chat_messages,
372
  "functions": function_schema,
373
  "function_call": "auto",
 
385
  args_json = msg["function_call"]["arguments"]
386
  args = json.loads(args_json)
387
 
 
388
  if func_name == "search_all_jsons":
389
  result = search_all_jsons(args.get("key"), args.get("value"))
390
  elif func_name == "fuzzy_value_search":
 
415
  "content": json.dumps(result, indent=2),
416
  })
417
 
 
418
  followup_messages = chat_messages + [
419
  {"role": "function", "name": func_name, "content": json.dumps(result, indent=2)}
420
  ]
 
422
  "https://api.openai.com/v1/chat/completions",
423
  headers=HEADERS,
424
  json={
425
+ "model": "gpt-4o",
426
  "messages": followup_messages,
427
  "temperature": 0,
428
  "max_tokens": 1200,
 
433
  final_json = final_resp.json()
434
  answer = final_json["choices"][0]["message"]["content"]
435
  st.session_state.messages.append({"role": "assistant", "content": answer})
 
 
436
  st.session_state.temp_input = ""
437
  except Exception as e:
438
  st.error("Exception: " + str(e))