broadfield-dev commited on
Commit
26cbf7f
Β·
verified Β·
1 Parent(s): 9afb363

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -232
app.py CHANGED
@@ -44,8 +44,10 @@ logger.info(f"App Config: WebSearch={WEB_SEARCH_ENABLED}, ToolDecisionProvider={
44
 
45
  # --- Helper Functions (format_insights_for_prompt, generate_interaction_metrics, etc.) ---
46
  # These functions (format_insights_for_prompt, generate_interaction_metrics,
47
- # process_user_interaction_gradio, deferred_learning_and_memory_task) remain the same as in the previous "full working file".
48
- # For brevity here, I will not repeat them. Ensure they are present in your actual app.py.
 
 
49
  def format_insights_for_prompt(retrieved_insights_list: list[str]) -> tuple[str, list[dict]]:
50
  if not retrieved_insights_list:
51
  return "No specific guiding principles or learned insights retrieved.", []
@@ -190,17 +192,14 @@ def deferred_learning_and_memory_task(user_input: str, bot_response: str, provid
190
  summary = f"User:\"{user_input}\"\nAI:\"{bot_response}\"\nMetrics(takeaway):{metrics.get('takeaway','N/A')},Success:{metrics.get('response_success_score','N/A')}"
191
  existing_rules_ctx = "\n".join([f"- \"{r}\"" for r in retrieve_rules_semantic(f"{summary}\n{user_input}", k=10)]) or "No existing rules context."
192
  insight_sys_prompt = """You are an expert AI knowledge base curator. Your primary function is to meticulously analyze an interaction and update the AI's guiding principles (insights/rules) to improve its future performance and self-understanding.
193
-
194
  **CRITICAL OUTPUT REQUIREMENT: You MUST output a single, valid JSON list of operation objects.**
195
  This list can and SHOULD contain MULTIPLE distinct operations if various learnings occurred.
196
  If no operations are warranted, output an empty JSON list: `[]`.
197
  ABSOLUTELY NO other text, explanations, or markdown should precede or follow this JSON list.
198
-
199
  Each operation object in the JSON list must have these keys and string values:
200
  1. `"action"`: A string, either `"add"` (for entirely new rules) or `"update"` (to replace an existing rule with a better one).
201
  2. `"insight"`: A string, the full, refined insight text including its `[TYPE|SCORE]` prefix (e.g., `"[CORE_RULE|1.0] My name is Lumina, an AI assistant."`).
202
  3. `"old_insight_to_replace"`: (ONLY for `"update"` action) A string, the *exact, full text* of an existing insight that the new `"insight"` should replace. If action is `"add"`, this key should be omitted or its value should be `null` or an empty string.
203
-
204
  **CRITICAL JSON STRING FORMATTING RULES (for values of "insight" and "old_insight_to_replace"):**
205
  - All string values MUST be enclosed in double quotes (`"`).
206
  - Any literal double quote (`"`) character *within* the string content MUST be escaped as `\\"`.
@@ -208,7 +207,6 @@ Each operation object in the JSON list must have these keys and string values:
208
  - Any newline characters *within* the string content MUST be escaped as `\\n`. Avoid literal newlines in JSON string values; use `\\n` instead.
209
  *Example of correctly escaped insight string in JSON:*
210
  `"insight": "[RESPONSE_PRINCIPLE|0.8] User prefers concise answers, stating: \\"Just the facts!\\". Avoid verbose explanations unless asked.\\nFollow up with a question if appropriate."`
211
-
212
  **Your Reflection Process (Consider each step and generate operations accordingly):**
213
  **STEP 1: Core Identity & Purpose Review (Result: Primarily 'update' operations)**
214
  - Examine all `CORE_RULE`s related to my identity (name, fundamental purpose, core unchanging capabilities, origin) from the "Potentially Relevant Existing Rules".
@@ -224,12 +222,10 @@ Each operation object in the JSON list must have these keys and string values:
224
  - Did I learn to modify or improve an existing behavior, response style, or operational guideline (that is NOT part of core identity)?
225
  - For example, if an existing `RESPONSE_PRINCIPLE` was "Be formal," and the interaction showed the user prefers informality, update that principle.
226
  - Propose 'update' operations for the relevant `RESPONSE_PRINCIPLE` or `BEHAVIORAL_ADJUSTMENT`. Only update if the change is significant.
227
-
228
  **General Guidelines for Insight Content and Actions:**
229
  - Ensure the "insight" field (for both add/update) always contains the properly formatted insight string: `[TYPE|SCORE] Text`. `TYPE` can be `CORE_RULE`, `RESPONSE_PRINCIPLE`, `BEHAVIORAL_ADJUSTMENT`. Scores should reflect confidence/importance (0.0-1.0).
230
  - Be precise with "old_insight_to_replace" – it must *exactly* match an existing rule string from the "Potentially Relevant Existing Rules" context.
231
  - Aim for a comprehensive set of operations that reflects ALL key learnings from the interaction.
232
-
233
  **Example of a comprehensive JSON output with MULTIPLE operations (This is how your output should look):**
234
  [
235
  {"action": "update", "old_insight_to_replace": "[CORE_RULE|1.0] My designated name is 'LearnerAI'.", "insight": "[CORE_RULE|1.0] I am Lumina, an AI assistant designed to chat, provide information, and remember context like the secret word 'rocksyrup'."},
@@ -261,7 +257,7 @@ Combine all findings into a single JSON list of operations. If there are multipl
261
  json_match_ops = re.search(r"```json\s*(\[.*?\])\s*```", raw_ops_json, re.DOTALL|re.I) or re.search(r"(\[.*?\])", raw_ops_json, re.DOTALL)
262
  if json_match_ops:
263
  try: ops = json.loads(json_match_ops.group(1))
264
- except Exception as e: logger.error(f"DEFERRED [{task_id}]: JSON ops parse error: {e}. Raw: {json_match_ops.group(1)[:500]}") # Log problematic part
265
  if isinstance(ops, list) and ops:
266
  logger.info(f"DEFERRED [{task_id}]: LLM provided {len(ops)} insight ops.")
267
  for op in ops:
@@ -399,238 +395,118 @@ def ui_upload_memories_action_fn(uploaded_file_obj, progress=gr.Progress()):
399
  logger.info(msg); return msg
400
 
401
  # --- UI Definition ---
402
- custom_theme = gr.themes.Soft(
403
  primary_hue="teal",
404
  secondary_hue="purple",
405
  neutral_hue="zinc",
406
- text_size="md",
407
- spacing_size="md",
408
  radius_size="sm",
409
- font=[gr.themes.GoogleFont("Inter"), "System UI", "sans-serif"] # Added Inter as primary
410
  )
411
 
 
412
  custom_css = """
413
- body, html {
414
- margin: 0 !important;
415
- padding: 0.5em !important;
416
- height: 100%;
417
- background: linear-gradient(to bottom right, #2c3e50, #34495e);
418
- color: #ecf0f1;
419
- font-family: 'Inter', 'System UI', sans-serif;
420
- }
421
- .gradio-container {
422
- background: transparent !important;
423
- max-width: 100% !important;
424
- padding: 2em 1em 1em 1em !important;
425
- box-sizing: border-box;
426
- min-height: 100vh;
427
- display: flex;
428
- flex-direction: column;
429
- overflow-y: auto !important;
430
- }
431
- .gr-block.gr-group, .gr-tabs, .gr-accordion {
432
- background-color: rgba(44, 62, 80, 0.85) !important;
433
- border: 1px solid rgba(189, 195, 199, 0.2) !important;
434
- border-radius: 8px !important;
435
- box-shadow: 0 3px 5px rgba(0,0,0,0.15);
436
- margin-bottom: 1em;
437
- }
438
- .gr-tabitem {
439
- background-color: rgba(52, 73, 94, 0.8) !important;
440
- border-radius: 6px !important;
441
- padding: 1em !important;
442
- border: 1px solid rgba(189, 195, 199, 0.1) !important;
443
- }
444
- .gr-textbox, .gr-dropdown, .gr-button, .gr-code, .gr-chat-message, .gr-json, .gr-file input[type="file"], .gr-file .OgniCn {
445
- border: 1px solid rgba(189, 195, 199, 0.3) !important;
446
- background-color: rgba(52, 73, 94, 0.9) !important;
447
- color: #ecf0f1 !important;
448
- border-radius: 6px !important;
449
- }
450
- .gr-file {
451
- background-color: transparent !important;
452
- border: none !important;
453
- padding: 0 !important;
454
- }
455
- .gr-file > .label-text {
456
- color: #bdc3c7 !important;
457
- }
458
- .gr-textarea textarea, .gr-textbox input {
459
- color: #ecf0f1 !important;
460
- }
461
- .gr-button.gr-button-primary {
462
- background-color: #1abc9c !important;
463
- color: white !important;
464
- border-color: #16a085 !important;
465
- }
466
- .gr-button.gr-button-secondary {
467
- background-color: #9b59b6 !important;
468
- color: white !important;
469
- border-color: #8e44ad !important;
470
- }
471
- .gr-button.gr-button-stop {
472
- background-color: #e74c3c !important;
473
- color: white !important;
474
- border-color: #c0392b !important;
475
- }
476
- .gr-markdown {
477
- padding: 0px;
478
- }
479
- .gr-markdown h1, .gr-markdown h2, .gr-markdown h3 {
480
- color: #1abc9c !important;
481
- border-bottom: 1px solid rgba(189, 195, 199, 0.2) !important;
482
- padding-bottom: 0.3em;
483
- margin-top: 0.8em;
484
- margin-bottom: 0.6em;
485
- }
486
- .gr-markdown p, .gr-markdown li {
487
- color: #ecf0f1 !important;
488
- line-height: 1.6;
489
- }
490
- .gr-markdown pre code {
491
- background-color: rgba(30, 40, 50, 0.95) !important;
492
- border: 1px solid rgba(189, 195, 199, 0.3) !important;
493
- color: #ecf0f1;
494
- border-radius: 4px;
495
- padding: 0.8em;
496
- }
497
- .gr-chatbot {
498
- background-color: rgba(44, 62, 80, 0.7) !important;
499
- border: 1px solid rgba(189, 195, 199, 0.2) !important;
500
- }
501
- .gr-chatbot .message {
502
- background-color: rgba(52, 73, 94, 0.9) !important;
503
- color: #ecf0f1 !important;
504
- border: 1px solid rgba(189, 195, 199, 0.2) !important;
505
- box-shadow: 0 1px 2px rgba(0,0,0,0.1) !important;
506
- }
507
- .gr-chatbot .message.user {
508
- background-color: rgba(46, 204, 113, 0.8) !important;
509
- color: #2c3e50 !important;
510
- }
511
- .gr-input-label > .label-text, .gr-dropdown-label > .label-text, .gr-checkbox-label > .label-text {
512
- color: #bdc3c7 !important;
513
- font-size: 0.9rem !important;
514
- margin-bottom: 0.2rem !important;
515
- }
516
- .gr-info {
517
- color: #bdc3c7 !important;
518
- font-size: 0.85rem !important;
519
- }
520
- .status-bar {
521
- background-color: rgba(44, 62, 80, 0.8) !important;
522
- padding: 0.5rem 1rem !important;
523
- border-radius: 6px;
524
- margin: 1rem 0 !important;
525
- }
526
- .tabnav button {
527
- background-color: rgba(52, 73, 94, 0.8) !important;
528
- color: #ecf0f1 !important;
529
- border-bottom: 2px solid transparent !important;
530
- border-top-left-radius: 6px !important;
531
- border-top-right-radius: 6px !important;
532
- margin-right: 2px !important;
533
- padding: 0.5em 1em !important;
534
- }
535
- .tabnav button.selected {
536
- background-color: rgba(44, 62, 80, 0.95) !important;
537
- color: #1abc9c !important;
538
- border-bottom: 2px solid #1abc9c !important;
539
- }
540
- .main-title {
541
- text-align: center;
542
- margin: 1.5rem 0 0.5rem 0 !important;
543
- }
544
- .main-subtitle {
545
- text-align: center;
546
- color: #bdc3c7;
547
- font-size: 0.95rem;
548
- margin: 0 0 1.5rem 0 !important;
549
- }
550
- @media (max-width: 768px) {
551
- .gradio-container {
552
- padding: 1.5em 0.5em !important;
553
- }
554
- .main-title {
555
- font-size: 1.5rem !important;
556
- margin-top: 1rem !important;
557
- }
558
- .main-subtitle {
559
- font-size: 0.85rem !important;
560
- }
561
- .status-bar {
562
- margin: 0.5rem 0 !important;
563
- }
564
- }
565
  """
566
 
567
- with gr.Blocks(theme=custom_theme, css=custom_css, title="AI Research Agent") as demo:
568
- gr.HTML("<style>" + custom_css + "</style>") # Ensure CSS is applied
569
 
570
- gr.Markdown("# πŸ€– AI Research Agent", elem_classes="main-title")
571
- gr.Markdown("Configure AI, chat for research, and manage its knowledge base.", elem_classes="main-subtitle")
572
-
573
- avail_provs = get_available_providers()
574
- def_prov = avail_provs[0] if avail_provs else None
575
- def_models = get_model_display_names_for_provider(def_prov) if def_prov else []
576
- def_model_disp = get_default_model_display_name_for_provider(def_prov) if def_prov else None
577
-
578
- with gr.Row(elem_classes="status-bar"):
579
- with gr.Column(scale=3): agent_stat_tb = gr.Textbox(label="Agent Status", interactive=False, lines=1, value="Initializing systems...")
580
- with gr.Column(scale=1): memory_backend_info_tb = gr.Textbox(label="Memory Backend", value=f"{MEMORY_STORAGE_BACKEND}", interactive=False)
581
- if MEMORY_STORAGE_BACKEND == "SQLITE":
582
- with gr.Column(scale=2): gr.Textbox(label="SQLite Path", value=f"{MEMORY_SQLITE_PATH}", interactive=False)
583
- elif MEMORY_STORAGE_BACKEND == "HF_DATASET":
584
- with gr.Column(scale=2): gr.Textbox(label="HF Repos", value=f"M: {MEMORY_HF_MEM_REPO}, R: {MEMORY_HF_RULES_REPO}", interactive=False)
 
585
 
586
- with gr.Row(equal_height=False):
587
- with gr.Column(scale=1, min_width=360): # Sidebar for configuration
588
- gr.Markdown("## βš™οΈ Configuration")
589
- with gr.Group():
590
- gr.Markdown("### AI Model Settings")
591
- api_key_tb = gr.Textbox(label="AI Provider API Key (Optional Override)", type="password", placeholder="Paste key or use .env")
592
- prov_sel_dd = gr.Dropdown(label="AI Provider", choices=avail_provs, value=def_prov, interactive=True)
593
- model_sel_dd = gr.Dropdown(label="AI Model", choices=def_models, value=def_model_disp, interactive=True)
594
- with gr.Group():
595
- gr.Markdown("### System Prompt")
596
- sys_prompt_tb = gr.Textbox(label="System Prompt Base", lines=12, value=DEFAULT_SYSTEM_PROMPT, interactive=True)
 
 
 
597
 
598
- with gr.Column(scale=2): # Main content area with Tabs
599
- with gr.Tabs():
600
- with gr.TabItem("πŸ’¬ AI Chat & Research Output"):
601
- gr.Markdown("## πŸ’¬ AI Chat Interface")
602
- main_chat_disp = gr.Chatbot(label="AI Research Chat", height=500, bubble_full_width=False, avatar_images=(None, "https://raw.githubusercontent.com/huggingface/brand-assets/main/hf-logo-with-title.png"), show_copy_button=True, render_markdown=True, sanitize_html=True)
603
- with gr.Row():
604
- user_msg_tb = gr.Textbox(show_label=False, placeholder="Ask your research question or give an instruction...", scale=7, lines=1, max_lines=5, autofocus=True)
605
- send_btn = gr.Button("Send", variant="primary", scale=1, min_width=100)
606
- with gr.Accordion("πŸ“ Full Response / Output Details", open=True): # Open by default
607
- fmt_report_tb = gr.Textbox(label="Full AI Response", lines=10, interactive=True, show_copy_button=True, value="*AI responses will appear here...*")
608
- dl_report_btn = gr.DownloadButton(value="Download Report", interactive=False, visible=False)
609
- detect_out_md = gr.Markdown("*Insights used or other intermediate details will show here...*")
610
 
611
- with gr.TabItem("🧠 Knowledge Base Management"):
612
- gr.Markdown("## Manage Stored Knowledge")
613
- with gr.Row(equal_height=False):
614
- with gr.Column(scale=1):
615
- with gr.Group():
616
- gr.Markdown("### πŸ“œ Rules (Learned Insights)")
617
- rules_disp_ta = gr.TextArea(label="View/Edit Rules", lines=15, interactive=True, placeholder="Load or type rules here (one per line or '---' separated for multiple).")
618
- with gr.Row():
619
- view_rules_btn = gr.Button("πŸ”„ Load/Refresh Rules"); save_edited_rules_btn = gr.Button("πŸ’Ύ Save Edited Rules", variant="primary")
620
- upload_rules_fobj = gr.File(label="Upload Rules File (.txt/.jsonl)", file_types=[".txt", ".jsonl"])
621
- rules_stat_tb = gr.Textbox(label="Rules Operation Status", interactive=False, lines=2, placeholder="Status...")
622
- with gr.Row():
623
- clear_rules_btn = gr.Button("⚠️ Clear All Rules", variant="stop")
624
- save_faiss_ram_btn_kb = gr.Button("Save FAISS Indices", visible=(MEMORY_STORAGE_BACKEND == "RAM"))
625
-
626
- with gr.Column(scale=1):
627
- with gr.Group():
628
- gr.Markdown("### πŸ“š Memories (Past Interactions)")
629
- mems_disp_json = gr.JSON(label="View Memories (JSON format)")
630
- with gr.Row(): view_mems_btn = gr.Button("πŸ”„ Load/Refresh Memories")
631
- upload_mems_fobj = gr.File(label="Upload Memories File (.jsonl)", file_types=[".jsonl"])
632
- mems_stat_tb = gr.Textbox(label="Memories Operation Status", interactive=False, lines=2, placeholder="Status...")
633
- clear_mems_btn = gr.Button("⚠️ Clear All Memories", variant="stop")
634
 
635
  def dyn_upd_model_dd(sel_prov_dyn:str): models_dyn, def_model_dyn = get_model_display_names_for_provider(sel_prov_dyn), get_default_model_display_name_for_provider(sel_prov_dyn); return gr.Dropdown(choices=models_dyn, value=def_model_dyn, interactive=True)
636
  prov_sel_dd.change(fn=dyn_upd_model_dd, inputs=prov_sel_dd, outputs=model_sel_dd)
@@ -659,9 +535,9 @@ with gr.Blocks(theme=custom_theme, css=custom_css, title="AI Research Agent") as
659
  upload_rules_fobj.upload(fn=ui_upload_rules_action_fn, inputs=[upload_rules_fobj], outputs=[rules_stat_tb], show_progress="full").then(fn=ui_view_rules_action_fn, outputs=rules_disp_ta)
660
  clear_rules_btn.click(fn=lambda: "All rules cleared." if clear_all_rules_data_backend() else "Error clearing rules.", outputs=rules_stat_tb).then(fn=ui_view_rules_action_fn, outputs=rules_disp_ta)
661
 
662
- if MEMORY_STORAGE_BACKEND == "RAM" and 'save_faiss_ram_btn_kb' in locals() and save_faiss_ram_btn_kb is not None :
663
- def save_faiss_action_with_feedback_fn_kb(): save_faiss_indices_to_disk(); gr.Info("Attempted to save FAISS indices to disk.")
664
- save_faiss_ram_btn_kb.click(fn=save_faiss_action_with_feedback_fn_kb, inputs=None, outputs=None)
665
 
666
  view_mems_btn.click(fn=ui_view_memories_action_fn, outputs=mems_disp_json)
667
  upload_mems_fobj.upload(fn=ui_upload_memories_action_fn, inputs=[upload_mems_fobj], outputs=[mems_stat_tb], show_progress="full").then(fn=ui_view_memories_action_fn, outputs=mems_disp_json)
@@ -678,7 +554,7 @@ with gr.Blocks(theme=custom_theme, css=custom_css, title="AI Research Agent") as
678
  demo.load(fn=app_load_fn, inputs=None, outputs=[agent_stat_tb, rules_disp_ta, mems_disp_json])
679
 
680
  if __name__ == "__main__":
681
- logger.info(f"Starting Gradio AI Research Mega Agent (v5.5 - UI Style Update, Memory: {MEMORY_STORAGE_BACKEND})...")
682
  app_port, app_server = int(os.getenv("GRADIO_PORT", 7860)), os.getenv("GRADIO_SERVER_NAME", "127.0.0.1")
683
  app_debug, app_share = os.getenv("GRADIO_DEBUG", "False").lower()=="true", os.getenv("GRADIO_SHARE", "False").lower()=="true"
684
  logger.info(f"Launching Gradio server: http://{app_server}:{app_port}. Debug: {app_debug}, Share: {app_share}")
 
44
 
45
  # --- Helper Functions (format_insights_for_prompt, generate_interaction_metrics, etc.) ---
46
  # These functions (format_insights_for_prompt, generate_interaction_metrics,
47
+ # process_user_interaction_gradio, deferred_learning_and_memory_task, handle_gradio_chat_submit,
48
+ # ui_view_rules_action_fn, ui_upload_rules_action_fn, ui_view_memories_action_fn, ui_upload_memories_action_fn)
49
+ # remain the same as in the previous "full working file".
50
+ # For brevity here, I will not repeat them fully. Ensure they are present in your actual app.py.
51
  def format_insights_for_prompt(retrieved_insights_list: list[str]) -> tuple[str, list[dict]]:
52
  if not retrieved_insights_list:
53
  return "No specific guiding principles or learned insights retrieved.", []
 
192
  summary = f"User:\"{user_input}\"\nAI:\"{bot_response}\"\nMetrics(takeaway):{metrics.get('takeaway','N/A')},Success:{metrics.get('response_success_score','N/A')}"
193
  existing_rules_ctx = "\n".join([f"- \"{r}\"" for r in retrieve_rules_semantic(f"{summary}\n{user_input}", k=10)]) or "No existing rules context."
194
  insight_sys_prompt = """You are an expert AI knowledge base curator. Your primary function is to meticulously analyze an interaction and update the AI's guiding principles (insights/rules) to improve its future performance and self-understanding.
 
195
  **CRITICAL OUTPUT REQUIREMENT: You MUST output a single, valid JSON list of operation objects.**
196
  This list can and SHOULD contain MULTIPLE distinct operations if various learnings occurred.
197
  If no operations are warranted, output an empty JSON list: `[]`.
198
  ABSOLUTELY NO other text, explanations, or markdown should precede or follow this JSON list.
 
199
  Each operation object in the JSON list must have these keys and string values:
200
  1. `"action"`: A string, either `"add"` (for entirely new rules) or `"update"` (to replace an existing rule with a better one).
201
  2. `"insight"`: A string, the full, refined insight text including its `[TYPE|SCORE]` prefix (e.g., `"[CORE_RULE|1.0] My name is Lumina, an AI assistant."`).
202
  3. `"old_insight_to_replace"`: (ONLY for `"update"` action) A string, the *exact, full text* of an existing insight that the new `"insight"` should replace. If action is `"add"`, this key should be omitted or its value should be `null` or an empty string.
 
203
  **CRITICAL JSON STRING FORMATTING RULES (for values of "insight" and "old_insight_to_replace"):**
204
  - All string values MUST be enclosed in double quotes (`"`).
205
  - Any literal double quote (`"`) character *within* the string content MUST be escaped as `\\"`.
 
207
  - Any newline characters *within* the string content MUST be escaped as `\\n`. Avoid literal newlines in JSON string values; use `\\n` instead.
208
  *Example of correctly escaped insight string in JSON:*
209
  `"insight": "[RESPONSE_PRINCIPLE|0.8] User prefers concise answers, stating: \\"Just the facts!\\". Avoid verbose explanations unless asked.\\nFollow up with a question if appropriate."`
 
210
  **Your Reflection Process (Consider each step and generate operations accordingly):**
211
  **STEP 1: Core Identity & Purpose Review (Result: Primarily 'update' operations)**
212
  - Examine all `CORE_RULE`s related to my identity (name, fundamental purpose, core unchanging capabilities, origin) from the "Potentially Relevant Existing Rules".
 
222
  - Did I learn to modify or improve an existing behavior, response style, or operational guideline (that is NOT part of core identity)?
223
  - For example, if an existing `RESPONSE_PRINCIPLE` was "Be formal," and the interaction showed the user prefers informality, update that principle.
224
  - Propose 'update' operations for the relevant `RESPONSE_PRINCIPLE` or `BEHAVIORAL_ADJUSTMENT`. Only update if the change is significant.
 
225
  **General Guidelines for Insight Content and Actions:**
226
  - Ensure the "insight" field (for both add/update) always contains the properly formatted insight string: `[TYPE|SCORE] Text`. `TYPE` can be `CORE_RULE`, `RESPONSE_PRINCIPLE`, `BEHAVIORAL_ADJUSTMENT`. Scores should reflect confidence/importance (0.0-1.0).
227
  - Be precise with "old_insight_to_replace" – it must *exactly* match an existing rule string from the "Potentially Relevant Existing Rules" context.
228
  - Aim for a comprehensive set of operations that reflects ALL key learnings from the interaction.
 
229
  **Example of a comprehensive JSON output with MULTIPLE operations (This is how your output should look):**
230
  [
231
  {"action": "update", "old_insight_to_replace": "[CORE_RULE|1.0] My designated name is 'LearnerAI'.", "insight": "[CORE_RULE|1.0] I am Lumina, an AI assistant designed to chat, provide information, and remember context like the secret word 'rocksyrup'."},
 
257
  json_match_ops = re.search(r"```json\s*(\[.*?\])\s*```", raw_ops_json, re.DOTALL|re.I) or re.search(r"(\[.*?\])", raw_ops_json, re.DOTALL)
258
  if json_match_ops:
259
  try: ops = json.loads(json_match_ops.group(1))
260
+ except Exception as e: logger.error(f"DEFERRED [{task_id}]: JSON ops parse error: {e}. Raw: {json_match_ops.group(1)[:500]}")
261
  if isinstance(ops, list) and ops:
262
  logger.info(f"DEFERRED [{task_id}]: LLM provided {len(ops)} insight ops.")
263
  for op in ops:
 
395
  logger.info(msg); return msg
396
 
397
  # --- UI Definition ---
398
+ custom_theme = gr.themes.Base(
399
  primary_hue="teal",
400
  secondary_hue="purple",
401
  neutral_hue="zinc",
402
+ text_size="sm",
403
+ spacing_size="md",
404
  radius_size="sm",
405
+ font=[gr.themes.GoogleFont("Inter"), "System UI", "sans-serif"]
406
  )
407
 
408
+ # Adjusted CSS for better layout control and scrolling
409
  custom_css = """
410
+ body, html { margin: 0; padding: 0; height: 100%; overflow-x: hidden; /* Prevent horizontal scroll on body */ }
411
+ body { background: linear-gradient(to bottom right, #2c3e50, #34495e); color: #ecf0f1; font-family: 'Inter', 'System UI', sans-serif;}
412
+ .gradio-container { background: transparent !important; max-width: 100% !important; padding: 0 !important; /* Remove container padding */ box-sizing: border-box; min-height: 100vh; display: flex; flex-direction: column; overflow: hidden !important; /* Gradio container should not scroll itself */ }
413
+ .main-app-content-wrapper { padding: 1rem; overflow-y: auto; flex-grow: 1; /* This makes the content area scrollable */ }
414
+ .gr-block.gr-group, .gr-tabs, .gr-accordion { background-color: rgba(44, 62, 80, 0.85) !important; border: 1px solid rgba(189, 195, 199, 0.2) !important; border-radius: 8px !important; box-shadow: 0 3px 5px rgba(0,0,0,0.15); margin-bottom: 1em; padding: 1em; }
415
+ .gr-tabitem { background-color: rgba(52, 73, 94, 0.8) !important; border-radius: 6px !important; padding: 1em !important; border: 1px solid rgba(189, 195, 199, 0.1) !important;}
416
+ .gr-textbox, .gr-dropdown, .gr-button, .gr-code, .gr-chat-message, .gr-json, .gr-file input[type="file"], .gr-file button { border: 1px solid rgba(189, 195, 199, 0.3) !important; background-color: rgba(52, 73, 94, 0.9) !important; color: #ecf0f1 !important; border-radius: 6px !important;}
417
+ .gr-file { background-color: transparent !important; border: none !important; padding:0 !important;}
418
+ .gr-file > .label-text { color: #bdc3c7 !important;}
419
+ .gr-textarea textarea, .gr-textbox input { color: #ecf0f1 !important; background-color: transparent !important; }
420
+ .gr-button.gr-button-primary { background-color: #1abc9c !important; color: white !important; border-color: #16a085 !important; }
421
+ .gr-button.gr-button-secondary { background-color: #9b59b6 !important; color: white !important; border-color: #8e44ad !important; }
422
+ .gr-button.gr-button-stop { background-color: #e74c3c !important; color: white !important; border-color: #c0392b !important; }
423
+ .gr-markdown { padding: 5px; }
424
+ .gr-markdown h1, .gr-markdown h2, .gr-markdown h3 { color: #1abc9c !important; border-bottom: 1px solid rgba(189, 195, 199, 0.2) !important; padding-bottom: 0.3em; margin-top:0.5em; margin-bottom: 0.5em; }
425
+ .gr-markdown p, .gr-markdown li { color: #ecf0f1 !important; line-height:1.6;}
426
+ .gr-markdown pre code { background-color: rgba(30, 40, 50, 0.95) !important; border: 1px solid rgba(189, 195, 199, 0.3) !important; color: #ecf0f1; border-radius: 4px; padding: 0.8em; }
427
+ .gr-chatbot { background-color: rgba(44, 62, 80, 0.7) !important; border: 1px solid rgba(189, 195, 199, 0.2) !important; }
428
+ .gr-chatbot .message { background-color: rgba(52, 73, 94, 0.9) !important; color: #ecf0f1 !important; border: 1px solid rgba(189, 195, 199, 0.2) !important; box-shadow: 0 1px 2px rgba(0,0,0,0.1) !important; }
429
+ .gr-chatbot .message.user { background-color: rgba(46, 204, 113, 0.8) !important; color: #2c3e50 !important; }
430
+ .gr-input-label > .label-text, .gr-dropdown-label > .label-text, .gr-checkbox-label > .label-text { color: #bdc3c7 !important; font-size: 0.9rem !important; margin-bottom: 0.2rem !important; }
431
+ .gr-info { color: #bdc3c7 !important; font-size: 0.85rem !important; }
432
+ .status-bar { background-color: rgba(44, 62, 80, 0.8) !important; padding: 0.75rem 1rem !important; border-radius: 8px; margin-bottom: 1rem; }
433
+ .tabnav button { background-color: rgba(52, 73, 94, 0.8) !important; color: #ecf0f1 !important; border-bottom: 3px solid transparent !important; border-top-left-radius: 6px !important; border-top-right-radius: 6px !important; margin-right: 3px !important; padding: 0.6em 1.2em !important; font-weight: 500;}
434
+ .tabnav button.selected { background-color: rgba(44, 62, 80, 0.95) !important; color: #1abc9c !important; border-bottom: 3px solid #1abc9c !important;}
435
+ .main-title { text-align: center; margin: 0 0 0.25rem 0 !important; padding-top: 0.5rem !important; color: #ecf0f1 !important; font-size: 2rem !important;} /* Added padding-top */
436
+ .main-subtitle { text-align: center; color: #bdc3c7; font-size: 1rem; margin: 0 0 1rem 0 !important; }
437
+ .sidebar-column { max-height: calc(100vh - 10rem); overflow-y: auto; /* Make sidebar scrollable if content exceeds height */ }
438
+ .main-content-column { max-height: calc(100vh - 10rem); overflow-y: auto; /* Make main content scrollable */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
439
  """
440
 
441
+ with gr.Blocks(theme=custom_theme, css=custom_css, title="AI Research Agent v5.6") as demo:
442
+ gr.HTML("<style>" + custom_css + "</style>")
443
 
444
+ with gr.Column(elem_classes="main-app-content-wrapper"): # This outer column will handle overall page scroll
445
+ gr.Markdown("# πŸ€– AI Research Agent", elem_classes="main-title")
446
+ gr.Markdown("Configure AI, chat for research, and manage its knowledge base.", elem_classes="main-subtitle")
447
+
448
+ avail_provs = get_available_providers()
449
+ def_prov = avail_provs[0] if avail_provs else None
450
+ def_models = get_model_display_names_for_provider(def_prov) if def_prov else []
451
+ def_model_disp = get_default_model_display_name_for_provider(def_prov) if def_prov else None
452
+
453
+ with gr.Row(elem_classes="status-bar"):
454
+ with gr.Column(scale=3): agent_stat_tb = gr.Textbox(label="Agent Status", interactive=False, lines=1, value="Initializing systems...")
455
+ with gr.Column(scale=1): memory_backend_info_tb = gr.Textbox(label="Memory Backend", value=f"{MEMORY_STORAGE_BACKEND}", interactive=False)
456
+ if MEMORY_STORAGE_BACKEND == "SQLITE":
457
+ with gr.Column(scale=2): gr.Textbox(label="SQLite Path", value=f"{MEMORY_SQLITE_PATH}", interactive=False)
458
+ elif MEMORY_STORAGE_BACKEND == "HF_DATASET":
459
+ with gr.Column(scale=2): gr.Textbox(label="HF Repos", value=f"M: {MEMORY_HF_MEM_REPO}, R: {MEMORY_HF_RULES_REPO}", interactive=False)
460
 
461
+ with gr.Row(equal_height=False):
462
+ with gr.Column(scale=1, min_width=360, elem_classes="sidebar-column"):
463
+ gr.Markdown("## βš™οΈ Configuration")
464
+ with gr.Group():
465
+ gr.Markdown("### AI Model Settings")
466
+ api_key_tb = gr.Textbox(label="AI Provider API Key (Optional Override)", type="password", placeholder="Paste key or use .env")
467
+ prov_sel_dd = gr.Dropdown(label="AI Provider", choices=avail_provs, value=def_prov, interactive=True)
468
+ model_sel_dd = gr.Dropdown(label="AI Model", choices=def_models, value=def_model_disp, interactive=True)
469
+ with gr.Group():
470
+ gr.Markdown("### System Prompt")
471
+ sys_prompt_tb = gr.Textbox(label="System Prompt Base", lines=12, value=DEFAULT_SYSTEM_PROMPT, interactive=True)
472
+ gr.Markdown(f"**Memory Backend:** {MEMORY_STORAGE_BACKEND}")
473
+ if MEMORY_STORAGE_BACKEND == "RAM":
474
+ save_faiss_sidebar_btn = gr.Button("Save FAISS Indices", variant="secondary")
475
 
476
+ with gr.Column(scale=2, elem_classes="main-content-column"):
477
+ with gr.Tabs():
478
+ with gr.TabItem("πŸ’¬ AI Chat & Research Output"):
479
+ gr.Markdown("## πŸ’¬ AI Chat Interface")
480
+ main_chat_disp = gr.Chatbot(label="AI Research Chat", height=500, bubble_full_width=False, avatar_images=(None, "https://raw.githubusercontent.com/huggingface/brand-assets/main/hf-logo-with-title.png"), show_copy_button=True, render_markdown=True, sanitize_html=True)
481
+ with gr.Row():
482
+ user_msg_tb = gr.Textbox(show_label=False, placeholder="Ask your research question or give an instruction...", scale=7, lines=1, max_lines=5, autofocus=True)
483
+ send_btn = gr.Button("Send", variant="primary", scale=1, min_width=100)
484
+ with gr.Accordion("πŸ“ Full Response / Output Details", open=True):
485
+ fmt_report_tb = gr.Textbox(label="Full AI Response", lines=10, interactive=True, show_copy_button=True, value="*AI responses will appear here...*")
486
+ dl_report_btn = gr.DownloadButton("Download Report", interactive=False, visible=False)
487
+ detect_out_md = gr.Markdown("*Insights used or other intermediate details will show here...*")
488
 
489
+ with gr.TabItem("🧠 Knowledge Base Management"):
490
+ gr.Markdown("## Manage Stored Knowledge")
491
+ with gr.Row(equal_height=False):
492
+ with gr.Column(scale=1):
493
+ with gr.Group():
494
+ gr.Markdown("### πŸ“œ Rules (Learned Insights)")
495
+ rules_disp_ta = gr.TextArea(label="View/Edit Rules", lines=15, interactive=True, placeholder="Load or type rules here (one per line or '---' separated for multiple).")
496
+ with gr.Row():
497
+ view_rules_btn = gr.Button("πŸ”„ Load/Refresh Rules"); save_edited_rules_btn = gr.Button("πŸ’Ύ Save Edited Rules", variant="primary")
498
+ upload_rules_fobj = gr.File(label="Upload Rules File (.txt/.jsonl)", file_types=[".txt", ".jsonl"])
499
+ rules_stat_tb = gr.Textbox(label="Rules Operation Status", interactive=False, lines=2, placeholder="Status...")
500
+ with gr.Row(): clear_rules_btn = gr.Button("⚠️ Clear All Rules", variant="stop")
501
+
502
+ with gr.Column(scale=1): # This was nested incorrectly before
503
+ with gr.Group():
504
+ gr.Markdown("### πŸ“š Memories (Past Interactions)")
505
+ mems_disp_json = gr.JSON(label="View Memories (JSON format)")
506
+ with gr.Row(): view_mems_btn = gr.Button("πŸ”„ Load/Refresh Memories")
507
+ upload_mems_fobj = gr.File(label="Upload Memories File (.jsonl)", file_types=[".jsonl"])
508
+ mems_stat_tb = gr.Textbox(label="Memories Operation Status", interactive=False, lines=2, placeholder="Status...")
509
+ clear_mems_btn = gr.Button("⚠️ Clear All Memories", variant="stop")
 
 
510
 
511
  def dyn_upd_model_dd(sel_prov_dyn:str): models_dyn, def_model_dyn = get_model_display_names_for_provider(sel_prov_dyn), get_default_model_display_name_for_provider(sel_prov_dyn); return gr.Dropdown(choices=models_dyn, value=def_model_dyn, interactive=True)
512
  prov_sel_dd.change(fn=dyn_upd_model_dd, inputs=prov_sel_dd, outputs=model_sel_dd)
 
535
  upload_rules_fobj.upload(fn=ui_upload_rules_action_fn, inputs=[upload_rules_fobj], outputs=[rules_stat_tb], show_progress="full").then(fn=ui_view_rules_action_fn, outputs=rules_disp_ta)
536
  clear_rules_btn.click(fn=lambda: "All rules cleared." if clear_all_rules_data_backend() else "Error clearing rules.", outputs=rules_stat_tb).then(fn=ui_view_rules_action_fn, outputs=rules_disp_ta)
537
 
538
+ if MEMORY_STORAGE_BACKEND == "RAM" and 'save_faiss_sidebar_btn' in locals() and save_faiss_sidebar_btn is not None: # Check button in sidebar
539
+ def save_faiss_action_with_feedback_sidebar_fn(): save_faiss_indices_to_disk(); gr.Info("Attempted to save FAISS indices to disk (Sidebar).")
540
+ save_faiss_sidebar_btn.click(fn=save_faiss_action_with_feedback_sidebar_fn, inputs=None, outputs=None)
541
 
542
  view_mems_btn.click(fn=ui_view_memories_action_fn, outputs=mems_disp_json)
543
  upload_mems_fobj.upload(fn=ui_upload_memories_action_fn, inputs=[upload_mems_fobj], outputs=[mems_stat_tb], show_progress="full").then(fn=ui_view_memories_action_fn, outputs=mems_disp_json)
 
554
  demo.load(fn=app_load_fn, inputs=None, outputs=[agent_stat_tb, rules_disp_ta, mems_disp_json])
555
 
556
  if __name__ == "__main__":
557
+ logger.info(f"Starting Gradio AI Research Mega Agent (v5.6 - UI Layout Fix, Memory: {MEMORY_STORAGE_BACKEND})...")
558
  app_port, app_server = int(os.getenv("GRADIO_PORT", 7860)), os.getenv("GRADIO_SERVER_NAME", "127.0.0.1")
559
  app_debug, app_share = os.getenv("GRADIO_DEBUG", "False").lower()=="true", os.getenv("GRADIO_SHARE", "False").lower()=="true"
560
  logger.info(f"Launching Gradio server: http://{app_server}:{app_port}. Debug: {app_debug}, Share: {app_share}")