groove-dev 0.27.143 → 0.27.145

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 (251) hide show
  1. package/CLAUDE.md +0 -7
  2. package/node_modules/@groove-dev/cli/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/package.json +1 -1
  4. package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
  5. package/node_modules/@groove-dev/daemon/src/conversations.js +18 -48
  6. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
  7. package/node_modules/@groove-dev/daemon/src/index.js +3 -0
  8. package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
  9. package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
  10. package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
  11. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  12. package/node_modules/@groove-dev/daemon/src/process.js +2 -2
  13. package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
  14. package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
  15. package/node_modules/@groove-dev/daemon/src/routes/agents.js +812 -0
  16. package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
  17. package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
  18. package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
  19. package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
  20. package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
  21. package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
  22. package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
  23. package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
  24. package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
  25. package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
  26. package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
  27. package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
  28. package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
  29. package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
  30. package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
  31. package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
  32. package/node_modules/@groove-dev/gui/dist/assets/index-Bxc0gU06.js +1006 -0
  33. package/node_modules/@groove-dev/gui/dist/assets/index-C0pztKBn.css +1 -0
  34. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  35. package/node_modules/@groove-dev/gui/package.json +1 -1
  36. package/node_modules/@groove-dev/gui/src/{app.jsx → App.jsx} +0 -2
  37. package/node_modules/@groove-dev/gui/src/app.css +35 -0
  38. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
  39. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +210 -112
  40. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
  41. package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +2 -70
  42. package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
  43. package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
  44. package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
  45. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
  46. package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
  47. package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
  48. package/node_modules/@groove-dev/gui/src/components/chat/chat-header.jsx +2 -0
  49. package/node_modules/@groove-dev/gui/src/components/chat/chat-input.jsx +68 -66
  50. package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +4 -8
  51. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
  52. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
  53. package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
  54. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  55. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
  56. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
  57. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  58. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  59. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
  60. package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +39 -31
  61. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
  62. package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
  63. package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +200 -18
  64. package/node_modules/@groove-dev/gui/src/components/lab/preset-manager.jsx +17 -14
  65. package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +335 -152
  66. package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +10 -8
  67. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
  68. package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
  69. package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
  70. package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
  71. package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
  72. package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
  73. package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
  74. package/node_modules/@groove-dev/gui/src/components/ui/slider.jsx +8 -8
  75. package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
  76. package/node_modules/@groove-dev/gui/src/lib/status.js +25 -24
  77. package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
  78. package/node_modules/@groove-dev/gui/src/stores/groove.js +51 -3144
  79. package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
  80. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +459 -0
  81. package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
  82. package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +226 -0
  83. package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
  84. package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
  85. package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
  86. package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
  87. package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
  88. package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
  89. package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
  90. package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
  91. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
  92. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
  93. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +54 -12
  94. package/node_modules/@groove-dev/gui/src/views/models.jsx +419 -496
  95. package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
  96. package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
  97. package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
  98. package/node_modules/axios/CHANGELOG.md +260 -0
  99. package/node_modules/axios/README.md +595 -223
  100. package/node_modules/axios/dist/axios.js +1460 -1090
  101. package/node_modules/axios/dist/axios.js.map +1 -1
  102. package/node_modules/axios/dist/axios.min.js +3 -3
  103. package/node_modules/axios/dist/axios.min.js.map +1 -1
  104. package/node_modules/axios/dist/browser/axios.cjs +1560 -1132
  105. package/node_modules/axios/dist/browser/axios.cjs.map +1 -1
  106. package/node_modules/axios/dist/esm/axios.js +1557 -1128
  107. package/node_modules/axios/dist/esm/axios.js.map +1 -1
  108. package/node_modules/axios/dist/esm/axios.min.js +2 -2
  109. package/node_modules/axios/dist/esm/axios.min.js.map +1 -1
  110. package/node_modules/axios/dist/node/axios.cjs +1594 -1057
  111. package/node_modules/axios/dist/node/axios.cjs.map +1 -1
  112. package/node_modules/axios/index.d.cts +40 -41
  113. package/node_modules/axios/index.d.ts +151 -227
  114. package/node_modules/axios/index.js +2 -0
  115. package/node_modules/axios/lib/adapters/adapters.js +4 -2
  116. package/node_modules/axios/lib/adapters/fetch.js +147 -16
  117. package/node_modules/axios/lib/adapters/http.js +306 -58
  118. package/node_modules/axios/lib/adapters/xhr.js +6 -2
  119. package/node_modules/axios/lib/core/Axios.js +7 -3
  120. package/node_modules/axios/lib/core/AxiosError.js +120 -34
  121. package/node_modules/axios/lib/core/AxiosHeaders.js +27 -25
  122. package/node_modules/axios/lib/core/buildFullPath.js +1 -1
  123. package/node_modules/axios/lib/core/dispatchRequest.js +19 -7
  124. package/node_modules/axios/lib/core/mergeConfig.js +21 -4
  125. package/node_modules/axios/lib/core/settle.js +7 -11
  126. package/node_modules/axios/lib/defaults/index.js +14 -9
  127. package/node_modules/axios/lib/env/data.js +1 -1
  128. package/node_modules/axios/lib/helpers/AxiosURLSearchParams.js +1 -2
  129. package/node_modules/axios/lib/helpers/buildURL.js +1 -1
  130. package/node_modules/axios/lib/helpers/cookies.js +14 -2
  131. package/node_modules/axios/lib/helpers/estimateDataURLDecodedBytes.js +28 -1
  132. package/node_modules/axios/lib/helpers/formDataToJSON.js +3 -1
  133. package/node_modules/axios/lib/helpers/formDataToStream.js +3 -2
  134. package/node_modules/axios/lib/helpers/parseProtocol.js +1 -1
  135. package/node_modules/axios/lib/helpers/progressEventReducer.js +5 -5
  136. package/node_modules/axios/lib/helpers/resolveConfig.js +54 -18
  137. package/node_modules/axios/lib/helpers/shouldBypassProxy.js +74 -2
  138. package/node_modules/axios/lib/helpers/toFormData.js +10 -2
  139. package/node_modules/axios/lib/helpers/validator.js +3 -1
  140. package/node_modules/axios/lib/utils.js +33 -21
  141. package/node_modules/axios/package.json +17 -24
  142. package/node_modules/follow-redirects/README.md +7 -5
  143. package/node_modules/follow-redirects/index.js +24 -1
  144. package/node_modules/follow-redirects/package.json +1 -1
  145. package/package.json +1 -1
  146. package/packages/cli/package.json +1 -1
  147. package/packages/daemon/package.json +1 -1
  148. package/packages/daemon/src/api.js +1086 -6532
  149. package/packages/daemon/src/conversations.js +18 -48
  150. package/packages/daemon/src/gateways/manager.js +35 -1
  151. package/packages/daemon/src/index.js +3 -0
  152. package/packages/daemon/src/journalist.js +23 -13
  153. package/packages/daemon/src/mlx-server.js +365 -0
  154. package/packages/daemon/src/model-lab.js +308 -12
  155. package/packages/daemon/src/pm.js +1 -1
  156. package/packages/daemon/src/process.js +2 -2
  157. package/packages/daemon/src/providers/local.js +36 -8
  158. package/packages/daemon/src/registry.js +21 -5
  159. package/packages/daemon/src/routes/agents.js +812 -0
  160. package/packages/daemon/src/routes/coordination.js +318 -0
  161. package/packages/daemon/src/routes/files.js +751 -0
  162. package/packages/daemon/src/routes/integrations.js +485 -0
  163. package/packages/daemon/src/routes/network.js +1784 -0
  164. package/packages/daemon/src/routes/providers.js +755 -0
  165. package/packages/daemon/src/routes/schedules.js +110 -0
  166. package/packages/daemon/src/routes/teams.js +650 -0
  167. package/packages/daemon/src/scheduler.js +456 -24
  168. package/packages/daemon/src/teams.js +1 -1
  169. package/packages/daemon/src/validate.js +38 -1
  170. package/packages/daemon/templates/mlx-setup.json +12 -0
  171. package/packages/daemon/templates/tgi-setup.json +1 -1
  172. package/packages/daemon/templates/vllm-setup.json +1 -1
  173. package/packages/gui/dist/assets/index-Bxc0gU06.js +1006 -0
  174. package/packages/gui/dist/assets/index-C0pztKBn.css +1 -0
  175. package/packages/gui/dist/index.html +2 -2
  176. package/packages/gui/package.json +1 -1
  177. package/packages/gui/src/{app.jsx → App.jsx} +0 -2
  178. package/packages/gui/src/app.css +35 -0
  179. package/packages/gui/src/components/agents/agent-config.jsx +1 -128
  180. package/packages/gui/src/components/agents/agent-feed.jsx +210 -112
  181. package/packages/gui/src/components/agents/agent-node.jsx +8 -13
  182. package/packages/gui/src/components/agents/agent-panel.jsx +2 -70
  183. package/packages/gui/src/components/agents/code-review.jsx +159 -122
  184. package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
  185. package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
  186. package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
  187. package/packages/gui/src/components/automations/automation-card.jsx +274 -0
  188. package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
  189. package/packages/gui/src/components/chat/chat-header.jsx +2 -0
  190. package/packages/gui/src/components/chat/chat-input.jsx +68 -66
  191. package/packages/gui/src/components/chat/chat-view.jsx +4 -8
  192. package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
  193. package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
  194. package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
  195. package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  196. package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
  197. package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
  198. package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
  199. package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  200. package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
  201. package/packages/gui/src/components/lab/chat-playground.jsx +39 -31
  202. package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
  203. package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
  204. package/packages/gui/src/components/lab/parameter-panel.jsx +200 -18
  205. package/packages/gui/src/components/lab/preset-manager.jsx +17 -14
  206. package/packages/gui/src/components/lab/runtime-config.jsx +335 -152
  207. package/packages/gui/src/components/lab/system-prompt-editor.jsx +10 -8
  208. package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
  209. package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
  210. package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
  211. package/packages/gui/src/components/network/network-health.jsx +2 -2
  212. package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
  213. package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
  214. package/packages/gui/src/components/ui/sheet.jsx +5 -2
  215. package/packages/gui/src/components/ui/slider.jsx +8 -8
  216. package/packages/gui/src/lib/cron.js +64 -0
  217. package/packages/gui/src/lib/status.js +25 -24
  218. package/packages/gui/src/lib/theme-hex.js +1 -0
  219. package/packages/gui/src/stores/groove.js +51 -3144
  220. package/packages/gui/src/stores/helpers.js +10 -0
  221. package/packages/gui/src/stores/slices/agents-slice.js +459 -0
  222. package/packages/gui/src/stores/slices/automations-slice.js +96 -0
  223. package/packages/gui/src/stores/slices/chat-slice.js +226 -0
  224. package/packages/gui/src/stores/slices/editor-slice.js +285 -0
  225. package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
  226. package/packages/gui/src/stores/slices/network-slice.js +361 -0
  227. package/packages/gui/src/stores/slices/preview-slice.js +109 -0
  228. package/packages/gui/src/stores/slices/providers-slice.js +897 -0
  229. package/packages/gui/src/stores/slices/teams-slice.js +413 -0
  230. package/packages/gui/src/stores/slices/ui-slice.js +98 -0
  231. package/packages/gui/src/views/agents.jsx +5 -5
  232. package/packages/gui/src/views/dashboard.jsx +12 -13
  233. package/packages/gui/src/views/marketplace.jsx +191 -3
  234. package/packages/gui/src/views/model-lab.jsx +54 -12
  235. package/packages/gui/src/views/models.jsx +419 -496
  236. package/packages/gui/src/views/network.jsx +3 -3
  237. package/packages/gui/src/views/settings.jsx +81 -94
  238. package/packages/gui/src/views/teams.jsx +40 -483
  239. package/SECURITY_SWEEP.md +0 -228
  240. package/TRAINING_DATA_v4.md +0 -6
  241. package/node_modules/@groove-dev/gui/dist/assets/index-CCVvAoQn.css +0 -1
  242. package/node_modules/@groove-dev/gui/dist/assets/index-DGIv_TRm.js +0 -984
  243. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -379
  244. package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
  245. package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
  246. package/packages/gui/dist/assets/index-CCVvAoQn.css +0 -1
  247. package/packages/gui/dist/assets/index-DGIv_TRm.js +0 -984
  248. package/packages/gui/src/components/agents/agent-chat.jsx +0 -379
  249. package/packages/gui/src/views/preview.jsx +0 -6
  250. package/packages/gui/src/views/subscription-panel.jsx +0 -327
  251. package/test.py +0 -571
@@ -1,5 +1,5 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
- import { useState, useEffect } from 'react';
2
+ import { useState, useEffect, useCallback } from 'react';
3
3
  import { useGrooveStore } from '../../stores/groove';
4
4
  import { cn } from '../../lib/cn';
5
5
  import { ScrollArea } from '../ui/scroll-area';
@@ -7,144 +7,161 @@ import { Button } from '../ui/button';
7
7
  import { DiffViewer } from './diff-viewer';
8
8
  import { api } from '../../lib/api';
9
9
  import {
10
- Check, X, MessageSquare, ChevronLeft, CheckCircle2,
11
- XCircle, Send, FilePlus, FileMinus, FileEdit,
10
+ ChevronLeft, ChevronRight, Send, FilePlus, FileMinus, FileEdit,
11
+ RotateCcw, MessageSquare, Eye,
12
12
  } from 'lucide-react';
13
13
 
14
+ function statusIcon(status) {
15
+ if (status === 'added') return <FilePlus size={12} className="text-success" />;
16
+ if (status === 'deleted') return <FileMinus size={12} className="text-danger" />;
17
+ return <FileEdit size={12} className="text-warning" />;
18
+ }
19
+
14
20
  export function CodeReview({ agentId: agentIdProp, onBack }) {
15
- const storeAgentId = useGrooveStore((s) => s.editorSelectedAgent);
21
+ const storeAgentId = useGrooveStore((s) => s.workspaceAgentId);
16
22
  const agentId = agentIdProp || storeAgentId;
23
+ const agents = useGrooveStore((s) => s.agents);
17
24
  const instructAgent = useGrooveStore((s) => s.instructAgent);
18
- const openFile = useGrooveStore((s) => s.openFile);
19
- const setViewMode = useGrooveStore((s) => s.setEditorViewMode);
25
+ const setEditorViewMode = useGrooveStore((s) => s.setEditorViewMode);
26
+
27
+ const agent = agents.find((a) => a.id === agentId);
28
+ const agentRunning = agent?.status === 'running' || agent?.status === 'starting';
20
29
 
21
30
  const [files, setFiles] = useState([]);
22
31
  const [loading, setLoading] = useState(true);
23
- const [selectedFile, setSelectedFile] = useState(null);
32
+ const [expandedFile, setExpandedFile] = useState(null);
33
+ const [fileContents, setFileContents] = useState({});
34
+ const [reverting, setReverting] = useState({});
24
35
  const [comments, setComments] = useState({});
25
- const [statuses, setStatuses] = useState({});
26
36
  const [commentingPath, setCommentingPath] = useState(null);
27
37
  const [commentText, setCommentText] = useState('');
28
38
 
29
- useEffect(() => {
30
- loadChanges();
31
- }, []);
32
-
33
- async function loadChanges() {
39
+ const loadChanges = useCallback(async () => {
34
40
  setLoading(true);
35
41
  try {
36
- const data = await api.get('/files/git-status');
37
- const changed = (data.entries || []).map((f) => ({
38
- path: f.path,
39
- status: f.status,
40
- additions: f.additions || 0,
41
- deletions: f.deletions || 0,
42
- }));
43
- setFiles(changed);
42
+ const url = agentId ? `/files/git-diff?agentId=${encodeURIComponent(agentId)}` : '/files/git-diff';
43
+ const data = await api.get(url);
44
+ const diffs = (data.diffs || []).filter((d) => !d.path.endsWith('/'));
45
+ setFiles(diffs);
44
46
  } catch {
45
47
  setFiles([]);
46
48
  }
47
49
  setLoading(false);
48
- }
50
+ }, [agentId]);
51
+
52
+ useEffect(() => { loadChanges(); }, [loadChanges]);
49
53
 
50
- function statusIcon(status) {
51
- if (status === 'added' || status === 'A' || status === '?') return <FilePlus size={12} className="text-success" />;
52
- if (status === 'deleted' || status === 'D') return <FileMinus size={12} className="text-danger" />;
53
- return <FileEdit size={12} className="text-warning" />;
54
+ async function loadFileContent(filePath) {
55
+ if (fileContents[filePath]) return;
56
+ try {
57
+ const [origRes, currRes] = await Promise.all([
58
+ api.get(`/files/git-show?path=${encodeURIComponent(filePath)}`),
59
+ api.get(`/files/read?path=${encodeURIComponent(filePath)}`).catch(() => null),
60
+ ]);
61
+ setFileContents((prev) => ({
62
+ ...prev,
63
+ [filePath]: {
64
+ original: origRes.content ?? '',
65
+ modified: currRes?.content ?? '',
66
+ },
67
+ }));
68
+ } catch {
69
+ setFileContents((prev) => ({
70
+ ...prev,
71
+ [filePath]: { original: '', modified: '', error: true },
72
+ }));
73
+ }
54
74
  }
55
75
 
56
- function handleComment(path) {
57
- if (!commentText.trim()) return;
58
- setComments((prev) => ({ ...prev, [path]: commentText.trim() }));
59
- setCommentText('');
60
- setCommentingPath(null);
76
+ function toggleExpand(filePath) {
77
+ if (expandedFile === filePath) {
78
+ setExpandedFile(null);
79
+ } else {
80
+ setExpandedFile(filePath);
81
+ loadFileContent(filePath);
82
+ }
61
83
  }
62
84
 
63
- function approveFile(path) {
64
- setStatuses((prev) => ({ ...prev, [path]: prev[path] === 'approved' ? 'pending' : 'approved' }));
85
+ async function revertFile(filePath) {
86
+ setReverting((prev) => ({ ...prev, [filePath]: true }));
87
+ try {
88
+ await api.post('/files/revert', { path: filePath });
89
+ setFiles((prev) => prev.filter((f) => f.path !== filePath));
90
+ setFileContents((prev) => { const next = { ...prev }; delete next[filePath]; return next; });
91
+ if (expandedFile === filePath) setExpandedFile(null);
92
+ } catch (err) {
93
+ useGrooveStore.getState().addToast('error', 'Failed to revert', err.message);
94
+ }
95
+ setReverting((prev) => ({ ...prev, [filePath]: false }));
65
96
  }
66
97
 
67
- function rejectFile(path) {
68
- setStatuses((prev) => ({ ...prev, [path]: prev[path] === 'rejected' ? 'pending' : 'rejected' }));
98
+ async function revertAll() {
99
+ for (const file of files) {
100
+ await revertFile(file.path);
101
+ }
69
102
  }
70
103
 
71
- function handleApproveAll() {
72
- const next = {};
73
- files.forEach((f) => { next[f.path] = 'approved'; });
74
- setStatuses(next);
104
+ function handleComment(path) {
105
+ if (!commentText.trim()) return;
106
+ setComments((prev) => ({ ...prev, [path]: commentText.trim() }));
107
+ setCommentText('');
108
+ setCommentingPath(null);
75
109
  }
76
110
 
77
- async function handleRequestChanges() {
111
+ async function handleSendFeedback() {
78
112
  if (!agentId) return;
79
- const reviewComments = files
80
- .filter((f) => comments[f.path] || statuses[f.path] === 'rejected')
81
- .map((f) => {
82
- const st = statuses[f.path] === 'rejected' ? '[REJECTED]' : '[COMMENT]';
83
- return `${st} ${f.path}: ${comments[f.path] || 'Changes needed'}`;
84
- });
85
- if (reviewComments.length > 0) {
86
- await instructAgent(agentId, `Code review feedback:\n${reviewComments.join('\n')}`);
87
- }
88
- if (onBack) onBack(); else setViewMode('code');
113
+ const feedbackLines = files
114
+ .filter((f) => comments[f.path])
115
+ .map((f) => `${f.path}: ${comments[f.path]}`);
116
+ if (feedbackLines.length === 0) return;
117
+ await instructAgent(agentId, `Code review feedback:\n${feedbackLines.join('\n')}`);
118
+ setComments({});
119
+ if (onBack) onBack(); else setEditorViewMode('code');
89
120
  }
90
121
 
91
- const approved = Object.values(statuses).filter((s) => s === 'approved').length;
92
- const rejected = Object.values(statuses).filter((s) => s === 'rejected').length;
93
-
94
- if (selectedFile) {
95
- return (
96
- <div className="flex flex-col h-full">
97
- <div className="flex items-center gap-2 px-4 py-2 bg-surface-1 border-b border-border flex-shrink-0">
98
- <button
99
- onClick={() => setSelectedFile(null)}
100
- className="p-1 rounded hover:bg-surface-4 text-text-3 hover:text-text-1 cursor-pointer"
101
- >
102
- <ChevronLeft size={14} />
103
- </button>
104
- <span className="text-xs font-mono text-text-1 truncate">{selectedFile}</span>
105
- </div>
106
- <div className="flex-1 min-h-0">
107
- <DiffViewer filePath={selectedFile} />
108
- </div>
109
- </div>
110
- );
111
- }
122
+ const totalAdds = files.reduce((s, f) => s + (f.additions || 0), 0);
123
+ const totalDels = files.reduce((s, f) => s + (f.deletions || 0), 0);
124
+ const hasComments = Object.keys(comments).length > 0;
112
125
 
113
126
  return (
114
127
  <div className="flex flex-col h-full">
128
+ {/* Header */}
115
129
  <div className="flex items-center gap-3 px-4 py-3 bg-surface-1 border-b border-border flex-shrink-0">
116
- <button onClick={() => onBack ? onBack() : setViewMode('code')} className="p-1 rounded hover:bg-surface-4 text-text-3 hover:text-text-1 cursor-pointer" title="Back to Editor">
130
+ <button onClick={() => onBack ? onBack() : setEditorViewMode('code')} className="p-1 rounded hover:bg-surface-4 text-text-3 hover:text-text-1 cursor-pointer" title="Back">
117
131
  <ChevronLeft size={16} />
118
132
  </button>
119
133
  <span className="text-sm font-semibold text-text-0 font-sans flex-1">Review Changes</span>
120
134
  <span className="text-xs text-text-3 font-sans">
121
- {files.length} file{files.length !== 1 ? 's' : ''} changed
135
+ {files.length} file{files.length !== 1 ? 's' : ''}
122
136
  </span>
123
- {approved > 0 && <span className="text-xs text-success font-sans">{approved} approved</span>}
124
- {rejected > 0 && <span className="text-xs text-danger font-sans">{rejected} rejected</span>}
137
+ {totalAdds > 0 && <span className="text-xs text-success font-sans">+{totalAdds}</span>}
138
+ {totalDels > 0 && <span className="text-xs text-danger font-sans">-{totalDels}</span>}
125
139
  </div>
126
140
 
141
+ {/* File list */}
127
142
  <ScrollArea className="flex-1">
128
143
  <div className="p-2 space-y-1">
129
144
  {loading && (
130
145
  <div className="flex items-center justify-center py-12 text-text-4 text-xs font-sans">
131
- Loading git changes...
146
+ Loading changes...
132
147
  </div>
133
148
  )}
134
149
  {!loading && files.length === 0 && (
135
150
  <div className="flex items-center justify-center py-12 text-text-4 text-xs font-sans">
136
- No modified files found
151
+ No changes found
137
152
  </div>
138
153
  )}
139
154
  {files.map((file) => (
140
- <div key={file.path} className="rounded-md border border-border-subtle bg-surface-2">
155
+ <div key={file.path} className="rounded-md border border-border-subtle bg-surface-2 overflow-hidden">
156
+ {/* File row */}
141
157
  <div className="flex items-center gap-2 px-3 py-2">
142
158
  {statusIcon(file.status)}
143
159
  <button
144
- onClick={() => { openFile(file.path); setSelectedFile(file.path); }}
145
- className="flex-1 min-w-0 text-xs font-mono text-text-1 hover:text-accent truncate text-left cursor-pointer"
160
+ onClick={() => toggleExpand(file.path)}
161
+ className="flex-1 min-w-0 flex items-center gap-1 text-xs font-mono text-text-1 hover:text-accent text-left cursor-pointer"
146
162
  >
147
- {file.path}
163
+ <ChevronRight size={10} className={cn('text-text-4 transition-transform flex-shrink-0', expandedFile === file.path && 'rotate-90')} />
164
+ <span className="truncate">{file.path}</span>
148
165
  </button>
149
166
  <div className="flex items-center gap-2 flex-shrink-0 text-2xs font-sans">
150
167
  {file.additions > 0 && <span className="text-success">+{file.additions}</span>}
@@ -152,43 +169,43 @@ export function CodeReview({ agentId: agentIdProp, onBack }) {
152
169
  </div>
153
170
  <div className="flex items-center gap-1 flex-shrink-0">
154
171
  <button
155
- onClick={() => approveFile(file.path)}
156
- className={cn(
157
- 'p-1 rounded cursor-pointer transition-colors',
158
- statuses[file.path] === 'approved'
159
- ? 'bg-success/15 text-success'
160
- : 'text-text-4 hover:text-success hover:bg-success/10',
161
- )}
162
- title="Approve"
172
+ onClick={() => toggleExpand(file.path)}
173
+ className="p-1 rounded text-text-4 hover:text-accent hover:bg-accent/10 cursor-pointer transition-colors"
174
+ title="View diff"
163
175
  >
164
- <Check size={14} />
176
+ <Eye size={13} />
165
177
  </button>
178
+ {agentRunning && (
179
+ <button
180
+ onClick={() => setCommentingPath(commentingPath === file.path ? null : file.path)}
181
+ className={cn(
182
+ 'p-1 rounded cursor-pointer transition-colors',
183
+ comments[file.path]
184
+ ? 'bg-accent/15 text-accent'
185
+ : 'text-text-4 hover:text-accent hover:bg-accent/10',
186
+ )}
187
+ title="Comment"
188
+ >
189
+ <MessageSquare size={13} />
190
+ </button>
191
+ )}
166
192
  <button
167
- onClick={() => rejectFile(file.path)}
193
+ onClick={() => revertFile(file.path)}
194
+ disabled={reverting[file.path]}
168
195
  className={cn(
169
196
  'p-1 rounded cursor-pointer transition-colors',
170
- statuses[file.path] === 'rejected'
171
- ? 'bg-danger/15 text-danger'
197
+ reverting[file.path]
198
+ ? 'text-text-4 opacity-50'
172
199
  : 'text-text-4 hover:text-danger hover:bg-danger/10',
173
200
  )}
174
- title="Reject"
175
- >
176
- <X size={14} />
177
- </button>
178
- <button
179
- onClick={() => setCommentingPath(commentingPath === file.path ? null : file.path)}
180
- className={cn(
181
- 'p-1 rounded cursor-pointer transition-colors',
182
- comments[file.path]
183
- ? 'bg-accent/15 text-accent'
184
- : 'text-text-4 hover:text-accent hover:bg-accent/10',
185
- )}
186
- title="Comment"
201
+ title="Revert file"
187
202
  >
188
- <MessageSquare size={14} />
203
+ <RotateCcw size={13} />
189
204
  </button>
190
205
  </div>
191
206
  </div>
207
+
208
+ {/* Comment input */}
192
209
  {comments[file.path] && commentingPath !== file.path && (
193
210
  <div className="px-3 pb-2 text-2xs text-text-2 font-sans italic">
194
211
  {comments[file.path]}
@@ -204,31 +221,51 @@ export function CodeReview({ agentId: agentIdProp, onBack }) {
204
221
  className="flex-1 h-7 px-2 text-xs bg-surface-0 border border-border-subtle rounded text-text-0 font-sans focus:outline-none focus:border-accent"
205
222
  autoFocus
206
223
  />
207
- <button
208
- onClick={() => handleComment(file.path)}
209
- className="p-1 text-accent hover:text-accent/80 cursor-pointer"
210
- >
224
+ <button onClick={() => handleComment(file.path)} className="p-1 text-accent hover:text-accent/80 cursor-pointer">
211
225
  <Send size={12} />
212
226
  </button>
213
227
  </div>
214
228
  )}
229
+
230
+ {/* Inline diff */}
231
+ {expandedFile === file.path && (
232
+ <div className="border-t border-border-subtle max-h-[400px] overflow-auto">
233
+ {fileContents[file.path] ? (
234
+ fileContents[file.path].error ? (
235
+ <div className="p-4 text-xs text-text-4 font-sans text-center">Could not load file contents</div>
236
+ ) : (
237
+ <DiffViewer
238
+ filePath={file.path}
239
+ originalContent={fileContents[file.path].original}
240
+ modifiedContent={fileContents[file.path].modified}
241
+ />
242
+ )
243
+ ) : (
244
+ <div className="p-4 text-xs text-text-4 font-sans text-center">Loading diff...</div>
245
+ )}
246
+ </div>
247
+ )}
215
248
  </div>
216
249
  ))}
217
250
  </div>
218
251
  </ScrollArea>
219
252
 
220
- <div className="flex items-center gap-2 px-4 py-3 border-t border-border bg-surface-1 flex-shrink-0">
221
- <Button variant="ghost" size="sm" onClick={handleApproveAll} className="gap-1.5">
222
- <CheckCircle2 size={13} />
223
- Approve All
224
- </Button>
225
- {agentId && (
226
- <Button variant="ghost" size="sm" onClick={handleRequestChanges} className="gap-1.5 text-warning">
227
- <XCircle size={13} />
228
- Send Review to Agent
253
+ {/* Footer actions */}
254
+ {files.length > 0 && (
255
+ <div className="flex items-center gap-2 px-4 py-3 border-t border-border bg-surface-1 flex-shrink-0">
256
+ <Button variant="ghost" size="sm" onClick={revertAll} className="gap-1.5 text-danger">
257
+ <RotateCcw size={13} />
258
+ Revert All
229
259
  </Button>
230
- )}
231
- </div>
260
+ <div className="flex-1" />
261
+ {agentRunning && hasComments && (
262
+ <Button variant="ghost" size="sm" onClick={handleSendFeedback} className="gap-1.5 text-accent">
263
+ <Send size={13} />
264
+ Send Feedback
265
+ </Button>
266
+ )}
267
+ </div>
268
+ )}
232
269
  </div>
233
270
  );
234
271
  }
@@ -6,7 +6,7 @@ import { ScrollArea } from '../ui/scroll-area';
6
6
  import { api } from '../../lib/api';
7
7
  import { Columns2, AlignJustify } from 'lucide-react';
8
8
 
9
- function computeDiff(original, modified) {
9
+ export function computeDiff(original, modified) {
10
10
  const origLines = (original || '').split('\n');
11
11
  const modLines = (modified || '').split('\n');
12
12
  const result = [];
@@ -71,14 +71,14 @@ function buildSideBySide(diffLines) {
71
71
 
72
72
  function UnifiedView({ diffLines }) {
73
73
  return (
74
- <div className="font-mono text-xs leading-5">
74
+ <div className="font-mono text-xs leading-5 overflow-x-auto">
75
75
  {diffLines.map((line, i) => (
76
76
  <div
77
77
  key={i}
78
78
  className={cn(
79
79
  'flex',
80
- line.type === 'add' && 'bg-success/8',
81
- line.type === 'del' && 'bg-danger/8',
80
+ line.type === 'add' && 'bg-success/15',
81
+ line.type === 'del' && 'bg-danger/15',
82
82
  )}
83
83
  >
84
84
  <span className={cn(
@@ -100,8 +100,8 @@ function UnifiedView({ diffLines }) {
100
100
  {line.type === 'add' ? '+' : line.type === 'del' ? '-' : ' '}
101
101
  </span>
102
102
  <span className={cn(
103
- 'flex-1 whitespace-pre px-2',
104
- line.type === 'add' ? 'text-success/90' : line.type === 'del' ? 'text-danger/90' : 'text-text-1',
103
+ 'whitespace-pre px-2 flex-1',
104
+ line.type === 'add' ? 'text-success' : line.type === 'del' ? 'text-danger' : 'text-text-1',
105
105
  )}>
106
106
  {line.text}
107
107
  </span>
@@ -113,14 +113,14 @@ function UnifiedView({ diffLines }) {
113
113
 
114
114
  function SideBySideView({ pairs }) {
115
115
  return (
116
- <div className="font-mono text-xs leading-5">
116
+ <div className="font-mono text-xs leading-5 overflow-x-auto">
117
117
  {pairs.map((pair, i) => (
118
118
  <div key={i} className="flex">
119
119
  {/* Left (original) */}
120
120
  <div className={cn(
121
121
  'flex flex-1 min-w-0 border-r border-border-subtle',
122
- pair.left.type === 'del' && 'bg-danger/8',
123
- pair.left.type === 'mod' && 'bg-warning/6',
122
+ pair.left.type === 'del' && 'bg-danger/15',
123
+ pair.left.type === 'mod' && 'bg-warning/10',
124
124
  pair.left.type === 'empty' && 'bg-surface-2/50',
125
125
  )}>
126
126
  <span className={cn(
@@ -130,9 +130,9 @@ function SideBySideView({ pairs }) {
130
130
  {pair.left.num}
131
131
  </span>
132
132
  <span className={cn(
133
- 'flex-1 whitespace-pre px-1 truncate',
134
- pair.left.type === 'del' ? 'text-danger/90' :
135
- pair.left.type === 'mod' ? 'text-warning/90' :
133
+ 'whitespace-pre px-1',
134
+ pair.left.type === 'del' ? 'text-danger' :
135
+ pair.left.type === 'mod' ? 'text-warning' :
136
136
  pair.left.type === 'empty' ? '' : 'text-text-1',
137
137
  )}>
138
138
  {pair.left.text}
@@ -141,8 +141,8 @@ function SideBySideView({ pairs }) {
141
141
  {/* Right (modified) */}
142
142
  <div className={cn(
143
143
  'flex flex-1 min-w-0',
144
- pair.right.type === 'add' && 'bg-success/8',
145
- pair.right.type === 'mod' && 'bg-success/6',
144
+ pair.right.type === 'add' && 'bg-success/15',
145
+ pair.right.type === 'mod' && 'bg-success/10',
146
146
  pair.right.type === 'empty' && 'bg-surface-2/50',
147
147
  )}>
148
148
  <span className={cn(
@@ -152,9 +152,9 @@ function SideBySideView({ pairs }) {
152
152
  {pair.right.num}
153
153
  </span>
154
154
  <span className={cn(
155
- 'flex-1 whitespace-pre px-1 truncate',
156
- pair.right.type === 'add' ? 'text-success/90' :
157
- pair.right.type === 'mod' ? 'text-success/90' :
155
+ 'whitespace-pre px-1',
156
+ pair.right.type === 'add' ? 'text-success' :
157
+ pair.right.type === 'mod' ? 'text-success' :
158
158
  pair.right.type === 'empty' ? '' : 'text-text-1',
159
159
  )}>
160
160
  {pair.right.text}
@@ -166,7 +166,7 @@ function SideBySideView({ pairs }) {
166
166
  );
167
167
  }
168
168
 
169
- export function DiffViewer({ filePath, gitDiffData }) {
169
+ export function DiffViewer({ filePath, gitDiffData, originalContent, modifiedContent }) {
170
170
  const file = useGrooveStore((s) => s.editorFiles[filePath]);
171
171
  const snapshot = useGrooveStore((s) => s.workspaceSnapshots[filePath]);
172
172
  const [viewMode, setViewMode] = useState('side-by-side');
@@ -175,15 +175,15 @@ export function DiffViewer({ filePath, gitDiffData }) {
175
175
  useEffect(() => {
176
176
  if (gitDiffData?.original !== undefined) {
177
177
  setGitOriginal(gitDiffData.original);
178
- } else if (!snapshot && !file?.originalContent) {
178
+ } else if (originalContent === undefined && !snapshot && !file?.originalContent) {
179
179
  api.get(`/files/git-diff?path=${encodeURIComponent(filePath)}`).then((data) => {
180
180
  if (data?.original !== undefined) setGitOriginal(data.original);
181
181
  }).catch(() => {});
182
182
  }
183
- }, [filePath, gitDiffData, snapshot, file?.originalContent]);
183
+ }, [filePath, gitDiffData, snapshot, file?.originalContent, originalContent]);
184
184
 
185
- const original = gitOriginal ?? snapshot ?? file?.originalContent ?? '';
186
- const modified = file?.content || '';
185
+ const original = originalContent ?? gitOriginal ?? snapshot ?? file?.originalContent ?? '';
186
+ const modified = modifiedContent ?? file?.content ?? '';
187
187
 
188
188
  const diffLines = useMemo(() => computeDiff(original, modified), [original, modified]);
189
189
  const sidePairs = useMemo(() => buildSideBySide(diffLines), [diffLines]);
@@ -197,7 +197,7 @@ export function DiffViewer({ filePath, gitDiffData }) {
197
197
  return { adds, dels };
198
198
  }, [diffLines]);
199
199
 
200
- if (!file) {
200
+ if (!original && !modified) {
201
201
  return (
202
202
  <div className="flex items-center justify-center h-full text-text-4 text-xs font-sans">
203
203
  No file loaded
@@ -47,7 +47,7 @@ export function JournalistPanel() {
47
47
  return (
48
48
  <div className="flex flex-col h-full">
49
49
  {/* Header */}
50
- <div className="px-5 py-4 border-b border-border-subtle flex items-center gap-2">
50
+ <div className="pl-5 pr-10 py-4 border-b border-border-subtle flex items-center gap-2">
51
51
  <Newspaper size={16} className="text-accent" />
52
52
  <h3 className="text-sm font-semibold text-text-0 font-sans flex-1">Journalist</h3>
53
53
  <Button