From f8a363059e89f0970fdc963263b8a5649a38582d Mon Sep 17 00:00:00 2001 From: Jenna Li Date: Sat, 1 Nov 2025 19:05:10 +0000 Subject: [PATCH 01/18] front-end initial commit --- README.md | 36 +++++ agents/allocation_agent.py | 0 agents/text_agent.py | 41 ++++-- .../.streamlit/config.toml | 23 +++ .../client_secret.json | 13 ++ .../data/transcripts_dataset.json | 3 + .../utils/genai_client.py | 12 ++ frontend/app.py | 133 ++++++++++++++++++ requirements.txt | 5 +- transcripts_dataset.json | 14 ++ 10 files changed, 268 insertions(+), 12 deletions(-) create mode 100644 README.md delete mode 100644 agents/allocation_agent.py create mode 100644 frontend/agentverse-streamlit-app/.streamlit/config.toml create mode 100644 frontend/agentverse-streamlit-app/client_secret.json create mode 100644 frontend/agentverse-streamlit-app/data/transcripts_dataset.json create mode 100644 frontend/agentverse-streamlit-app/utils/genai_client.py create mode 100644 transcripts_dataset.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..ac1c545 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# Agentverse Streamlit Chat + +This repository includes a minimal Streamlit web UI that lets users log in with Google (OAuth 2.0), send messages to a text agent, and view the agent's response. + +Key pieces +- `frontend/app.py` - Streamlit app. Shows "Login with Google" button, chat UI, and forwards messages to `agents/text_agent.py`. +- `agents/text_agent.py` - Refactored text agent with `generate_text(prompt)` and CLI entrypoint; writes `text_agent_output.json`. +- `requirements.txt` - lists required Python packages (Streamlit and Google auth libs added). + +Quick setup +1. Create a Google OAuth client (Web application) in Google Cloud Console. + - Set the authorized redirect URI to `http://localhost:8501/` (for local Streamlit runs). + - Download the JSON and save it as `frontend/agentverse-streamlit-app/client_secrets.json`. + +2. Install Python deps + python -m pip install -r requirements.txt + +3. Set the GenAI API key for the text agent as an environment variable: + export GENAI_API_KEY="your_genai_api_key" + +4. Run the Streamlit app from repository root: + streamlit run frontend/app.py + +5. Click "Login with Google" and complete the consent flow. After redirect back to the app, you'll be logged in. Type into the chat box and press Send. + +Notes and assumptions +- The app implements a server-side OAuth flow using `google-auth-oauthlib`. The `client_secrets.json` must contain the web client credentials (the file Google gives you for web apps). +- This setup assumes you register `http://localhost:8501/` as the OAuth redirect URI in Google Cloud Console. +- The text agent uses the `google.genai` library (existing in the repo). Ensure the `GENAI_API_KEY` env var is set. + +Security +- Never commit `client_secrets.json` or API keys to source control. Keep them local or in a secure secret manager. + +If you want, I can: +- Add a client-side Google Identity button instead of server-side OAuth. +- Improve the chat UI with nicer bubbles and history persistence. diff --git a/agents/allocation_agent.py b/agents/allocation_agent.py deleted file mode 100644 index e69de29..0000000 diff --git a/agents/text_agent.py b/agents/text_agent.py index ea45f9d..b63331c 100644 --- a/agents/text_agent.py +++ b/agents/text_agent.py @@ -1,15 +1,34 @@ -from flask import Flask, request -import requests, json +import os +import sys +import json from google import genai -client = genai.Client(api_key="AIzaSyBZ7LA5XV7kdbJLvLtNswCMirlxdo4l6w0") +# Text agent entrypoint and function. Reads GENAI_API_KEY from environment. +API_KEY = os.environ.get("GENAI_API_KEY") or "AIzaSyBZ7LA5XV7kdbJLvLtNswCMirlxdo4l6w0" -response = client.models.generate_content( - model="gemini-2.5-flash", - contents="what is the meaning of life", -) +def generate_text(prompt: str) -> str: + """Generate text using GenAI and persist to `text_agent_output.json`. -## save output in json -output = {"text_output": response.text} -with open("text_agent_output.json", "a") as f: - json.dump(output, f, indent=2) + Returns the generated text. + """ + client = genai.Client(api_key=API_KEY) + response = client.models.generate_content( + model="gemini-2.5-flash", + contents=prompt, + ) + output = {"text_output": response.text} + # write (overwrite) the output file so UI can read latest + with open("text_agent_output.json", "w", encoding="utf-8") as f: + json.dump(output, f, indent=2) + return response.text + + +if __name__ == "__main__": + # Allow calling as: python agents/text_agent.py "your prompt here" + prompt = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else "what is the meaning of life" + try: + text = generate_text(prompt) + print(text) + except Exception as exc: + print(f"Error generating text: {exc}") + raise diff --git a/frontend/agentverse-streamlit-app/.streamlit/config.toml b/frontend/agentverse-streamlit-app/.streamlit/config.toml new file mode 100644 index 0000000..58d3686 --- /dev/null +++ b/frontend/agentverse-streamlit-app/.streamlit/config.toml @@ -0,0 +1,23 @@ +[general] +email = "your_email@example.com" +# The email address used for Streamlit sharing notifications. + +[server] +headless = true +# Run the app in headless mode (no browser UI). +port = 8501 +# The port on which the Streamlit app will run. +enableCORS = false +# Disable CORS for local development. + +[theme] +primaryColor = "#1f77b4" +# The primary color for the app's theme. +backgroundColor = "#ffffff" +# The background color for the app. +secondaryBackgroundColor = "#f0f0f0" +# The secondary background color for the app. +textColor = "#000000" +# The text color for the app. +font = "sans serif" +# The font used in the app. \ No newline at end of file diff --git a/frontend/agentverse-streamlit-app/client_secret.json b/frontend/agentverse-streamlit-app/client_secret.json new file mode 100644 index 0000000..e40d827 --- /dev/null +++ b/frontend/agentverse-streamlit-app/client_secret.json @@ -0,0 +1,13 @@ +{ + "web": { + "client_id": "1050515079869-r385r3ol558o5fqjc3upukc9bqc80ufq.apps.googleusercontent.com", + "project_id": "theta-carving-476918-g7", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_secret": "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", + "redirect_uris": [ + "http://localhost:8501/" + ] + } +} \ No newline at end of file diff --git a/frontend/agentverse-streamlit-app/data/transcripts_dataset.json b/frontend/agentverse-streamlit-app/data/transcripts_dataset.json new file mode 100644 index 0000000..1a095b5 --- /dev/null +++ b/frontend/agentverse-streamlit-app/data/transcripts_dataset.json @@ -0,0 +1,3 @@ +{ + "transcripts": [] +} \ No newline at end of file diff --git a/frontend/agentverse-streamlit-app/utils/genai_client.py b/frontend/agentverse-streamlit-app/utils/genai_client.py new file mode 100644 index 0000000..135f600 --- /dev/null +++ b/frontend/agentverse-streamlit-app/utils/genai_client.py @@ -0,0 +1,12 @@ +from google import genai + +class GenAIClient: + def __init__(self, api_key): + self.client = genai.Client(api_key=api_key) + + def generate_content(self, prompt): + response = self.client.models.generate_content( + model="gemini-2.5-flash", + contents=prompt, + ) + return response.text \ No newline at end of file diff --git a/frontend/app.py b/frontend/app.py index e69de29..f59d23b 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -0,0 +1,133 @@ +import os +import json +import subprocess +import sys +from pathlib import Path + +import streamlit as st + +from google_auth_oauthlib.flow import Flow +from google.oauth2 import id_token +from google.auth.transport import requests as grequests + +# Paths and config +HERE = Path(__file__).parent +AGENTS_TEXT_AGENT = Path(__file__).parent.joinpath("..", "agents", "text_agent.py").resolve() +OUTPUT_JSON = Path("text_agent_output.json") + +CLIENT_SECRETS_FILE = HERE.joinpath("agentverse-streamlit-app", "client_secrets.json") + +SCOPES = ["openid", "email", "profile"] + + +def load_client_config(): + if CLIENT_SECRETS_FILE.exists(): + return json.loads(CLIENT_SECRETS_FILE.read_text(encoding="utf-8")) + return None + + +def login_flow(): + client_config = load_client_config() + if not client_config: + st.error("Google client_secrets.json not found in frontend/agentverse-streamlit-app/. Please add it.") + return None + + flow = Flow.from_client_config(client_config, scopes=SCOPES, redirect_uri="http://localhost:8501/") + auth_url, state = flow.authorization_url(prompt="consent", include_granted_scopes="true", access_type="offline") + return auth_url, state + + +def exchange_code_for_user(code: str, state: str): + client_config = load_client_config() + flow = Flow.from_client_config(client_config, scopes=SCOPES, state=state, redirect_uri="http://localhost:8501/") + flow.fetch_token(code=code) + credentials = flow.credentials + # verify id token and extract user info + idinfo = id_token.verify_oauth2_token(credentials.id_token, grequests.Request(), client_config["web"]["client_id"]) + return {"name": idinfo.get("name"), "email": idinfo.get("email")} + + +st.set_page_config(page_title="Agentverse Chat", layout="wide") + +st.title("Agentverse — Chat with Text Agent") + +# Authentication section +params = st.experimental_get_query_params() + +if "user" not in st.session_state: + st.session_state.user = None + +if "messages" not in st.session_state: + st.session_state.messages = [] + +if params.get("code"): + code = params.get("code")[0] + state = params.get("state", [""])[0] + try: + user = exchange_code_for_user(code, state) + st.session_state.user = user + # Clean query params by reloading without params + st.experimental_set_query_params() + st.success(f"Logged in as {user.get('name')}") + except Exception as exc: + st.error(f"Login failed: {exc}") + +col1, col2 = st.columns([1, 3]) + +with col1: + st.header("Account") + if st.session_state.user: + st.write(f"**Name:** {st.session_state.user.get('name')} ") + st.write(f"**Email:** {st.session_state.user.get('email')} ") + if st.button("Logout"): + st.session_state.user = None + st.experimental_rerun() + else: + st.write("You are not logged in.") + auth = login_flow() + if auth: + auth_url, state = auth + st.markdown(f'', unsafe_allow_html=True) + else: + st.info("Place your Google OAuth client_secrets.json at `frontend/agentverse-streamlit-app/client_secrets.json`.") + +with col2: + st.header("Chat") + + def render_messages(): + for m in st.session_state.messages: + role = m.get("role") + text = m.get("text") + if role == "user": + st.markdown(f"
**You:** {text}
", unsafe_allow_html=True) + else: + st.markdown(f"
**Assistant:** {text}
", unsafe_allow_html=True) + + render_messages() + + user_input = st.text_input("Message", key="user_input") + if st.button("Send") and user_input: + # append user message + st.session_state.messages.append({"role": "user", "text": user_input}) + + # call text agent script (uses GENAI_API_KEY env var) + try: + subprocess.run([sys.executable, str(AGENTS_TEXT_AGENT), user_input], check=True) + if OUTPUT_JSON.exists(): + data = json.loads(OUTPUT_JSON.read_text(encoding="utf-8")) + assistant_text = data.get("text_output", "") + else: + assistant_text = "(no response, text_agent did not produce output)" + except Exception as exc: + assistant_text = f"Error running text agent: {exc}" + + st.session_state.messages.append({"role": "assistant", "text": assistant_text}) + # re-render + st.experimental_rerun() + + # small note about identity tracking + if st.session_state.user: + st.caption(f"Requests are associated with {st.session_state.user.get('email')}") + else: + st.caption("You can log in with Google to associate messages with your identity.") + diff --git a/requirements.txt b/requirements.txt index 454799d..dbc4033 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ flask django -requests \ No newline at end of file +requests +streamlit +google-auth +google-auth-oauthlib \ No newline at end of file diff --git a/transcripts_dataset.json b/transcripts_dataset.json new file mode 100644 index 0000000..1bf2b79 --- /dev/null +++ b/transcripts_dataset.json @@ -0,0 +1,14 @@ +[ + { + "speaker": "0", + "transcript": "Hello, hello hello.", + "start_time": 1.006, + "end_time": 3.146 + }, + { + "speaker": "1", + "transcript": "I'm happy. E is working.", + "start_time": 3.806, + "end_time": 6.026 + } +] \ No newline at end of file From 7d42c2179815a8872e5819b5f2b875d719f0723c Mon Sep 17 00:00:00 2001 From: Jenna Li Date: Sat, 1 Nov 2025 19:57:25 +0000 Subject: [PATCH 02/18] trying to fix Google OAuth issue, need help --- ...client_secret.json => client_secrets.json} | 0 frontend/app.py | 123 ++++++++++++++++-- 2 files changed, 110 insertions(+), 13 deletions(-) rename frontend/agentverse-streamlit-app/{client_secret.json => client_secrets.json} (100%) diff --git a/frontend/agentverse-streamlit-app/client_secret.json b/frontend/agentverse-streamlit-app/client_secrets.json similarity index 100% rename from frontend/agentverse-streamlit-app/client_secret.json rename to frontend/agentverse-streamlit-app/client_secrets.json diff --git a/frontend/app.py b/frontend/app.py index f59d23b..1f3305b 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -17,7 +17,13 @@ CLIENT_SECRETS_FILE = HERE.joinpath("agentverse-streamlit-app", "client_secrets.json") -SCOPES = ["openid", "email", "profile"] +# SCOPES = ["openid", "email", "profile"] +SCOPES = [ + "openid", + # Use the explicit userinfo scopes Google returns for profile/email + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/userinfo.profile", +] def load_client_config(): @@ -34,6 +40,13 @@ def login_flow(): flow = Flow.from_client_config(client_config, scopes=SCOPES, redirect_uri="http://localhost:8501/") auth_url, state = flow.authorization_url(prompt="consent", include_granted_scopes="true", access_type="offline") + # Persist the state so we can validate it when Google redirects back. + # Streamlit session_state survives until the user closes the browser tab. + try: + st.session_state["oauth_state"] = state + except Exception: + # If session_state isn't available for some reason, continue without storing. + pass return auth_url, state @@ -51,8 +64,43 @@ def exchange_code_for_user(code: str, state: str): st.title("Agentverse — Chat with Text Agent") + +def _rerun_compat(): + """Try to programmatically rerun the Streamlit script in a version-compatible way. + + Tries in order: + - call st.experimental_rerun() if present + - raise Streamlit's internal RerunException from known import paths + - if none available, instruct the user to refresh manually + """ + # Preferred API + if hasattr(st, "experimental_rerun"): + try: + st.experimental_rerun() + return + except Exception: + # fall through to internal approach + pass + + # Fallback: attempt to import and raise the internal RerunException + for import_path in ( + "streamlit.runtime.scriptrunner.script_runner", + "streamlit.report_thread", + ): + try: + mod = __import__(import_path, fromlist=["RerunException"]) + RerunException = getattr(mod, "RerunException") + raise RerunException() + except Exception: + continue + + # Last resort: tell the user to refresh + st.warning("Unable to programmatically rerun Streamlit for this environment — please refresh the page manually.") + # Authentication section -params = st.experimental_get_query_params() +# Use the stable `st.query_params` property instead of the deprecated +# `st.experimental_get_query_params()` which was removed after 2024-04-11. +params = st.query_params if "user" not in st.session_state: st.session_state.user = None @@ -62,15 +110,58 @@ def exchange_code_for_user(code: str, state: str): if params.get("code"): code = params.get("code")[0] - state = params.get("state", [""])[0] - try: - user = exchange_code_for_user(code, state) - st.session_state.user = user - # Clean query params by reloading without params - st.experimental_set_query_params() - st.success(f"Logged in as {user.get('name')}") - except Exception as exc: - st.error(f"Login failed: {exc}") + returned_state = params.get("state", [""])[0] + # Validate state against stored session state (if present) + stored_state = st.session_state.get("oauth_state") + if stored_state and stored_state != returned_state: + st.error("Login failed: OAuth state mismatch. Please try signing in again.") + # clear stored state to force a fresh login next time + st.session_state.pop("oauth_state", None) + else: + try: + user = exchange_code_for_user(code, returned_state) + st.session_state.user = user + # clear stored oauth_state + st.session_state.pop("oauth_state", None) + # Clean query params by reloading without params + st.experimental_set_query_params() + st.success(f"Logged in as {user.get('name')}") + except Exception as exc: + # Provide a more actionable error message for common causes + msg = str(exc) + if "invalid_grant" in msg or "Malformed auth code" in msg: + st.error("Login failed: received an invalid or malformed auth code. Possible causes: you copied the code manually, the code was modified by the browser, or the code expired.") + # Show extra debug hints that are safe (no secrets): code length, state, client config shape + try: + code_preview = (code[:8] + "...") if code else "(empty)" + st.info(f"Code preview: {code_preview} (length={len(code) if code else 0})") + except Exception: + pass + try: + client_cfg = load_client_config() + if client_cfg is None: + st.info("client_secrets.json missing or unreadable") + else: + st.info(f"client_secrets contains keys: {list(client_cfg.keys())}") + web = client_cfg.get("web") or client_cfg.get("installed") + if web: + st.info(f"client_id: {web.get('client_id')}") + uris = web.get('redirect_uris') or web.get('redirect_uri') or [] + st.info(f"configured redirect_uris: {uris}") + except Exception: + pass + st.write("Try these steps:\n- Close other tabs for this app and try again.\n- Use an incognito window.\n- Ensure the redirect URI in the Google Cloud Console is exactly `http://localhost:8501/` (including trailing slash).\n- If you've previously authorized the app, remove its access from your Google Account and retry.") + # Clear stored state to force a fresh login on next attempt + st.session_state.pop("oauth_state", None) + if st.button("Retry login"): + st.session_state.pop("oauth_state", None) + _rerun_compat() + else: + st.error(f"Login failed: {exc}") + # expose a retry option for other errors as well + if st.button("Retry login"): + st.session_state.pop("oauth_state", None) + _rerun_compat() col1, col2 = st.columns([1, 3]) @@ -81,13 +172,19 @@ def exchange_code_for_user(code: str, state: str): st.write(f"**Email:** {st.session_state.user.get('email')} ") if st.button("Logout"): st.session_state.user = None - st.experimental_rerun() + # st.experimental_rerun() + _rerun_compat() else: st.write("You are not logged in.") auth = login_flow() if auth: auth_url, state = auth st.markdown(f'', unsafe_allow_html=True) + # Use a same-tab navigation button (onclick) so Streamlit session_state remains the + # same browser session. Opening the auth URL in a different tab can cause an + # OAuth state mismatch because Streamlit session_state is tab-scoped. + ##btn_html = f"" + ##st.markdown(btn_html, unsafe_allow_html=True) else: st.info("Place your Google OAuth client_secrets.json at `frontend/agentverse-streamlit-app/client_secrets.json`.") @@ -123,7 +220,7 @@ def render_messages(): st.session_state.messages.append({"role": "assistant", "text": assistant_text}) # re-render - st.experimental_rerun() + _rerun_compat() # small note about identity tracking if st.session_state.user: From bbb3a5848d5a891643e6d430c4fc4c32338b65b1 Mon Sep 17 00:00:00 2001 From: Tan Wee Joe <84664178+w3joe@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:08:20 +0000 Subject: [PATCH 03/18] Update app.py --- frontend/app.py | 139 ++++++++++++++++++++---------------------------- 1 file changed, 57 insertions(+), 82 deletions(-) diff --git a/frontend/app.py b/frontend/app.py index 1f3305b..49fc47b 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -17,10 +17,8 @@ CLIENT_SECRETS_FILE = HERE.joinpath("agentverse-streamlit-app", "client_secrets.json") -# SCOPES = ["openid", "email", "profile"] SCOPES = [ "openid", - # Use the explicit userinfo scopes Google returns for profile/email "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile", ] @@ -40,12 +38,9 @@ def login_flow(): flow = Flow.from_client_config(client_config, scopes=SCOPES, redirect_uri="http://localhost:8501/") auth_url, state = flow.authorization_url(prompt="consent", include_granted_scopes="true", access_type="offline") - # Persist the state so we can validate it when Google redirects back. - # Streamlit session_state survives until the user closes the browser tab. try: st.session_state["oauth_state"] = state except Exception: - # If session_state isn't available for some reason, continue without storing. pass return auth_url, state @@ -66,40 +61,17 @@ def exchange_code_for_user(code: str, state: str): def _rerun_compat(): - """Try to programmatically rerun the Streamlit script in a version-compatible way. - - Tries in order: - - call st.experimental_rerun() if present - - raise Streamlit's internal RerunException from known import paths - - if none available, instruct the user to refresh manually - """ - # Preferred API - if hasattr(st, "experimental_rerun"): + """Try to programmatically rerun the Streamlit script in a version-compatible way.""" + try: + st.rerun() + except AttributeError: try: st.experimental_rerun() - return - except Exception: - # fall through to internal approach - pass - - # Fallback: attempt to import and raise the internal RerunException - for import_path in ( - "streamlit.runtime.scriptrunner.script_runner", - "streamlit.report_thread", - ): - try: - mod = __import__(import_path, fromlist=["RerunException"]) - RerunException = getattr(mod, "RerunException") - raise RerunException() except Exception: - continue + st.warning("Please refresh the page manually.") - # Last resort: tell the user to refresh - st.warning("Unable to programmatically rerun Streamlit for this environment — please refresh the page manually.") # Authentication section -# Use the stable `st.query_params` property instead of the deprecated -# `st.experimental_get_query_params()` which was removed after 2024-04-11. params = st.query_params if "user" not in st.session_state: @@ -108,58 +80,71 @@ def _rerun_compat(): if "messages" not in st.session_state: st.session_state.messages = [] -if params.get("code"): - code = params.get("code")[0] - returned_state = params.get("state", [""])[0] - # Validate state against stored session state (if present) +# Handle OAuth callback +if "code" in params: + code = params["code"] + returned_state = params.get("state", "") stored_state = st.session_state.get("oauth_state") + if stored_state and stored_state != returned_state: st.error("Login failed: OAuth state mismatch. Please try signing in again.") - # clear stored state to force a fresh login next time st.session_state.pop("oauth_state", None) else: try: user = exchange_code_for_user(code, returned_state) st.session_state.user = user - # clear stored oauth_state st.session_state.pop("oauth_state", None) - # Clean query params by reloading without params - st.experimental_set_query_params() + + # Clear query params - FIXED VERSION + st.query_params.clear() st.success(f"Logged in as {user.get('name')}") + _rerun_compat() + except Exception as exc: - # Provide a more actionable error message for common causes msg = str(exc) - if "invalid_grant" in msg or "Malformed auth code" in msg: - st.error("Login failed: received an invalid or malformed auth code. Possible causes: you copied the code manually, the code was modified by the browser, or the code expired.") - # Show extra debug hints that are safe (no secrets): code length, state, client config shape - try: - code_preview = (code[:8] + "...") if code else "(empty)" - st.info(f"Code preview: {code_preview} (length={len(code) if code else 0})") - except Exception: - pass - try: - client_cfg = load_client_config() - if client_cfg is None: - st.info("client_secrets.json missing or unreadable") - else: - st.info(f"client_secrets contains keys: {list(client_cfg.keys())}") - web = client_cfg.get("web") or client_cfg.get("installed") - if web: - st.info(f"client_id: {web.get('client_id')}") - uris = web.get('redirect_uris') or web.get('redirect_uri') or [] - st.info(f"configured redirect_uris: {uris}") - except Exception: - pass - st.write("Try these steps:\n- Close other tabs for this app and try again.\n- Use an incognito window.\n- Ensure the redirect URI in the Google Cloud Console is exactly `http://localhost:8501/` (including trailing slash).\n- If you've previously authorized the app, remove its access from your Google Account and retry.") - # Clear stored state to force a fresh login on next attempt + if "invalid_grant" in msg.lower() or "malformed" in msg.lower(): + st.error("Login failed: received an invalid or malformed auth code.") + + with st.expander("Debug Information"): + try: + code_preview = (code[:8] + "...") if code else "(empty)" + st.info(f"Code preview: {code_preview} (length={len(code) if code else 0})") + except: + pass + + try: + client_cfg = load_client_config() + if client_cfg is None: + st.info("client_secrets.json missing or unreadable") + else: + st.info(f"client_secrets contains keys: {list(client_cfg.keys())}") + web = client_cfg.get("web") or client_cfg.get("installed") + if web: + st.info(f"client_id: {web.get('client_id')}") + uris = web.get('redirect_uris') or web.get('redirect_uri') or [] + st.info(f"configured redirect_uris: {uris}") + except: + pass + + st.markdown(""" + **Try these steps:** + 1. Close all other tabs with this app + 2. Use an incognito/private window + 3. Verify redirect URI in Google Cloud Console is exactly `http://localhost:8501/` + 4. Remove app access from your Google Account and retry + 5. Check that your `client_secrets.json` is valid + """) + st.session_state.pop("oauth_state", None) - if st.button("Retry login"): + + if st.button("Clear and Retry Login"): + st.query_params.clear() st.session_state.pop("oauth_state", None) _rerun_compat() else: st.error(f"Login failed: {exc}") - # expose a retry option for other errors as well if st.button("Retry login"): + st.query_params.clear() st.session_state.pop("oauth_state", None) _rerun_compat() @@ -168,23 +153,18 @@ def _rerun_compat(): with col1: st.header("Account") if st.session_state.user: - st.write(f"**Name:** {st.session_state.user.get('name')} ") - st.write(f"**Email:** {st.session_state.user.get('email')} ") + st.write(f"**Name:** {st.session_state.user.get('name')}") + st.write(f"**Email:** {st.session_state.user.get('email')}") if st.button("Logout"): st.session_state.user = None - # st.experimental_rerun() + st.query_params.clear() _rerun_compat() else: st.write("You are not logged in.") auth = login_flow() if auth: auth_url, state = auth - st.markdown(f'', unsafe_allow_html=True) - # Use a same-tab navigation button (onclick) so Streamlit session_state remains the - # same browser session. Opening the auth URL in a different tab can cause an - # OAuth state mismatch because Streamlit session_state is tab-scoped. - ##btn_html = f"" - ##st.markdown(btn_html, unsafe_allow_html=True) + st.link_button("Login with Google", auth_url) else: st.info("Place your Google OAuth client_secrets.json at `frontend/agentverse-streamlit-app/client_secrets.json`.") @@ -204,10 +184,8 @@ def render_messages(): user_input = st.text_input("Message", key="user_input") if st.button("Send") and user_input: - # append user message st.session_state.messages.append({"role": "user", "text": user_input}) - # call text agent script (uses GENAI_API_KEY env var) try: subprocess.run([sys.executable, str(AGENTS_TEXT_AGENT), user_input], check=True) if OUTPUT_JSON.exists(): @@ -219,12 +197,9 @@ def render_messages(): assistant_text = f"Error running text agent: {exc}" st.session_state.messages.append({"role": "assistant", "text": assistant_text}) - # re-render _rerun_compat() - # small note about identity tracking if st.session_state.user: st.caption(f"Requests are associated with {st.session_state.user.get('email')}") else: - st.caption("You can log in with Google to associate messages with your identity.") - + st.caption("You can log in with Google to associate messages with your identity.") \ No newline at end of file From de43d297f2afc7ffc0be1f0a3b65b549da255794 Mon Sep 17 00:00:00 2001 From: mariguima Date: Sat, 1 Nov 2025 20:22:04 +0000 Subject: [PATCH 04/18] change gemini --- agents/text_agent.py | 2 +- frontend/text_agent_output.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 frontend/text_agent_output.json diff --git a/agents/text_agent.py b/agents/text_agent.py index b63331c..1f1075f 100644 --- a/agents/text_agent.py +++ b/agents/text_agent.py @@ -4,7 +4,7 @@ from google import genai # Text agent entrypoint and function. Reads GENAI_API_KEY from environment. -API_KEY = os.environ.get("GENAI_API_KEY") or "AIzaSyBZ7LA5XV7kdbJLvLtNswCMirlxdo4l6w0" +API_KEY = os.environ.get("GENAI_API_KEY") or "AIzaSyBuL7i9DR602P2PYE65ZbesoD0nj-fAKXM" def generate_text(prompt: str) -> str: """Generate text using GenAI and persist to `text_agent_output.json`. diff --git a/frontend/text_agent_output.json b/frontend/text_agent_output.json new file mode 100644 index 0000000..61efb6c --- /dev/null +++ b/frontend/text_agent_output.json @@ -0,0 +1,3 @@ +{ + "text_output": "4 + 9 = 13" +} \ No newline at end of file From b712f88342ba1c54fafeed8a401d3cb15dcc2e03 Mon Sep 17 00:00:00 2001 From: mariguima Date: Sat, 1 Nov 2025 20:38:23 +0000 Subject: [PATCH 05/18] gemini --- agents/text_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agents/text_agent.py b/agents/text_agent.py index 1f1075f..8d6b18a 100644 --- a/agents/text_agent.py +++ b/agents/text_agent.py @@ -4,7 +4,7 @@ from google import genai # Text agent entrypoint and function. Reads GENAI_API_KEY from environment. -API_KEY = os.environ.get("GENAI_API_KEY") or "AIzaSyBuL7i9DR602P2PYE65ZbesoD0nj-fAKXM" +API_KEY = os.getenv("GENAI_API_KEY") def generate_text(prompt: str) -> str: """Generate text using GenAI and persist to `text_agent_output.json`. From fc0beb52a99af86e09eedf1096d656c7f270495b Mon Sep 17 00:00:00 2001 From: Jenna Li <139101659+jennajiali@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:43:17 +0000 Subject: [PATCH 06/18] Delete frontend/agentverse-streamlit-app/client_secrets.json --- .../agentverse-streamlit-app/client_secrets.json | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 frontend/agentverse-streamlit-app/client_secrets.json diff --git a/frontend/agentverse-streamlit-app/client_secrets.json b/frontend/agentverse-streamlit-app/client_secrets.json deleted file mode 100644 index e40d827..0000000 --- a/frontend/agentverse-streamlit-app/client_secrets.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "web": { - "client_id": "1050515079869-r385r3ol558o5fqjc3upukc9bqc80ufq.apps.googleusercontent.com", - "project_id": "theta-carving-476918-g7", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_secret": "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", - "redirect_uris": [ - "http://localhost:8501/" - ] - } -} \ No newline at end of file From 16ee901eebc13da1eca3b3b81d5ed2b8f3b68122 Mon Sep 17 00:00:00 2001 From: Jenna Li <139101659+jennajiali@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:45:52 +0000 Subject: [PATCH 07/18] Implement environment variable support for OAuth Added environment variable support for Google OAuth configuration to enhance security by avoiding hardcoded secrets. --- frontend/app.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/frontend/app.py b/frontend/app.py index 49fc47b..386688f 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -25,8 +25,29 @@ def load_client_config(): + # First, allow configuration via environment variables so secrets are not + # stored in the repository. Set these in your shell or use a secret manager: + # GOOGLE_OAUTH_CLIENT_ID, GOOGLE_OAUTH_CLIENT_SECRET, GOOGLE_OAUTH_REDIRECT_URI + client_id = os.environ.get("GOOGLE_OAUTH_CLIENT_ID") or os.environ.get("CLIENT_ID") + client_secret = os.environ.get("GOOGLE_OAUTH_CLIENT_SECRET") or os.environ.get("CLIENT_SECRET") + redirect_uri = os.environ.get("GOOGLE_OAUTH_REDIRECT_URI") or "http://localhost:8501/" + if client_id and client_secret: + return { + "web": { + "client_id": client_id, + "client_secret": client_secret, + "redirect_uris": [redirect_uri], + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + } + } + + # Fallback to reading the client_secrets.json file (for compatibility). if CLIENT_SECRETS_FILE.exists(): - return json.loads(CLIENT_SECRETS_FILE.read_text(encoding="utf-8")) + try: + return json.loads(CLIENT_SECRETS_FILE.read_text(encoding="utf-8")) + except Exception: + return None return None @@ -202,4 +223,4 @@ def render_messages(): if st.session_state.user: st.caption(f"Requests are associated with {st.session_state.user.get('email')}") else: - st.caption("You can log in with Google to associate messages with your identity.") \ No newline at end of file + st.caption("You can log in with Google to associate messages with your identity.") From a58bfd045391ca7b76498b3411dfd0096963c563 Mon Sep 17 00:00:00 2001 From: Jenna Li <139101659+jennajiali@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:47:35 +0000 Subject: [PATCH 08/18] Update README to remove feature suggestions Removed optional feature suggestions from README. --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index ac1c545..04e9814 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,3 @@ Notes and assumptions Security - Never commit `client_secrets.json` or API keys to source control. Keep them local or in a secure secret manager. - -If you want, I can: -- Add a client-side Google Identity button instead of server-side OAuth. -- Improve the chat UI with nicer bubbles and history persistence. From 7493ee07cfb4f8199f0bdefece0df19a278abc87 Mon Sep 17 00:00:00 2001 From: mariguima Date: Sat, 1 Nov 2025 20:52:56 +0000 Subject: [PATCH 09/18] button audio transcription --- frontend/app.py | 8 ++++++++ frontend/transcripts_dataset.json | 26 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 frontend/transcripts_dataset.json diff --git a/frontend/app.py b/frontend/app.py index 49fc47b..e836127 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -14,6 +14,7 @@ HERE = Path(__file__).parent AGENTS_TEXT_AGENT = Path(__file__).parent.joinpath("..", "agents", "text_agent.py").resolve() OUTPUT_JSON = Path("text_agent_output.json") +AUDIO_AGENT_PATH = Path(__file__).parent.joinpath("..", "agents", "audio_agent.py").resolve() CLIENT_SECRETS_FILE = HERE.joinpath("agentverse-streamlit-app", "client_secrets.json") @@ -152,6 +153,13 @@ def _rerun_compat(): with col1: st.header("Account") + if st.button("Start Audio Transcription"): + try: + subprocess.run([sys.executable, str(AUDIO_AGENT_PATH)], check=True) + st.success("Audio transcription started.") + except Exception as exc: + st.error(f"Failed to start audio transcription: {exc}") + if st.session_state.user: st.write(f"**Name:** {st.session_state.user.get('name')}") st.write(f"**Email:** {st.session_state.user.get('email')}") diff --git a/frontend/transcripts_dataset.json b/frontend/transcripts_dataset.json new file mode 100644 index 0000000..07d9f36 --- /dev/null +++ b/frontend/transcripts_dataset.json @@ -0,0 +1,26 @@ +[ + { + "speaker": "0", + "transcript": "OK." + }, + { + "speaker": "2", + "transcript": "Can you hear me" + }, + { + "speaker": "1", + "transcript": "Hey" + }, + { + "speaker": "0", + "transcript": "Oh, no, the button is working." + }, + { + "speaker": "1", + "transcript": "And" + }, + { + "speaker": "1", + "transcript": "everything's OK." + } +] \ No newline at end of file From 6b47a4e5b2981a0a78472043f54892c602a83d79 Mon Sep 17 00:00:00 2001 From: Jenna Li Date: Sat, 1 Nov 2025 20:54:39 +0000 Subject: [PATCH 10/18] Removing json from git history; update app.py --- .gitignore | 4 ++++ frontend/app.py | 1 + 2 files changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c60fa63 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +## Ignore local OAuth client secrets and generated outputs +frontend/agentverse-streamlit-app/client_secrets.json +frontend/agentverse-streamlit-app/client_secret_1050515079869-a70t0iliuss50871rd837ivehoesm308.apps.googleusercontent.com.json +text_agent_output.json \ No newline at end of file diff --git a/frontend/app.py b/frontend/app.py index e265760..7d8d347 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -232,3 +232,4 @@ def render_messages(): st.caption(f"Requests are associated with {st.session_state.user.get('email')}") else: st.caption("You can log in with Google to associate messages with your identity.") + From 9470ad365f3c9e6b48a506fa31f666706ce5eb67 Mon Sep 17 00:00:00 2001 From: Jenna Li Date: Sat, 1 Nov 2025 22:41:39 +0000 Subject: [PATCH 11/18] update app.py --- frontend/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/app.py b/frontend/app.py index 7d8d347..e265760 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -232,4 +232,3 @@ def render_messages(): st.caption(f"Requests are associated with {st.session_state.user.get('email')}") else: st.caption("You can log in with Google to associate messages with your identity.") - From a9dd79b45c56aba5a6e1c44af1b66820a1ce882f Mon Sep 17 00:00:00 2001 From: Jenna Li Date: Sat, 1 Nov 2025 22:46:15 +0000 Subject: [PATCH 12/18] update gitignore file --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index c60fa63..bee65b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ ## Ignore local OAuth client secrets and generated outputs frontend/agentverse-streamlit-app/client_secrets.json -frontend/agentverse-streamlit-app/client_secret_1050515079869-a70t0iliuss50871rd837ivehoesm308.apps.googleusercontent.com.json text_agent_output.json \ No newline at end of file From cb039f3243d036f4b5979f58be3e4502d886058b Mon Sep 17 00:00:00 2001 From: Jenna Li Date: Sat, 1 Nov 2025 23:00:29 +0000 Subject: [PATCH 13/18] Adding debugging error message for text agent --- frontend/app.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/frontend/app.py b/frontend/app.py index e265760..28bb7af 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -215,15 +215,32 @@ def render_messages(): if st.button("Send") and user_input: st.session_state.messages.append({"role": "user", "text": user_input}) + # Prefer calling the text agent in-process (gives clearer errors). If that + # fails (missing deps or import errors), fall back to running it as a + # subprocess and capture output. + assistant_text = None try: - subprocess.run([sys.executable, str(AGENTS_TEXT_AGENT), user_input], check=True) - if OUTPUT_JSON.exists(): - data = json.loads(OUTPUT_JSON.read_text(encoding="utf-8")) - assistant_text = data.get("text_output", "") - else: - assistant_text = "(no response, text_agent did not produce output)" + # import dynamically so changes to the agent are picked up without + # restarting the Streamlit process. + import importlib + from agents import text_agent as _ta + importlib.reload(_ta) + assistant_text = _ta.generate_text(user_input) except Exception as exc: - assistant_text = f"Error running text agent: {exc}" + # fallback to subprocess and capture output for diagnostics + try: + proc = subprocess.run([sys.executable, str(AGENTS_TEXT_AGENT), user_input], capture_output=True, text=True) + if proc.returncode == 0: + # try read json output first + if OUTPUT_JSON.exists(): + data = json.loads(OUTPUT_JSON.read_text(encoding="utf-8")) + assistant_text = data.get("text_output", proc.stdout.strip()) + else: + assistant_text = proc.stdout.strip() or proc.stderr.strip() or f"Subprocess exited with {proc.returncode}" + else: + assistant_text = f"Error running text agent (subprocess exit {proc.returncode}): {proc.stderr.strip() or proc.stdout.strip()}" + except Exception as exc2: + assistant_text = f"Error running text agent: {exc} ; fallback failed: {exc2}" st.session_state.messages.append({"role": "assistant", "text": assistant_text}) _rerun_compat() From 5aa4b533fd29aba4e1c04b59dff4cbfe6e201245 Mon Sep 17 00:00:00 2001 From: Tan Wee Joe <84664178+w3joe@users.noreply.github.com> Date: Sat, 1 Nov 2025 23:45:41 +0000 Subject: [PATCH 14/18] bug: fixed a couple of bugs --- agents/text_agent.py | 3 + agents/transcripts_dataset.json | 26 ++ api/README.md | 258 ++++++++++++++++++ frontend/.gitignore | 34 +++ .../pages/audio_viewer.py | 55 ++++ .../pages/graph_viewer.py | 132 +++++++++ .../agentverse-streamlit-app/requirements.txt | 7 + frontend/app.py | 135 ++++++--- frontend/requirements.txt | 8 + frontend/styles.css | 0 10 files changed, 619 insertions(+), 39 deletions(-) create mode 100644 agents/transcripts_dataset.json create mode 100644 api/README.md create mode 100644 frontend/.gitignore create mode 100644 frontend/agentverse-streamlit-app/pages/audio_viewer.py create mode 100644 frontend/agentverse-streamlit-app/pages/graph_viewer.py create mode 100644 frontend/agentverse-streamlit-app/requirements.txt create mode 100644 frontend/requirements.txt create mode 100644 frontend/styles.css diff --git a/agents/text_agent.py b/agents/text_agent.py index 8d6b18a..5eab638 100644 --- a/agents/text_agent.py +++ b/agents/text_agent.py @@ -2,6 +2,9 @@ import sys import json from google import genai +from dotenv import load_dotenv + +load_dotenv() # Text agent entrypoint and function. Reads GENAI_API_KEY from environment. API_KEY = os.getenv("GENAI_API_KEY") diff --git a/agents/transcripts_dataset.json b/agents/transcripts_dataset.json new file mode 100644 index 0000000..bd36932 --- /dev/null +++ b/agents/transcripts_dataset.json @@ -0,0 +1,26 @@ +[ + { + "speaker": "0", + "transcript": "Test 123" + }, + { + "speaker": "0", + "transcript": "test 123" + }, + { + "speaker": "0", + "transcript": "tests 123, 123, yeah, yeah, yeah." + }, + { + "speaker": "2", + "transcript": "I" + }, + { + "speaker": "2", + "transcript": "I" + }, + { + "speaker": "2", + "transcript": "I" + } +] \ No newline at end of file diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..e6429d8 --- /dev/null +++ b/api/README.md @@ -0,0 +1,258 @@ +Neo4j Flask API + +This is a Flask-based API to interact with a Neo4j database. It allows you to push graph data, query the database, and clear all data from the Neo4j instance. The API is built to interact with Neo4j via HTTP requests. + +Table of Contents + +- Base URL +- Authentication +- Endpoints + - Home + - Health Check + - Push Graph Data + - Query Graph + - Clear Database +- Example Requests + +--- + +Base URL + +http://:8080 + +Replace with the actual IP address or hostname of the server where the Flask application is running. + +--- + +Authentication + +The API uses environment variables to configure the connection to the Neo4j database: + +- NEO4J_URI: The URI of your Neo4j instance (e.g., bolt://localhost:7687). +- NEO4J_USER: The username for Neo4j authentication (default is neo4j). +- NEO4J_PASSWORD: The password for Neo4j authentication. + +These values must be set in your environment for the API to connect to Neo4j. The application uses the python-dotenv package to load these variables from a .env file. + +--- + +Endpoints + +1. Home + +Endpoint: / +Method: GET + +Returns a simple message indicating that the API is running and lists available endpoints. + +Response Example: +{ + "message": "Neo4j Flask API is running!", + "endpoints": ["/push", "/query", "/health"] +} + +--- + +2. Health Check + +Endpoint: /health +Method: GET + +Checks the health of the Neo4j database by running a simple query. If the Neo4j connection is working, it will return a success message. + +Response Example: +{ + "status": "healthy", + "neo4j": [{"status": "connected"}] +} + +If the connection fails: +{ + "status": "unhealthy", + "error": "Error message" +} + +--- + +3. Push Graph Data + +Endpoint: /push +Method: POST + +Pushes graph data to the Neo4j database. The body of the request should contain the graph data in JSON format, including nodes and relationships. + +Request Body Example: +{ + "nodes": [ + { + "id": "1", + "label": "Person", + "properties": { + "name": "John", + "age": 30 + } + }, + { + "id": "2", + "label": "Company", + "properties": { + "name": "Acme Corp" + } + } + ], + "relationships": [ + { + "from": "1", + "to": "2", + "type": "WORKS_AT", + "properties": { + "since": 2020 + } + } + ] +} + +Response Example: +{ + "status": "success", + "nodes_created": 2, + "relationships_created": 1 +} + +If the request is missing required fields: +{ + "error": "Missing 'nodes' field in JSON" +} + +--- + +4. Query Graph + +Endpoint: /query +Method: POST + +Executes a custom Cypher query or searches for nodes by label and property. The query can either be passed directly as a Cypher string or using label/property/value pairs. + +1. Request Body Example (using label/property/value): +{ + "label": "Person", + "property": "name", + "value": "John" +} + +### Example Labels, Properties, and Values + +Below is a list of all possible labels, their properties, and sample values based on the provided graph data: + +**Label:** `Project` +- Properties: + - `name`: "Team Nebula — Real-Time Dashboard" + - `description`: "Real-time dashboard project. Frontend completed UI and query button; backend connected FastAPI to data feed. Current problem: Query button intermittently fails to re-render charts." + - `status`: "in progress" + - `lastWorkedOn`: "2025-11-01" + +**Label:** `Person` +- Properties: + - `name`: "Dana", "Ben", "Alice" + - `role`: "Frontend", "Backend", "Project Lead" + - `details`: + - "Finished the dashboard UI and query button; suspects useEffect hook missed queryParam causing re-render bug." + - "Connected FastAPI to the data feed; will pair with Dana to debug re-rendering issue." + - "Ran quick sync; coordinated the fix activity." + - `lastActivity`: "2025-11-01" + +**Label:** `Issue` +- Properties: + - `title`: "Query button intermittently doesn’t re-render charts" + - `description`: "The Query button sometimes fails to re-render charts; likely cause: frontend useEffect hook missing dependency (queryParam)." + - `severity`: "medium" + - `status`: "open" + - `reportedBy`: "Alice" + - `assignedTo`: ["Dana", "Ben"] + - `targetFixTiming`: "before code freeze" + - `lastObserved`: "2025-11-01" + +**Relationship Types:** +- `WORKS_ON` +- `MANAGES` +- `REPORTS` +- `AFFECTS` +- `ASSIGNED_TO` + +2. Request Body Example (using Cypher query): +{ + "cypher": "MATCH (n:Person) RETURN n LIMIT 10" +} + +3. Response Example (Cypher query): +{ + "result": [ + {"n": {"id": "1", "name": "John", "age": 30}}, + {"n": {"id": "2", "name": "Jane", "age": 25}} + ], + "count": 2 +} + +If there’s an error: +{ + "error": "Missing 'cypher' or 'label' and 'property' fields in JSON" +} + +--- + +5. Clear Database + +Endpoint: /clear +Method: DELETE + +Clears all nodes and relationships from the database. Use this with caution, as it will delete all data. + +Response Example: +{ + "status": "success", + "message": "Database cleared" +} + +If there’s an error: +{ + "error": "Error message" +} + +--- + +Example Requests + +Here’s how you might interact with the API using curl from the command line. + +Health Check +curl http://localhost:8080/health + +Push Graph Data +curl -X POST http://localhost:8080/push -H "Content-Type: application/json" -d '{ + "nodes": [ + {"id": "1", "label": "Person", "properties": {"name": "John", "age": 30}}, + {"id": "2", "label": "Company", "properties": {"name": "Acme Corp"}} + ], + "relationships": [ + {"from": "1", "to": "2", "type": "WORKS_AT", "properties": {"since": 2020}} + ] +}' + +Query Graph +curl -X POST http://localhost:8080/query -H "Content-Type: application/json" -d '{ + "label": "Person", + "property": "name", + "value": "John" +}' + +Clear Database +curl -X DELETE http://localhost:8080/clear + +--- + +Conclusion + +With these API endpoints, you can easily interact with a Neo4j database, including creating nodes and relationships, querying data, and clearing the database. For any issues, please ensure the correct environment variables are set and the Neo4j database is running and accessible. + + + diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..8ab367c --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,34 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Virtual environments +venv/ +ENV/ +env/ +.venv/ +env.bak/ +venv.bak/ + +# Streamlit cache +.streamlit/ + +# Jupyter Notebook +.ipynb_checkpoints + +# dotenv +.env +.env.* + +# VS Code +.vscode/ + +# Mac +.DS_Store + +# Logs +*.log + +# Other +*.sqlite3 diff --git a/frontend/agentverse-streamlit-app/pages/audio_viewer.py b/frontend/agentverse-streamlit-app/pages/audio_viewer.py new file mode 100644 index 0000000..663636c --- /dev/null +++ b/frontend/agentverse-streamlit-app/pages/audio_viewer.py @@ -0,0 +1,55 @@ +import streamlit as st +from audio_recorder_streamlit import audio_recorder +import subprocess +import sys +from pathlib import Path +import json +import tempfile +import threading + +AUDIO_AGENT_PATH = Path(__file__).parent.parent.parent.parent.joinpath("agents", "audio_agent.py").resolve() +OUTPUT_JSON = Path("transcripts_dataset.json") + +def main(): + """Main function to display Audio Agent interface""" + st.title("🎤 Audio Agent - Live Transcription") + + # Debug: Show the resolved path + st.caption(f"Audio agent path: {AUDIO_AGENT_PATH}") + if not AUDIO_AGENT_PATH.exists(): + st.error(f"Audio agent script not found at: {AUDIO_AGENT_PATH}") + return + + st.write("Start the audio transcription service to capture and transcribe speech in real-time.") + + # Initialize session state + if "transcriptions" not in st.session_state: + st.session_state.transcriptions = [] + if "transcription_running" not in st.session_state: + st.session_state.transcription_running = False + + # Start/Stop Audio Transcription Button + st.header("Continuous Transcription") + if not st.session_state.transcription_running: + if st.button("Start Audio Transcription", type="primary"): + try: + # Run audio agent in background thread + def run_audio_agent(): + subprocess.run([sys.executable, str(AUDIO_AGENT_PATH)], check=True) + + thread = threading.Thread(target=run_audio_agent, daemon=True) + thread.start() + st.session_state.transcription_running = True + st.success("Audio transcription started.") + st.rerun() + except Exception as exc: + st.error(f"Failed to start audio transcription: {exc}") + else: + st.info("🔴 Transcription is running...") + if st.button("Stop Audio Transcription"): + st.session_state.transcription_running = False + st.success("Audio transcription stopped.") + st.rerun() + +if __name__ == "__main__": + main() diff --git a/frontend/agentverse-streamlit-app/pages/graph_viewer.py b/frontend/agentverse-streamlit-app/pages/graph_viewer.py new file mode 100644 index 0000000..b95b97f --- /dev/null +++ b/frontend/agentverse-streamlit-app/pages/graph_viewer.py @@ -0,0 +1,132 @@ +import streamlit as st +from neo4j import GraphDatabase +from pyvis.network import Network +import streamlit.components.v1 as components +import os +from dotenv import load_dotenv + +load_dotenv() + +class Neo4jGraphViewer: + def __init__(self, uri, user, password): + """Initialize Neo4j connection""" + try: + self.driver = GraphDatabase.driver(uri, auth=(user, password)) + self.connected = True + except Exception as e: + st.error(f"Failed to connect to Neo4j: {str(e)}") + self.connected = False + + def close(self): + """Close the connection""" + if hasattr(self, 'driver'): + self.driver.close() + + def get_all_relationships(self, limit=200): + """Fetch all relationships from the graph""" + if not self.connected: + return [] + + with self.driver.session() as session: + result = session.run(f""" + MATCH (n)-[r]->(m) + RETURN n, r, m + LIMIT {limit} + """) + return [(record["n"], record["r"], record["m"]) for record in result] + +def create_graph_visualization(relationships): + """Create an interactive network graph using PyVis""" + net = Network( + height="750px", + width="100%", + bgcolor="#222222", + font_color="white", + directed=True + ) + + # Physics settings for better layout + net.barnes_hut( + gravity=-8000, + central_gravity=0.3, + spring_length=250, + spring_strength=0.001, + damping=0.09 + ) + + # Color coding for different node types + colors = { + "Person": "#FF6B6B", + "Project": "#4ECDC4", + "Company": "#45B7D1", + "Department": "#FFA07A", + "Technology": "#98D8C8", + "Meeting": "#F7DC6F", + "Task": "#BB8FCE", + "Document": "#85C1E2" + } + + added_nodes = set() + + for source_node, relationship, target_node in relationships: + # Add source node + source_id = source_node.get("id", str(id(source_node))) + if source_id not in added_nodes: + label = list(source_node.labels)[0] if source_node.labels else "Unknown" + tooltip = "\n".join([f"{k}: {v}" for k, v in dict(source_node).items()]) + net.add_node( + source_id, + label=source_node.get("name", source_id), + title=tooltip, + color=colors.get(label, "#95A5A6"), + size=25 + ) + added_nodes.add(source_id) + + # Add target node + target_id = target_node.get("id", str(id(target_node))) + if target_id not in added_nodes: + label = list(target_node.labels)[0] if target_node.labels else "Unknown" + tooltip = "\n".join([f"{k}: {v}" for k, v in dict(target_node).items()]) + net.add_node( + target_id, + label=target_node.get("name", target_id), + title=tooltip, + color=colors.get(label, "#95A5A6"), + size=25 + ) + added_nodes.add(target_id) + + # Add relationship + edge_tooltip = f"{relationship.type}" + net.add_edge(source_id, target_id, title=edge_tooltip, label=relationship.type) + + return net.generate_html() + +def main(): + """Main function to display Neo4j graph""" + st.title("🕸️ Knowledge Graph") + + # Use environment variables for connection + uri = os.getenv("NEO4J_URI", "") + user = os.getenv("NEO4J_USER", "neo4j") + password = os.getenv("NEO4J_PASSWORD", "") + + limit = st.slider("Max nodes to display", 50, 500, 200) + + viewer = Neo4jGraphViewer(uri, user, password) + if viewer.connected: + with st.spinner("Loading graph..."): + relationships = viewer.get_all_relationships(limit=limit) + + if relationships: + html = create_graph_visualization(relationships) + components.html(html, height=800, scrolling=False) + st.info(f"Displaying {len(relationships)} relationships") + else: + st.warning("No data found in the graph") + else: + st.error("Could not connect to Neo4j. Check your .env file and Neo4j server.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/frontend/agentverse-streamlit-app/requirements.txt b/frontend/agentverse-streamlit-app/requirements.txt new file mode 100644 index 0000000..9b008bb --- /dev/null +++ b/frontend/agentverse-streamlit-app/requirements.txt @@ -0,0 +1,7 @@ +streamlit +neo4j +plotly +pyvis +networkx +python-dotenv +pandas \ No newline at end of file diff --git a/frontend/app.py b/frontend/app.py index 28bb7af..636002a 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -3,6 +3,7 @@ import subprocess import sys from pathlib import Path +import importlib.util import streamlit as st @@ -14,7 +15,8 @@ HERE = Path(__file__).parent AGENTS_TEXT_AGENT = Path(__file__).parent.joinpath("..", "agents", "text_agent.py").resolve() OUTPUT_JSON = Path("text_agent_output.json") -AUDIO_AGENT_PATH = Path(__file__).parent.joinpath("..", "agents", "audio_agent.py").resolve() +GRAPH_VIEWER_PATH = HERE.joinpath("agentverse-streamlit-app", "pages", "graph_viewer.py") +AUDIO_VIEWER_MODULE_PATH = HERE.joinpath("agentverse-streamlit-app", "pages", "audio_viewer.py") CLIENT_SECRETS_FILE = HERE.joinpath("agentverse-streamlit-app", "client_secrets.json") @@ -77,10 +79,19 @@ def exchange_code_for_user(code: str, state: str): return {"name": idinfo.get("name"), "email": idinfo.get("email")} -st.set_page_config(page_title="Agentverse Chat", layout="wide") +def load_graph_viewer_module(): + spec = importlib.util.spec_from_file_location("graph_viewer", str(GRAPH_VIEWER_PATH)) + graph_viewer = importlib.util.module_from_spec(spec) + spec.loader.exec_module(graph_viewer) + return graph_viewer -st.title("Agentverse — Chat with Text Agent") +def load_audio_viewer_module(): + spec = importlib.util.spec_from_file_location("audio_viewer", str(AUDIO_VIEWER_MODULE_PATH)) + audio_viewer = importlib.util.module_from_spec(spec) + spec.loader.exec_module(audio_viewer) + return audio_viewer +st.set_page_config(page_title="Agentverse", layout="wide") def _rerun_compat(): """Try to programmatically rerun the Streamlit script in a version-compatible way.""" @@ -92,6 +103,9 @@ def _rerun_compat(): except Exception: st.warning("Please refresh the page manually.") +# Initialize active page in session state +if "active_page" not in st.session_state: + st.session_state.active_page = "Chat" # Authentication section params = st.query_params @@ -102,7 +116,7 @@ def _rerun_compat(): if "messages" not in st.session_state: st.session_state.messages = [] -# Handle OAuth callback +# Comment out OAuth callback handling for testing if required if "code" in params: code = params["code"] returned_state = params.get("state", "") @@ -170,44 +184,71 @@ def _rerun_compat(): st.session_state.pop("oauth_state", None) _rerun_compat() -col1, col2 = st.columns([1, 3]) - -with col1: - st.header("Account") - if st.button("Start Audio Transcription"): - try: - subprocess.run([sys.executable, str(AUDIO_AGENT_PATH)], check=True) - st.success("Audio transcription started.") - except Exception as exc: - st.error(f"Failed to start audio transcription: {exc}") - - if st.session_state.user: +# Sidebar navigation (only show if logged in) +if st.session_state.user: + with st.sidebar: + st.header("Navigation") + if st.button("💬 Chat", use_container_width=True, type="primary" if st.session_state.active_page == "Chat" else "secondary"): + st.session_state.active_page = "Chat" + _rerun_compat() + + if st.button("🎤 Audio Agent", use_container_width=True, type="primary" if st.session_state.active_page == "Audio" else "secondary"): + st.session_state.active_page = "Audio" + _rerun_compat() + + if st.button("🕸️ Knowledge Graph", use_container_width=True, type="primary" if st.session_state.active_page == "Graph" else "secondary"): + st.session_state.active_page = "Graph" + _rerun_compat() + + st.divider() + st.subheader("Account") st.write(f"**Name:** {st.session_state.user.get('name')}") st.write(f"**Email:** {st.session_state.user.get('email')}") - if st.button("Logout"): + if st.button("Logout", use_container_width=True): st.session_state.user = None + st.session_state.active_page = "Chat" st.query_params.clear() _rerun_compat() - else: - st.write("You are not logged in.") + +# Main content area +if not st.session_state.user: + st.title("Agentverse — Chat with Text Agent") + col1, col2 = st.columns([1, 1]) + with col1: + st.subheader("Please log in to continue") auth = login_flow() if auth: auth_url, state = auth st.link_button("Login with Google", auth_url) else: st.info("Place your Google OAuth client_secrets.json at `frontend/agentverse-streamlit-app/client_secrets.json`.") - -with col2: - st.header("Chat") - +elif st.session_state.active_page == "Chat": + st.title("💬 Chat with Text Agent") + def render_messages(): for m in st.session_state.messages: role = m.get("role") text = m.get("text") if role == "user": - st.markdown(f"
**You:** {text}
", unsafe_allow_html=True) + st.markdown(f""" +
+
+ {text} +
+
+ """, unsafe_allow_html=True) else: - st.markdown(f"
**Assistant:** {text}
", unsafe_allow_html=True) + st.markdown(f""" +
+
+ {text} +
+
+ """, unsafe_allow_html=True) render_messages() @@ -215,17 +256,20 @@ def render_messages(): if st.button("Send") and user_input: st.session_state.messages.append({"role": "user", "text": user_input}) - # Prefer calling the text agent in-process (gives clearer errors). If that - # fails (missing deps or import errors), fall back to running it as a - # subprocess and capture output. - assistant_text = None try: - # import dynamically so changes to the agent are picked up without - # restarting the Streamlit process. - import importlib - from agents import text_agent as _ta - importlib.reload(_ta) - assistant_text = _ta.generate_text(user_input) + result = subprocess.run( + [sys.executable, str(AGENTS_TEXT_AGENT), user_input], + check=True, + capture_output=True, + text=True + ) + if OUTPUT_JSON.exists(): + data = json.loads(OUTPUT_JSON.read_text(encoding="utf-8")) + assistant_text = data.get("text_output", "") + else: + assistant_text = "(no response, text_agent did not produce output)" + except subprocess.CalledProcessError as exc: + assistant_text = f"Error running text agent:\n\nSTDOUT: {exc.stdout}\n\nSTDERR: {exc.stderr}" except Exception as exc: # fallback to subprocess and capture output for diagnostics try: @@ -245,7 +289,20 @@ def render_messages(): st.session_state.messages.append({"role": "assistant", "text": assistant_text}) _rerun_compat() - if st.session_state.user: - st.caption(f"Requests are associated with {st.session_state.user.get('email')}") - else: - st.caption("You can log in with Google to associate messages with your identity.") + st.caption(f"Requests are associated with {st.session_state.user.get('email')}") + +elif st.session_state.active_page == "Audio": + try: + audio_viewer = load_audio_viewer_module() + audio_viewer.main() + except Exception as e: + st.error(f"Error loading audio viewer: {e}") + st.info("Make sure `agentverse-streamlit-app/pages/audio_viewer.py` exists and contains a `main()` function.") + +elif st.session_state.active_page == "Graph": + try: + graph_viewer = load_graph_viewer_module() + graph_viewer.main() + except Exception as e: + st.error(f"Error loading graph viewer: {e}") + st.info("Make sure `pages/graph_viewer.py` exists and contains a `main()` function.") \ No newline at end of file diff --git a/frontend/requirements.txt b/frontend/requirements.txt new file mode 100644 index 0000000..49cac26 --- /dev/null +++ b/frontend/requirements.txt @@ -0,0 +1,8 @@ +streamlit +neo4j +plotly +pyvis +networkx +python-dotenv +pandas +audio_recorder_streamlit \ No newline at end of file diff --git a/frontend/styles.css b/frontend/styles.css new file mode 100644 index 0000000..e69de29 From beb6dfad6c92620ee7e766939d7192bf5f038e15 Mon Sep 17 00:00:00 2001 From: Tan Wee Joe <84664178+w3joe@users.noreply.github.com> Date: Sat, 1 Nov 2025 23:47:56 +0000 Subject: [PATCH 15/18] Create .gitignore --- agents/.gitignore | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 agents/.gitignore diff --git a/agents/.gitignore b/agents/.gitignore new file mode 100644 index 0000000..8ab367c --- /dev/null +++ b/agents/.gitignore @@ -0,0 +1,34 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Virtual environments +venv/ +ENV/ +env/ +.venv/ +env.bak/ +venv.bak/ + +# Streamlit cache +.streamlit/ + +# Jupyter Notebook +.ipynb_checkpoints + +# dotenv +.env +.env.* + +# VS Code +.vscode/ + +# Mac +.DS_Store + +# Logs +*.log + +# Other +*.sqlite3 From e73133a5d9de243511932242a166f753a4270530 Mon Sep 17 00:00:00 2001 From: mariguima Date: Sun, 2 Nov 2025 10:05:39 +0000 Subject: [PATCH 16/18] requirements and env change --- .gitignore | 3 +- agents/audio_agent.py | 2 +- .../pages/audio_viewer.py | 3 + .../pages/graph_viewer.py | 2 +- frontend/app.py | 9 +- frontend/text_agent_output.json | 2 +- frontend/transcripts_dataset.json | 634 +++++++++++++++++- requirements.txt | 7 +- 8 files changed, 650 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index bee65b8..069e125 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ ## Ignore local OAuth client secrets and generated outputs frontend/agentverse-streamlit-app/client_secrets.json -text_agent_output.json \ No newline at end of file +text_agent_output.json +.env \ No newline at end of file diff --git a/agents/audio_agent.py b/agents/audio_agent.py index 3c4e317..12d2146 100644 --- a/agents/audio_agent.py +++ b/agents/audio_agent.py @@ -123,4 +123,4 @@ async def main(): await transcribe_stream() if __name__ == "__main__": - asyncio.run(main()) + asyncio.run(main()) \ No newline at end of file diff --git a/frontend/agentverse-streamlit-app/pages/audio_viewer.py b/frontend/agentverse-streamlit-app/pages/audio_viewer.py index 663636c..c0c9aea 100644 --- a/frontend/agentverse-streamlit-app/pages/audio_viewer.py +++ b/frontend/agentverse-streamlit-app/pages/audio_viewer.py @@ -6,6 +6,9 @@ import json import tempfile import threading +from dotenv import load_dotenv + +load_dotenv() AUDIO_AGENT_PATH = Path(__file__).parent.parent.parent.parent.joinpath("agents", "audio_agent.py").resolve() OUTPUT_JSON = Path("transcripts_dataset.json") diff --git a/frontend/agentverse-streamlit-app/pages/graph_viewer.py b/frontend/agentverse-streamlit-app/pages/graph_viewer.py index b95b97f..15f27ad 100644 --- a/frontend/agentverse-streamlit-app/pages/graph_viewer.py +++ b/frontend/agentverse-streamlit-app/pages/graph_viewer.py @@ -111,10 +111,10 @@ def main(): uri = os.getenv("NEO4J_URI", "") user = os.getenv("NEO4J_USER", "neo4j") password = os.getenv("NEO4J_PASSWORD", "") - limit = st.slider("Max nodes to display", 50, 500, 200) viewer = Neo4jGraphViewer(uri, user, password) + if viewer.connected: with st.spinner("Loading graph..."): relationships = viewer.get_all_relationships(limit=limit) diff --git a/frontend/app.py b/frontend/app.py index 636002a..28b7965 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -4,13 +4,15 @@ import sys from pathlib import Path import importlib.util - +from dotenv import load_dotenv import streamlit as st from google_auth_oauthlib.flow import Flow from google.oauth2 import id_token from google.auth.transport import requests as grequests +load_dotenv() + # Paths and config HERE = Path(__file__).parent AGENTS_TEXT_AGENT = Path(__file__).parent.joinpath("..", "agents", "text_agent.py").resolve() @@ -18,7 +20,7 @@ GRAPH_VIEWER_PATH = HERE.joinpath("agentverse-streamlit-app", "pages", "graph_viewer.py") AUDIO_VIEWER_MODULE_PATH = HERE.joinpath("agentverse-streamlit-app", "pages", "audio_viewer.py") -CLIENT_SECRETS_FILE = HERE.joinpath("agentverse-streamlit-app", "client_secrets.json") +#CLIENT_SECRETS_FILE = HERE.joinpath("agentverse-streamlit-app", "client_secrets.json") SCOPES = [ "openid", @@ -45,7 +47,8 @@ def load_client_config(): } } - # Fallback to reading the client_secrets.json file (for compatibility). + # # Fallback to reading the client_secrets.json file (for compatibility). + if CLIENT_SECRETS_FILE.exists(): try: return json.loads(CLIENT_SECRETS_FILE.read_text(encoding="utf-8")) diff --git a/frontend/text_agent_output.json b/frontend/text_agent_output.json index 61efb6c..28d62ba 100644 --- a/frontend/text_agent_output.json +++ b/frontend/text_agent_output.json @@ -1,3 +1,3 @@ { - "text_output": "4 + 9 = 13" + "text_output": "I do not have access to past conversations. Each interaction with me is independent.\n\nTo tell you what your previous question was, I would need you to remind me of the conversation context or the question itself." } \ No newline at end of file diff --git a/frontend/transcripts_dataset.json b/frontend/transcripts_dataset.json index 07d9f36..ee7d651 100644 --- a/frontend/transcripts_dataset.json +++ b/frontend/transcripts_dataset.json @@ -13,14 +13,642 @@ }, { "speaker": "0", - "transcript": "Oh, no, the button is working." + "transcript": "Working" }, { - "speaker": "1", + "speaker": "0", "transcript": "And" }, { - "speaker": "1", + "speaker": "0", "transcript": "everything's OK." + }, + { + "speaker": "1", + "transcript": "Oh, I forgot to create another branch. I committed to the front end bra." + }, + { + "speaker": "3", + "transcript": "Oh yeah, it's fine. No worries. I'll just pull it." + }, + { + "speaker": "0", + "transcript": "Mm." + }, + { + "speaker": "0", + "transcript": "right" + }, + { + "speaker": "0", + "transcript": "Um" + }, + { + "speaker": "0", + "transcript": "I tried to push stuff to the" + }, + { + "speaker": "1", + "transcript": "the repository, but I got rejected." + }, + { + "speaker": "4", + "transcript": "Oh, yeah, cause it says I need you to make it. Oh yeah." + }, + { + "speaker": "1", + "transcript": "Daniel." + }, + { + "speaker": "1", + "transcript": "Pass the rooms, um, especially like cause the the buildings are closed down like, like you have to be out. So 90, so you don't, you probably still have a little bit of time, but don't stay after 9. Yeah, try to take with you and like do it out. OK. Thanks." + }, + { + "speaker": "0", + "transcript": "OK" + }, + { + "speaker": "1", + "transcript": "I'll" + }, + { + "speaker": "0", + "transcript": "Oh." + }, + { + "speaker": "1", + "transcript": "Hi, hello, hello, hi, hi." + }, + { + "speaker": "0", + "transcript": "Good" + }, + { + "speaker": "0", + "transcript": "um" + }, + { + "speaker": "0", + "transcript": "I don't think it'll work." + }, + { + "speaker": "0", + "transcript": "Um, so you probably officially assessing you." + }, + { + "speaker": "0", + "transcript": "Hi, can you hear me? Hi." + }, + { + "speaker": "0", + "transcript": "for your information for the points are up." + }, + { + "speaker": "0", + "transcript": "And every company" + }, + { + "speaker": "0", + "transcript": "OK you want want the, um," + }, + { + "speaker": "0", + "transcript": "runs." + }, + { + "speaker": "0", + "transcript": "I" + }, + { + "speaker": "0", + "transcript": "So" + }, + { + "speaker": "2", + "transcript": "Mhm." + }, + { + "speaker": "2", + "transcript": "Mm." + }, + { + "speaker": "2", + "transcript": "we" + }, + { + "speaker": "0", + "transcript": "Um, so" + }, + { + "speaker": "0", + "transcript": "because" + }, + { + "speaker": "2", + "transcript": "OK." + }, + { + "speaker": "2", + "transcript": "Can I?" + }, + { + "speaker": "2", + "transcript": "Yeah, well, um." + }, + { + "speaker": "2", + "transcript": "But do you want now on some tours I." + }, + { + "speaker": "2", + "transcript": "Well, you know," + }, + { + "speaker": "2", + "transcript": "Oh" + }, + { + "speaker": "2", + "transcript": "I" + }, + { + "speaker": "2", + "transcript": "Mhm" + }, + { + "speaker": "2", + "transcript": "s" + }, + { + "speaker": "2", + "transcript": "I sweat" + }, + { + "speaker": "2", + "transcript": "I pop ups." + }, + { + "speaker": "2", + "transcript": "I mean he's" + }, + { + "speaker": "2", + "transcript": "Yeah, um," + }, + { + "speaker": "2", + "transcript": "uh" + }, + { + "speaker": "2", + "transcript": "It was using API to call the database or it was using something to call the API the new API to call the database." + }, + { + "speaker": "4", + "transcript": "cause I, cause I see this thing in your." + }, + { + "speaker": "4", + "transcript": "It's like, can I make it like speech agent use API target databases that how it works. Yeah, that's how it works. I think we don't need this one." + }, + { + "speaker": "2", + "transcript": "Um" + }, + { + "speaker": "5", + "transcript": "Yeah, you can combine it into one, simplify, yeah." + }, + { + "speaker": "2", + "transcript": "I" + }, + { + "speaker": "2", + "transcript": "It's uh, but I think this is gonna be sharp. Um, this you can just like once you are in here." + }, + { + "speaker": "2", + "transcript": "And" + }, + { + "speaker": "0", + "transcript": "we have more of the steps we do." + }, + { + "speaker": "0", + "transcript": "please" + }, + { + "speaker": "0", + "transcript": "is not password" + }, + { + "speaker": "2", + "transcript": "There's a line connecting database and reba it's probably. Yeah, it's true if you cause your, so you can just draw one direct, uh, one day direct error from database to front end API call. Do we have a special for oh, you mean the database itself is stored as graph." + }, + { + "speaker": "5", + "transcript": "All right." + }, + { + "speaker": "2", + "transcript": "Uh, no, it's just, uh, this is empty." + }, + { + "speaker": "2", + "transcript": "coming off the." + }, + { + "speaker": "2", + "transcript": "Mhm" + }, + { + "speaker": "2", + "transcript": "Yes." + }, + { + "speaker": "2", + "transcript": "Let's just end up" + }, + { + "speaker": "2", + "transcript": "moss." + }, + { + "speaker": "2", + "transcript": "change." + }, + { + "speaker": "2", + "transcript": "I don't think so." + }, + { + "speaker": "2", + "transcript": "Mhm." + }, + { + "speaker": "2", + "transcript": "I." + }, + { + "speaker": "0", + "transcript": "It's just pretty set won't send the last one." + }, + { + "speaker": "2", + "transcript": "I" + }, + { + "speaker": "2", + "transcript": "some." + }, + { + "speaker": "2", + "transcript": "I'm pretty sure that already." + }, + { + "speaker": "2", + "transcript": "sure" + }, + { + "speaker": "2", + "transcript": "What is the lady so I can get this." + }, + { + "speaker": "2", + "transcript": "Why?" + }, + { + "speaker": "3", + "transcript": "I just wanted to add something I think you're probably gonna, I'm probably gonna have to rewrite some. Yeah, so I would just get" + }, + { + "speaker": "2", + "transcript": "excited." + }, + { + "speaker": "2", + "transcript": "I." + }, + { + "speaker": "2", + "transcript": "OK." + }, + { + "speaker": "2", + "transcript": "right" + }, + { + "speaker": "2", + "transcript": "Um" + }, + { + "speaker": "3", + "transcript": "Yeah, my, um, I said, my knees are prompts. Will they be shown in it or are you gonna do something there yourself. So that's what I'm working on." + }, + { + "speaker": "2", + "transcript": "No, no, they don't worry about it's, uh, what we're basically doing is just a graph, uh OK. So because I'm not not how this getting used. How you your accommodation for. OK." + }, + { + "speaker": "2", + "transcript": "Um" + }, + { + "speaker": "5", + "transcript": "I" + }, + { + "speaker": "0", + "transcript": "um" + }, + { + "speaker": "2", + "transcript": "right." + }, + { + "speaker": "2", + "transcript": "Yeah." + }, + { + "speaker": "2", + "transcript": "no" + }, + { + "speaker": "2", + "transcript": "OK" + }, + { + "speaker": "2", + "transcript": "OK." + }, + { + "speaker": "2", + "transcript": "OK." + }, + { + "speaker": "3", + "transcript": "So we um medicine." + }, + { + "speaker": "2", + "transcript": "Um, and you." + }, + { + "speaker": "2", + "transcript": "You." + }, + { + "speaker": "2", + "transcript": "Is this the thing." + }, + { + "speaker": "3", + "transcript": "Uh, I will text. not able to go around it." + }, + { + "speaker": "2", + "transcript": "Um, yeah, from this one you can just use like uh to" + }, + { + "speaker": "2", + "transcript": "to in the same line but two eggs." + }, + { + "speaker": "2", + "transcript": "Excuse me, I mean, I actually, I actually want to write like speech agendas using insert command and this is used. 00, is in this restaurant some text. Uh, which one?" + }, + { + "speaker": "5", + "transcript": "Is it for the arrows? Yeah, yeah, yeah, I mean, I want right here as uh." + }, + { + "speaker": "2", + "transcript": "search uh queries, and I wanna write this as um it's done for 10,000 of those you think it would be better to simply do the like one line to, I think, I think 1920s and then you just been writing the writing API first, yeah, yeah, it looks good simulations of it, um, but if you want to see, you could just" + }, + { + "speaker": "3", + "transcript": "say that 10,000 top and see your discs, right, um" + }, + { + "speaker": "3", + "transcript": "here and return." + }, + { + "speaker": "3", + "transcript": "you find the price. So I'm gonna overthought that you have one, but it doesn't make sense because the average price is so different to act presidency." + }, + { + "speaker": "2", + "transcript": "Um, but if you change um in our insurance or" + }, + { + "speaker": "3", + "transcript": "3 things, 87 or on Sundays 714 over a year, so she would be trained days. 117." + }, + { + "speaker": "3", + "transcript": "98153, and then say, oh well" + }, + { + "speaker": "2", + "transcript": "I think so yeah." + }, + { + "speaker": "2", + "transcript": "So what we can do is take the, the drawing out of this as the the model we have that in the" + }, + { + "speaker": "2", + "transcript": "Yeah, is it a rare? Uh, yeah, in the knees, yeah." + }, + { + "speaker": "2", + "transcript": "Yeah. I react well." + }, + { + "speaker": "2", + "transcript": "I just uh." + }, + { + "speaker": "2", + "transcript": "No." + }, + { + "speaker": "2", + "transcript": "you want to." + }, + { + "speaker": "2", + "transcript": "I just, uh, what you want to push." + }, + { + "speaker": "2", + "transcript": "you get" + }, + { + "speaker": "3", + "transcript": "you know get up and." + }, + { + "speaker": "2", + "transcript": "and send." + }, + { + "speaker": "2", + "transcript": "So let's say you want to talk to" + }, + { + "speaker": "2", + "transcript": "what are we getting from? Why is there a line for front end text agents." + }, + { + "speaker": "2", + "transcript": "Penny, we do need to, for example, like send word files to the agent." + }, + { + "speaker": "5", + "transcript": "Currently it's just a textbook that you can chat with. The thing is, it's not farm works. You mean, you mean the chatbox be something like past the instead of like tell you like we wanna use workout or you can use it for both." + }, + { + "speaker": "5", + "transcript": "Yeah." + }, + { + "speaker": "2", + "transcript": "It" + }, + { + "speaker": "2", + "transcript": "At the moment it's us. Your mind is another line from the text agent to the database." + }, + { + "speaker": "5", + "transcript": "Yeah that that" + }, + { + "speaker": "5", + "transcript": "I think we missed it" + }, + { + "speaker": "2", + "transcript": "we might." + }, + { + "speaker": "2", + "transcript": "thinking like war." + }, + { + "speaker": "4", + "transcript": "him." + }, + { + "speaker": "2", + "transcript": "is" + }, + { + "speaker": "2", + "transcript": "we" + }, + { + "speaker": "2", + "transcript": "OK." + }, + { + "speaker": "2", + "transcript": "Well." + }, + { + "speaker": "2", + "transcript": "Yeah, no, that's wrong direction." + }, + { + "speaker": "2", + "transcript": "you need a form where you can stay. No, I didn't know that was, that was something like that." + }, + { + "speaker": "4", + "transcript": "And we do need more coding about the text agent, right? Yeah." + }, + { + "speaker": "2", + "transcript": "Or do we need another 8 different?" + }, + { + "speaker": "4", + "transcript": "or simply a text stage" + }, + { + "speaker": "4", + "transcript": "I think simply a text agent, um." + }, + { + "speaker": "2", + "transcript": "Would you have students." + }, + { + "speaker": "2", + "transcript": "Well" + }, + { + "speaker": "2", + "transcript": "So should decide." + }, + { + "speaker": "4", + "transcript": "whether it's a query or for." + }, + { + "speaker": "2", + "transcript": "Yeah, cause it will be sending everything to the allocation. Yes." + }, + { + "speaker": "4", + "transcript": "and fun, you know." + }, + { + "speaker": "0", + "transcript": "It's, uh, if you go" + }, + { + "speaker": "0", + "transcript": "you go straight and look at the top of all the see all the pressure I had done the things to" + }, + { + "speaker": "4", + "transcript": "to the allocation agent." + }, + { + "speaker": "2", + "transcript": "and also ask the text agent like how we prompt uh GPT 5 in the, in the speech agent that if you need to search, you just search and everything of this chat history would be dubbed to the outcome. And so just allocation don't know if the allocation can actually do anything should be like she said that she's gonna use to do it so." + }, + { + "speaker": "4", + "transcript": "you should be able to foster for tests." + }, + { + "speaker": "5", + "transcript": "especially away from the command life that's like march was short my life. Oh, that's, uh." + }, + { + "speaker": "0", + "transcript": "everything. Why is SEC." + }, + { + "speaker": "2", + "transcript": "I." + }, + { + "speaker": "2", + "transcript": "because she can go she look for." + }, + { + "speaker": "0", + "transcript": "get cold, colds and caresthetic er up for me. No, I watch my eyes. I love that and someone else takes change you will like that." + }, + { + "speaker": "2", + "transcript": "you know." } ] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index dbc4033..c64d3fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,9 @@ flask -django requests streamlit google-auth -google-auth-oauthlib \ No newline at end of file +google-auth-oauthlib +python-dotenv==1.1.1 +pyvis +neo4j +audio-recorder-streamlit \ No newline at end of file From 0b6060c89bec8004a81e37bbce7c657a9292c75b Mon Sep 17 00:00:00 2001 From: Tan Wee Joe <84664178+w3joe@users.noreply.github.com> Date: Sun, 2 Nov 2025 10:23:11 +0000 Subject: [PATCH 17/18] bug: audio recording still runs after stop --- .../pages/audio_viewer.py | 70 +++++++++++++++++-- frontend/app.py | 2 +- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/frontend/agentverse-streamlit-app/pages/audio_viewer.py b/frontend/agentverse-streamlit-app/pages/audio_viewer.py index c0c9aea..b47845e 100644 --- a/frontend/agentverse-streamlit-app/pages/audio_viewer.py +++ b/frontend/agentverse-streamlit-app/pages/audio_viewer.py @@ -6,6 +6,9 @@ import json import tempfile import threading +import signal +import os +import psutil from dotenv import load_dotenv load_dotenv() @@ -13,6 +16,40 @@ AUDIO_AGENT_PATH = Path(__file__).parent.parent.parent.parent.joinpath("agents", "audio_agent.py").resolve() OUTPUT_JSON = Path("transcripts_dataset.json") +def kill_process_tree(pid): + """Kill a process and all its children""" + try: + parent = psutil.Process(pid) + children = parent.children(recursive=True) + + # Terminate children first + for child in children: + try: + child.terminate() + except psutil.NoSuchProcess: + pass + + # Terminate parent + try: + parent.terminate() + except psutil.NoSuchProcess: + pass + + # Wait for termination + gone, alive = psutil.wait_procs(children + [parent], timeout=3) + + # Force kill any remaining processes + for p in alive: + try: + p.kill() + except psutil.NoSuchProcess: + pass + + except psutil.NoSuchProcess: + pass + except Exception as exc: + st.warning(f"Error killing process tree: {exc}") + def main(): """Main function to display Audio Agent interface""" st.title("🎤 Audio Agent - Live Transcription") @@ -30,18 +67,23 @@ def main(): st.session_state.transcriptions = [] if "transcription_running" not in st.session_state: st.session_state.transcription_running = False + if "audio_process" not in st.session_state: + st.session_state.audio_process = None # Start/Stop Audio Transcription Button st.header("Continuous Transcription") if not st.session_state.transcription_running: if st.button("Start Audio Transcription", type="primary"): try: - # Run audio agent in background thread - def run_audio_agent(): - subprocess.run([sys.executable, str(AUDIO_AGENT_PATH)], check=True) - - thread = threading.Thread(target=run_audio_agent, daemon=True) - thread.start() + # Start audio agent as subprocess + process = subprocess.Popen( + [sys.executable, str(AUDIO_AGENT_PATH)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + preexec_fn=os.setsid if sys.platform != 'win32' else None, + creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if sys.platform == 'win32' else 0 + ) + st.session_state.audio_process = process st.session_state.transcription_running = True st.success("Audio transcription started.") st.rerun() @@ -50,6 +92,22 @@ def run_audio_agent(): else: st.info("🔴 Transcription is running...") if st.button("Stop Audio Transcription"): + # Terminate the audio process and all its children + if st.session_state.audio_process: + try: + pid = st.session_state.audio_process.pid + kill_process_tree(pid) + st.session_state.audio_process = None + except Exception as exc: + st.warning(f"Error stopping process: {exc}") + # Fallback: try force kill + try: + if st.session_state.audio_process: + st.session_state.audio_process.kill() + st.session_state.audio_process = None + except: + pass + st.session_state.transcription_running = False st.success("Audio transcription stopped.") st.rerun() diff --git a/frontend/app.py b/frontend/app.py index 28b7965..5c33629 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -20,7 +20,7 @@ GRAPH_VIEWER_PATH = HERE.joinpath("agentverse-streamlit-app", "pages", "graph_viewer.py") AUDIO_VIEWER_MODULE_PATH = HERE.joinpath("agentverse-streamlit-app", "pages", "audio_viewer.py") -#CLIENT_SECRETS_FILE = HERE.joinpath("agentverse-streamlit-app", "client_secrets.json") +CLIENT_SECRETS_FILE = HERE.joinpath("agentverse-streamlit-app", "client_secrets.json") SCOPES = [ "openid", From 754baad0962f114fa0ce4a7af39dbb5b410fd9f5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Nov 2025 10:31:29 +0000 Subject: [PATCH 18/18] Initial plan