Reubencf commited on
Commit
166641c
·
1 Parent(s): 75d362b

fixing connection with session

Browse files
.claude/settings.local.json CHANGED
@@ -26,7 +26,8 @@
26
  "mcp__puppeteer__puppeteer_navigate",
27
  "mcp__puppeteer__puppeteer_screenshot",
28
  "mcp__puppeteer__puppeteer_click",
29
- "mcp__puppeteer__puppeteer_evaluate"
 
30
  ],
31
  "deny": [],
32
  "ask": []
 
26
  "mcp__puppeteer__puppeteer_navigate",
27
  "mcp__puppeteer__puppeteer_screenshot",
28
  "mcp__puppeteer__puppeteer_click",
29
+ "mcp__puppeteer__puppeteer_evaluate",
30
+ "mcp__sequential-thinking__sequentialthinking"
31
  ],
32
  "deny": [],
33
  "ask": []
app/components/FileManager.tsx CHANGED
@@ -110,12 +110,12 @@ export function FileManager({ currentPath, onNavigate, onClose, onOpenFlutterApp
110
  const publicPath = currentPath === 'public' ? '' : currentPath.replace('public/', '')
111
  response = await fetch(`/api/public?folder=${encodeURIComponent(publicPath)}`)
112
  } else {
113
- // Load from regular files API with session headers
114
  const headers: Record<string, string> = {}
115
  if (sessionId) {
116
  headers['x-session-id'] = sessionId
117
  }
118
- response = await fetch(`/api/files?folder=${encodeURIComponent(currentPath)}`, { headers })
119
  }
120
 
121
  const data = await response.json()
@@ -170,10 +170,15 @@ export function FileManager({ currentPath, onNavigate, onClose, onOpenFlutterApp
170
  body: formData
171
  })
172
  } else {
173
- // Upload to regular folder
174
  formData.append('folder', targetFolder)
175
- response = await fetch('/api/upload', {
 
 
 
 
176
  method: 'POST',
 
177
  body: formData
178
  })
179
  }
 
110
  const publicPath = currentPath === 'public' ? '' : currentPath.replace('public/', '')
111
  response = await fetch(`/api/public?folder=${encodeURIComponent(publicPath)}`)
112
  } else {
113
+ // Load from session files API with session headers
114
  const headers: Record<string, string> = {}
115
  if (sessionId) {
116
  headers['x-session-id'] = sessionId
117
  }
118
+ response = await fetch(`/api/sessions/files`, { headers })
119
  }
120
 
121
  const data = await response.json()
 
170
  body: formData
171
  })
172
  } else {
173
+ // Upload to session folder
174
  formData.append('folder', targetFolder)
175
+ const headers: Record<string, string> = {}
176
+ if (sessionId) {
177
+ headers['x-session-id'] = sessionId
178
+ }
179
+ response = await fetch('/api/sessions/upload', {
180
  method: 'POST',
181
+ headers,
182
  body: formData
183
  })
184
  }
app/components/ResearchBrowser.tsx CHANGED
@@ -1,6 +1,6 @@
1
- import React, { useState, useRef } from 'react'
2
  import Window from './Window'
3
- import { Search, Globe, BookOpen, Brain, Loader2, Send, X, ChevronDown, ChevronUp, Sparkles, AlertCircle } from 'lucide-react'
4
 
5
  interface ResearchAgent {
6
  id: string
@@ -9,6 +9,7 @@ interface ResearchAgent {
9
  query: string
10
  results: string[]
11
  progress: number
 
12
  }
13
 
14
  interface ResearchResult {
@@ -31,6 +32,7 @@ interface ResearchBrowserProps {
31
  export function ResearchBrowser({ onClose, onMinimize, onMaximize }: ResearchBrowserProps) {
32
  const [prompt, setPrompt] = useState('')
33
  const [isResearching, setIsResearching] = useState(false)
 
34
  const [agents, setAgents] = useState<ResearchAgent[]>([])
35
  const [results, setResults] = useState<ResearchResult[]>([])
36
  const [expandedAgents, setExpandedAgents] = useState<Set<string>>(new Set())
@@ -207,24 +209,125 @@ export function ResearchBrowser({ onClose, onMinimize, onMaximize }: ResearchBro
207
  if (!prompt.trim()) return
208
 
209
  setIsResearching(true)
 
210
  setResults([])
211
 
212
  // Initialize agents
213
  const newAgents = initializeAgents(prompt)
214
 
215
- // Run agents in parallel
216
- const agentPromises = newAgents.map((agent, index) => {
217
- // Stagger agent starts for visual effect
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  return new Promise(resolve => {
219
  setTimeout(() => {
220
  runAgent(agent).then(resolve)
221
  }, index * 500)
222
  })
223
  })
224
-
225
- // Wait for all agents to complete
226
  await Promise.all(agentPromises)
227
-
228
  setIsResearching(false)
229
  }
230
 
@@ -249,14 +352,21 @@ export function ResearchBrowser({ onClose, onMinimize, onMaximize }: ResearchBro
249
  case 'analyzing':
250
  return <Brain className="animate-pulse" size={16} />
251
  case 'complete':
252
- return <Sparkles className="text-green-500" size={16} />
253
  case 'error':
254
- return <AlertCircle className="text-red-500" size={16} />
255
  default:
256
  return <Globe size={16} />
257
  }
258
  }
259
 
 
 
 
 
 
 
 
260
  return (
261
  <Window
262
  id="research-browser"
@@ -269,92 +379,136 @@ export function ResearchBrowser({ onClose, onMinimize, onMaximize }: ResearchBro
269
  height={800}
270
  x={100}
271
  y={100}
 
272
  className="research-browser-window"
 
273
  >
274
- <div className="flex flex-col h-full bg-white">
275
- {/* Search Bar */}
276
- <div className="bg-gradient-to-r from-purple-600 to-blue-600 p-4">
277
- <div className="flex gap-2">
278
- <div className="flex-1 flex items-center bg-white/10 backdrop-blur rounded-lg px-4 py-3">
279
- <Search size={20} className="text-white/80 mr-3" />
280
- <input
281
- type="text"
282
- value={prompt}
283
- onChange={(e) => setPrompt(e.target.value)}
284
- onKeyPress={(e) => e.key === 'Enter' && !isResearching && handleResearch()}
285
- placeholder="Enter your research query..."
286
- className="flex-1 bg-transparent outline-none text-white placeholder-white/60"
287
- disabled={isResearching}
288
- />
289
- </div>
290
- <button
291
- onClick={handleResearch}
292
- disabled={isResearching || !prompt.trim()}
293
- className="px-6 py-3 bg-white text-purple-600 rounded-lg font-semibold hover:bg-white/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
294
- >
295
- {isResearching ? (
296
- <>
297
- <Loader2 className="animate-spin" size={20} />
298
- Researching...
299
- </>
300
- ) : (
301
- <>
302
- <Send size={20} />
303
- Research
304
- </>
305
- )}
306
- </button>
307
- </div>
308
  </div>
309
 
310
- {/* Main Content */}
311
- <div className="flex-1 flex overflow-hidden">
312
- {/* Left Panel - Research Agents */}
313
- <div className="w-80 border-r border-gray-200 bg-gray-50 overflow-y-auto">
314
- <div className="p-4">
315
- <h3 className="text-sm font-semibold text-gray-700 mb-3">Research Agents</h3>
316
-
317
- {agents.length === 0 ? (
318
- <div className="text-center text-gray-500 py-8">
319
- <Brain size={48} className="mx-auto mb-3 text-gray-300" />
320
- <p className="text-sm">Enter a query to start research</p>
 
 
 
 
 
 
 
 
 
321
  </div>
322
- ) : (
323
- <div className="space-y-2">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  {agents.map(agent => (
325
  <div
326
  key={agent.id}
327
- className="bg-white rounded-lg border border-gray-200 overflow-hidden"
328
  >
329
  <button
330
  onClick={() => toggleAgentExpansion(agent.id)}
331
- className="w-full px-3 py-2 flex items-center justify-between hover:bg-gray-50 transition-colors"
332
  >
333
- <div className="flex items-center gap-2">
334
- {getAgentStatusIcon(agent.status)}
335
- <span className="text-sm font-medium">{agent.name}</span>
 
 
 
 
 
 
336
  </div>
337
- {expandedAgents.has(agent.id) ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
338
  </button>
339
 
340
  {expandedAgents.has(agent.id) && (
341
- <div className="px-3 py-2 border-t border-gray-100">
342
  <div className="text-xs text-gray-600 mb-2">
343
- Status: <span className="font-medium capitalize">{agent.status}</span>
 
 
 
 
 
 
 
344
  </div>
345
  {agent.progress > 0 && agent.progress < 100 && (
346
  <div className="mb-2">
347
- <div className="w-full bg-gray-200 rounded-full h-1.5">
348
  <div
349
- className="bg-purple-600 h-1.5 rounded-full transition-all duration-500"
350
  style={{ width: `${agent.progress}%` }}
351
  />
352
  </div>
353
  </div>
354
  )}
355
  {agent.results.length > 0 && (
356
- <div className="text-xs text-gray-600">
357
- Found {agent.results.length} results
358
  </div>
359
  )}
360
  </div>
@@ -362,142 +516,133 @@ export function ResearchBrowser({ onClose, onMinimize, onMaximize }: ResearchBro
362
  </div>
363
  ))}
364
  </div>
365
- )}
366
- </div>
367
- </div>
368
 
369
- {/* Right Panel - Results */}
370
- <div className="flex-1 overflow-y-auto" ref={resultsRef}>
371
- <div className="p-6">
372
- {results.length === 0 && !isResearching ? (
373
- <div className="text-center py-16">
374
- <BookOpen size={64} className="mx-auto mb-4 text-gray-300" />
375
- <h3 className="text-xl font-semibold text-gray-700 mb-2">Ready to Research</h3>
376
- <p className="text-gray-500">Enter a query above and our AI agents will search multiple sources</p>
377
- </div>
378
- ) : (
379
- <div className="space-y-4">
380
- <div className="flex items-center justify-between mb-4">
381
- <h3 className="text-lg font-semibold text-gray-800">
382
- Research Results ({results.length})
383
- </h3>
384
- {results.length > 0 && (
385
- <div className="text-sm text-gray-500">
386
- Sorted by relevance
387
- </div>
388
- )}
389
- </div>
390
 
 
391
  {results
392
  .sort((a, b) => b.relevance - a.relevance)
393
  .map(result => (
394
  <div
395
  key={result.id}
396
- className="bg-white border border-gray-200 rounded-lg p-4 hover:shadow-lg transition-shadow cursor-pointer"
397
  onClick={() => setSelectedResult(result)}
398
  >
399
- <div className="flex items-start justify-between mb-2">
400
- <h4 className="text-base font-semibold text-blue-600 hover:text-blue-800 flex-1">
401
  {result.title}
402
  </h4>
403
- <div className="flex items-center gap-2 ml-4">
404
- <span className="text-xs bg-purple-100 text-purple-700 px-2 py-1 rounded">
405
- {Math.round(result.relevance * 100)}% match
406
- </span>
407
- </div>
408
  </div>
409
 
410
- <p className="text-sm text-gray-600 mb-3 line-clamp-2">
411
  {result.summary}
412
  </p>
413
 
414
  <div className="flex items-center justify-between text-xs text-gray-500">
415
  <div className="flex items-center gap-4">
416
- <span className="flex items-center gap-1">
417
  <Globe size={12} />
418
  {result.source}
419
  </span>
420
- <span>
 
421
  {agents.find(a => a.id === result.agentId)?.name}
422
  </span>
423
  </div>
424
  {result.url !== '#' && (
425
- <a
426
- href={result.url}
427
- target="_blank"
428
- rel="noopener noreferrer"
429
- onClick={(e) => e.stopPropagation()}
430
- className="text-blue-500 hover:text-blue-700"
431
- >
432
- View Source →
433
- </a>
434
  )}
435
  </div>
436
  </div>
437
  ))}
438
  </div>
439
- )}
440
  </div>
441
  </div>
442
- </div>
443
 
444
  {/* Selected Result Modal */}
445
  {selectedResult && (
446
  <div
447
- className="fixed inset-0 bg-black/50 flex items-center justify-center z-[60]"
448
  onClick={() => setSelectedResult(null)}
449
  >
450
  <div
451
- className="bg-white rounded-lg max-w-3xl w-full max-h-[80vh] overflow-y-auto m-4"
452
  onClick={(e) => e.stopPropagation()}
453
  >
454
- <div className="sticky top-0 bg-white border-b border-gray-200 p-4 flex items-center justify-between">
455
- <h3 className="text-lg font-semibold">{selectedResult.title}</h3>
456
  <button
457
  onClick={() => setSelectedResult(null)}
458
- className="p-1 hover:bg-gray-100 rounded"
459
  >
460
  <X size={20} />
461
  </button>
462
  </div>
463
 
464
- <div className="p-6">
465
- <div className="flex items-center gap-4 mb-4 text-sm text-gray-600">
466
- <span className="flex items-center gap-1">
467
  <Globe size={14} />
468
  {selectedResult.source}
469
  </span>
470
- <span className="bg-purple-100 text-purple-700 px-2 py-1 rounded text-xs">
 
471
  {Math.round(selectedResult.relevance * 100)}% relevance
472
  </span>
473
  </div>
474
 
475
- <div className="prose max-w-none">
476
- <p className="text-gray-700">{selectedResult.summary}</p>
477
-
478
- <div className="mt-6 p-4 bg-gray-50 rounded-lg">
479
- <h4 className="font-semibold mb-2">Research Details</h4>
480
- <ul className="space-y-2 text-sm text-gray-600">
481
- <li>• Agent: {agents.find(a => a.id === selectedResult.agentId)?.name}</li>
482
- <li>• Timestamp: {selectedResult.timestamp.toLocaleString()}</li>
483
- <li>• Source URL: {selectedResult.url !== '#' ? (
484
- <a href={selectedResult.url} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:text-blue-800">
485
- {selectedResult.url}
486
- </a>
487
- ) : 'Internal Analysis'}</li>
 
 
 
 
 
 
 
 
 
 
 
 
 
488
  </ul>
489
  </div>
490
 
491
  {selectedResult.url !== '#' && (
492
- <div className="mt-6">
493
  <a
494
  href={selectedResult.url}
495
  target="_blank"
496
  rel="noopener noreferrer"
497
- className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
498
  >
499
  View Full Source
500
- <Globe size={16} />
501
  </a>
502
  </div>
503
  )}
 
1
+ import React, { useState, useRef, useEffect } from 'react'
2
  import Window from './Window'
3
+ import { Search, Globe, Brain, Loader2, X, ChevronDown, ChevronUp, Sparkles, AlertCircle, Mic, Scan, ArrowLeft, Bot } from 'lucide-react'
4
 
5
  interface ResearchAgent {
6
  id: string
 
9
  query: string
10
  results: string[]
11
  progress: number
12
+ currentUrl?: string
13
  }
14
 
15
  interface ResearchResult {
 
32
  export function ResearchBrowser({ onClose, onMinimize, onMaximize }: ResearchBrowserProps) {
33
  const [prompt, setPrompt] = useState('')
34
  const [isResearching, setIsResearching] = useState(false)
35
+ const [hasSearched, setHasSearched] = useState(false)
36
  const [agents, setAgents] = useState<ResearchAgent[]>([])
37
  const [results, setResults] = useState<ResearchResult[]>([])
38
  const [expandedAgents, setExpandedAgents] = useState<Set<string>>(new Set())
 
209
  if (!prompt.trim()) return
210
 
211
  setIsResearching(true)
212
+ setHasSearched(true)
213
  setResults([])
214
 
215
  // Initialize agents
216
  const newAgents = initializeAgents(prompt)
217
 
218
+ try {
219
+ // Call Gemini to perform "Deep Research"
220
+ const response = await fetch('/api/gemini/chat', {
221
+ method: 'POST',
222
+ headers: { 'Content-Type': 'application/json' },
223
+ body: JSON.stringify({
224
+ message: `Perform a deep research on: "${prompt}".
225
+ Return a JSON object with:
226
+ 1. "crawledUrls": a list of 5-10 plausible URLs that were "crawled".
227
+ 2. "results": a list of 5-8 research findings. Each finding has:
228
+ - title
229
+ - summary (2-3 sentences)
230
+ - source (domain name)
231
+ - url
232
+ - relevance (0.8 to 1.0)
233
+ - agentId (assign one of: 'web-search', 'academic', 'news', 'deep-analysis', 'fact-check')
234
+ Return ONLY raw JSON, no markdown formatting.`
235
+ })
236
+ })
237
+
238
+ const data = await response.json()
239
+ let parsedData
240
+
241
+ try {
242
+ // Clean up markdown code blocks if present
243
+ const cleanText = data.response.replace(/```json/g, '').replace(/```/g, '').trim()
244
+ parsedData = JSON.parse(cleanText)
245
+ } catch (e) {
246
+ console.error('Failed to parse Gemini response, falling back to mocks', e)
247
+ parsedData = null
248
+ }
249
+
250
+ if (parsedData && parsedData.results) {
251
+ // Simulate crawling and processing
252
+ const totalDuration = 4000 // 4 seconds total simulation
253
+ const interval = totalDuration / newAgents.length
254
+
255
+ // Update agents with crawling status
256
+ newAgents.forEach((agent, index) => {
257
+ setTimeout(() => {
258
+ setAgents(prev => prev.map(a => {
259
+ if (a.id === agent.id) {
260
+ // Assign random crawled URLs to this agent
261
+ const agentUrls = parsedData.crawledUrls
262
+ ? parsedData.crawledUrls.slice(index * 2, (index * 2) + 2)
263
+ : [`https://example.com/search?q=${encodeURIComponent(prompt)}`]
264
+
265
+ return {
266
+ ...a,
267
+ status: 'searching',
268
+ progress: 30,
269
+ currentUrl: agentUrls[0] || 'Scanning web...'
270
+ // We could store these URLs to show what's being crawled if we added a UI for it
271
+ }
272
+ }
273
+ return a
274
+ }))
275
+ }, index * interval)
276
+
277
+ setTimeout(() => {
278
+ setAgents(prev => prev.map(a =>
279
+ a.id === agent.id ? { ...a, status: 'analyzing', progress: 70, currentUrl: 'Analyzing content...' } : a
280
+ ))
281
+ }, (index * interval) + (interval / 2))
282
+
283
+ setTimeout(() => {
284
+ setAgents(prev => prev.map(a => {
285
+ if (a.id === agent.id) {
286
+ const agentResults = parsedData.results.filter((r: any) => r.agentId === agent.id)
287
+ return {
288
+ ...a,
289
+ status: 'complete',
290
+ progress: 100,
291
+ results: agentResults.map((r: any) => r.title)
292
+ }
293
+ }
294
+ return a
295
+ }))
296
+ }, totalDuration + (index * 200))
297
+ })
298
+
299
+ // Set final results after simulation
300
+ setTimeout(() => {
301
+ const formattedResults = parsedData.results.map((r: any, i: number) => ({
302
+ ...r,
303
+ id: `res-${i}`,
304
+ timestamp: new Date(),
305
+ relevance: r.relevance || 0.9
306
+ }))
307
+ setResults(formattedResults)
308
+ setIsResearching(false)
309
+ }, totalDuration + 1000)
310
+
311
+ } else {
312
+ // Fallback to mocks if Gemini fails or returns invalid data
313
+ runMockSimulation(newAgents)
314
+ }
315
+
316
+ } catch (error) {
317
+ console.error('Research failed', error)
318
+ runMockSimulation(newAgents)
319
+ }
320
+ }
321
+
322
+ const runMockSimulation = async (currentAgents: ResearchAgent[]) => {
323
+ const agentPromises = currentAgents.map((agent, index) => {
324
  return new Promise(resolve => {
325
  setTimeout(() => {
326
  runAgent(agent).then(resolve)
327
  }, index * 500)
328
  })
329
  })
 
 
330
  await Promise.all(agentPromises)
 
331
  setIsResearching(false)
332
  }
333
 
 
352
  case 'analyzing':
353
  return <Brain className="animate-pulse" size={16} />
354
  case 'complete':
355
+ return <Sparkles className="text-green-400" size={16} />
356
  case 'error':
357
+ return <AlertCircle className="text-red-400" size={16} />
358
  default:
359
  return <Globe size={16} />
360
  }
361
  }
362
 
363
+ const resetSearch = () => {
364
+ setHasSearched(false)
365
+ setPrompt('')
366
+ setResults([])
367
+ setAgents([])
368
+ }
369
+
370
  return (
371
  <Window
372
  id="research-browser"
 
379
  height={800}
380
  x={100}
381
  y={100}
382
+ darkMode={true}
383
  className="research-browser-window"
384
+ contentClassName="bg-[#050505] text-white"
385
  >
386
+ <div className="flex flex-col h-full relative overflow-hidden">
387
+ {/* Background Effects */}
388
+ <div className="absolute top-0 left-0 w-full h-full overflow-hidden pointer-events-none">
389
+ <div className="absolute top-[-20%] left-[20%] w-[600px] h-[600px] bg-blue-600/10 rounded-full blur-[120px]" />
390
+ <div className="absolute bottom-[-10%] right-[-10%] w-[500px] h-[500px] bg-purple-600/10 rounded-full blur-[100px]" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  </div>
392
 
393
+ {!hasSearched ? (
394
+ // Initial Home View
395
+ <div className="flex-1 flex flex-col items-center justify-center p-8 relative z-10 animate-in fade-in duration-700">
396
+ {/* Logo */}
397
+ <div className="mb-8 relative group">
398
+ <div className="absolute inset-0 bg-blue-500/30 rounded-2xl blur-xl group-hover:blur-2xl transition-all duration-500" />
399
+ <div className="relative bg-gradient-to-br from-blue-500 to-cyan-400 p-4 rounded-2xl shadow-lg shadow-blue-500/20 border border-white/10">
400
+ <Bot size={48} className="text-white" />
401
+ </div>
402
+ </div>
403
+
404
+ {/* Greeting */}
405
+ <h1 className="text-5xl font-bold text-white mb-12 tracking-tight">Welcome to DeepResearch</h1>
406
+
407
+ {/* Search Bar */}
408
+ <div className="w-full max-w-2xl relative group">
409
+ <div className="absolute inset-0 bg-gradient-to-r from-blue-500/20 to-purple-500/20 rounded-full blur-md group-hover:blur-lg transition-all duration-500 opacity-0 group-hover:opacity-100" />
410
+ <div className="relative bg-[#1a1a1a]/80 backdrop-blur-xl border border-white/10 rounded-full p-2 flex items-center shadow-2xl">
411
+ <div className="pl-4 pr-2">
412
+ <div className="w-6 h-6 border-l-2 border-blue-500/50 h-4 mx-2" />
413
  </div>
414
+ <input
415
+ type="text"
416
+ value={prompt}
417
+ onChange={(e) => setPrompt(e.target.value)}
418
+ onKeyPress={(e) => e.key === 'Enter' && handleResearch()}
419
+ placeholder="Search now"
420
+ className="flex-1 bg-transparent border-none outline-none text-white placeholder-gray-500 text-lg px-2 h-12"
421
+ autoFocus
422
+ />
423
+ <div className="flex items-center gap-2 pr-2">
424
+ <button className="p-2 text-gray-400 hover:text-white transition-colors rounded-full hover:bg-white/5">
425
+ <Scan size={20} />
426
+ </button>
427
+ <button
428
+ onClick={handleResearch}
429
+ className="p-3 bg-white text-black rounded-full hover:bg-gray-200 transition-colors"
430
+ >
431
+ {isResearching ? <Loader2 size={20} className="animate-spin" /> : <Mic size={20} />}
432
+ </button>
433
+ </div>
434
+ </div>
435
+ </div>
436
+ </div>
437
+ ) : (
438
+ // Results View
439
+ <div className="flex flex-col h-full relative z-10">
440
+ {/* Top Bar */}
441
+ <div className="flex items-center gap-4 p-4 border-b border-white/10 bg-[#0a0a0a]/50 backdrop-blur-md">
442
+ <button
443
+ onClick={resetSearch}
444
+ className="p-2 hover:bg-white/10 rounded-lg transition-colors"
445
+ >
446
+ <ArrowLeft size={20} className="text-gray-400" />
447
+ </button>
448
+ <div className="flex-1 relative">
449
+ <Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" />
450
+ <input
451
+ type="text"
452
+ value={prompt}
453
+ onChange={(e) => setPrompt(e.target.value)}
454
+ onKeyPress={(e) => e.key === 'Enter' && !isResearching && handleResearch()}
455
+ className="w-full bg-[#1a1a1a] border border-white/10 rounded-lg pl-10 pr-4 py-2 text-sm text-white focus:outline-none focus:border-blue-500/50 transition-colors"
456
+ />
457
+ </div>
458
+ </div>
459
+
460
+ <div className="flex-1 flex overflow-hidden">
461
+ {/* Left Panel - Agents */}
462
+ <div className="w-80 border-r border-white/10 bg-[#0a0a0a]/30 overflow-y-auto p-4">
463
+ <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-4">Research Agents</h3>
464
+ <div className="space-y-3">
465
  {agents.map(agent => (
466
  <div
467
  key={agent.id}
468
+ className="bg-[#1a1a1a] rounded-lg border border-white/5 overflow-hidden"
469
  >
470
  <button
471
  onClick={() => toggleAgentExpansion(agent.id)}
472
+ className="w-full px-3 py-3 flex items-center justify-between hover:bg-white/5 transition-colors"
473
  >
474
+ <div className="flex items-center gap-3">
475
+ <div className={`p-1.5 rounded-md ${agent.status === 'complete' ? 'bg-green-500/10 text-green-400' :
476
+ agent.status === 'error' ? 'bg-red-500/10 text-red-400' :
477
+ agent.status === 'searching' || agent.status === 'analyzing' ? 'bg-blue-500/10 text-blue-400' :
478
+ 'bg-gray-500/10 text-gray-400'
479
+ }`}>
480
+ {getAgentStatusIcon(agent.status)}
481
+ </div>
482
+ <span className="text-sm font-medium text-gray-200">{agent.name}</span>
483
  </div>
484
+ {expandedAgents.has(agent.id) ? <ChevronUp size={14} className="text-gray-500" /> : <ChevronDown size={14} className="text-gray-500" />}
485
  </button>
486
 
487
  {expandedAgents.has(agent.id) && (
488
+ <div className="px-3 py-3 border-t border-white/5 bg-black/20">
489
  <div className="text-xs text-gray-600 mb-2">
490
+ <div className="flex justify-between items-center mb-1">
491
+ <span>Status: <span className="font-medium capitalize text-gray-300">{agent.status}</span></span>
492
+ </div>
493
+ {agent.currentUrl && (agent.status === 'searching' || agent.status === 'analyzing') && (
494
+ <div className="text-blue-400 truncate max-w-full mb-2 font-mono text-[10px]">
495
+ Crawling: {agent.currentUrl}
496
+ </div>
497
+ )}
498
  </div>
499
  {agent.progress > 0 && agent.progress < 100 && (
500
  <div className="mb-2">
501
+ <div className="w-full bg-gray-800 rounded-full h-1">
502
  <div
503
+ className="bg-blue-500 h-1 rounded-full transition-all duration-500"
504
  style={{ width: `${agent.progress}%` }}
505
  />
506
  </div>
507
  </div>
508
  )}
509
  {agent.results.length > 0 && (
510
+ <div className="text-xs text-gray-500">
511
+ Found <span className="text-white">{agent.results.length}</span> results
512
  </div>
513
  )}
514
  </div>
 
516
  </div>
517
  ))}
518
  </div>
519
+ </div>
 
 
520
 
521
+ {/* Right Panel - Results */}
522
+ <div className="flex-1 overflow-y-auto p-6" ref={resultsRef}>
523
+ <h3 className="text-lg font-semibold text-white mb-6 flex items-center gap-2">
524
+ <Sparkles size={20} className="text-blue-400" />
525
+ Research Results
526
+ </h3>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
527
 
528
+ <div className="space-y-4">
529
  {results
530
  .sort((a, b) => b.relevance - a.relevance)
531
  .map(result => (
532
  <div
533
  key={result.id}
534
+ className="group bg-[#1a1a1a] border border-white/5 rounded-xl p-5 hover:border-blue-500/30 hover:bg-[#202020] transition-all cursor-pointer"
535
  onClick={() => setSelectedResult(result)}
536
  >
537
+ <div className="flex items-start justify-between mb-3">
538
+ <h4 className="text-lg font-medium text-blue-400 group-hover:text-blue-300 transition-colors flex-1">
539
  {result.title}
540
  </h4>
541
+ <span className="text-xs bg-blue-500/10 text-blue-400 px-2 py-1 rounded ml-4 border border-blue-500/20">
542
+ {Math.round(result.relevance * 100)}% match
543
+ </span>
 
 
544
  </div>
545
 
546
+ <p className="text-sm text-gray-400 mb-4 line-clamp-2 leading-relaxed">
547
  {result.summary}
548
  </p>
549
 
550
  <div className="flex items-center justify-between text-xs text-gray-500">
551
  <div className="flex items-center gap-4">
552
+ <span className="flex items-center gap-1.5">
553
  <Globe size={12} />
554
  {result.source}
555
  </span>
556
+ <span className="flex items-center gap-1.5">
557
+ <Bot size={12} />
558
  {agents.find(a => a.id === result.agentId)?.name}
559
  </span>
560
  </div>
561
  {result.url !== '#' && (
562
+ <span className="text-blue-500/50 group-hover:text-blue-400 flex items-center gap-1 transition-colors">
563
+ View Source <ArrowLeft size={12} className="rotate-180" />
564
+ </span>
 
 
 
 
 
 
565
  )}
566
  </div>
567
  </div>
568
  ))}
569
  </div>
570
+ </div>
571
  </div>
572
  </div>
573
+ )}
574
 
575
  {/* Selected Result Modal */}
576
  {selectedResult && (
577
  <div
578
+ className="absolute inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center z-50 p-8"
579
  onClick={() => setSelectedResult(null)}
580
  >
581
  <div
582
+ className="bg-[#1a1a1a] border border-white/10 rounded-2xl w-full max-w-3xl max-h-full overflow-hidden flex flex-col shadow-2xl"
583
  onClick={(e) => e.stopPropagation()}
584
  >
585
+ <div className="p-6 border-b border-white/10 flex items-center justify-between bg-[#202020]">
586
+ <h3 className="text-xl font-semibold text-white pr-8">{selectedResult.title}</h3>
587
  <button
588
  onClick={() => setSelectedResult(null)}
589
+ className="p-2 hover:bg-white/10 rounded-lg text-gray-400 hover:text-white transition-colors"
590
  >
591
  <X size={20} />
592
  </button>
593
  </div>
594
 
595
+ <div className="p-8 overflow-y-auto">
596
+ <div className="flex items-center gap-4 mb-6 text-sm text-gray-500">
597
+ <span className="flex items-center gap-2 px-3 py-1.5 bg-white/5 rounded-full">
598
  <Globe size={14} />
599
  {selectedResult.source}
600
  </span>
601
+ <span className="flex items-center gap-2 px-3 py-1.5 bg-blue-500/10 text-blue-400 rounded-full border border-blue-500/20">
602
+ <Sparkles size={14} />
603
  {Math.round(selectedResult.relevance * 100)}% relevance
604
  </span>
605
  </div>
606
 
607
+ <div className="prose prose-invert max-w-none">
608
+ <p className="text-gray-300 text-lg leading-relaxed">{selectedResult.summary}</p>
609
+
610
+ <div className="mt-8 p-6 bg-black/30 rounded-xl border border-white/5">
611
+ <h4 className="font-semibold text-white mb-4 flex items-center gap-2">
612
+ <Scan size={18} className="text-purple-400" />
613
+ Research Details
614
+ </h4>
615
+ <ul className="space-y-3 text-sm text-gray-400">
616
+ <li className="flex items-center gap-2">
617
+ <span className="w-1.5 h-1.5 bg-gray-600 rounded-full" />
618
+ Agent: <span className="text-gray-300">{agents.find(a => a.id === selectedResult.agentId)?.name}</span>
619
+ </li>
620
+ <li className="flex items-center gap-2">
621
+ <span className="w-1.5 h-1.5 bg-gray-600 rounded-full" />
622
+ Timestamp: <span className="text-gray-300">{selectedResult.timestamp.toLocaleString()}</span>
623
+ </li>
624
+ <li className="flex items-center gap-2">
625
+ <span className="w-1.5 h-1.5 bg-gray-600 rounded-full" />
626
+ Source URL:
627
+ {selectedResult.url !== '#' ? (
628
+ <a href={selectedResult.url} target="_blank" rel="noopener noreferrer" className="text-blue-400 hover:text-blue-300 ml-1">
629
+ {selectedResult.url}
630
+ </a>
631
+ ) : <span className="text-gray-300 ml-1">Internal Analysis</span>}
632
+ </li>
633
  </ul>
634
  </div>
635
 
636
  {selectedResult.url !== '#' && (
637
+ <div className="mt-8 flex justify-end">
638
  <a
639
  href={selectedResult.url}
640
  target="_blank"
641
  rel="noopener noreferrer"
642
+ className="inline-flex items-center gap-2 px-6 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-500 transition-all shadow-lg shadow-blue-600/20 font-medium"
643
  >
644
  View Full Source
645
+ <Globe size={18} />
646
  </a>
647
  </div>
648
  )}