leckan commited on
Commit
b81fd16
·
verified ·
1 Parent(s): 0ba6603

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -1,13 +1,6 @@
1
  ---
2
- title: Deep Research
3
- emoji: 💻
4
- colorFrom: indigo
5
- colorTo: indigo
6
- sdk: gradio
7
- sdk_version: 6.20.0
8
- python_version: '3.13'
9
  app_file: app.py
10
- pinned: false
 
11
  ---
12
-
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: deep_research
 
 
 
 
 
 
3
  app_file: app.py
4
+ sdk: gradio
5
+ sdk_version: 6.14.0
6
  ---
 
 
__pycache__/email_agent.cpython-312.pyc ADDED
Binary file (1.7 kB). View file
 
__pycache__/messenger.cpython-312.pyc ADDED
Binary file (1.94 kB). View file
 
__pycache__/planner_agent.cpython-312.pyc ADDED
Binary file (1.68 kB). View file
 
__pycache__/research_manager.cpython-312.pyc ADDED
Binary file (4.33 kB). View file
 
__pycache__/search_agent.cpython-312.pyc ADDED
Binary file (989 Bytes). View file
 
__pycache__/styles.cpython-312.pyc ADDED
Binary file (9.27 kB). View file
 
__pycache__/writer_agent.cpython-312.pyc ADDED
Binary file (1.53 kB). View file
 
app.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from dotenv import load_dotenv
3
+ from research_manager import ResearchManager
4
+ from styles import CSS, JS, EXAMPLES, HEADER_HTML
5
+
6
+ load_dotenv(override=True)
7
+
8
+
9
+ async def run(query: str):
10
+ async for status_update in ResearchManager().run(query):
11
+ yield status_update
12
+
13
+
14
+ with gr.Blocks(title="Deep Research") as ui:
15
+ gr.HTML(HEADER_HTML)
16
+
17
+ with gr.Row(elem_classes="dr-query-row"):
18
+ query_textbox = gr.Textbox(
19
+ placeholder="Type a research question...",
20
+ show_label=False,
21
+ container=False,
22
+ autofocus=True,
23
+ elem_id="dr-query",
24
+ scale=5,
25
+ )
26
+ run_button = gr.Button("Investigate", variant="primary", elem_id="dr-run", scale=1)
27
+
28
+ gr.HTML('<div class="dr-examples-label">Try one</div>')
29
+ gr.Examples(examples=EXAMPLES, inputs=query_textbox, elem_id="dr-examples")
30
+
31
+ report = gr.Markdown(elem_id="dr-report")
32
+
33
+ run_button.click(run, inputs=query_textbox, outputs=report)
34
+ query_textbox.submit(run, inputs=query_textbox, outputs=report)
35
+
36
+
37
+ if __name__ == "__main__":
38
+ ui.launch(css=CSS, js=JS, theme=gr.themes.Base())
email_agent.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from agents import Agent, function_tool, ModelSettings
2
+ from messenger import send_email, push
3
+ import os
4
+ from dotenv import load_dotenv
5
+ load_dotenv(override=True)
6
+
7
+ MODEL_NAME = os.getenv("DEFAULT_MODEL_NAME", "gpt-5.4-mini")
8
+ USE_EMAIL = os.getenv("USE_EMAIL", "true").lower() == "true"
9
+
10
+ settings = ModelSettings(tool_choice="required")
11
+
12
+ @function_tool
13
+ def send_email_tool(subject: str, text_body: str, html_body: str) -> str:
14
+ """
15
+ Send out an email with the given subject and body
16
+
17
+ Args:
18
+ subject: The subject of the email
19
+ text_body: The body of the email as plain text
20
+ html_body: The HTML body of the email
21
+ """
22
+ if USE_EMAIL:
23
+ send_email(subject, text_body, html_body)
24
+ else:
25
+ push(f"Subject: {subject}\n\n{text_body}")
26
+ return "Email sent successfully"
27
+
28
+
29
+ INSTRUCTIONS = """
30
+ You are provided with a detailed report. Use your tool to send an email, converting the report into
31
+ a clean, well presented HTML email with an appropriate subject line.
32
+ """
33
+
34
+ email_agent = Agent(name="Email Agent", instructions=INSTRUCTIONS, tools=[send_email_tool], model=MODEL_NAME, model_settings=settings)
messenger.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ import requests
3
+ import os
4
+ import smtplib
5
+ from email.message import EmailMessage
6
+ load_dotenv(override=True)
7
+
8
+
9
+ EMAIL_ADDRESS = os.getenv("EMAIL_ADDRESS")
10
+ EMAIL_SMTP_SERVER = os.getenv("EMAIL_SMTP_SERVER")
11
+ EMAIL_APP_PASSWORD = os.getenv("EMAIL_APP_PASSWORD")
12
+
13
+ def send_email(subject, text_body, html_body):
14
+ msg = EmailMessage()
15
+ msg["From"] = EMAIL_ADDRESS
16
+ msg["To"] = EMAIL_ADDRESS
17
+ msg["Subject"] = subject
18
+ msg.set_content(text_body)
19
+ msg.add_alternative(html_body, subtype="html")
20
+
21
+ with smtplib.SMTP(EMAIL_SMTP_SERVER, 587) as server:
22
+ server.starttls()
23
+ server.login(EMAIL_ADDRESS, EMAIL_APP_PASSWORD)
24
+ server.send_message(msg)
25
+
26
+
27
+ pushover_user = os.getenv("PUSHOVER_USER")
28
+ pushover_token = os.getenv("PUSHOVER_TOKEN")
29
+ pushover_url = "https://api.pushover.net/1/messages.json"
30
+
31
+ def push(message):
32
+ print(f"Push: {message}")
33
+ payload = {"user": pushover_user, "token": pushover_token, "message": message}
34
+ requests.post(pushover_url, data=payload)
35
+
planner_agent.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from agents import Agent
3
+ import os
4
+ from dotenv import load_dotenv
5
+ load_dotenv(override=True)
6
+
7
+ MODEL_NAME = os.getenv("DEFAULT_MODEL_NAME", "gpt-5.4-mini")
8
+ HOW_MANY_SEARCHES = int(os.getenv("HOW_MANY_SEARCHES", 5))
9
+
10
+
11
+ INSTRUCTIONS = f"""
12
+ You are a research assistant. Given a user query, come up with a set of web searches
13
+ to perform to best answer the query. Output {HOW_MANY_SEARCHES} terms to query for.
14
+ """
15
+
16
+ class WebSearchItem(BaseModel):
17
+ reason: str = Field(description="Your reasoning for why this search is important to the query.")
18
+ query: str = Field(description="The search term to use for the web search.")
19
+
20
+
21
+ class WebSearchPlan(BaseModel):
22
+ searches: list[WebSearchItem] = Field(description="A list of web searches to perform to best answer the query.")
23
+
24
+ planner_agent = Agent(name="Planner Agent", instructions=INSTRUCTIONS, model=MODEL_NAME, output_type=WebSearchPlan)
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ pydantic
2
+ python-dotenv
3
+ openai
4
+ openai-agents
5
+ gradio
6
+ requests
research_manager.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from agents import Runner, trace, gen_trace_id
2
+ from search_agent import search_agent
3
+ from planner_agent import planner_agent, WebSearchItem, WebSearchPlan
4
+ from writer_agent import writer_agent, ReportData
5
+ from email_agent import email_agent
6
+ import asyncio
7
+
8
+ class ResearchManager:
9
+
10
+ async def run(self, query: str):
11
+ """ Run the deep research process, yielding the status updates and the final report"""
12
+ trace_id = gen_trace_id()
13
+ with trace("Research trace", trace_id=trace_id):
14
+ yield f"Starting research. Trace: https://platform.openai.com/traces/trace?trace_id={trace_id}"
15
+ search_plan = await self.plan_searches(query)
16
+ yield f"Searches planned, starting {len(search_plan.searches)} searches..."
17
+ search_results = await self.perform_searches(search_plan)
18
+ yield "Searches complete, writing report..."
19
+ report = await self.write_report(query, search_results)
20
+ yield "Report written, sending email..."
21
+ await self.send_email(report)
22
+ yield "Email sent, research complete"
23
+ yield report.markdown_report
24
+
25
+ async def plan_searches(self, query: str) -> WebSearchPlan:
26
+ """ Plan the searches to perform for the query """
27
+ result = await Runner.run(planner_agent, f"Query: {query}")
28
+ return result.final_output
29
+
30
+ async def perform_searches(self, search_plan: WebSearchPlan) -> list[str]:
31
+ """ Perform the searches to perform for the query """
32
+ tasks = [self.search(item) for item in search_plan.searches]
33
+ return await asyncio.gather(*tasks)
34
+
35
+ async def search(self, item: WebSearchItem) -> str | None:
36
+ """ Perform a search for the query """
37
+ input_message = f"Search term: {item.query}\nReason for searching: {item.reason}"
38
+ result = await Runner.run(search_agent, input_message)
39
+ return result.final_output
40
+
41
+ async def write_report(self, query: str, search_results: list[str]) -> ReportData:
42
+ """ Write the report for the query """
43
+ input_message = f"Original query: {query}\nSummarized search results: {search_results}"
44
+ result = await Runner.run(writer_agent, input_message)
45
+ return result.final_output
46
+
47
+ async def send_email(self, report: ReportData) -> None:
48
+ await Runner.run(email_agent, report.markdown_report)
search_agent.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from agents import Agent, WebSearchTool, ModelSettings
2
+ from dotenv import load_dotenv
3
+ import os
4
+
5
+ load_dotenv(override=True)
6
+ MODEL_NAME = os.getenv("DEFAULT_MODEL_NAME", "gpt-5.4-mini")
7
+
8
+ INSTRUCTIONS = """
9
+ You are a research assistant. Given a search term, you search the web for that term and
10
+ produce a concise summary of the results. The summary must 2-3 paragraphs and less than 300 words.
11
+ Capture the main points and be succinct. Reply only with the summary.
12
+ """
13
+
14
+ settings = ModelSettings(tool_choice="required")
15
+ tools = [WebSearchTool()]
16
+
17
+ search_agent = Agent(name="Search Agent", instructions=INSTRUCTIONS, tools=tools, model=MODEL_NAME, model_settings=settings)
simple.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from dotenv import load_dotenv
3
+ from research_manager import ResearchManager
4
+
5
+ load_dotenv(override=True)
6
+
7
+
8
+ async def run(query: str):
9
+ async for status_update in ResearchManager().run(query):
10
+ yield status_update
11
+
12
+
13
+ with gr.Blocks() as ui:
14
+ query_textbox = gr.Textbox(label="What topic would you like to research?")
15
+ run_button = gr.Button("Run", variant="primary")
16
+ report = gr.Markdown(label="Report")
17
+
18
+ run_button.click(run, inputs=query_textbox, outputs=report)
19
+ query_textbox.submit(run, inputs=query_textbox, outputs=report)
20
+
21
+ ui.launch(theme=gr.themes.Default(primary_hue="sky"))
22
+
styles.py ADDED
@@ -0,0 +1,364 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ EXAMPLES = [
2
+ "Most popular AI Agent frameworks in 2026",
3
+ "Most commercially successful Agentic AI implementations in 2026",
4
+ "Celebrities who don't like cheese",
5
+ ]
6
+
7
+ HEADER_HTML = """
8
+ <div class="dr-brand">
9
+ <div class="dr-mark">
10
+ <span class="dr-bar dr-bar-1"></span>
11
+ <span class="dr-bar dr-bar-2"></span>
12
+ <span class="dr-bar dr-bar-3"></span>
13
+ </div>
14
+ <div class="dr-titles">
15
+ <h1>Deep<span class="dr-sep">/</span>Research</h1>
16
+ <p>Multi-search web investigation</p>
17
+ </div>
18
+ </div>
19
+ """
20
+
21
+ CSS = """
22
+ .gradio-container {
23
+ --dr-bg: #fafaf7;
24
+ --dr-surface: #ffffff;
25
+ --dr-line: #0c0c0d;
26
+ --dr-line-soft: #e1e1da;
27
+ --dr-text: #0c0c0d;
28
+ --dr-muted: #6f6f72;
29
+ --dr-amber: #ecad0a;
30
+ --dr-blue: #209dd7;
31
+ --dr-purple: #753991;
32
+
33
+ max-width: 1080px !important;
34
+ margin: 0 auto !important;
35
+ padding: 2.5rem 2rem 4rem !important;
36
+ background: var(--dr-bg) !important;
37
+ color: var(--dr-text) !important;
38
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif !important;
39
+ }
40
+
41
+ .gradio-container.dark,
42
+ .dark .gradio-container,
43
+ body.dark .gradio-container,
44
+ html.dark .gradio-container {
45
+ --dr-bg: #0b0b0c;
46
+ --dr-surface: #161618;
47
+ --dr-line: #f1f1ec;
48
+ --dr-line-soft: #2a2a2d;
49
+ --dr-text: #f1f1ec;
50
+ --dr-muted: #8a8a8e;
51
+ }
52
+
53
+ body { background: var(--dr-bg, #fafaf7); }
54
+
55
+ /* === HEADER === */
56
+ .dr-brand {
57
+ display: grid;
58
+ grid-template-columns: auto 1fr;
59
+ align-items: center;
60
+ gap: 1.4rem;
61
+ padding-bottom: 1.25rem;
62
+ border-bottom: 3px solid var(--dr-line);
63
+ margin-bottom: 2.5rem;
64
+ }
65
+
66
+ .dr-mark {
67
+ display: flex;
68
+ flex-direction: column;
69
+ gap: 5px;
70
+ width: 38px;
71
+ }
72
+
73
+ .dr-bar { height: 7px; display: block; }
74
+ .dr-bar-1 { background: var(--dr-amber); width: 100%; }
75
+ .dr-bar-2 { background: var(--dr-blue); width: 70%; }
76
+ .dr-bar-3 { background: var(--dr-purple); width: 45%; }
77
+
78
+ .dr-titles h1 {
79
+ font-size: clamp(1.8rem, 4vw, 2.6rem);
80
+ font-weight: 900;
81
+ letter-spacing: -0.045em;
82
+ margin: 0;
83
+ line-height: 0.95;
84
+ text-transform: uppercase;
85
+ color: var(--dr-text);
86
+ }
87
+
88
+ .dr-sep {
89
+ color: var(--dr-amber);
90
+ font-weight: 300;
91
+ margin: 0 0.04em;
92
+ }
93
+
94
+ .dr-titles p {
95
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
96
+ font-size: 0.7rem;
97
+ letter-spacing: 0.22em;
98
+ text-transform: uppercase;
99
+ margin: 0.55rem 0 0;
100
+ color: var(--dr-muted);
101
+ }
102
+
103
+ /* === QUERY ROW === */
104
+ .dr-query-row {
105
+ gap: 0 !important;
106
+ align-items: stretch !important;
107
+ }
108
+
109
+ #dr-query, #dr-query > div, #dr-query .wrap, #dr-query .form, #dr-query .block {
110
+ background: transparent !important;
111
+ border: none !important;
112
+ box-shadow: none !important;
113
+ padding: 0 !important;
114
+ border-radius: 0 !important;
115
+ }
116
+
117
+ #dr-query textarea, #dr-query input {
118
+ background: var(--dr-surface) !important;
119
+ color: var(--dr-text) !important;
120
+ border: 2px solid var(--dr-line) !important;
121
+ border-radius: 0 !important;
122
+ padding: 1.05rem 1.2rem !important;
123
+ font-size: 1.05rem !important;
124
+ font-family: inherit !important;
125
+ box-shadow: none !important;
126
+ line-height: 1.45 !important;
127
+ resize: none !important;
128
+ min-height: 56px !important;
129
+ transition: border-color 0.15s, box-shadow 0.15s !important;
130
+ }
131
+
132
+ #dr-query textarea:focus, #dr-query input:focus {
133
+ outline: none !important;
134
+ border-color: var(--dr-blue) !important;
135
+ box-shadow: 6px 6px 0 0 var(--dr-blue) !important;
136
+ }
137
+
138
+ #dr-query textarea::placeholder, #dr-query input::placeholder {
139
+ color: var(--dr-muted) !important;
140
+ opacity: 1 !important;
141
+ }
142
+
143
+ #dr-run {
144
+ background: var(--dr-amber) !important;
145
+ color: #0c0c0d !important;
146
+ border: 2px solid var(--dr-line) !important;
147
+ border-left: none !important;
148
+ border-radius: 0 !important;
149
+ font-weight: 800 !important;
150
+ text-transform: uppercase !important;
151
+ letter-spacing: 0.14em !important;
152
+ font-size: 0.85rem !important;
153
+ box-shadow: none !important;
154
+ transition: background 0.15s, color 0.15s, transform 0.08s !important;
155
+ min-width: 150px !important;
156
+ padding: 1rem 1.5rem !important;
157
+ }
158
+
159
+ #dr-run:hover {
160
+ background: var(--dr-purple) !important;
161
+ color: #ffffff !important;
162
+ }
163
+
164
+ #dr-run:active { transform: translate(2px, 2px) !important; }
165
+
166
+ /* === EXAMPLES === */
167
+ .dr-examples-label {
168
+ font-family: ui-monospace, SFMono-Regular, monospace;
169
+ font-size: 0.65rem;
170
+ letter-spacing: 0.28em;
171
+ color: var(--dr-muted);
172
+ text-transform: uppercase;
173
+ margin: 2rem 0 0.85rem 0;
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 0.85rem;
177
+ }
178
+
179
+ .dr-examples-label::after {
180
+ content: "";
181
+ flex: 1;
182
+ height: 1px;
183
+ background: var(--dr-line-soft);
184
+ }
185
+
186
+ #dr-examples, #dr-examples > div, #dr-examples .wrap, #dr-examples .block {
187
+ background: transparent !important;
188
+ border: none !important;
189
+ padding: 0 !important;
190
+ box-shadow: none !important;
191
+ }
192
+
193
+ #dr-examples label, #dr-examples .label-wrap, #dr-examples > div > .label-wrap {
194
+ display: none !important;
195
+ }
196
+
197
+ #dr-examples table {
198
+ border-collapse: separate !important;
199
+ border-spacing: 0 !important;
200
+ width: auto !important;
201
+ background: transparent !important;
202
+ border: none !important;
203
+ }
204
+
205
+ #dr-examples thead { display: none !important; }
206
+
207
+ #dr-examples tbody { background: transparent !important; }
208
+
209
+ #dr-examples tr {
210
+ background: transparent !important;
211
+ display: flex !important;
212
+ flex-wrap: wrap !important;
213
+ gap: 8px !important;
214
+ border: none !important;
215
+ }
216
+
217
+ #dr-examples td, #dr-examples button {
218
+ background: var(--dr-surface) !important;
219
+ border: 1.5px solid var(--dr-line-soft) !important;
220
+ padding: 0.7rem 1.05rem !important;
221
+ cursor: pointer !important;
222
+ transition: border-color 0.15s, color 0.15s, transform 0.1s !important;
223
+ font-size: 0.9rem !important;
224
+ color: var(--dr-text) !important;
225
+ border-radius: 0 !important;
226
+ margin: 0 !important;
227
+ text-align: left !important;
228
+ box-shadow: none !important;
229
+ }
230
+
231
+ #dr-examples td:hover, #dr-examples button:hover {
232
+ border-color: var(--dr-purple) !important;
233
+ color: var(--dr-purple) !important;
234
+ transform: translateY(-1px);
235
+ }
236
+
237
+ /* === REPORT === */
238
+ #dr-report {
239
+ margin-top: 2.5rem !important;
240
+ padding: 0 !important;
241
+ background: transparent !important;
242
+ border: none !important;
243
+ box-shadow: none !important;
244
+ color: var(--dr-text) !important;
245
+ min-height: 40px;
246
+ }
247
+
248
+ #dr-report > div, #dr-report .prose {
249
+ background: transparent !important;
250
+ color: var(--dr-text) !important;
251
+ }
252
+
253
+ #dr-report:not(:empty) {
254
+ border-top: 1px solid var(--dr-line-soft) !important;
255
+ padding-top: 1.75rem !important;
256
+ }
257
+
258
+ #dr-report h1 {
259
+ font-size: 1.85rem;
260
+ font-weight: 900;
261
+ color: var(--dr-blue);
262
+ border-bottom: 2px solid var(--dr-line);
263
+ padding-bottom: 0.45rem;
264
+ margin: 1.5rem 0 1rem;
265
+ letter-spacing: -0.025em;
266
+ }
267
+
268
+ #dr-report h2 {
269
+ font-size: 1.35rem;
270
+ color: var(--dr-purple);
271
+ font-weight: 800;
272
+ margin-top: 1.75rem;
273
+ letter-spacing: -0.015em;
274
+ }
275
+
276
+ #dr-report h3 {
277
+ font-size: 1.1rem;
278
+ color: var(--dr-text);
279
+ font-weight: 800;
280
+ margin-top: 1.5rem;
281
+ }
282
+
283
+ #dr-report p { line-height: 1.7; }
284
+
285
+ #dr-report a {
286
+ color: var(--dr-blue);
287
+ text-decoration: underline;
288
+ text-decoration-thickness: 2px;
289
+ text-underline-offset: 3px;
290
+ }
291
+
292
+ #dr-report a:hover { color: var(--dr-amber); }
293
+
294
+ #dr-report code {
295
+ background: var(--dr-surface);
296
+ border: 1px solid var(--dr-line-soft);
297
+ padding: 0.1rem 0.4rem;
298
+ font-size: 0.92em;
299
+ border-radius: 0;
300
+ }
301
+
302
+ #dr-report pre {
303
+ background: var(--dr-surface);
304
+ border: 1.5px solid var(--dr-line-soft);
305
+ border-radius: 0;
306
+ padding: 1rem 1.25rem;
307
+ }
308
+
309
+ #dr-report blockquote {
310
+ border-left: none !important;
311
+ background: var(--dr-surface);
312
+ padding: 1rem 1.25rem;
313
+ margin: 1rem 0;
314
+ color: var(--dr-text);
315
+ }
316
+
317
+ #dr-report ul, #dr-report ol { padding-left: 1.5rem; }
318
+ #dr-report li { margin: 0.3rem 0; line-height: 1.6; }
319
+
320
+ #dr-report table {
321
+ border-collapse: collapse;
322
+ border: 1.5px solid var(--dr-line);
323
+ }
324
+
325
+ #dr-report th, #dr-report td {
326
+ border: 1px solid var(--dr-line-soft);
327
+ padding: 0.5rem 0.85rem;
328
+ text-align: left;
329
+ }
330
+
331
+ #dr-report th {
332
+ background: var(--dr-surface);
333
+ font-weight: 800;
334
+ color: var(--dr-blue);
335
+ }
336
+
337
+ footer { display: none !important; }
338
+
339
+ @media (max-width: 700px) {
340
+ .gradio-container { padding: 1.5rem 1rem 3rem !important; }
341
+ .dr-query-row { flex-direction: column !important; }
342
+ #dr-run {
343
+ border-left: 2px solid var(--dr-line) !important;
344
+ border-top: none !important;
345
+ width: 100% !important;
346
+ }
347
+ }
348
+ """
349
+
350
+ JS = """
351
+ () => {
352
+ const focus = () => {
353
+ const el = document.querySelector("#dr-query textarea, #dr-query input");
354
+ if (el) { el.focus(); return true; }
355
+ return false;
356
+ };
357
+ if (!focus()) {
358
+ let tries = 0;
359
+ const i = setInterval(() => {
360
+ if (focus() || ++tries > 20) clearInterval(i);
361
+ }, 100);
362
+ }
363
+ }
364
+ """
writer_agent.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from agents import Agent
3
+ from dotenv import load_dotenv
4
+ import os
5
+
6
+ load_dotenv(override=True)
7
+ MODEL_NAME = os.getenv("DEFAULT_MODEL_NAME", "gpt-5.4-mini")
8
+
9
+ INSTRUCTIONS = """
10
+ You are a senior researcher tasked with writing a cohesive report for a research query.
11
+ You will be provided with the original query, and some research.
12
+ Generate a comprehensive report based on the research and the query.
13
+ The final output should be in markdown format, and it should be lengthy and detailed. Aim
14
+ for 5-10 pages of content, at least 1000 words.
15
+ """
16
+
17
+
18
+ class ReportData(BaseModel):
19
+ short_summary: str = Field(description="A short 2-3 sentence summary of the findings.")
20
+ markdown_report: str = Field(description="The final report")
21
+ follow_up_questions: list[str] = Field(description="Suggested topics to research further")
22
+
23
+
24
+ writer_agent = Agent(name="Writer Agent", instructions=INSTRUCTIONS, model=MODEL_NAME, output_type=ReportData)