groove-dev 0.27.60 → 0.27.61
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.
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +69 -52
- package/node_modules/@groove-dev/daemon/src/conversations.js +75 -31
- package/node_modules/@groove-dev/daemon/src/journalist.js +1 -0
- package/node_modules/@groove-dev/daemon/src/process.js +17 -7
- package/node_modules/@groove-dev/daemon/src/providers/base.js +4 -0
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +63 -0
- package/node_modules/@groove-dev/daemon/src/providers/codex.js +55 -0
- package/node_modules/@groove-dev/daemon/src/providers/gemini.js +53 -0
- package/node_modules/@groove-dev/daemon/src/providers/local.js +44 -0
- package/node_modules/@groove-dev/daemon/src/providers/ollama.js +44 -0
- package/node_modules/@groove-dev/daemon/src/rotator.js +4 -0
- package/node_modules/@groove-dev/gui/dist/assets/{index-DD6taBMp.css → index-B3AqeyS4.css} +1 -1
- package/node_modules/@groove-dev/gui/dist/assets/{index-DcnRqlqB.js → index-DWao9glo.js} +178 -178
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +3 -2
- package/node_modules/@groove-dev/gui/src/components/chat/model-picker.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/layout/status-bar.jsx +13 -7
- package/node_modules/@groove-dev/gui/src/components/ui/update-modal.jsx +70 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +27 -6
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +69 -52
- package/packages/daemon/src/conversations.js +75 -31
- package/packages/daemon/src/journalist.js +1 -0
- package/packages/daemon/src/process.js +17 -7
- package/packages/daemon/src/providers/base.js +4 -0
- package/packages/daemon/src/providers/claude-code.js +63 -0
- package/packages/daemon/src/providers/codex.js +55 -0
- package/packages/daemon/src/providers/gemini.js +53 -0
- package/packages/daemon/src/providers/local.js +44 -0
- package/packages/daemon/src/providers/ollama.js +44 -0
- package/packages/daemon/src/rotator.js +4 -0
- package/packages/gui/dist/assets/{index-DD6taBMp.css → index-B3AqeyS4.css} +1 -1
- package/packages/gui/dist/assets/{index-DcnRqlqB.js → index-DWao9glo.js} +178 -178
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/chat/chat-view.jsx +3 -2
- package/packages/gui/src/components/chat/model-picker.jsx +1 -1
- package/packages/gui/src/components/layout/status-bar.jsx +13 -7
- package/packages/gui/src/components/ui/update-modal.jsx +70 -0
- package/packages/gui/src/stores/groove.js +27 -6
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
8
8
|
<title>Groove GUI</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-DWao9glo.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
|
|
14
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B3AqeyS4.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
|
@@ -38,6 +38,7 @@ export function ChatView() {
|
|
|
38
38
|
const stopAgent = useGrooveStore((s) => s.stopAgent);
|
|
39
39
|
const stopChatStreaming = useGrooveStore((s) => s.stopChatStreaming);
|
|
40
40
|
const setConversationMode = useGrooveStore((s) => s.setConversationMode);
|
|
41
|
+
const setConversationModel = useGrooveStore((s) => s.setConversationModel);
|
|
41
42
|
|
|
42
43
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
43
44
|
|
|
@@ -74,11 +75,11 @@ export function ChatView() {
|
|
|
74
75
|
|
|
75
76
|
const handleModelChange = useCallback(async (selection) => {
|
|
76
77
|
if (activeConversationId) {
|
|
77
|
-
|
|
78
|
+
await setConversationModel(activeConversationId, selection.provider, selection.model);
|
|
78
79
|
} else {
|
|
79
80
|
await handleNewChat(selection.provider, selection.model);
|
|
80
81
|
}
|
|
81
|
-
}, [activeConversationId, handleNewChat]);
|
|
82
|
+
}, [activeConversationId, setConversationModel, handleNewChat]);
|
|
82
83
|
|
|
83
84
|
const currentModel = activeConversation
|
|
84
85
|
? { provider: activeConversation.provider, model: activeConversation.model }
|
|
@@ -83,7 +83,7 @@ export function ModelPicker({ value, onChange, disabled }) {
|
|
|
83
83
|
</button>
|
|
84
84
|
|
|
85
85
|
{open && (
|
|
86
|
-
<div className="absolute top-full
|
|
86
|
+
<div className="absolute top-full right-0 mt-1 w-72 max-h-80 overflow-y-auto rounded-lg border border-border bg-surface-1 shadow-xl z-50">
|
|
87
87
|
{providers.length === 0 && (
|
|
88
88
|
<div className="px-4 py-6 text-center text-xs text-text-3 font-sans">No providers available</div>
|
|
89
89
|
)}
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
import { Terminal, BookOpen, Radio, Plug, Globe, ArrowUpCircle, X, Unplug } from 'lucide-react';
|
|
3
3
|
import { cn } from '../../lib/cn';
|
|
4
4
|
import { StatusDot } from '../ui/status-dot';
|
|
5
|
+
import { Badge } from '../ui/badge';
|
|
5
6
|
import { fmtUptime } from '../../lib/format';
|
|
6
7
|
import { useGrooveStore } from '../../stores/groove';
|
|
7
8
|
import { isElectron, openExternal } from '../../lib/electron';
|
|
9
|
+
import { UpdateModal } from '../ui/update-modal';
|
|
8
10
|
|
|
9
11
|
export function StatusBar({
|
|
10
12
|
connected,
|
|
@@ -18,7 +20,8 @@ export function StatusBar({
|
|
|
18
20
|
const tunneled = useGrooveStore((s) => s.tunneled);
|
|
19
21
|
const version = useGrooveStore((s) => s.version);
|
|
20
22
|
const updateReady = useGrooveStore((s) => s.updateReady);
|
|
21
|
-
const
|
|
23
|
+
const updateProgress = useGrooveStore((s) => s.updateProgress);
|
|
24
|
+
const setUpdateModalOpen = useGrooveStore((s) => s.setUpdateModalOpen);
|
|
22
25
|
const subscription = useGrooveStore((s) => s.subscription);
|
|
23
26
|
const navigate = useGrooveStore((s) => s.setActiveView);
|
|
24
27
|
const activeTunnel = savedTunnels.find((t) => t.active);
|
|
@@ -110,14 +113,16 @@ export function StatusBar({
|
|
|
110
113
|
<div className="flex-1" />
|
|
111
114
|
|
|
112
115
|
{/* Right: version + docs + terminal toggle */}
|
|
113
|
-
{updateReady ? (
|
|
116
|
+
{updateReady || updateProgress ? (
|
|
114
117
|
<button
|
|
115
|
-
onClick={
|
|
116
|
-
className="flex items-center gap-1
|
|
117
|
-
title={`Update to v${updateReady}`}
|
|
118
|
+
onClick={() => setUpdateModalOpen(true)}
|
|
119
|
+
className="flex items-center gap-1 px-2 h-full cursor-pointer"
|
|
120
|
+
title={updateReady ? `Update to v${updateReady}` : 'Downloading update\u2026'}
|
|
118
121
|
>
|
|
119
|
-
<
|
|
120
|
-
|
|
122
|
+
<Badge variant="warning" className="cursor-pointer">
|
|
123
|
+
<ArrowUpCircle size={10} />
|
|
124
|
+
{updateReady ? 'Update Available' : 'Downloading\u2026'}
|
|
125
|
+
</Badge>
|
|
121
126
|
</button>
|
|
122
127
|
) : version ? (
|
|
123
128
|
<span className="text-text-4 px-2">v{version}</span>
|
|
@@ -146,6 +151,7 @@ export function StatusBar({
|
|
|
146
151
|
<span>Terminal</span>
|
|
147
152
|
<kbd className="font-mono text-text-4 ml-0.5">Cmd+J</kbd>
|
|
148
153
|
</button>
|
|
154
|
+
<UpdateModal />
|
|
149
155
|
</footer>
|
|
150
156
|
);
|
|
151
157
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { ArrowUpCircle, Loader2 } from 'lucide-react';
|
|
3
|
+
import { Dialog, DialogContent } from './dialog';
|
|
4
|
+
import { Button } from './button';
|
|
5
|
+
import { useGrooveStore } from '../../stores/groove';
|
|
6
|
+
|
|
7
|
+
export function UpdateModal() {
|
|
8
|
+
const open = useGrooveStore((s) => s.updateModalOpen);
|
|
9
|
+
const setOpen = useGrooveStore((s) => s.setUpdateModalOpen);
|
|
10
|
+
const version = useGrooveStore((s) => s.version);
|
|
11
|
+
const updateReady = useGrooveStore((s) => s.updateReady);
|
|
12
|
+
const updateProgress = useGrooveStore((s) => s.updateProgress);
|
|
13
|
+
const installUpdate = useGrooveStore((s) => s.installUpdate);
|
|
14
|
+
|
|
15
|
+
const downloading = updateProgress && !updateReady;
|
|
16
|
+
const percent = downloading ? Math.max(0, Math.min(100, updateProgress.percent || 0)) : 100;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
20
|
+
<DialogContent title="Update Available" description="Desktop app update">
|
|
21
|
+
<div className="px-5 py-4 flex flex-col gap-3">
|
|
22
|
+
<div className="flex items-center gap-3">
|
|
23
|
+
<div className="flex items-center justify-center w-10 h-10 rounded-lg bg-accent/12">
|
|
24
|
+
<ArrowUpCircle size={20} className="text-accent" />
|
|
25
|
+
</div>
|
|
26
|
+
<div>
|
|
27
|
+
<p className="text-sm text-text-1 font-sans font-medium">
|
|
28
|
+
{downloading ? 'Downloading update\u2026' : `Ready to update`}
|
|
29
|
+
</p>
|
|
30
|
+
<p className="text-xs text-text-3 font-sans mt-0.5">
|
|
31
|
+
{version && <span className="font-mono">{version}</span>}
|
|
32
|
+
{version && updateReady && ' \u2192 '}
|
|
33
|
+
{updateReady && <span className="font-mono text-accent">{updateReady}</span>}
|
|
34
|
+
</p>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
{downloading && (
|
|
38
|
+
<div className="flex items-center gap-2 mt-1">
|
|
39
|
+
<Loader2 size={12} className="animate-spin text-accent flex-shrink-0" />
|
|
40
|
+
<div className="flex-1 h-1.5 rounded-full bg-surface-3 overflow-hidden">
|
|
41
|
+
<div
|
|
42
|
+
className="h-full rounded-full bg-accent transition-all duration-500 ease-out"
|
|
43
|
+
style={{ width: `${percent}%` }}
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
<span className="text-2xs font-mono text-text-3 tabular-nums">{percent}%</span>
|
|
47
|
+
</div>
|
|
48
|
+
)}
|
|
49
|
+
{!downloading && (
|
|
50
|
+
<p className="text-xs text-text-3 font-sans leading-relaxed">
|
|
51
|
+
The app will restart to apply the update. Your work is saved automatically.
|
|
52
|
+
</p>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
<div className="flex items-center justify-end gap-2 px-5 py-3 border-t border-border-subtle bg-surface-0">
|
|
56
|
+
<Button variant="ghost" size="sm" onClick={() => setOpen(false)}>Later</Button>
|
|
57
|
+
<Button
|
|
58
|
+
variant="primary"
|
|
59
|
+
size="sm"
|
|
60
|
+
disabled={downloading}
|
|
61
|
+
onClick={() => { installUpdate(); setOpen(false); }}
|
|
62
|
+
>
|
|
63
|
+
<ArrowUpCircle size={12} />
|
|
64
|
+
Update & Restart
|
|
65
|
+
</Button>
|
|
66
|
+
</div>
|
|
67
|
+
</DialogContent>
|
|
68
|
+
</Dialog>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -123,6 +123,8 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
123
123
|
// ── Version / Auto-Update ──────────────────────────────────
|
|
124
124
|
version: null,
|
|
125
125
|
updateReady: null,
|
|
126
|
+
updateProgress: null,
|
|
127
|
+
updateModalOpen: false,
|
|
126
128
|
|
|
127
129
|
// ── Toasts ────────────────────────────────────────────────
|
|
128
130
|
toasts: [],
|
|
@@ -184,10 +186,14 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
184
186
|
if (data) set({ subscription: { ...get().subscription, ...data } });
|
|
185
187
|
});
|
|
186
188
|
}
|
|
189
|
+
if (window.groove?.update?.onUpdateProgress) {
|
|
190
|
+
window.groove.update.onUpdateProgress((data) => {
|
|
191
|
+
set({ updateProgress: data });
|
|
192
|
+
});
|
|
193
|
+
}
|
|
187
194
|
if (window.groove?.update?.onUpdateDownloaded) {
|
|
188
195
|
window.groove.update.onUpdateDownloaded((data) => {
|
|
189
|
-
set({ updateReady: data.version });
|
|
190
|
-
get().addToast('info', 'Update available', `v${data.version} downloaded — restart to apply`);
|
|
196
|
+
set({ updateReady: data.version, updateModalOpen: true, updateProgress: null });
|
|
191
197
|
});
|
|
192
198
|
}
|
|
193
199
|
};
|
|
@@ -861,7 +867,6 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
861
867
|
arr.push({ from: 'assistant', text, timestamp: Date.now() });
|
|
862
868
|
}
|
|
863
869
|
msgs[conversationId] = arr.slice(-200);
|
|
864
|
-
persistJSON('groove:conversationMessages', msgs);
|
|
865
870
|
return { conversationMessages: msgs, streamingConversationId: conversationId };
|
|
866
871
|
});
|
|
867
872
|
break;
|
|
@@ -869,10 +874,10 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
869
874
|
|
|
870
875
|
case 'conversation:complete': {
|
|
871
876
|
const { conversationId } = msg.data || msg;
|
|
872
|
-
if (conversationId) {
|
|
877
|
+
if (conversationId && get().streamingConversationId === conversationId) {
|
|
873
878
|
set({ sendingMessage: false, streamingConversationId: null });
|
|
874
|
-
persistJSON('groove:conversationMessages', get().conversationMessages);
|
|
875
879
|
}
|
|
880
|
+
if (conversationId) persistJSON('groove:conversationMessages', get().conversationMessages);
|
|
876
881
|
break;
|
|
877
882
|
}
|
|
878
883
|
|
|
@@ -884,7 +889,8 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
884
889
|
if (!msgs[conversationId]) msgs[conversationId] = [];
|
|
885
890
|
msgs[conversationId] = [...msgs[conversationId], { from: 'system', text: `Error: ${error || 'Unknown error'}`, timestamp: Date.now() }];
|
|
886
891
|
persistJSON('groove:conversationMessages', msgs);
|
|
887
|
-
|
|
892
|
+
const isActive = s.streamingConversationId === conversationId;
|
|
893
|
+
return { conversationMessages: msgs, sendingMessage: isActive ? false : s.sendingMessage, streamingConversationId: isActive ? null : s.streamingConversationId };
|
|
888
894
|
});
|
|
889
895
|
}
|
|
890
896
|
break;
|
|
@@ -1071,6 +1077,12 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
1071
1077
|
installUpdate() {
|
|
1072
1078
|
window.groove?.update?.installUpdate();
|
|
1073
1079
|
},
|
|
1080
|
+
setUpdateModalOpen(open) {
|
|
1081
|
+
set({ updateModalOpen: open });
|
|
1082
|
+
},
|
|
1083
|
+
checkForUpdate() {
|
|
1084
|
+
window.groove?.update?.checkForUpdate();
|
|
1085
|
+
},
|
|
1074
1086
|
|
|
1075
1087
|
// ── Marketplace Auth ────────────────────────────────────────
|
|
1076
1088
|
|
|
@@ -1718,6 +1730,15 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
1718
1730
|
}
|
|
1719
1731
|
},
|
|
1720
1732
|
|
|
1733
|
+
async setConversationModel(id, provider, model) {
|
|
1734
|
+
try {
|
|
1735
|
+
const conv = await api.patch(`/conversations/${encodeURIComponent(id)}`, { provider, model });
|
|
1736
|
+
set((s) => ({ conversations: s.conversations.map((c) => c.id === id ? { ...c, ...conv } : c) }));
|
|
1737
|
+
} catch (err) {
|
|
1738
|
+
get().addToast('error', 'Model change failed', err.message);
|
|
1739
|
+
}
|
|
1740
|
+
},
|
|
1741
|
+
|
|
1721
1742
|
async stopChatStreaming(conversationId) {
|
|
1722
1743
|
try {
|
|
1723
1744
|
await api.post(`/conversations/${encodeURIComponent(conversationId)}/stop`);
|