├── src ├── agent.py ├── __init__.py ├── tools │ ├── __init__.py │ ├── README.md │ ├── test_tools.py │ ├── examples │ │ ├── math_example.py │ │ ├── puppeteer_example.py │ │ ├── simple_math_client.py │ │ ├── math_custom_adapter.py │ │ └── playwright_example.py │ ├── simple_test.py │ ├── mcp_README.md │ ├── math_server.py │ ├── test_mcp_tools.py │ └── registry.py ├── test_agent_architecture.py └── configuration.py ├── log-browser.yml ├── log-server.yml ├── models ├── __init__.py ├── research.py └── file_analysis.py ├── routers ├── __init__.py └── database.py ├── services └── __init__.py ├── assets ├── edr_ppl.png ├── benchmarks.png ├── edr-logo.png └── leaderboard.png ├── ai-research-assistant ├── postcss.config.js ├── public │ ├── sfr_logo.jpeg │ └── index.html ├── .gitignore ├── src │ ├── index.js │ ├── components │ │ ├── ResearchItem.js │ │ ├── CodeWithVisualization.js │ │ ├── Navbar.js │ │ ├── LoadingIndicator.js │ │ └── CodeSnippetViewer.js │ ├── index.css │ └── App.js ├── tailwind.config.js └── package.json ├── Tech_Report__Enterprise_Deep_Research.pdf ├── CODEOWNERS ├── e2b.Dockerfile ├── langgraph.json ├── mcp_agent.secrets.yaml ├── replit.nix ├── SECURITY.md ├── e2b.toml ├── package.json ├── AI_ETHICS.md ├── math_server.py ├── benchmarks ├── run_research.sh ├── process_drb.py └── README.md ├── pyproject.toml ├── model_test.py ├── .gitignore ├── requirements.txt ├── .env.sample ├── graph_test.py ├── test_graph.py ├── test_agents.py ├── math_client_new.py ├── session_store.py ├── math_client_langgraph.py ├── test_visualization.py ├── CODE_OF_CONDUCT.md ├── app.py ├── CONTRIBUTING.md ├── test_specialized_searches.py ├── test_unified_query.py └── test_benchmark.py /src/agent.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /log-browser.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /log-server.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /models/__init__.py: -------------------------------------------------------------------------------- 1 | # models package -------------------------------------------------------------------------------- /routers/__init__.py: -------------------------------------------------------------------------------- 1 | # routers package -------------------------------------------------------------------------------- /services/__init__.py: -------------------------------------------------------------------------------- 1 | # services package -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | # Make src directory a Python package 3 | -------------------------------------------------------------------------------- /assets/edr_ppl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalesforceAIResearch/enterprise-deep-research/HEAD/assets/edr_ppl.png -------------------------------------------------------------------------------- /assets/benchmarks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalesforceAIResearch/enterprise-deep-research/HEAD/assets/benchmarks.png -------------------------------------------------------------------------------- /assets/edr-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalesforceAIResearch/enterprise-deep-research/HEAD/assets/edr-logo.png -------------------------------------------------------------------------------- /assets/leaderboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalesforceAIResearch/enterprise-deep-research/HEAD/assets/leaderboard.png -------------------------------------------------------------------------------- /ai-research-assistant/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; -------------------------------------------------------------------------------- /Tech_Report__Enterprise_Deep_Research.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalesforceAIResearch/enterprise-deep-research/HEAD/Tech_Report__Enterprise_Deep_Research.pdf -------------------------------------------------------------------------------- /ai-research-assistant/public/sfr_logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalesforceAIResearch/enterprise-deep-research/HEAD/ai-research-assistant/public/sfr_logo.jpeg -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Comment line immediately above ownership line is reserved for related other information. Please be careful while editing. 2 | #ECCN:Open Source 3 | #GUSINFO:Open Source,Open Source Workflow 4 | -------------------------------------------------------------------------------- /e2b.Dockerfile: -------------------------------------------------------------------------------- 1 | # You can use most Debian-based base images 2 | # FROM ubuntu:22.04 3 | FROM e2bdev/code-interpreter:latest 4 | # Install dependencies and customize sandbox 5 | 6 | # Install some Python packages 7 | RUN pip install cowsay 8 | 9 | -------------------------------------------------------------------------------- /langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "dockerfile_lines": [], 3 | "graphs": { 4 | "frank_deep_researcher": "./src/graph.py:graph" 5 | }, 6 | "python_version": "3.11", 7 | "env": "./.env", 8 | "dependencies": [ 9 | "." 10 | ] 11 | } -------------------------------------------------------------------------------- /ai-research-assistant/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | directly */
144 | }
145 |
146 | /* General code tag styling (applies to inline and block if not overridden) */
147 | code {
148 | font-family: 'Menlo', 'Monaco', 'Courier New', monospace !important;
149 | font-size: 0.85em !important; /* Relative to parent, usually or */
150 | }
151 |
152 | /* Inline code specific styling (rendered by FinalReport.js with .final-report-inline-code) */
153 | .final-report-inline-code {
154 | background-color: rgba(27,31,35,.07) !important; /* GitHub-like subtle gray */
155 | padding: .2em .4em !important;
156 | margin: 0 !important;
157 | font-size: 85% !important; /* Relative to its parent (e.g.,
) */
158 | border-radius: 3px !important; /* Softer radius */
159 | font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;
160 | color: #1f2328 !important; /* Darker text for better readability */
161 | white-space: nowrap !important; /* Keep inline code on one line */
162 | display: inline !important; /* Ensure it behaves as inline */
163 | vertical-align: baseline !important;
164 | }
165 |
166 | .dark .final-report-inline-code {
167 | background-color: rgba(110,118,129,0.4) !important;
168 | color: #c9d1d9 !important;
169 | }
170 |
171 | /* Minimal code block styling (rendered by FinalReport.js with .final-report-minimal-block) */
172 | .final-report-minimal-block {
173 | display: block !important;
174 | padding: 0.2em 0.5em !important;
175 | margin: 0.5em 0 !important;
176 | background-color: #f0f2f5 !important;
177 | border: 1px solid #d9dcdf !important;
178 | border-radius: 4px !important;
179 | font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;
180 | font-size: 0.8em !important; /* Explicit size for the block */
181 | white-space: pre !important;
182 | overflow-x: auto !important;
183 | color: #303133 !important;
184 | line-height: 1.4 !important;
185 | min-height: auto !important;
186 | }
187 | .final-report-minimal-block code { /* Styling for inside minimal block */
188 | font-family: inherit !important; /* Inherit from .final-report-minimal-block */
189 | background-color: transparent !important;
190 | padding: 0 !important;
191 | font-size: inherit !important; /* Inherit size from pre */
192 | color: inherit !important;
193 | }
194 | .dark .final-report-minimal-block {
195 | background-color: #2c2c2c !important;
196 | border-color: #444 !important;
197 | color: #c9d1d9 !important;
198 | }
199 |
200 | /* Empty code block styling (rendered by FinalReport.js with .final-report-empty-block) */
201 | .final-report-empty-block {
202 | display: block !important;
203 | padding: 0.5em 0.8em !important;
204 | margin: 0.5em 0 !important;
205 | background-color: #f9f9f9 !important;
206 | border: 1px dashed #ccc !important;
207 | border-radius: 4px !important;
208 | font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;
209 | font-size: 0.75em !important;
210 | color: #777 !important;
211 | min-height: 2em !important;
212 | line-height: 1 !important;
213 | text-align: center !important;
214 | }
215 | .final-report-empty-block code { /* Styling for inside empty block */
216 | font-family: inherit !important;
217 | background-color: transparent !important;
218 | padding: 0 !important;
219 | font-size: inherit !important;
220 | color: inherit !important;
221 | }
222 | .dark .final-report-empty-block {
223 | background-color: #252525 !important;
224 | border-color: #555 !important;
225 | color: #888 !important;
226 | }
227 |
228 | /* Ensure SyntaxHighlighter's doesn't add extra margins or borders */
229 | .code-block-content pre {
230 | margin: 0 !important;
231 | border-radius: 0 !important;
232 | border: none !important;
233 | }
234 |
235 | /* Ensure keywords in code blocks are properly styled by SyntaxHighlighter */
236 | /* These are examples, actual classes depend on SyntaxHighlighter's theme */
237 | .token.keyword,
238 | .token.function,
239 | .token.builtin,
240 | .token.class-name {
241 | /* color: #d73a49; Example color, theme will override */
242 | }
243 |
244 | /* Ensure all text within SyntaxHighlighter uses monospace */
245 | .prism-code .token { /* General token class for Prism */
246 | font-family: 'Menlo', 'Monaco', 'Courier New', monospace !important;
247 | }
--------------------------------------------------------------------------------
/test_benchmark.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import logging
3 | from src.graph import create_fresh_graph
4 | from src.state import SummaryState
5 | from models.research import ResearchRequest
6 | from src.configuration import Configuration
7 |
8 | # Set up logging
9 | logging.basicConfig(level=logging.INFO)
10 | logger = logging.getLogger(__name__)
11 |
12 | async def test_benchmark_question():
13 | print("\n=== Starting Benchmark Test ===")
14 |
15 | # Define the benchmark question
16 | question = """An African author tragically passed away in a tragic road accident. As a child, he'd wanted to be a police officer. He lectured at a private university from 2018 until his death. In 2018, this author spoke about writing stories that have no sell by date in an interview. One of his books was selected to be a compulsory school reading in an African country in 2017. Which years did this author work as a probation officer?"""
17 | expected_answer = "1988-96"
18 |
19 | print(f"Question: {question}")
20 | print(f"Expected Answer: {expected_answer}")
21 | print("=== Running Research ===\n")
22 |
23 | # Create configuration
24 | config = {
25 | "configurable": {
26 | "thread_id": "benchmark_test",
27 | "llm_provider": "google",
28 | "llm_model": "gemini-2.5-pro",
29 | "max_web_research_loops": 10
30 | },
31 | "recursion_limit": 100, # Set recursion limit to 100
32 | }
33 |
34 | # config = {
35 | # "configurable": {
36 | # "thread_id": "benchmark_test",
37 | # "llm_provider": "anthropic",
38 | # "llm_model": "claude-3-5-sonnet",
39 | # "max_web_research_loops": 10
40 | # },
41 | # "recursion_limit": 100, # Set recursion limit to 100
42 | # }
43 |
44 | # Create initial state with benchmark mode explicitly enabled
45 | initial_state = SummaryState(
46 | research_topic=question,
47 | benchmark_mode=True, # Explicitly enable benchmark mode
48 | extra_effort=True, # Enable thorough research
49 | research_loop_count=0,
50 | running_summary="",
51 | search_query="",
52 | research_complete=False,
53 | knowledge_gap="",
54 | search_results_empty=False,
55 | selected_search_tool="general_search",
56 | sources_gathered=[],
57 | web_research_results=[],
58 | source_citations={},
59 | research_plan={}, # Initialize with empty research_plan to avoid AttributeError
60 | previous_answers=[], # Initialize empty previous_answers list
61 | reflection_history=[], # Initialize empty reflection_history list
62 | llm_provider=config["configurable"]["llm_provider"],
63 | llm_model=config["configurable"]["llm_model"],
64 | config={
65 | "benchmark": {
66 | "expected_answer": expected_answer,
67 | "confidence_threshold": 0.8
68 | },
69 | "configurable": config["configurable"]
70 | }
71 | )
72 |
73 | # Log the initial state
74 | logger.info(f"Initial state benchmark_mode: {initial_state.benchmark_mode}")
75 | logger.info(f"Initial state config: {initial_state.config}")
76 |
77 | # Create graph and run research
78 | graph = create_fresh_graph()
79 |
80 | # Debug log before invoking graph
81 | print("\nState before graph invocation:")
82 | print(f"benchmark_mode: {initial_state.benchmark_mode}")
83 | print(f"research_topic: {initial_state.research_topic}")
84 |
85 | try:
86 | final_state = await graph.ainvoke(
87 | initial_state,
88 | config=config
89 | )
90 |
91 | # Debug log after graph execution
92 | print("\nState after graph execution:")
93 | print(f"benchmark_mode: {getattr(final_state, 'benchmark_mode', False)}")
94 | print(f"research_complete: {getattr(final_state, 'research_complete', False)}")
95 |
96 | # Debugging - print all available keys in the state
97 | print("\nDetailed state inspection:")
98 | if isinstance(final_state, dict):
99 | print("State is a dictionary with keys:", list(final_state.keys()))
100 | elif hasattr(final_state, '__dict__'):
101 | print("State attributes:", list(final_state.__dict__.keys()))
102 | else:
103 | print(f"State type: {type(final_state)}")
104 |
105 | # Try to find benchmark_result
106 | benchmark_result = None
107 |
108 | # Check if state is a dictionary (new way state is returned)
109 | if isinstance(final_state, dict):
110 | if 'benchmark_result' in final_state:
111 | benchmark_result = final_state['benchmark_result']
112 | print("Found benchmark_result in dictionary state")
113 | else:
114 | print("benchmark_result not found in dictionary state")
115 |
116 | # Check if state is an object (old way state is returned)
117 | elif hasattr(final_state, 'benchmark_result') and final_state.benchmark_result is not None:
118 | benchmark_result = final_state.benchmark_result
119 | print("Found benchmark_result in object state")
120 | elif hasattr(final_state, '__dict__') and 'benchmark_result' in final_state.__dict__:
121 | benchmark_result = final_state.__dict__['benchmark_result']
122 | print("Found benchmark_result in object __dict__")
123 | else:
124 | print("benchmark_result not found in any state form")
125 |
126 | # Additional fallback check - look in the final answer
127 | if not benchmark_result and hasattr(final_state, 'previous_answers') and final_state.previous_answers:
128 | print("Checking previous_answers for final result")
129 | # The last answer in previous_answers might contain our answer
130 | last_answer = final_state.previous_answers[-1]
131 | print(f"Last answer: {last_answer}")
132 |
133 | print("\n=== Results ===")
134 |
135 | if benchmark_result:
136 | answer = benchmark_result.get('answer', 'No answer generated')
137 | confidence = benchmark_result.get('confidence', 0.0)
138 | sources = benchmark_result.get('sources', [])
139 |
140 | # Verify the answer
141 | is_correct = expected_answer.lower().strip() in answer.lower().strip()
142 |
143 | print(f"Generated Answer: {answer}")
144 | print(f"Expected Answer: {expected_answer}")
145 | print(f"Correct: {is_correct}")
146 | print(f"Confidence: {confidence}")
147 | print(f"Sources: {sources}")
148 |
149 | # Print detailed analysis
150 | if not is_correct:
151 | print("\nAnalysis:")
152 | print(f"- Generated answer differs from expected answer")
153 | print(f"- Confidence level: {'High' if confidence > 0.8 else 'Medium' if confidence > 0.5 else 'Low'}")
154 | if not sources:
155 | print("- No sources were cited to support the answer")
156 | else:
157 | print("No benchmark result found in final state")
158 | print("Final Summary:", getattr(final_state, 'running_summary', 'No summary available'))
159 | print("Research Complete:", getattr(final_state, 'research_complete', False))
160 |
161 | # Check if previous_answers exists and has entries
162 | previous_answers = getattr(final_state, 'previous_answers', [])
163 | if previous_answers:
164 | print("\nFound answers in previous_answers:")
165 | for i, answer in enumerate(previous_answers):
166 | print(f"\nAnswer {i+1}:")
167 | print(f"- Answer: {answer.get('answer', 'No answer')}")
168 | print(f"- Confidence: {answer.get('confidence', 0.0)}")
169 | print(f"- Sources: {answer.get('sources', [])}")
170 |
171 | except Exception as e:
172 | print(f"\nERROR: Exception encountered during execution: {e}")
173 | import traceback
174 | print(traceback.format_exc())
175 |
176 | if __name__ == "__main__":
177 | asyncio.run(test_benchmark_question())
--------------------------------------------------------------------------------
/src/tools/examples/playwright_example.py:
--------------------------------------------------------------------------------
1 | """
2 | Example script demonstrating how to use the Playwright MCP server with our tool registry.
3 |
4 | This example shows how to:
5 | 1. Start the Playwright MCP server (via stdio)
6 | 2. Register it with our tool registry
7 | 3. Execute some web browsing commands using the Playwright tools
8 |
9 | Prerequisites:
10 | - Requires Node.js and npx to be installed.
11 | - The Playwright MCP server will be downloaded via npx if not already present.
12 | """
13 | import asyncio
14 | import sys
15 | import os
16 | from pathlib import Path
17 |
18 | # Add the project root to the Python path
19 | project_root = Path(__file__).parent.parent.parent.parent
20 | sys.path.append(str(project_root))
21 |
22 | from src.tools.registry import SearchToolRegistry
23 | from src.tools.executor import ToolExecutor
24 | from src.tools.mcp_tools import MCPToolManager
25 |
26 | async def main():
27 | # Create a tool registry
28 | registry = SearchToolRegistry()
29 | executor = ToolExecutor(registry)
30 |
31 | # Create an MCP tool manager
32 | mcp_manager = MCPToolManager(registry)
33 |
34 | try:
35 | # Start the Playwright MCP server as a subprocess using stdio
36 | # Using headless mode by default
37 | print("Registering Playwright MCP server via stdio (headless)...")
38 | tools = await mcp_manager.register_stdio_server(
39 | name="playwright",
40 | command="npx",
41 | args=["-y", "@playwright/mcp@latest", "--headless"]
42 | # args=["-y", "@playwright/mcp@latest"]
43 | )
44 |
45 | print(f"Registered {len(tools)} tools from Playwright MCP server:")
46 | for tool in tools:
47 | print(f" - {tool.name}: {tool.description}")
48 |
49 | # Example: Execute some web browsing commands
50 |
51 | # Navigate to a website
52 | print("\nNavigating to https://example.com...")
53 | nav_result = await executor.execute_tool(
54 | "mcp.playwright.browser_navigate",
55 | {"url": "https://xplorestaging.ieee.org/author/37086453282"},
56 | config={}
57 | )
58 | print(f"Navigation result: {nav_result}")
59 |
60 | # Take a screenshot after navigating (using PDF as workaround)
61 | print("\nTaking screenshot (using PDF as workaround)...")
62 | screenshots_dir = os.path.join(project_root, "screenshots")
63 | output_file_path = os.path.join(screenshots_dir, "example_com.pdf")
64 |
65 | try:
66 | pdf_result = await executor.execute_tool(
67 | "mcp.playwright.browser_pdf_save",
68 | {"path": "example_com.pdf"}, # This will be ignored, but we include it anyway
69 | config={}
70 | )
71 | print(f"PDF save command executed. Result: {pdf_result}")
72 |
73 | # Extract the actual path from the result
74 | if isinstance(pdf_result, str) and pdf_result.startswith("Saved as "):
75 | actual_path = pdf_result.replace("Saved as ", "").strip()
76 | print(f"Actual PDF file location: {actual_path}")
77 |
78 | # Copy the file to our screenshots directory
79 | import shutil
80 | os.makedirs(screenshots_dir, exist_ok=True) # Ensure screenshots directory exists
81 | shutil.copy2(actual_path, output_file_path)
82 |
83 | if os.path.exists(output_file_path):
84 | print(f"Successfully saved visual capture to: {output_file_path}")
85 | else:
86 | print(f"Failed to save visual capture to: {output_file_path}")
87 | else:
88 | print(f"Unexpected PDF result format: {pdf_result}")
89 | except Exception as e:
90 | print(f"Error capturing visual: {e}")
91 | import traceback
92 | traceback.print_exc()
93 |
94 | # ALSO attempt to take an actual screenshot (as a second approach)
95 | print("\nAlso attempting to take a PNG screenshot...")
96 | output_screenshot_path = os.path.join(screenshots_dir, "linkedin_profile.png")
97 |
98 | try:
99 | screenshot_result = await executor.execute_tool(
100 | "mcp.playwright.browser_take_screenshot",
101 | {"path": "linkedin_profile.png"},
102 | config={}
103 | )
104 | print(f"Screenshot command executed. Result: {screenshot_result}")
105 |
106 | # Check if Playwright saved the screenshot somewhere and reported the location
107 | if isinstance(screenshot_result, str) and "Saved as" in screenshot_result:
108 | # Extract the actual path from the result
109 | actual_path = screenshot_result.replace("Saved as", "").strip()
110 | print(f"Actual screenshot file location: {actual_path}")
111 |
112 | # Copy the file to our screenshots directory
113 | import shutil
114 | shutil.copy2(actual_path, output_screenshot_path)
115 |
116 | if os.path.exists(output_screenshot_path):
117 | print(f"Successfully copied screenshot to: {output_screenshot_path}")
118 | else:
119 | print(f"Failed to copy screenshot to: {output_screenshot_path}")
120 | else:
121 | # In case it worked but with a different result format
122 | print(f"Screenshot result doesn't contain path info. Looking for files...")
123 | import glob
124 | # Try to find any recent png files in typical temp directories
125 | tmp_files = glob.glob("/tmp/*.png") + glob.glob("/var/tmp/*.png")
126 | # Sort by creation time, newest first
127 | tmp_files.sort(key=lambda x: os.path.getctime(x), reverse=True)
128 |
129 | if tmp_files:
130 | newest_file = tmp_files[0]
131 | print(f"Found possible screenshot at: {newest_file}")
132 | import shutil
133 | shutil.copy2(newest_file, output_screenshot_path)
134 | if os.path.exists(output_screenshot_path):
135 | print(f"Successfully copied most recent PNG to: {output_screenshot_path}")
136 | else:
137 | print(f"Failed to copy most recent PNG to: {output_screenshot_path}")
138 | else:
139 | print("No recent PNG files found in temp directories")
140 | except Exception as e:
141 | print(f"Error processing screenshot: {e}")
142 | import traceback
143 | traceback.print_exc()
144 |
145 | # Take an accessibility snapshot - Temporarily commented out for debugging
146 | # print("\nTaking accessibility snapshot...")
147 | # snapshot_result = await executor.execute_tool(
148 | # "mcp.playwright.browser_snapshot",
149 | # {},
150 | # config={}
151 | # )
152 | # # Snapshot result can be large, print only a part or confirmation
153 | # if isinstance(snapshot_result, dict) and 'snapshot' in snapshot_result:
154 | # print(f"Snapshot captured successfully (first 200 chars):\n{str(snapshot_result['snapshot'])[:200]}...")
155 | # else:
156 | # print(f"Snapshot result: {snapshot_result}")
157 |
158 |
159 | # Example: Typing into a non-existent element (will likely fail, demonstrating error handling)
160 | # On example.com, there isn't an obvious input field without more complex interaction
161 | # print("\nAttempting to type (expected to fail)...")
162 | # try:
163 | # type_result = await executor.execute_tool(
164 | # "mcp.playwright.browser_type",
165 | # {"ref": "input#search", "element": "search input", "text": "hello world"}
166 | # )
167 | # print(f"Type result: {type_result}")
168 | # except Exception as e:
169 | # print(f"Typing failed as expected: {e}")
170 |
171 | except Exception as e:
172 | print(f"An error occurred: {e}")
173 | finally:
174 | # Close all MCP connections
175 | print("\nClosing MCP connections...")
176 | await mcp_manager.close_all()
177 | print("Connections closed.")
178 |
179 | if __name__ == "__main__":
180 | asyncio.run(main())
--------------------------------------------------------------------------------
/ai-research-assistant/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from 'react';
2 | import Navbar from './components/Navbar';
3 | import ResearchPanel from './components/ResearchPanel';
4 | import DetailsPanel from './components/DetailsPanel'; // Now handles both item details and report content
5 | import './App.css'; // For global styles
6 |
7 | function App() {
8 | const [isResearching, setIsResearching] = useState(false);
9 | const [currentQuery, setCurrentQuery] = useState('');
10 | const [extraEffort, setExtraEffort] = useState(false);
11 | const [minimumEffort, setMinimumEffort] = useState(false);
12 | const [benchmarkMode, setBenchmarkMode] = useState(false);
13 | const [modelProvider, setModelProvider] = useState('google'); // Default provider
14 | const [modelName, setModelName] = useState('gemini-2.5-pro'); // Default model
15 | const [uploadedFileContent, setUploadedFileContent] = useState(null); // Added state for uploaded file content
16 | const [databaseInfo, setDatabaseInfo] = useState(null); // Added state for database info
17 |
18 | const [isDetailsPanelOpen, setIsDetailsPanelOpen] = useState(false);
19 | const [detailsPanelContentType, setDetailsPanelContentType] = useState(null); // 'item' or 'report'
20 | const [detailsPanelContentData, setDetailsPanelContentData] = useState(null);
21 |
22 | // Steering state
23 | const [currentTodoPlan, setCurrentTodoPlan] = useState("");
24 | const [todoPlanVersion, setTodoPlanVersion] = useState(0);
25 |
26 | const handleBeginResearch = useCallback((query, extra, minimum, benchmark, modelConfig, fileContent, databaseInfo) => { // Added fileContent and databaseInfo
27 | setCurrentQuery(query);
28 | setExtraEffort(extra);
29 | setMinimumEffort(minimum);
30 | setBenchmarkMode(benchmark);
31 | if (modelConfig) {
32 | setModelProvider(modelConfig.provider);
33 | setModelName(modelConfig.model);
34 | }
35 |
36 | setUploadedFileContent(fileContent); // Set uploaded file content
37 | // Store database info for the research agent
38 | if (databaseInfo && databaseInfo.length > 0) {
39 | console.log('Database info passed to research agent:', databaseInfo);
40 | setDatabaseInfo(databaseInfo); // Store database info in state
41 | }
42 | setIsResearching(true);
43 | setIsDetailsPanelOpen(false); // Close details panel when new research starts
44 | // Wait for animation to complete before resetting content
45 | setTimeout(() => {
46 | setDetailsPanelContentType(null);
47 | setDetailsPanelContentData(null);
48 | }, 300);
49 | }, [uploadedFileContent]);
50 |
51 | const handleShowItemDetails = useCallback((item) => {
52 | // Set data first, then trigger animation
53 | setDetailsPanelContentData(item);
54 | setDetailsPanelContentType('item');
55 | // Small delay to ensure data is set before animation starts
56 | setTimeout(() => {
57 | setIsDetailsPanelOpen(true);
58 | }, 10);
59 | }, []);
60 |
61 | const handleShowReportDetails = useCallback((reportContent) => {
62 | // Set data first, then trigger animation
63 | setDetailsPanelContentData(reportContent);
64 | setDetailsPanelContentType('report');
65 | // Small delay to ensure data is set before animation starts
66 | setTimeout(() => {
67 | setIsDetailsPanelOpen(true);
68 | }, 10);
69 | }, []);
70 |
71 | const handleCloseDetailsPanel = useCallback(() => {
72 | setIsDetailsPanelOpen(false);
73 | // Wait for animation to complete before clearing data
74 | setTimeout(() => {
75 | setDetailsPanelContentData(null);
76 | setDetailsPanelContentType(null);
77 | }, 300); // Match the transition timing in CSS
78 | }, []);
79 |
80 | // This callback is used by ResearchPanel to inform App.js that a report is ready.
81 | const [finalReportData, setFinalReportData] = useState(null);
82 | const handleReportGenerated = useCallback((report) => {
83 | setFinalReportData(report); // Store report data
84 | // Optionally, automatically open the report:
85 | // handleShowReportDetails(report);
86 | }, []);
87 |
88 | const handleStopResearch = useCallback(() => {
89 | console.log('Stopping research from App.js');
90 | setIsResearching(false);
91 |
92 | // Clear all research-related state
93 | setFinalReportData(null);
94 | setCurrentTodoPlan("");
95 | setTodoPlanVersion(0); // Reset version counter
96 |
97 | // Close details panel if open
98 | if (isDetailsPanelOpen) {
99 | setIsDetailsPanelOpen(false);
100 | setTimeout(() => {
101 | setDetailsPanelContentType(null);
102 | setDetailsPanelContentData(null);
103 | }, 300);
104 | }
105 | }, [isDetailsPanelOpen]);
106 |
107 | const handleTodoPlanUpdate = useCallback((todoPlan) => {
108 | if (todoPlan !== currentTodoPlan) {
109 | setCurrentTodoPlan(todoPlan);
110 | setTodoPlanVersion(prev => prev + 1);
111 | }
112 |
113 | if (todoPlan && !isDetailsPanelOpen) {
114 | setDetailsPanelContentType('todo');
115 | setDetailsPanelContentData(todoPlan);
116 | setTimeout(() => {
117 | setIsDetailsPanelOpen(true);
118 | }, 10);
119 | } else if (detailsPanelContentType === 'todo' && isDetailsPanelOpen) {
120 | setDetailsPanelContentData(todoPlan);
121 | } else if (!isDetailsPanelOpen && detailsPanelContentType === 'todo') {
122 | setDetailsPanelContentData(todoPlan);
123 | }
124 | }, [isDetailsPanelOpen, detailsPanelContentType, currentTodoPlan]);
125 |
126 | const handleToggleProgress = useCallback(() => {
127 | if (detailsPanelContentType === 'todo' && isDetailsPanelOpen) {
128 | handleCloseDetailsPanel();
129 | } else if (currentTodoPlan) {
130 | handleTodoPlanUpdate(currentTodoPlan);
131 | }
132 | }, [detailsPanelContentType, isDetailsPanelOpen, currentTodoPlan, handleCloseDetailsPanel, handleTodoPlanUpdate]);
133 |
134 | const handleToggleReport = useCallback(() => {
135 | if (detailsPanelContentType === 'report' && isDetailsPanelOpen) {
136 | handleCloseDetailsPanel();
137 | } else if (finalReportData) {
138 | handleShowReportDetails(finalReportData);
139 | }
140 | }, [detailsPanelContentType, isDetailsPanelOpen, finalReportData, handleCloseDetailsPanel, handleShowReportDetails]);
141 |
142 | return (
143 |
144 |
152 |
153 |
154 |
155 |
172 |
173 |
174 |
186 |
187 |
188 | );
189 | }
190 |
191 | export default App;
--------------------------------------------------------------------------------