groove-dev 0.27.139 → 0.27.141

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/node_modules/@groove-dev/cli/package.json +1 -1
  2. package/node_modules/@groove-dev/daemon/integrations-registry.json +12 -44
  3. package/node_modules/@groove-dev/daemon/package.json +1 -1
  4. package/node_modules/@groove-dev/daemon/src/api.js +225 -16
  5. package/node_modules/@groove-dev/daemon/src/index.js +2 -0
  6. package/node_modules/@groove-dev/daemon/src/integrations.js +10 -0
  7. package/node_modules/@groove-dev/daemon/src/introducer.js +17 -1
  8. package/node_modules/@groove-dev/daemon/src/journalist.js +169 -0
  9. package/node_modules/@groove-dev/daemon/src/keeper.js +277 -0
  10. package/node_modules/@groove-dev/daemon/src/model-lab.js +11 -0
  11. package/node_modules/@groove-dev/daemon/src/process.js +76 -0
  12. package/node_modules/@groove-dev/daemon/src/validate.js +9 -0
  13. package/node_modules/@groove-dev/gui/dist/assets/index-A4e1gIDh.css +1 -0
  14. package/node_modules/@groove-dev/gui/dist/assets/index-P1hsM27-.js +8696 -0
  15. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  16. package/node_modules/@groove-dev/gui/package.json +1 -1
  17. package/node_modules/@groove-dev/gui/src/app.jsx +4 -0
  18. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +41 -17
  19. package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +10 -3
  20. package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +5 -4
  21. package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +160 -12
  22. package/node_modules/@groove-dev/gui/src/components/editor/ai-panel.jsx +77 -6
  23. package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +2 -86
  24. package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +15 -4
  25. package/node_modules/@groove-dev/gui/src/components/keeper/global-modals.jsx +177 -0
  26. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +11 -6
  27. package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +152 -3
  28. package/node_modules/@groove-dev/gui/src/components/marketplace/integration-wizard.jsx +223 -18
  29. package/node_modules/@groove-dev/gui/src/stores/groove.js +302 -20
  30. package/node_modules/@groove-dev/gui/src/views/agents.jsx +118 -60
  31. package/node_modules/@groove-dev/gui/src/views/editor.jsx +67 -219
  32. package/node_modules/@groove-dev/gui/src/views/memory.jsx +460 -0
  33. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +1 -6
  34. package/node_modules/@groove-dev/gui/src/views/models.jsx +658 -565
  35. package/package.json +1 -1
  36. package/packages/cli/package.json +1 -1
  37. package/packages/daemon/integrations-registry.json +12 -44
  38. package/packages/daemon/package.json +1 -1
  39. package/packages/daemon/src/api.js +225 -16
  40. package/packages/daemon/src/index.js +2 -0
  41. package/packages/daemon/src/integrations.js +10 -0
  42. package/packages/daemon/src/introducer.js +17 -1
  43. package/packages/daemon/src/journalist.js +169 -0
  44. package/packages/daemon/src/keeper.js +277 -0
  45. package/packages/daemon/src/model-lab.js +11 -0
  46. package/packages/daemon/src/process.js +76 -0
  47. package/packages/daemon/src/validate.js +9 -0
  48. package/packages/gui/dist/assets/index-A4e1gIDh.css +1 -0
  49. package/packages/gui/dist/assets/index-P1hsM27-.js +8696 -0
  50. package/packages/gui/dist/index.html +2 -2
  51. package/packages/gui/package.json +1 -1
  52. package/packages/gui/src/app.jsx +4 -0
  53. package/packages/gui/src/components/agents/agent-chat.jsx +41 -17
  54. package/packages/gui/src/components/agents/agent-file-tree.jsx +10 -3
  55. package/packages/gui/src/components/agents/code-review.jsx +5 -4
  56. package/packages/gui/src/components/agents/workspace-mode.jsx +160 -12
  57. package/packages/gui/src/components/editor/ai-panel.jsx +77 -6
  58. package/packages/gui/src/components/editor/file-tree.jsx +2 -86
  59. package/packages/gui/src/components/editor/terminal.jsx +15 -4
  60. package/packages/gui/src/components/keeper/global-modals.jsx +177 -0
  61. package/packages/gui/src/components/layout/activity-bar.jsx +11 -6
  62. package/packages/gui/src/components/layout/terminal-panel.jsx +152 -3
  63. package/packages/gui/src/components/marketplace/integration-wizard.jsx +223 -18
  64. package/packages/gui/src/stores/groove.js +302 -20
  65. package/packages/gui/src/views/agents.jsx +118 -60
  66. package/packages/gui/src/views/editor.jsx +67 -219
  67. package/packages/gui/src/views/memory.jsx +460 -0
  68. package/packages/gui/src/views/model-lab.jsx +1 -6
  69. package/packages/gui/src/views/models.jsx +658 -565
  70. package/plan_files/keeper-manual.md +295 -0
  71. package/plan_files/keeper-memory-system.md +223 -0
  72. package/node_modules/@groove-dev/gui/dist/assets/index-AkOtskHS.css +0 -1
  73. package/node_modules/@groove-dev/gui/dist/assets/index-B4uYLR57.js +0 -8694
  74. package/packages/gui/dist/assets/index-AkOtskHS.css +0 -1
  75. package/packages/gui/dist/assets/index-B4uYLR57.js +0 -8694
@@ -10,7 +10,7 @@ import { RootNode } from '../components/agents/root-node';
10
10
  import { cn } from '../lib/cn';
11
11
  import { Button } from '../components/ui/button';
12
12
  import { Badge } from '../components/ui/badge';
13
- import { Plus, Users, UserPlus, Zap, X, Check, Rocket, Server, Monitor, Code2, TestTube, Shield, Pencil, Copy, Trash2, ChevronDown, ChevronLeft, ChevronRight, FolderOpen, Eye, Settings2, Search, GripVertical, Cloud, FileText, Database, Megaphone, Calculator, UserCheck, Headphones, BarChart3, Pen, Presentation, Globe, MessageCircle, Save, Layers, Box, HardDrive, LayoutGrid } from 'lucide-react';
13
+ import { Plus, Users, UserPlus, Zap, X, Check, Rocket, Server, Monitor, Code2, TestTube, Shield, Pencil, Copy, Trash2, ChevronDown, ChevronLeft, ChevronRight, FolderOpen, Eye, Settings2, Search, GripVertical, Cloud, FileText, Database, Megaphone, Calculator, UserCheck, Headphones, BarChart3, Pen, Presentation, Globe, MessageCircle, Save, Layers, LayoutGrid, Activity, Gauge, Cpu } from 'lucide-react';
14
14
  import { PreviewWorkspace } from '../components/preview/preview-workspace';
15
15
  import { WorkspaceMode } from '../components/agents/workspace-mode';
16
16
  import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuSeparator } from '../components/ui/context-menu';
@@ -1258,7 +1258,7 @@ function RecommendedTeamCard() {
1258
1258
  const [tsModel, setTsModel] = useState(teamLaunchConfig?.model || '');
1259
1259
  const [tsReasoning, setTsReasoning] = useState(teamLaunchConfig?.reasoningEffort ?? 50);
1260
1260
  const [tsTemp, setTsTemp] = useState(teamLaunchConfig?.temperature ?? 0.5);
1261
- const [tsMode, setTsMode] = useState(teamLaunchConfig?.mode || 'sandbox');
1261
+ const [expandedAgent, setExpandedAgent] = useState(null);
1262
1262
 
1263
1263
  useEffect(() => {
1264
1264
  fetchProviders().then((list) => {
@@ -1283,6 +1283,15 @@ function RecommendedTeamCard() {
1283
1283
  setEditedAgents(next);
1284
1284
  }
1285
1285
 
1286
+ function handleAgentField(i, updates) {
1287
+ if (typeof updates === 'string') {
1288
+ const [field, value] = [updates, arguments[2]];
1289
+ setEditedAgents((prev) => (prev ?? agentEdits).map((a, idx) => idx === i ? { ...a, [field]: value } : a));
1290
+ } else {
1291
+ setEditedAgents((prev) => (prev ?? agentEdits).map((a, idx) => idx === i ? { ...a, ...updates } : a));
1292
+ }
1293
+ }
1294
+
1286
1295
  function handleTsProviderChange(id) {
1287
1296
  setTsProvider(id);
1288
1297
  const p = providers.find((x) => x.id === id);
@@ -1298,7 +1307,6 @@ function RecommendedTeamCard() {
1298
1307
  ...(tsProvider && { provider: tsProvider, model: tsModel }),
1299
1308
  reasoningEffort: tsReasoning,
1300
1309
  ...(showTemp && { temperature: tsTemp }),
1301
- mode: tsMode,
1302
1310
  },
1303
1311
  });
1304
1312
  try {
@@ -1376,41 +1384,6 @@ function RecommendedTeamCard() {
1376
1384
  formatValue={(v) => v.toFixed(2)}
1377
1385
  />
1378
1386
  )}
1379
- {/* Build Mode */}
1380
- <div className="space-y-1">
1381
- <label className="text-2xs text-text-3 font-sans">Build Mode</label>
1382
- <div className="flex rounded-md bg-surface-4 border border-border-subtle p-0.5">
1383
- <button
1384
- onClick={() => setTsMode('sandbox')}
1385
- className={cn(
1386
- 'flex-1 flex items-center justify-center gap-1.5 rounded px-2 py-1.5 text-xs font-sans transition-all cursor-pointer',
1387
- tsMode === 'sandbox'
1388
- ? 'bg-surface-2 text-text-0 font-semibold shadow-sm'
1389
- : 'text-text-3 hover:text-text-1',
1390
- )}
1391
- >
1392
- <Box size={11} />
1393
- Sandbox
1394
- </button>
1395
- <button
1396
- onClick={() => setTsMode('production')}
1397
- className={cn(
1398
- 'flex-1 flex items-center justify-center gap-1.5 rounded px-2 py-1.5 text-xs font-sans transition-all cursor-pointer',
1399
- tsMode === 'production'
1400
- ? 'bg-surface-2 text-text-0 font-semibold shadow-sm'
1401
- : 'text-text-3 hover:text-text-1',
1402
- )}
1403
- >
1404
- <HardDrive size={11} />
1405
- Production
1406
- </button>
1407
- </div>
1408
- <p className="text-2xs text-text-4 font-sans">
1409
- {tsMode === 'sandbox'
1410
- ? 'Files live in a team directory, removable with the team'
1411
- : 'Files live in the project directory, persist forever'}
1412
- </p>
1413
- </div>
1414
1387
  </div>
1415
1388
  )}
1416
1389
  </div>
@@ -1419,31 +1392,116 @@ function RecommendedTeamCard() {
1419
1392
  {agentEdits.map((a, i) => {
1420
1393
  const Icon = ROLE_ICONS[a.role] || Code2;
1421
1394
  const nameValid = !a.name || NAME_RE.test(a.name);
1395
+ const isExpanded = expandedAgent === i;
1396
+ const agentProvider = providers.find((p) => p.id === (a.provider || tsProvider));
1397
+ const agentModels = (agentProvider?.models || []).filter((m) => m.type !== 'image' && !m.disabled);
1422
1398
  return (
1423
- <div key={i} className="flex items-center gap-2 px-2.5 py-1.5 rounded-md bg-surface-4 border border-border-subtle">
1424
- <Icon size={12} className="text-text-2 shrink-0" />
1425
- <input
1426
- type="text"
1427
- value={a.name}
1428
- onChange={(e) => handleNameChange(i, e.target.value)}
1429
- placeholder={a.role}
1430
- className={cn(
1431
- 'flex-1 min-w-0 bg-transparent text-xs font-mono text-text-0 outline-none placeholder:text-text-4',
1432
- !nameValid && 'text-red-400',
1399
+ <div key={i} className="rounded-md bg-surface-4 border border-border-subtle overflow-hidden">
1400
+ <div
1401
+ className="flex items-center gap-2 px-2.5 py-1.5 cursor-pointer hover:bg-surface-5/50 transition-colors"
1402
+ onClick={() => setExpandedAgent(isExpanded ? null : i)}
1403
+ >
1404
+ <Icon size={12} className="text-text-2 shrink-0" />
1405
+ <input
1406
+ type="text"
1407
+ value={a.name}
1408
+ onChange={(e) => handleNameChange(i, e.target.value)}
1409
+ onClick={(e) => e.stopPropagation()}
1410
+ placeholder={a.role}
1411
+ className={cn(
1412
+ 'flex-1 min-w-0 bg-transparent text-xs font-mono text-text-0 outline-none placeholder:text-text-4',
1413
+ !nameValid && 'text-red-400',
1414
+ )}
1415
+ maxLength={64}
1416
+ spellCheck={false}
1417
+ />
1418
+ {a.provider && a.provider !== tsProvider && (
1419
+ <span className="text-2xs text-accent font-mono shrink-0">{a.provider}</span>
1433
1420
  )}
1434
- maxLength={64}
1435
- spellCheck={false}
1436
- />
1437
- {a.scope?.length > 0 && (
1438
- <span className="text-2xs text-text-4 font-mono shrink-0 truncate max-w-[120px]">
1439
- {a.scope[0]}{a.scope.length > 1 ? ` +${a.scope.length - 1}` : ''}
1440
- </span>
1421
+ {a.scope?.length > 0 && (
1422
+ <span className="text-2xs text-text-4 font-mono shrink-0 truncate max-w-[120px]">
1423
+ {a.scope[0]}{a.scope.length > 1 ? ` +${a.scope.length - 1}` : ''}
1424
+ </span>
1425
+ )}
1426
+ <ChevronDown size={10} className={cn('text-text-4 shrink-0 transition-transform duration-200', !isExpanded && '-rotate-90')} />
1427
+ </div>
1428
+ {isExpanded && (
1429
+ <div className="px-2.5 pb-2.5 pt-1 space-y-2.5 border-t border-border-subtle">
1430
+ <div className="flex gap-2">
1431
+ <div className="flex-1 space-y-1">
1432
+ <label className="flex items-center gap-1 text-2xs text-text-3 font-sans"><Cpu size={10} />Provider</label>
1433
+ <Select value={a.provider || ''} onValueChange={(id) => {
1434
+ const p = providers.find((x) => x.id === id);
1435
+ const pModels = (p?.models || []).filter((m) => m.type !== 'image' && !m.disabled);
1436
+ handleAgentField(i, { provider: id, model: pModels[0]?.id || '' });
1437
+ }}>
1438
+ <SelectTrigger placeholder="Team default" className="bg-surface-3 h-7 text-xs" />
1439
+ <SelectContent>
1440
+ <SelectItem value="">Team default</SelectItem>
1441
+ {providers.map((p) => (
1442
+ <SelectItem key={p.id} value={p.id}>{p.displayName || p.name || p.id}</SelectItem>
1443
+ ))}
1444
+ </SelectContent>
1445
+ </Select>
1446
+ </div>
1447
+ <div className="flex-1 space-y-1">
1448
+ <label className="text-2xs text-text-3 font-sans">Model</label>
1449
+ <Select value={a.model || ''} onValueChange={(v) => handleAgentField(i, 'model', v)}>
1450
+ <SelectTrigger placeholder="Auto" className="bg-surface-3 h-7 text-xs" />
1451
+ <SelectContent>
1452
+ <SelectItem value="">Auto</SelectItem>
1453
+ {agentModels.map((m) => (
1454
+ <SelectItem key={m.id} value={m.id}>{m.name || m.id}</SelectItem>
1455
+ ))}
1456
+ </SelectContent>
1457
+ </Select>
1458
+ </div>
1459
+ </div>
1460
+ <div className="space-y-1">
1461
+ <label className="flex items-center gap-1 text-2xs text-text-3 font-sans"><Activity size={10} />Model Routing</label>
1462
+ <div className="flex bg-surface-3 rounded-md p-0.5 border border-border-subtle">
1463
+ {[{ value: 'fixed', label: 'Fixed' }, { value: 'auto', label: 'Auto' }, { value: 'auto-floor', label: 'Auto + Floor' }].map((opt) => (
1464
+ <button
1465
+ key={opt.value}
1466
+ onClick={() => handleAgentField(i, 'routingMode', opt.value)}
1467
+ className={cn(
1468
+ 'flex-1 px-2 py-1 text-2xs font-semibold font-sans rounded transition-all cursor-pointer',
1469
+ (a.routingMode || 'auto') === opt.value
1470
+ ? 'bg-accent/15 text-accent shadow-sm'
1471
+ : 'text-text-3 hover:text-text-1',
1472
+ )}
1473
+ >
1474
+ {opt.label}
1475
+ </button>
1476
+ ))}
1477
+ </div>
1478
+ </div>
1479
+ <div className="space-y-1">
1480
+ <label className="flex items-center gap-1 text-2xs text-text-3 font-sans"><Gauge size={10} />Effort Level</label>
1481
+ <div className="flex bg-surface-3 rounded-md p-0.5 border border-border-subtle">
1482
+ {[{ value: 'min', label: 'Min' }, { value: 'low', label: 'Low' }, { value: 'default', label: 'Default' }, { value: 'high', label: 'High' }, { value: 'max', label: 'Max' }].map((opt) => (
1483
+ <button
1484
+ key={opt.value}
1485
+ onClick={() => handleAgentField(i, 'effort', opt.value)}
1486
+ className={cn(
1487
+ 'flex-1 px-1.5 py-1 text-2xs font-semibold font-sans rounded transition-all cursor-pointer',
1488
+ (a.effort || 'default') === opt.value
1489
+ ? 'bg-accent/15 text-accent shadow-sm'
1490
+ : 'text-text-3 hover:text-text-1',
1491
+ )}
1492
+ >
1493
+ {opt.label}
1494
+ </button>
1495
+ ))}
1496
+ </div>
1497
+ </div>
1498
+ </div>
1441
1499
  )}
1442
1500
  </div>
1443
1501
  );
1444
1502
  })}
1445
1503
 
1446
- {recommendedTeam.projectDir && tsMode === 'sandbox' && (
1504
+ {recommendedTeam.projectDir && (
1447
1505
  <div className="flex items-center gap-1.5 text-2xs text-text-2 font-mono pt-0.5">
1448
1506
  <span className="text-text-4">Project:</span>
1449
1507
  <span className="text-accent">{recommendedTeam.projectDir}/</span>
@@ -1569,7 +1627,7 @@ export default function AgentsView() {
1569
1627
  <Tooltip content="Spawn agent" side="top">
1570
1628
  <button
1571
1629
  onClick={() => openDetail({ type: 'spawn' })}
1572
- className="flex items-center justify-center w-7 h-7 rounded border border-text-3/25 bg-surface-0/80 text-text-3 hover:text-accent hover:border-accent/40 hover:bg-accent/10 transition-colors cursor-pointer"
1630
+ className="flex items-center justify-center w-8 h-8 rounded border border-text-3/20 bg-surface-2/50 text-text-3 hover:text-accent hover:border-accent/40 hover:bg-accent/10 transition-colors cursor-pointer"
1573
1631
  >
1574
1632
  <Plus size={15} />
1575
1633
  </button>
@@ -1594,7 +1652,7 @@ export default function AgentsView() {
1594
1652
  saveRoleLayout(layout);
1595
1653
  addToast('success', 'Layout saved', 'Future spawns will use these positions');
1596
1654
  }}
1597
- className="flex items-center justify-center w-7 h-7 rounded border border-text-3/25 bg-surface-0/80 text-text-3 hover:text-accent hover:border-accent/40 hover:bg-accent/10 transition-colors cursor-pointer"
1655
+ className="flex items-center justify-center w-8 h-8 rounded border border-text-3/20 bg-surface-2/50 text-text-3 hover:text-accent hover:border-accent/40 hover:bg-accent/10 transition-colors cursor-pointer"
1598
1656
  >
1599
1657
  <LayoutGrid size={15} />
1600
1658
  </button>
@@ -1602,7 +1660,7 @@ export default function AgentsView() {
1602
1660
  <Tooltip content="Workspace" side="top">
1603
1661
  <button
1604
1662
  onClick={() => setWorkspaceMode(true)}
1605
- className="flex items-center justify-center w-7 h-7 rounded border border-text-3/25 bg-surface-0/80 text-text-3 hover:text-accent hover:border-accent/40 hover:bg-accent/10 transition-colors cursor-pointer"
1663
+ className="flex items-center justify-center w-8 h-8 rounded border border-text-3/20 bg-surface-2/50 text-text-3 hover:text-accent hover:border-accent/40 hover:bg-accent/10 transition-colors cursor-pointer"
1606
1664
  >
1607
1665
  <Code2 size={15} />
1608
1666
  </button>
@@ -1628,7 +1686,7 @@ export default function AgentsView() {
1628
1686
  }
1629
1687
  }}
1630
1688
  className={cn(
1631
- 'flex items-center justify-center w-7 h-7 rounded border bg-surface-0/80 transition-colors cursor-pointer',
1689
+ 'flex items-center justify-center w-8 h-8 rounded border bg-surface-2/50 transition-colors cursor-pointer',
1632
1690
  isActive
1633
1691
  ? 'border-text-3/25 text-text-3 hover:text-info hover:border-info/40 hover:bg-info/10'
1634
1692
  : 'border-text-4/25 text-text-4 hover:text-warning hover:border-warning/40 hover:bg-warning/10',
@@ -7,13 +7,7 @@ import { CodeEditor } from '../components/editor/code-editor';
7
7
  import { MediaViewer, isMediaFile } from '../components/editor/media-viewer';
8
8
  import { EditorStatusBar } from '../components/editor/editor-status-bar';
9
9
  import { GotoLine } from '../components/editor/goto-line';
10
- import { EditorToolbar } from '../components/editor/editor-toolbar';
11
- import { AiPanel } from '../components/editor/ai-panel';
12
- import { QuickSearch } from '../components/editor/quick-search';
13
- import { SelectionMenu } from '../components/editor/selection-menu';
14
- import { InlinePrompt } from '../components/editor/inline-prompt';
15
- import { DiffViewer } from '../components/agents/diff-viewer';
16
- import { CodeReview } from '../components/agents/code-review';
10
+ import { Breadcrumbs } from '../components/editor/breadcrumbs';
17
11
  import { Code2, Eye, FileCode, PanelLeftOpen } from 'lucide-react';
18
12
  import { api } from '../lib/api';
19
13
  import { cn } from '../lib/cn';
@@ -34,11 +28,6 @@ export default function EditorView() {
34
28
  const saveFile = useGrooveStore((s) => s.saveFile);
35
29
  const sidebarWidth = useGrooveStore((s) => s.editorSidebarWidth);
36
30
  const setSidebarWidth = useGrooveStore((s) => s.setEditorSidebarWidth);
37
- const viewMode = useGrooveStore((s) => s.editorViewMode);
38
- const aiPanelOpen = useGrooveStore((s) => s.editorAiPanelOpen);
39
- const aiPanelWidth = useGrooveStore((s) => s.editorAiPanelWidth);
40
- const setAiPanelWidth = useGrooveStore((s) => s.setEditorAiPanelWidth);
41
- const setQuickSearchOpen = useGrooveStore((s) => s.setEditorQuickSearchOpen);
42
31
 
43
32
  const projectDir = useGrooveStore((s) => s.projectDir);
44
33
  const [rootDir, setRootDir] = useState('');
@@ -47,17 +36,11 @@ export default function EditorView() {
47
36
  const [cursorPos, setCursorPos] = useState({ line: 1, col: 1 });
48
37
  const [showGotoLine, setShowGotoLine] = useState(false);
49
38
  const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
50
- const [selection, setSelection] = useState(null);
51
- const [inlinePrompt, setInlinePrompt] = useState(null);
52
39
 
53
40
  const editorViewRef = useRef(null);
54
- const editorContainerRef = useRef(null);
55
41
  const dragging = useRef(false);
56
42
  const startX = useRef(0);
57
43
  const startW = useRef(0);
58
- const aiDragging = useRef(false);
59
- const aiStartX = useRef(0);
60
- const aiStartW = useRef(0);
61
44
 
62
45
  // Fetch root dir on mount and when project directory changes
63
46
  useEffect(() => {
@@ -79,72 +62,6 @@ export default function EditorView() {
79
62
  return () => document.removeEventListener('keydown', handleKeyDown);
80
63
  }, []);
81
64
 
82
- // AI panel resize handler
83
- const onAiPanelMouseDown = useCallback((e) => {
84
- e.preventDefault();
85
- aiDragging.current = true;
86
- aiStartX.current = e.clientX;
87
- aiStartW.current = aiPanelWidth;
88
-
89
- function onMouseMove(e) {
90
- if (!aiDragging.current) return;
91
- const delta = aiStartX.current - e.clientX;
92
- const newW = Math.min(Math.max(aiStartW.current + delta, 280), 600);
93
- setAiPanelWidth(newW);
94
- }
95
-
96
- function onMouseUp() {
97
- aiDragging.current = false;
98
- document.removeEventListener('mousemove', onMouseMove);
99
- document.removeEventListener('mouseup', onMouseUp);
100
- }
101
-
102
- document.addEventListener('mousemove', onMouseMove);
103
- document.addEventListener('mouseup', onMouseUp);
104
- }, [aiPanelWidth, setAiPanelWidth]);
105
-
106
- // Track text selection in the code editor
107
- const handleEditorMouseUp = useCallback(() => {
108
- const view = editorViewRef.current;
109
- if (!view) return;
110
- const sel = view.state.selection.main;
111
- if (sel.empty) { setSelection(null); return; }
112
- const text = view.state.sliceDoc(sel.from, sel.to);
113
- if (!text.trim()) { setSelection(null); return; }
114
- const fromLine = view.state.doc.lineAt(sel.from);
115
- const toLine = view.state.doc.lineAt(sel.to);
116
- const coords = view.coordsAtPos(sel.to);
117
- if (coords) {
118
- setSelection({
119
- x: Math.min(coords.left + 10, window.innerWidth - 220),
120
- y: coords.bottom + 4,
121
- lineStart: fromLine.number,
122
- lineEnd: toLine.number,
123
- selectedCode: text,
124
- });
125
- }
126
- }, []);
127
-
128
- // Right-click on a selection opens the AI selection menu
129
- const handleEditorContextMenu = useCallback((e) => {
130
- const view = editorViewRef.current;
131
- if (!view) return;
132
- const sel = view.state.selection.main;
133
- if (sel.empty) return;
134
- const text = view.state.sliceDoc(sel.from, sel.to);
135
- if (!text.trim()) return;
136
- e.preventDefault();
137
- const fromLine = view.state.doc.lineAt(sel.from);
138
- const toLine = view.state.doc.lineAt(sel.to);
139
- setSelection({
140
- x: Math.min(e.clientX, window.innerWidth - 220),
141
- y: e.clientY + 4,
142
- lineStart: fromLine.number,
143
- lineEnd: toLine.number,
144
- selectedCode: text,
145
- });
146
- }, []);
147
-
148
65
  // Sidebar resize handlers
149
66
  const onSidebarMouseDown = useCallback((e) => {
150
67
  e.preventDefault();
@@ -184,92 +101,6 @@ export default function EditorView() {
184
101
  const isMedia = activeFile && isMediaFile(activeFile);
185
102
  const isHtml = activeFile && isHtmlFile(activeFile);
186
103
 
187
- const renderCodeContent = () => {
188
- if (!activeFile) {
189
- return (
190
- <div className="w-full h-full flex items-center justify-center text-text-4 font-sans">
191
- <div className="text-center space-y-2">
192
- <Code2 size={32} className="mx-auto" />
193
- <p className="text-sm">Open a file from the tree</p>
194
- </div>
195
- </div>
196
- );
197
- }
198
-
199
- if (isMedia) return <MediaViewer path={activeFile} />;
200
-
201
- if (isHtml) {
202
- return (
203
- <>
204
- <div className="absolute top-0 right-4 z-10 flex items-center gap-0.5 mt-2 bg-surface-2 border border-border-subtle rounded-md p-0.5">
205
- <button
206
- onClick={() => setPreviewMode(false)}
207
- className={cn(
208
- 'flex items-center gap-1.5 px-2.5 py-1 text-xs font-sans rounded cursor-pointer transition-colors',
209
- !previewMode ? 'bg-surface-4 text-text-0 font-medium' : 'text-text-3 hover:text-text-1',
210
- )}
211
- >
212
- <FileCode size={12} /> Code
213
- </button>
214
- <button
215
- onClick={() => { setPreviewMode(true); setPreviewKey((k) => k + 1); }}
216
- className={cn(
217
- 'flex items-center gap-1.5 px-2.5 py-1 text-xs font-sans rounded cursor-pointer transition-colors',
218
- previewMode ? 'bg-surface-4 text-text-0 font-medium' : 'text-text-3 hover:text-text-1',
219
- )}
220
- >
221
- <Eye size={12} /> Preview
222
- </button>
223
- </div>
224
-
225
- {previewMode ? (
226
- <iframe
227
- key={previewKey}
228
- src={`/api/files/raw?path=${encodeURIComponent(activeFile)}`}
229
- className="w-full h-full border-0"
230
- sandbox="allow-scripts allow-same-origin"
231
- title="HTML Preview"
232
- />
233
- ) : (
234
- file && (
235
- <div className="w-full h-full" onMouseUp={handleEditorMouseUp} onContextMenu={handleEditorContextMenu}>
236
- <CodeEditor
237
- content={file.content}
238
- language={file.language}
239
- filePath={activeFile}
240
- onChange={(content) => updateFileContent(activeFile, content)}
241
- onSave={() => saveFile(activeFile)}
242
- onCursorChange={setCursorPos}
243
- onCmdK={({ line, coords }) => setInlinePrompt({ line, coords })}
244
- viewRef={editorViewRef}
245
- />
246
- </div>
247
- )
248
- )}
249
- </>
250
- );
251
- }
252
-
253
- if (file) {
254
- return (
255
- <div className="w-full h-full" onMouseUp={handleEditorMouseUp} onContextMenu={handleEditorContextMenu}>
256
- <CodeEditor
257
- content={file.content}
258
- language={file.language}
259
- filePath={activeFile}
260
- onChange={(content) => updateFileContent(activeFile, content)}
261
- onSave={() => saveFile(activeFile)}
262
- onCursorChange={setCursorPos}
263
- onCmdK={({ line, coords }) => setInlinePrompt({ line, coords })}
264
- viewRef={editorViewRef}
265
- />
266
- </div>
267
- );
268
- }
269
-
270
- return null;
271
- };
272
-
273
104
  return (
274
105
  <div className="flex h-full">
275
106
  {/* File tree sidebar */}
@@ -303,14 +134,11 @@ export default function EditorView() {
303
134
  {/* Tab bar */}
304
135
  <EditorTabs />
305
136
 
306
- {/* Toolbar (replaces breadcrumbs) */}
307
- <EditorToolbar
308
- onCmdP={() => setQuickSearchOpen(true)}
309
- onCmdK={() => setInlinePrompt({ line: cursorPos.line, coords: null })}
310
- />
137
+ {/* Breadcrumbs */}
138
+ {activeFile && !isMedia && <Breadcrumbs path={activeFile} />}
311
139
 
312
140
  {/* Content */}
313
- <div className="flex-1 relative min-h-0" ref={editorContainerRef}>
141
+ <div className="flex-1 relative min-h-0">
314
142
  {/* Go to line dialog */}
315
143
  {showGotoLine && (
316
144
  <GotoLine
@@ -320,62 +148,82 @@ export default function EditorView() {
320
148
  />
321
149
  )}
322
150
 
323
- {/* View mode content */}
324
- {viewMode === 'code' && renderCodeContent()}
325
- {viewMode === 'diff' && activeFile && (
326
- <DiffViewer filePath={activeFile} />
327
- )}
328
- {viewMode === 'diff' && !activeFile && (
329
- <div className="w-full h-full flex items-center justify-center text-text-4 font-sans text-sm">
330
- Open a file to see diff
151
+ {/* Editor / Media / Empty */}
152
+ {!activeFile && (
153
+ <div className="w-full h-full flex items-center justify-center text-text-4 font-sans">
154
+ <div className="text-center space-y-2">
155
+ <Code2 size={32} className="mx-auto" />
156
+ <p className="text-sm">Open a file from the tree</p>
157
+ </div>
331
158
  </div>
332
159
  )}
333
- {viewMode === 'review' && <CodeReview />}
334
160
 
335
- {/* Selection menu */}
336
- {selection && viewMode === 'code' && (
337
- <SelectionMenu
338
- x={selection.x}
339
- y={selection.y}
340
- filePath={activeFile}
341
- lineStart={selection.lineStart}
342
- lineEnd={selection.lineEnd}
343
- selectedCode={selection.selectedCode}
344
- onClose={() => setSelection(null)}
345
- />
161
+ {activeFile && isMedia && <MediaViewer path={activeFile} />}
162
+
163
+ {activeFile && !isMedia && isHtml && (
164
+ <>
165
+ {/* Code / Preview toggle for HTML files */}
166
+ <div className="absolute top-0 right-4 z-10 flex items-center gap-0.5 mt-2 bg-surface-2 border border-border-subtle rounded-md p-0.5">
167
+ <button
168
+ onClick={() => setPreviewMode(false)}
169
+ className={cn(
170
+ 'flex items-center gap-1.5 px-2.5 py-1 text-xs font-sans rounded cursor-pointer transition-colors',
171
+ !previewMode ? 'bg-surface-4 text-text-0 font-medium' : 'text-text-3 hover:text-text-1',
172
+ )}
173
+ >
174
+ <FileCode size={12} /> Code
175
+ </button>
176
+ <button
177
+ onClick={() => { setPreviewMode(true); setPreviewKey((k) => k + 1); }}
178
+ className={cn(
179
+ 'flex items-center gap-1.5 px-2.5 py-1 text-xs font-sans rounded cursor-pointer transition-colors',
180
+ previewMode ? 'bg-surface-4 text-text-0 font-medium' : 'text-text-3 hover:text-text-1',
181
+ )}
182
+ >
183
+ <Eye size={12} /> Preview
184
+ </button>
185
+ </div>
186
+
187
+ {previewMode ? (
188
+ <iframe
189
+ key={previewKey}
190
+ src={`/api/files/raw?path=${encodeURIComponent(activeFile)}`}
191
+ className="w-full h-full border-0"
192
+ sandbox="allow-scripts allow-same-origin"
193
+ title="HTML Preview"
194
+ />
195
+ ) : (
196
+ file && (
197
+ <CodeEditor
198
+ content={file.content}
199
+ language={file.language}
200
+ onChange={(content) => updateFileContent(activeFile, content)}
201
+ onSave={() => saveFile(activeFile)}
202
+ onCursorChange={setCursorPos}
203
+ viewRef={editorViewRef}
204
+ />
205
+ )
206
+ )}
207
+ </>
346
208
  )}
347
209
 
348
- {/* Inline prompt (Cmd+K) */}
349
- {inlinePrompt && viewMode === 'code' && (
350
- <InlinePrompt
351
- line={inlinePrompt.line}
352
- coords={inlinePrompt.coords}
353
- filePath={activeFile}
354
- onClose={() => setInlinePrompt(null)}
210
+ {activeFile && !isMedia && !isHtml && file && (
211
+ <CodeEditor
212
+ content={file.content}
213
+ language={file.language}
214
+ onChange={(content) => updateFileContent(activeFile, content)}
215
+ onSave={() => saveFile(activeFile)}
216
+ onCursorChange={setCursorPos}
217
+ viewRef={editorViewRef}
355
218
  />
356
219
  )}
357
220
  </div>
358
221
 
359
222
  {/* Status bar */}
360
- {activeFile && !isMedia && viewMode === 'code' && (
223
+ {activeFile && !isMedia && (
361
224
  <EditorStatusBar cursorPos={cursorPos} language={file?.language} />
362
225
  )}
363
226
  </div>
364
-
365
- {/* AI Panel */}
366
- {aiPanelOpen && (
367
- <div className="relative flex-shrink-0" style={{ width: aiPanelWidth }}>
368
- {/* Resize handle */}
369
- <div
370
- className="absolute top-0 left-0 bottom-0 w-1 cursor-col-resize hover:bg-accent/30 transition-colors z-10"
371
- onMouseDown={onAiPanelMouseDown}
372
- />
373
- <AiPanel />
374
- </div>
375
- )}
376
-
377
- {/* Quick Search modal */}
378
- <QuickSearch />
379
227
  </div>
380
228
  );
381
229
  }