diff --git a/.gitignore b/.gitignore index 4333e31..7059ae9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -agents/.env -frontend/.env +## Ignore local OAuth client secrets and generated outputs frontend/agentverse-streamlit-app/client_secrets.json +text_agent_output.json +.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..04e9814 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# 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. 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 diff --git a/agents/text_agent.py b/agents/text_agent.py index ea45f9d..5eab638 100644 --- a/agents/text_agent.py +++ b/agents/text_agent.py @@ -1,15 +1,37 @@ -from flask import Flask, request -import requests, json +import os +import sys +import json from google import genai +from dotenv import load_dotenv -client = genai.Client(api_key="AIzaSyBZ7LA5XV7kdbJLvLtNswCMirlxdo4l6w0") +load_dotenv() -response = client.models.generate_content( - model="gemini-2.5-flash", - contents="what is the meaning of life", -) +# Text agent entrypoint and function. Reads GENAI_API_KEY from environment. +API_KEY = os.getenv("GENAI_API_KEY") -## save output in json -output = {"text_output": response.text} -with open("text_agent_output.json", "a") as f: - json.dump(output, f, indent=2) +def generate_text(prompt: str) -> str: + """Generate text using GenAI and persist to `text_agent_output.json`. + + 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/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 index 72c59bb..e6429d8 100644 --- a/api/README.md +++ b/api/README.md @@ -1,27 +1,8 @@ -# Neo4j Flask API +Neo4j Flask API -A simple Flask API to interact with a Neo4j database. You can push graph data, query nodes, and clear the database. +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. -## How to Run - -1. **Install dependencies:** - ``` - pip install -r requirements.txt - ``` - -2. **Set up your `.env` file:** - ``` - NEO4J_USER=GET FROM ME - NEO4J_PASSWORD=GET FROM ME - ``` - -3. **Start the API:** - ``` - python neo4j_api.py - ``` - The server will run at `http://localhost:8080`. - -## Table of Contents +Table of Contents - Base URL - Authentication @@ -35,7 +16,7 @@ A simple Flask API to interact with a Neo4j database. You can push graph data, q --- -## Base URL +Base URL http://:8080 @@ -43,7 +24,7 @@ Replace with the actual IP address or hostname of the server wh --- -## Authentication +Authentication The API uses environment variables to configure the connection to the Neo4j database: @@ -55,7 +36,7 @@ These values must be set in your environment for the API to connect to Neo4j. Th --- -## Endpoints +Endpoints 1. Home @@ -239,17 +220,14 @@ If there’s an error: --- -## Example Requests +Example Requests Here’s how you might interact with the API using curl from the command line. -**Health Check** -``` +Health Check curl http://localhost:8080/health -``` -**Push Graph Data** -``` +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}}, @@ -259,25 +237,20 @@ curl -X POST http://localhost:8080/push -H "Content-Type: application/json" -d ' {"from": "1", "to": "2", "type": "WORKS_AT", "properties": {"since": 2020}} ] }' -``` -**Query Graph** -``` +Query Graph curl -X POST http://localhost:8080/query -H "Content-Type: application/json" -d '{ "label": "Person", "property": "name", "value": "John" }' -``` -**Clear Database** -``` +Clear Database curl -X DELETE http://localhost:8080/clear -``` --- -## Conclusion +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/.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/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/pages/audio_viewer.py b/frontend/agentverse-streamlit-app/pages/audio_viewer.py new file mode 100644 index 0000000..b47845e --- /dev/null +++ b/frontend/agentverse-streamlit-app/pages/audio_viewer.py @@ -0,0 +1,116 @@ +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 +import signal +import os +import psutil +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") + +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") + + # 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 + 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: + # 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() + 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"): + # 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() + +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..15f27ad --- /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/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..5c33629 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -0,0 +1,311 @@ +import os +import json +import subprocess +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() +OUTPUT_JSON = Path("text_agent_output.json") +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") + +SCOPES = [ + "openid", + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/userinfo.profile", +] + + +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(): + try: + return json.loads(CLIENT_SECRETS_FILE.read_text(encoding="utf-8")) + except Exception: + return None + 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") + try: + st.session_state["oauth_state"] = state + except Exception: + pass + 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")} + + +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 + +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.""" + try: + st.rerun() + except AttributeError: + try: + st.experimental_rerun() + 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 + +if "user" not in st.session_state: + st.session_state.user = None + +if "messages" not in st.session_state: + st.session_state.messages = [] + +# Comment out OAuth callback handling for testing if required +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.") + st.session_state.pop("oauth_state", None) + else: + try: + user = exchange_code_for_user(code, returned_state) + st.session_state.user = user + st.session_state.pop("oauth_state", None) + + # Clear query params - FIXED VERSION + st.query_params.clear() + st.success(f"Logged in as {user.get('name')}") + _rerun_compat() + + except Exception as exc: + msg = str(exc) + 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("Clear and Retry Login"): + st.query_params.clear() + st.session_state.pop("oauth_state", None) + _rerun_compat() + else: + st.error(f"Login failed: {exc}") + if st.button("Retry login"): + st.query_params.clear() + st.session_state.pop("oauth_state", None) + _rerun_compat() + +# 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", use_container_width=True): + st.session_state.user = None + st.session_state.active_page = "Chat" + st.query_params.clear() + _rerun_compat() + +# 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`.") +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""" +
+
+ {text} +
+
+ """, unsafe_allow_html=True) + else: + st.markdown(f""" +
+
+ {text} +
+
+ """, unsafe_allow_html=True) + + render_messages() + + user_input = st.text_input("Message", key="user_input") + if st.button("Send") and user_input: + st.session_state.messages.append({"role": "user", "text": user_input}) + + try: + 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: + 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() + + 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/agents/allocation_agent.py b/frontend/styles.css similarity index 100% rename from agents/allocation_agent.py rename to frontend/styles.css diff --git a/frontend/text_agent_output.json b/frontend/text_agent_output.json new file mode 100644 index 0000000..28d62ba --- /dev/null +++ b/frontend/text_agent_output.json @@ -0,0 +1,3 @@ +{ + "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/requirements.txt b/requirements.txt index 454799d..c64d3fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,9 @@ flask -django -requests \ No newline at end of file +requests +streamlit +google-auth +google-auth-oauthlib +python-dotenv==1.1.1 +pyvis +neo4j +audio-recorder-streamlit \ 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