| import os |
| import json |
| import uuid |
| import pandas as pd |
| import gradio as gr |
| from dotenv import load_dotenv |
|
|
| |
| load_dotenv() |
|
|
| |
| from agent import agent |
|
|
| |
| |
| |
|
|
| def get_progress_html(): |
| """Builds the HTML progress bar by checking for local checkpoint files.""" |
| phases = { |
| "Phase 1: Familiarisation": "summaries.json", |
| "Phase 2: Initial Codes": "labels.json", |
| "Phase 3: Themes": "themes.json", |
| "Phase 5.5: PAJAIS": "taxonomy_map.json", |
| "Phase 6: Report": "comparison.csv" |
| } |
|
|
| html = "<div style='display: flex; justify-content: space-between; padding: 15px; background: #2b2b2b; color: white; border-radius: 8px; font-family: sans-serif;'>" |
| for name, file in phases.items(): |
| status = "✅" if os.path.exists(file) else "⬜" |
| html += f"<span>{status} <b>{name}</b></span>" |
| html += "</div>" |
| return html |
|
|
| def load_review_table(): |
| """Loads the highest priority JSON into the Review Table format based on Phase progress.""" |
| columns = ["#", "Topic Label", "Top Evidence", "Sentences", "Papers", "Approve", "Rename To", "Reasoning"] |
| empty_df = pd.DataFrame(columns=columns) |
|
|
| try: |
| |
| if os.path.exists("taxonomy_map.json"): |
| with open("taxonomy_map.json", "r") as f: data = json.load(f) |
| rows = [[i, k, f"→ {v.get('pajais_match', 'NOVEL')} | {v.get('reasoning', '')}", "", "", "", "", ""] for i, (k, v) in enumerate(data.items())] |
| return pd.DataFrame(rows, columns=columns) |
|
|
| |
| if os.path.exists("themes.json"): |
| with open("themes.json", "r") as f: data = json.load(f) |
| rows = [[i, k, " | ".join(v.get("top_sentences", [])), v.get("size", ""), v.get("papers_count", ""), "", "", ""] for i, (k, v) in enumerate(data.items())] |
| return pd.DataFrame(rows, columns=columns) |
|
|
| |
| if os.path.exists("labels.json"): |
| with open("labels.json", "r") as f: data = json.load(f) |
| rows = [[i, v.get("label", "Unknown"), f"Category: {v.get('category', '')} | {v.get('reasoning', '')}", "", "", "", "", ""] for i, (k, v) in enumerate(data.items())] |
| return pd.DataFrame(rows, columns=columns) |
|
|
| |
| if os.path.exists("summaries.json"): |
| with open("summaries.json", "r") as f: data = json.load(f) |
| rows = [[i, f"Topic {k}", " | ".join(v.get("top_sentences", [])), v.get("size", ""), v.get("papers_count", ""), "", "", ""] for i, (k, v) in enumerate(data.items())] |
| return pd.DataFrame(rows, columns=columns) |
|
|
| except Exception: |
| pass |
|
|
| return empty_df |
|
|
| def get_available_downloads(): |
| """Returns a list of all current checkpoint files for the Download tab.""" |
| target_files = ["processed_data.json", "summaries.json", "emb.npy", "charts.html", "labels.json", "themes.json", "taxonomy_map.json", "comparison.csv", "narrative.txt"] |
| return [f for f in target_files if os.path.exists(f)] |
|
|
| def load_chart_view(chart_name): |
| """Loads a mock view of the requested chart (assumes charts.html holds them).""" |
| if os.path.exists("charts.html"): |
| with open("charts.html", "r") as f: |
| return f.read() |
| return "<div style='padding: 20px;'>Charts not yet generated. Complete Phase 2.</div>" |
|
|
| |
| |
| |
|
|
| def interact_with_agent(user_message, chat_history, thread_id): |
| """Sends a message to LangGraph and extracts the AI response.""" |
|
|
| |
| chat_history.append({"role": "user", "content": user_message}) |
|
|
| |
| chat_history.append({"role": "assistant", "content": "⏳ Thinking..."}) |
|
|
| yield chat_history, get_progress_html(), load_review_table(), get_available_downloads() |
|
|
| config = {"configurable": {"thread_id": thread_id}} |
|
|
| try: |
| |
| result = agent.invoke({"messages": [("user", user_message)]}, config=config) |
|
|
| |
| ai_response = result["messages"][-1].content |
|
|
| |
| chat_history[-1] = {"role": "assistant", "content": ai_response} |
|
|
| except Exception as e: |
| |
| chat_history[-1] = {"role": "assistant", "content": f"❌ Error communicating with agent: {str(e)}"} |
|
|
| |
| yield chat_history, get_progress_html(), load_review_table(), get_available_downloads() |
|
|
| def handle_csv_upload(file_obj, chat_history, thread_id): |
| if file_obj is None: |
| return chat_history, get_progress_html(), load_review_table(), get_available_downloads() |
| |
| import shutil |
| shutil.copy(file_obj.name, "Scopus.csv") |
| |
| yield from interact_with_agent("Analyze my Scopus CSV", chat_history, thread_id) |
|
|
| def handle_submit_review(df, chat_history, thread_id): |
| """Converts the Gradio Dataframe edits into a string and sends to the agent.""" |
| |
| review_data = df[df["Approve"].astype(str).str.strip() != ""].to_dict(orient="records") |
|
|
| if not review_data: |
| msg = "I submitted the table, but I didn't make any edits." |
| else: |
| msg = f"Here is my submitted review table data:\n{json.dumps(review_data, indent=2)}" |
|
|
| yield from interact_with_agent(msg, chat_history, thread_id) |
|
|
|
|
| |
| |
| |
|
|
| with gr.Blocks(title="B&C Thematic Analysis Agent", theme=gr.themes.Soft()) as demo: |
| |
| session_thread_id = gr.State(value=lambda: str(uuid.uuid4())) |
|
|
| gr.Markdown("# 🧠 Braun & Clarke (2006) Thematic Analysis AI Agent") |
|
|
| |
| progress_bar = gr.HTML(value=get_progress_html()) |
|
|
| with gr.Row(): |
| |
| with gr.Column(scale=1, variant="panel"): |
| gr.Markdown("### ① DATA INPUT") |
| csv_upload = gr.File(label="Upload Scopus CSV", file_types=[".csv"]) |
|
|
| gr.Markdown("### ② AGENT CONVERSATION") |
| chatbot = gr.Chatbot(label="Agent Dialogue", height=400) |
| with gr.Row(): |
| user_input = gr.Textbox(show_label=False, placeholder="Type your message...", scale=4) |
| send_btn = gr.Button("Send", variant="primary", scale=1) |
|
|
| |
| with gr.Column(scale=2, variant="panel"): |
| gr.Markdown("### ③ RESULTS") |
| with gr.Tabs(): |
|
|
| |
| with gr.TabItem("Review Table"): |
| gr.Markdown("*Edit the 'Approve', 'Rename To', and 'Reasoning' columns to guide the agent.*") |
| review_table = gr.Dataframe( |
| value=load_review_table(), |
| headers=["#", "Topic Label", "Top Evidence", "Sentences", "Papers", "Approve", "Rename To", "Reasoning"], |
| datatype=["number", "str", "str", "str", "str", "str", "str", "str"], |
| interactive=True, |
| wrap=True |
| ) |
| submit_review_btn = gr.Button("Submit Review", variant="primary") |
|
|
| |
| with gr.TabItem("Charts"): |
| chart_dropdown = gr.Dropdown(choices=["Intertopic Map", "Bar Chart", "Hierarchy", "Heatmap"], label="Select Chart", value="Bar Chart") |
| chart_html = gr.HTML(value=load_chart_view("Bar Chart")) |
|
|
| |
| with gr.TabItem("Download"): |
| download_files = gr.File(label="Checkpoint Files", file_count="multiple", value=get_available_downloads()) |
|
|
| |
| |
| |
|
|
| |
| update_outputs = [chatbot, progress_bar, review_table, download_files] |
|
|
| |
| csv_upload.upload( |
| handle_csv_upload, |
| inputs=[csv_upload, chatbot, session_thread_id], |
| outputs=update_outputs |
| ) |
|
|
| |
| send_btn.click( |
| interact_with_agent, |
| inputs=[user_input, chatbot, session_thread_id], |
| outputs=update_outputs |
| ).then(lambda: "", None, user_input) |
|
|
| user_input.submit( |
| interact_with_agent, |
| inputs=[user_input, chatbot, session_thread_id], |
| outputs=update_outputs |
| ).then(lambda: "", None, user_input) |
|
|
| |
| submit_review_btn.click( |
| handle_submit_review, |
| inputs=[review_table, chatbot, session_thread_id], |
| outputs=update_outputs |
| ) |
|
|
| |
| chart_dropdown.change( |
| load_chart_view, |
| inputs=[chart_dropdown], |
| outputs=[chart_html] |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |
|
|