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.
- package/CLAUDE.md +0 -7
- 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 +1086 -6532
- package/node_modules/@groove-dev/daemon/src/conversations.js +18 -48
- package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
- package/node_modules/@groove-dev/daemon/src/index.js +3 -0
- package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
- package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
- package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
- package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
- package/node_modules/@groove-dev/daemon/src/process.js +2 -2
- package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
- package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
- package/node_modules/@groove-dev/daemon/src/routes/agents.js +812 -0
- package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
- package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
- package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
- package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
- package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
- package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
- package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
- package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
- package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
- package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
- package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
- package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
- package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
- package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
- package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-Bxc0gU06.js +1006 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-C0pztKBn.css +1 -0
- 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/{app.jsx → App.jsx} +0 -2
- package/node_modules/@groove-dev/gui/src/app.css +35 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +210 -112
- package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
- package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +2 -70
- package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
- package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
- package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
- package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
- package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
- package/node_modules/@groove-dev/gui/src/components/chat/chat-header.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/components/chat/chat-input.jsx +68 -66
- package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +4 -8
- package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
- package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
- package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
- package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
- package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +39 -31
- package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
- package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
- package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +200 -18
- package/node_modules/@groove-dev/gui/src/components/lab/preset-manager.jsx +17 -14
- package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +335 -152
- package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +10 -8
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
- package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
- package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
- package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
- package/node_modules/@groove-dev/gui/src/components/ui/slider.jsx +8 -8
- package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
- package/node_modules/@groove-dev/gui/src/lib/status.js +25 -24
- package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +51 -3144
- package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +459 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +226 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
- package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +54 -12
- package/node_modules/@groove-dev/gui/src/views/models.jsx +419 -496
- package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
- package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
- package/node_modules/axios/CHANGELOG.md +260 -0
- package/node_modules/axios/README.md +595 -223
- package/node_modules/axios/dist/axios.js +1460 -1090
- package/node_modules/axios/dist/axios.js.map +1 -1
- package/node_modules/axios/dist/axios.min.js +3 -3
- package/node_modules/axios/dist/axios.min.js.map +1 -1
- package/node_modules/axios/dist/browser/axios.cjs +1560 -1132
- package/node_modules/axios/dist/browser/axios.cjs.map +1 -1
- package/node_modules/axios/dist/esm/axios.js +1557 -1128
- package/node_modules/axios/dist/esm/axios.js.map +1 -1
- package/node_modules/axios/dist/esm/axios.min.js +2 -2
- package/node_modules/axios/dist/esm/axios.min.js.map +1 -1
- package/node_modules/axios/dist/node/axios.cjs +1594 -1057
- package/node_modules/axios/dist/node/axios.cjs.map +1 -1
- package/node_modules/axios/index.d.cts +40 -41
- package/node_modules/axios/index.d.ts +151 -227
- package/node_modules/axios/index.js +2 -0
- package/node_modules/axios/lib/adapters/adapters.js +4 -2
- package/node_modules/axios/lib/adapters/fetch.js +147 -16
- package/node_modules/axios/lib/adapters/http.js +306 -58
- package/node_modules/axios/lib/adapters/xhr.js +6 -2
- package/node_modules/axios/lib/core/Axios.js +7 -3
- package/node_modules/axios/lib/core/AxiosError.js +120 -34
- package/node_modules/axios/lib/core/AxiosHeaders.js +27 -25
- package/node_modules/axios/lib/core/buildFullPath.js +1 -1
- package/node_modules/axios/lib/core/dispatchRequest.js +19 -7
- package/node_modules/axios/lib/core/mergeConfig.js +21 -4
- package/node_modules/axios/lib/core/settle.js +7 -11
- package/node_modules/axios/lib/defaults/index.js +14 -9
- package/node_modules/axios/lib/env/data.js +1 -1
- package/node_modules/axios/lib/helpers/AxiosURLSearchParams.js +1 -2
- package/node_modules/axios/lib/helpers/buildURL.js +1 -1
- package/node_modules/axios/lib/helpers/cookies.js +14 -2
- package/node_modules/axios/lib/helpers/estimateDataURLDecodedBytes.js +28 -1
- package/node_modules/axios/lib/helpers/formDataToJSON.js +3 -1
- package/node_modules/axios/lib/helpers/formDataToStream.js +3 -2
- package/node_modules/axios/lib/helpers/parseProtocol.js +1 -1
- package/node_modules/axios/lib/helpers/progressEventReducer.js +5 -5
- package/node_modules/axios/lib/helpers/resolveConfig.js +54 -18
- package/node_modules/axios/lib/helpers/shouldBypassProxy.js +74 -2
- package/node_modules/axios/lib/helpers/toFormData.js +10 -2
- package/node_modules/axios/lib/helpers/validator.js +3 -1
- package/node_modules/axios/lib/utils.js +33 -21
- package/node_modules/axios/package.json +17 -24
- package/node_modules/follow-redirects/README.md +7 -5
- package/node_modules/follow-redirects/index.js +24 -1
- package/node_modules/follow-redirects/package.json +1 -1
- 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 +1086 -6532
- package/packages/daemon/src/conversations.js +18 -48
- package/packages/daemon/src/gateways/manager.js +35 -1
- package/packages/daemon/src/index.js +3 -0
- package/packages/daemon/src/journalist.js +23 -13
- package/packages/daemon/src/mlx-server.js +365 -0
- package/packages/daemon/src/model-lab.js +308 -12
- package/packages/daemon/src/pm.js +1 -1
- package/packages/daemon/src/process.js +2 -2
- package/packages/daemon/src/providers/local.js +36 -8
- package/packages/daemon/src/registry.js +21 -5
- package/packages/daemon/src/routes/agents.js +812 -0
- package/packages/daemon/src/routes/coordination.js +318 -0
- package/packages/daemon/src/routes/files.js +751 -0
- package/packages/daemon/src/routes/integrations.js +485 -0
- package/packages/daemon/src/routes/network.js +1784 -0
- package/packages/daemon/src/routes/providers.js +755 -0
- package/packages/daemon/src/routes/schedules.js +110 -0
- package/packages/daemon/src/routes/teams.js +650 -0
- package/packages/daemon/src/scheduler.js +456 -24
- package/packages/daemon/src/teams.js +1 -1
- package/packages/daemon/src/validate.js +38 -1
- package/packages/daemon/templates/mlx-setup.json +12 -0
- package/packages/daemon/templates/tgi-setup.json +1 -1
- package/packages/daemon/templates/vllm-setup.json +1 -1
- package/packages/gui/dist/assets/index-Bxc0gU06.js +1006 -0
- package/packages/gui/dist/assets/index-C0pztKBn.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/{app.jsx → App.jsx} +0 -2
- package/packages/gui/src/app.css +35 -0
- package/packages/gui/src/components/agents/agent-config.jsx +1 -128
- package/packages/gui/src/components/agents/agent-feed.jsx +210 -112
- package/packages/gui/src/components/agents/agent-node.jsx +8 -13
- package/packages/gui/src/components/agents/agent-panel.jsx +2 -70
- package/packages/gui/src/components/agents/code-review.jsx +159 -122
- package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
- package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
- package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
- package/packages/gui/src/components/automations/automation-card.jsx +274 -0
- package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
- package/packages/gui/src/components/chat/chat-header.jsx +2 -0
- package/packages/gui/src/components/chat/chat-input.jsx +68 -66
- package/packages/gui/src/components/chat/chat-view.jsx +4 -8
- package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
- package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
- package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
- package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
- package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
- package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
- package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
- package/packages/gui/src/components/lab/chat-playground.jsx +39 -31
- package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
- package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
- package/packages/gui/src/components/lab/parameter-panel.jsx +200 -18
- package/packages/gui/src/components/lab/preset-manager.jsx +17 -14
- package/packages/gui/src/components/lab/runtime-config.jsx +335 -152
- package/packages/gui/src/components/lab/system-prompt-editor.jsx +10 -8
- package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
- package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
- package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
- package/packages/gui/src/components/network/network-health.jsx +2 -2
- package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
- package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
- package/packages/gui/src/components/ui/sheet.jsx +5 -2
- package/packages/gui/src/components/ui/slider.jsx +8 -8
- package/packages/gui/src/lib/cron.js +64 -0
- package/packages/gui/src/lib/status.js +25 -24
- package/packages/gui/src/lib/theme-hex.js +1 -0
- package/packages/gui/src/stores/groove.js +51 -3144
- package/packages/gui/src/stores/helpers.js +10 -0
- package/packages/gui/src/stores/slices/agents-slice.js +459 -0
- package/packages/gui/src/stores/slices/automations-slice.js +96 -0
- package/packages/gui/src/stores/slices/chat-slice.js +226 -0
- package/packages/gui/src/stores/slices/editor-slice.js +285 -0
- package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
- package/packages/gui/src/stores/slices/network-slice.js +361 -0
- package/packages/gui/src/stores/slices/preview-slice.js +109 -0
- package/packages/gui/src/stores/slices/providers-slice.js +897 -0
- package/packages/gui/src/stores/slices/teams-slice.js +413 -0
- package/packages/gui/src/stores/slices/ui-slice.js +98 -0
- package/packages/gui/src/views/agents.jsx +5 -5
- package/packages/gui/src/views/dashboard.jsx +12 -13
- package/packages/gui/src/views/marketplace.jsx +191 -3
- package/packages/gui/src/views/model-lab.jsx +54 -12
- package/packages/gui/src/views/models.jsx +419 -496
- package/packages/gui/src/views/network.jsx +3 -3
- package/packages/gui/src/views/settings.jsx +81 -94
- package/packages/gui/src/views/teams.jsx +40 -483
- package/SECURITY_SWEEP.md +0 -228
- package/TRAINING_DATA_v4.md +0 -6
- package/node_modules/@groove-dev/gui/dist/assets/index-CCVvAoQn.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-DGIv_TRm.js +0 -984
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -379
- package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
- package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
- package/packages/gui/dist/assets/index-CCVvAoQn.css +0 -1
- package/packages/gui/dist/assets/index-DGIv_TRm.js +0 -984
- package/packages/gui/src/components/agents/agent-chat.jsx +0 -379
- package/packages/gui/src/views/preview.jsx +0 -6
- package/packages/gui/src/views/subscription-panel.jsx +0 -327
- package/test.py +0 -571
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
Loader2, MessageSquare, SendHorizontal, Pause,
|
|
5
5
|
FileEdit, Search, Terminal, CheckCircle2, AlertCircle,
|
|
6
6
|
RotateCw, Zap, Wrench, Eye, Code2, Bug,
|
|
7
|
-
ChevronDown,
|
|
7
|
+
ChevronDown, Paperclip, GripHorizontal,
|
|
8
|
+
FileCode, X,
|
|
8
9
|
} from 'lucide-react';
|
|
9
10
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
10
11
|
import { useGrooveStore } from '../../stores/groove';
|
|
@@ -15,6 +16,18 @@ import { ThinkingIndicator } from '../ui/thinking-indicator';
|
|
|
15
16
|
import { TableTree } from '../ui/table-tree';
|
|
16
17
|
|
|
17
18
|
const EMPTY = [];
|
|
19
|
+
const KEEPER_RE = /(\[(?:save|append|update|delete|view|doc|link|read|instruct)\]|#[\w/.-]+)/gi;
|
|
20
|
+
const KEEPER_CMD_RE = /^\[(?:save|append|update|delete|view|doc|link|read|instruct)\]$/i;
|
|
21
|
+
const KEEPER_TAG_RE = /^#[\w/.-]+$/;
|
|
22
|
+
const KEEPER_DETECT_RE = /\[(?:save|append|update|delete|view|doc|link|read|instruct)\]/i;
|
|
23
|
+
|
|
24
|
+
function highlightKeeperInput(text) {
|
|
25
|
+
return text.split(KEEPER_RE).map((part, i) => {
|
|
26
|
+
if (KEEPER_CMD_RE.test(part)) return <span key={i} className="text-accent">{part}</span>;
|
|
27
|
+
if (KEEPER_TAG_RE.test(part)) return <span key={i} className="text-accent">{part}</span>;
|
|
28
|
+
return <span key={i} className="text-text-0">{part}</span>;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
18
31
|
|
|
19
32
|
// ── Activity metadata ────────────────────────────────────────
|
|
20
33
|
function activityMeta(text) {
|
|
@@ -221,22 +234,10 @@ function FormattedText({ text }) {
|
|
|
221
234
|
// ── Message components ───────────────────────────────────────
|
|
222
235
|
|
|
223
236
|
function UserMessage({ msg }) {
|
|
224
|
-
const isQuery = msg.isQuery;
|
|
225
237
|
return (
|
|
226
238
|
<div className="flex justify-end pl-8">
|
|
227
239
|
<div className="max-w-[90%]">
|
|
228
|
-
|
|
229
|
-
<div className="flex items-center justify-end gap-1 mb-1">
|
|
230
|
-
<HelpCircle size={9} className="text-info" />
|
|
231
|
-
<span className="text-2xs text-info font-sans font-medium">Query</span>
|
|
232
|
-
</div>
|
|
233
|
-
)}
|
|
234
|
-
<div className={cn(
|
|
235
|
-
'px-3.5 py-2.5 rounded-lg border',
|
|
236
|
-
isQuery
|
|
237
|
-
? 'bg-info/10 border-info/25'
|
|
238
|
-
: 'bg-info/10 border-info/25',
|
|
239
|
-
)}>
|
|
240
|
+
<div className="px-3.5 py-2.5 rounded-lg border bg-info/10 border-info/25">
|
|
240
241
|
<div className="text-[12px] font-sans whitespace-pre-wrap break-words leading-relaxed text-text-0">
|
|
241
242
|
<FormattedText text={msg.text} />
|
|
242
243
|
</div>
|
|
@@ -369,7 +370,7 @@ function StreamingBar({ agent }) {
|
|
|
369
370
|
<div className="flex items-center gap-3 px-4 h-8 border-b border-border-subtle bg-surface-1/80 flex-shrink-0">
|
|
370
371
|
<div className="flex items-center gap-2 flex-1 min-w-0">
|
|
371
372
|
<div className="relative flex items-center justify-center w-4 h-4">
|
|
372
|
-
<span className="absolute inset-0 rounded-full bg-accent/15 animate-ping
|
|
373
|
+
<span className="absolute inset-0 rounded-full bg-accent/15 animate-ping [animation-duration:2s]" />
|
|
373
374
|
<span className="relative w-1.5 h-1.5 rounded-full bg-accent" />
|
|
374
375
|
</div>
|
|
375
376
|
{isRecent ? (
|
|
@@ -432,8 +433,8 @@ function BootSequence({ agent }) {
|
|
|
432
433
|
{/* Agent identity */}
|
|
433
434
|
<div className="flex items-center gap-3 mb-5">
|
|
434
435
|
<div className="relative w-9 h-9">
|
|
435
|
-
<span className="absolute inset-0 rounded-full border-2 border-accent/20 animate-ping
|
|
436
|
-
<span className="absolute inset-0 rounded-full border-2 border-transparent border-t-accent animate-spin
|
|
436
|
+
<span className="absolute inset-0 rounded-full border-2 border-accent/20 animate-ping [animation-duration:2s]" />
|
|
437
|
+
<span className="absolute inset-0 rounded-full border-2 border-transparent border-t-accent animate-spin [animation-duration:1s]" />
|
|
437
438
|
<span className="absolute inset-[5px] rounded-full bg-accent/8" />
|
|
438
439
|
</div>
|
|
439
440
|
<div>
|
|
@@ -467,9 +468,9 @@ function BootSequence({ agent }) {
|
|
|
467
468
|
</span>
|
|
468
469
|
{isLast && visible && (
|
|
469
470
|
<span className="flex gap-0.5 ml-1">
|
|
470
|
-
<span className="w-1 h-1 rounded-full bg-accent animate-pulse
|
|
471
|
-
<span className="w-1 h-1 rounded-full bg-accent animate-pulse
|
|
472
|
-
<span className="w-1 h-1 rounded-full bg-accent animate-pulse
|
|
471
|
+
<span className="w-1 h-1 rounded-full bg-accent animate-pulse [animation-delay:0ms]" />
|
|
472
|
+
<span className="w-1 h-1 rounded-full bg-accent animate-pulse [animation-delay:200ms]" />
|
|
473
|
+
<span className="w-1 h-1 rounded-full bg-accent animate-pulse [animation-delay:400ms]" />
|
|
473
474
|
</span>
|
|
474
475
|
)}
|
|
475
476
|
</div>
|
|
@@ -480,13 +481,39 @@ function BootSequence({ agent }) {
|
|
|
480
481
|
);
|
|
481
482
|
}
|
|
482
483
|
|
|
484
|
+
// ── Snippet Tag ─────────────────────────────────────────────
|
|
485
|
+
|
|
486
|
+
function SnippetTag({ snippet, onRemove }) {
|
|
487
|
+
const isCode = snippet.type === 'code';
|
|
488
|
+
const Icon = isCode ? FileCode : Terminal;
|
|
489
|
+
const lines = snippet.code.split('\n').length;
|
|
490
|
+
let label;
|
|
491
|
+
if (isCode && snippet.filePath) {
|
|
492
|
+
const fileName = snippet.filePath.split('/').pop();
|
|
493
|
+
label = `${fileName}:${snippet.lineStart}-${snippet.lineEnd}`;
|
|
494
|
+
} else {
|
|
495
|
+
label = `${isCode ? '' : 'Terminal · '}${lines} line${lines !== 1 ? 's' : ''}`;
|
|
496
|
+
}
|
|
497
|
+
return (
|
|
498
|
+
<div className="flex items-center gap-1.5 px-2 py-1 rounded-md bg-accent/10 border border-accent/20 text-accent">
|
|
499
|
+
<Icon size={11} className="flex-shrink-0" />
|
|
500
|
+
<span className="text-2xs font-sans font-medium truncate max-w-[160px]">{label}</span>
|
|
501
|
+
{snippet.instruction && (
|
|
502
|
+
<span className="text-2xs text-accent/60 truncate max-w-[100px]">· {snippet.instruction}</span>
|
|
503
|
+
)}
|
|
504
|
+
<button onClick={onRemove} className="p-0.5 rounded hover:bg-accent/20 cursor-pointer flex-shrink-0">
|
|
505
|
+
<X size={9} />
|
|
506
|
+
</button>
|
|
507
|
+
</div>
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
483
511
|
// ── Main Feed ────────────────────────────────────────────────
|
|
484
512
|
|
|
485
513
|
export function AgentFeed({ agent }) {
|
|
486
514
|
const rawChatHistory = useGrooveStore((s) => s.chatHistory[agent.id]) || EMPTY;
|
|
487
515
|
const rawActivityLog = useGrooveStore((s) => s.activityLog[agent.id]) || EMPTY;
|
|
488
516
|
const instructAgent = useGrooveStore((s) => s.instructAgent);
|
|
489
|
-
const queryAgent = useGrooveStore((s) => s.queryAgent);
|
|
490
517
|
const isThinking = useGrooveStore((s) => s.thinkingAgents?.has(agent.id));
|
|
491
518
|
const cachedChatRef = useRef(EMPTY);
|
|
492
519
|
const cachedActivityRef = useRef(EMPTY);
|
|
@@ -495,19 +522,39 @@ export function AgentFeed({ agent }) {
|
|
|
495
522
|
const chatHistory = rawChatHistory.length > 0 ? rawChatHistory : cachedChatRef.current;
|
|
496
523
|
const activityLog = rawActivityLog.length > 0 ? rawActivityLog : cachedActivityRef.current;
|
|
497
524
|
|
|
525
|
+
const pendingSnippet = useGrooveStore((s) => s.editorPendingSnippet);
|
|
526
|
+
const clearSnippet = useGrooveStore((s) => s.clearSnippet);
|
|
527
|
+
|
|
498
528
|
const storeInput = useGrooveStore((s) => s.chatInputs[agent.id] || '');
|
|
499
|
-
const setStoreInput = (val) => useGrooveStore.setState((s) =>
|
|
529
|
+
const setStoreInput = (val) => useGrooveStore.setState((s) => {
|
|
530
|
+
const current = s.chatInputs[agent.id] || '';
|
|
531
|
+
const next = typeof val === 'function' ? val(current) : val;
|
|
532
|
+
return { chatInputs: { ...s.chatInputs, [agent.id]: next } };
|
|
533
|
+
});
|
|
500
534
|
const input = storeInput;
|
|
501
535
|
const setInput = setStoreInput;
|
|
502
|
-
const [mode, setMode] = useState('instruct'); // instruct | query
|
|
503
536
|
const [sending, setSending] = useState(false);
|
|
504
|
-
const [inputHeight, setInputHeight] = useState(
|
|
537
|
+
const [inputHeight, setInputHeight] = useState(88);
|
|
538
|
+
const [providerModels, setProviderModels] = useState([]);
|
|
505
539
|
const dragRef = useRef(null);
|
|
506
540
|
const scrollRef = useRef(null);
|
|
507
541
|
const inputRef = useRef(null);
|
|
508
542
|
const fileInputRef = useRef(null);
|
|
543
|
+
const highlightRef = useRef(null);
|
|
509
544
|
const isAtBottomRef = useRef(true);
|
|
510
545
|
|
|
546
|
+
useEffect(() => {
|
|
547
|
+
if (pendingSnippet) inputRef.current?.focus();
|
|
548
|
+
}, [pendingSnippet]);
|
|
549
|
+
|
|
550
|
+
useEffect(() => {
|
|
551
|
+
if (!agent.provider) return;
|
|
552
|
+
api.get('/providers').then((data) => {
|
|
553
|
+
const p = (Array.isArray(data) ? data : []).find((pr) => pr.id === agent.provider);
|
|
554
|
+
setProviderModels((p?.models || []).filter((m) => !m.disabled));
|
|
555
|
+
}).catch(() => {});
|
|
556
|
+
}, [agent.provider]);
|
|
557
|
+
|
|
511
558
|
useEffect(() => {
|
|
512
559
|
const el = scrollRef.current;
|
|
513
560
|
if (!el) return;
|
|
@@ -522,7 +569,7 @@ export function AgentFeed({ agent }) {
|
|
|
522
569
|
e.preventDefault();
|
|
523
570
|
const startY = e.clientY;
|
|
524
571
|
const startH = inputHeight;
|
|
525
|
-
const onMove = (ev) => setInputHeight(Math.min(Math.max(
|
|
572
|
+
const onMove = (ev) => setInputHeight(Math.min(Math.max(56, startH - (ev.clientY - startY)), 280));
|
|
526
573
|
const onUp = () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
|
|
527
574
|
window.addEventListener('mousemove', onMove);
|
|
528
575
|
window.addEventListener('mouseup', onUp);
|
|
@@ -616,7 +663,7 @@ export function AgentFeed({ agent }) {
|
|
|
616
663
|
|
|
617
664
|
async function handleSend() {
|
|
618
665
|
const text = input.trim();
|
|
619
|
-
if (!text || sending) return;
|
|
666
|
+
if ((!text && !pendingSnippet) || sending) return;
|
|
620
667
|
|
|
621
668
|
if (text === '/rotate') {
|
|
622
669
|
const rotateAgent = useGrooveStore.getState().rotateAgent;
|
|
@@ -625,18 +672,29 @@ export function AgentFeed({ agent }) {
|
|
|
625
672
|
return;
|
|
626
673
|
}
|
|
627
674
|
|
|
675
|
+
const parts = [];
|
|
676
|
+
if (text) parts.push(text);
|
|
677
|
+
if (pendingSnippet) {
|
|
678
|
+
const s = pendingSnippet;
|
|
679
|
+
if (s.type === 'code' && s.filePath) {
|
|
680
|
+
if (s.instruction && !text) parts.push(s.instruction);
|
|
681
|
+
parts.push(`File: ${s.filePath} (lines ${s.lineStart}-${s.lineEnd})`);
|
|
682
|
+
parts.push('```\n' + s.code + '\n```');
|
|
683
|
+
} else if (s.code) {
|
|
684
|
+
parts.push('```\n' + s.code + '\n```');
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
const message = parts.join('\n\n');
|
|
688
|
+
|
|
628
689
|
setInput('');
|
|
690
|
+
clearSnippet();
|
|
629
691
|
setSending(true);
|
|
630
692
|
isAtBottomRef.current = true;
|
|
631
693
|
requestAnimationFrame(() => {
|
|
632
694
|
if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
633
695
|
});
|
|
634
696
|
try {
|
|
635
|
-
|
|
636
|
-
await queryAgent(agent.id, text);
|
|
637
|
-
} else {
|
|
638
|
-
await instructAgent(agent.id, text);
|
|
639
|
-
}
|
|
697
|
+
await instructAgent(agent.id, message);
|
|
640
698
|
} catch { /* toast handles */ }
|
|
641
699
|
setSending(false);
|
|
642
700
|
inputRef.current?.focus();
|
|
@@ -707,41 +765,27 @@ export function AgentFeed({ agent }) {
|
|
|
707
765
|
</div>
|
|
708
766
|
|
|
709
767
|
<div className="px-4 pb-3">
|
|
710
|
-
{/*
|
|
711
|
-
|
|
712
|
-
<
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
mode === 'instruct'
|
|
717
|
-
? 'bg-accent/12 text-accent border border-accent/20'
|
|
718
|
-
: 'text-text-3 hover:text-text-1 hover:bg-surface-3',
|
|
719
|
-
)}
|
|
720
|
-
>
|
|
721
|
-
<Pencil size={10} />
|
|
722
|
-
Instruct
|
|
723
|
-
</button>
|
|
724
|
-
<button
|
|
725
|
-
onClick={() => setMode('query')}
|
|
726
|
-
className={cn(
|
|
727
|
-
'flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[11px] font-sans font-medium transition-colors cursor-pointer',
|
|
728
|
-
mode === 'query'
|
|
729
|
-
? 'bg-info/12 text-info border border-info/20'
|
|
730
|
-
: 'text-text-3 hover:text-text-1 hover:bg-surface-3',
|
|
731
|
-
)}
|
|
732
|
-
>
|
|
733
|
-
<HelpCircle size={10} />
|
|
734
|
-
Query
|
|
735
|
-
</button>
|
|
736
|
-
<span className="text-[10px] text-text-4 font-sans ml-auto">
|
|
737
|
-
{mode === 'query' ? 'Read-only — agent keeps working' : isAlive ? 'Directs the agent' : 'Continues the session'}
|
|
738
|
-
</span>
|
|
739
|
-
</div>
|
|
768
|
+
{/* Snippet tag */}
|
|
769
|
+
{pendingSnippet && (
|
|
770
|
+
<div className="mb-2">
|
|
771
|
+
<SnippetTag snippet={pendingSnippet} onRemove={clearSnippet} />
|
|
772
|
+
</div>
|
|
773
|
+
)}
|
|
740
774
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
775
|
+
{/* Keeper command indicator */}
|
|
776
|
+
{input && /\[(?:save|append|update|delete|view|doc|link|read|instruct)\]/i.test(input) && (() => {
|
|
777
|
+
const cmdMatch = input.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]/i);
|
|
778
|
+
const tags = (input.match(/#[\w/.-]+/g) || []);
|
|
779
|
+
return (
|
|
780
|
+
<div className="flex items-center gap-1.5 px-3 py-1 mb-2 rounded-lg bg-accent/5 border border-accent/10">
|
|
781
|
+
<span className="px-1.5 py-0.5 rounded bg-accent/15 text-accent font-semibold font-mono text-[10px]">{cmdMatch[0]}</span>
|
|
782
|
+
{tags.map((tag, i) => <span key={i} className="text-accent font-medium text-[10px]">{tag}</span>)}
|
|
783
|
+
<span className="text-[10px] text-text-4 ml-auto">memory command</span>
|
|
784
|
+
</div>
|
|
785
|
+
);
|
|
786
|
+
})()}
|
|
787
|
+
|
|
788
|
+
<div className="flex flex-col rounded-lg border border-border-subtle bg-surface-0 transition-colors overflow-hidden focus-within:border-text-4/40">
|
|
745
789
|
<input
|
|
746
790
|
ref={fileInputRef}
|
|
747
791
|
type="file"
|
|
@@ -750,57 +794,111 @@ export function AgentFeed({ agent }) {
|
|
|
750
794
|
onChange={handleFileSelect}
|
|
751
795
|
className="hidden"
|
|
752
796
|
/>
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
onKeyDown={onKeyDown}
|
|
765
|
-
placeholder={mode === 'query'
|
|
766
|
-
? 'Ask about this agent\'s work...'
|
|
767
|
-
: isAlive ? 'Send an instruction...' : 'Continue this session...'}
|
|
768
|
-
rows={1}
|
|
769
|
-
className={cn(
|
|
770
|
-
'flex-1 resize-none px-3 py-2 text-[13px]',
|
|
771
|
-
'bg-transparent text-text-0 font-sans',
|
|
772
|
-
'placeholder:text-text-4',
|
|
773
|
-
'focus:outline-none',
|
|
797
|
+
{/* Textarea — full width */}
|
|
798
|
+
<div className="relative px-1">
|
|
799
|
+
{input && KEEPER_DETECT_RE.test(input) && (
|
|
800
|
+
<div
|
|
801
|
+
ref={highlightRef}
|
|
802
|
+
aria-hidden
|
|
803
|
+
className="absolute inset-0 px-3 py-2.5 text-[13px] leading-[20px] font-sans pointer-events-none whitespace-pre-wrap break-words overflow-y-hidden"
|
|
804
|
+
style={{ height: inputHeight }}
|
|
805
|
+
>
|
|
806
|
+
{highlightKeeperInput(input)}
|
|
807
|
+
</div>
|
|
774
808
|
)}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
809
|
+
<textarea
|
|
810
|
+
ref={inputRef}
|
|
811
|
+
value={input}
|
|
812
|
+
onChange={(e) => setInput(e.target.value)}
|
|
813
|
+
onKeyDown={onKeyDown}
|
|
814
|
+
onScroll={(e) => { if (highlightRef.current) highlightRef.current.scrollTop = e.target.scrollTop; }}
|
|
815
|
+
onDragOver={(e) => e.preventDefault()}
|
|
816
|
+
onDrop={(e) => {
|
|
817
|
+
e.preventDefault();
|
|
818
|
+
if (e.dataTransfer?.files?.length) {
|
|
819
|
+
const dt = new DataTransfer();
|
|
820
|
+
for (const f of e.dataTransfer.files) dt.items.add(f);
|
|
821
|
+
fileInputRef.current.files = dt.files;
|
|
822
|
+
fileInputRef.current.dispatchEvent(new Event('change', { bubbles: true }));
|
|
823
|
+
}
|
|
824
|
+
}}
|
|
825
|
+
placeholder={pendingSnippet ? 'Add a message (optional)...'
|
|
826
|
+
: isAlive ? 'Send an instruction...' : 'Continue this session...'}
|
|
827
|
+
rows={1}
|
|
828
|
+
className={cn(
|
|
829
|
+
'w-full resize-none px-3 py-2.5 text-[13px] leading-[20px]',
|
|
830
|
+
'bg-transparent font-sans relative z-10',
|
|
831
|
+
'placeholder:text-text-4',
|
|
832
|
+
'focus:outline-none',
|
|
833
|
+
input && KEEPER_DETECT_RE.test(input)
|
|
834
|
+
? 'text-transparent caret-text-0'
|
|
835
|
+
: 'text-text-0',
|
|
836
|
+
)}
|
|
837
|
+
style={{ height: inputHeight }}
|
|
838
|
+
/>
|
|
839
|
+
</div>
|
|
840
|
+
{/* Bottom toolbar */}
|
|
841
|
+
<div className="flex items-center gap-1 px-1.5 pb-1.5 pt-0.5">
|
|
842
|
+
{/* Left: attach */}
|
|
778
843
|
<button
|
|
779
|
-
onClick={() =>
|
|
780
|
-
|
|
781
|
-
|
|
844
|
+
onClick={() => fileInputRef.current?.click()}
|
|
845
|
+
className="w-7 h-7 flex items-center justify-center rounded-md text-text-4 hover:text-text-1 transition-colors cursor-pointer"
|
|
846
|
+
title="Attach file"
|
|
782
847
|
>
|
|
783
|
-
<
|
|
784
|
-
<span className="absolute inset-0 rounded-full bg-danger/30 animate-ping" style={{ animationDuration: '2s' }} />
|
|
785
|
-
<span className="relative w-2.5 h-2.5 rounded-full bg-danger" />
|
|
786
|
-
</span>
|
|
848
|
+
<Paperclip size={14} />
|
|
787
849
|
</button>
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
850
|
+
{/* Model selector */}
|
|
851
|
+
{providerModels.length > 1 && (
|
|
852
|
+
<div className="relative flex items-center">
|
|
853
|
+
<select
|
|
854
|
+
value={agent.model || ''}
|
|
855
|
+
onChange={(e) => {
|
|
856
|
+
if (!e.target.value) return;
|
|
857
|
+
api.patch(`/agents/${agent.id}`, { model: e.target.value }).catch(() => {});
|
|
858
|
+
}}
|
|
859
|
+
className="h-7 pl-2 pr-5 text-[11px] font-mono bg-transparent text-text-3 hover:text-text-1 rounded-md cursor-pointer focus:outline-none appearance-none border-none"
|
|
860
|
+
title="Switch model"
|
|
861
|
+
>
|
|
862
|
+
{providerModels.map((m) => (
|
|
863
|
+
<option key={m.id} value={m.id}>{m.name || m.id}</option>
|
|
864
|
+
))}
|
|
865
|
+
</select>
|
|
866
|
+
<ChevronDown size={10} className="absolute right-1.5 top-1/2 -translate-y-1/2 text-text-4 pointer-events-none" />
|
|
867
|
+
</div>
|
|
868
|
+
)}
|
|
869
|
+
{/* Pulsating activity indicator */}
|
|
870
|
+
{isAlive && (
|
|
871
|
+
<span className="relative flex items-center justify-center w-3 h-3 mr-auto">
|
|
872
|
+
<span className="absolute inset-0 rounded-full bg-accent/30 animate-ping [animation-duration:2s]" />
|
|
873
|
+
<span className="relative w-2 h-2 rounded-full bg-accent" />
|
|
874
|
+
</span>
|
|
800
875
|
)}
|
|
801
|
-
|
|
802
|
-
{
|
|
803
|
-
|
|
876
|
+
<div className="flex-1" />
|
|
877
|
+
{/* Right: pause (when alive) or send (when idle) */}
|
|
878
|
+
{isAlive ? (
|
|
879
|
+
<button
|
|
880
|
+
onClick={() => useGrooveStore.getState().stopAgent(agent.id)}
|
|
881
|
+
className="flex items-center gap-1.5 h-7 px-2 rounded-md text-text-0 hover:text-text-1 hover:bg-surface-3 transition-colors cursor-pointer"
|
|
882
|
+
>
|
|
883
|
+
<Pause size={13} />
|
|
884
|
+
<span className="text-[11px] font-sans font-medium">Pause</span>
|
|
885
|
+
</button>
|
|
886
|
+
) : (
|
|
887
|
+
<button
|
|
888
|
+
onClick={handleSend}
|
|
889
|
+
disabled={(!input.trim() && !pendingSnippet) || sending}
|
|
890
|
+
className={cn(
|
|
891
|
+
'w-7 h-7 flex items-center justify-center rounded-md transition-colors cursor-pointer',
|
|
892
|
+
'disabled:opacity-15 disabled:cursor-not-allowed',
|
|
893
|
+
(input.trim() || pendingSnippet)
|
|
894
|
+
? 'text-text-0 hover:text-text-1'
|
|
895
|
+
: 'text-text-4',
|
|
896
|
+
)}
|
|
897
|
+
>
|
|
898
|
+
{sending ? <Loader2 size={15} className="animate-spin" /> : <SendHorizontal size={15} />}
|
|
899
|
+
</button>
|
|
900
|
+
)}
|
|
901
|
+
</div>
|
|
804
902
|
</div>
|
|
805
903
|
</div>
|
|
806
904
|
</div>
|
|
@@ -8,7 +8,6 @@ import { fmtNum, fmtDollar, fmtUptime } from '../../lib/format';
|
|
|
8
8
|
|
|
9
9
|
const EMPTY = [];
|
|
10
10
|
const ERROR_RE = /error|crash|fail/i;
|
|
11
|
-
const BAR_BG = 'rgba(51, 175, 188, 0.15)';
|
|
12
11
|
const BAR_H = 'h-[2px]';
|
|
13
12
|
|
|
14
13
|
function shortModel(id) {
|
|
@@ -98,13 +97,9 @@ const AgentNode = memo(({ data, selected }) => {
|
|
|
98
97
|
|
|
99
98
|
{/* Scan line — running only */}
|
|
100
99
|
{isAlive && (
|
|
101
|
-
<div className="absolute inset-0 overflow-hidden pointer-events-none"
|
|
100
|
+
<div className="absolute inset-0 overflow-hidden pointer-events-none rounded-[3px]">
|
|
102
101
|
<div
|
|
103
|
-
className="absolute left-0 right-0 h-px"
|
|
104
|
-
style={{
|
|
105
|
-
background: 'linear-gradient(90deg, transparent 0%, rgba(97,175,239,0.25) 50%, transparent 100%)',
|
|
106
|
-
animation: 'node-scan 3s ease-in-out infinite',
|
|
107
|
-
}}
|
|
102
|
+
className="absolute left-0 right-0 h-px [background:linear-gradient(90deg,transparent_0%,rgba(97,175,239,0.25)_50%,transparent_100%)] [animation:node-scan_3s_ease-in-out_infinite]"
|
|
108
103
|
/>
|
|
109
104
|
</div>
|
|
110
105
|
)}
|
|
@@ -116,8 +111,8 @@ const AgentNode = memo(({ data, selected }) => {
|
|
|
116
111
|
<span className="absolute inset-0 rounded-sm" style={{ background: sColor }} />
|
|
117
112
|
{isAlive && (
|
|
118
113
|
<span
|
|
119
|
-
className="absolute inset-[-2px] rounded-sm"
|
|
120
|
-
style={{ background: sColor
|
|
114
|
+
className="absolute inset-[-2px] rounded-sm opacity-[0.15] [animation:node-pulse-bar_2s_ease-in-out_infinite]"
|
|
115
|
+
style={{ background: sColor }}
|
|
121
116
|
/>
|
|
122
117
|
)}
|
|
123
118
|
</span>
|
|
@@ -155,7 +150,7 @@ const AgentNode = memo(({ data, selected }) => {
|
|
|
155
150
|
</div>
|
|
156
151
|
|
|
157
152
|
{/* Context bar */}
|
|
158
|
-
<div className={`mt-1.5 ${BAR_H} rounded-sm overflow-hidden`}
|
|
153
|
+
<div className={`mt-1.5 ${BAR_H} rounded-sm overflow-hidden bg-[rgba(51,175,188,0.15)]`}>
|
|
159
154
|
<div
|
|
160
155
|
className="h-full rounded-sm transition-all duration-700"
|
|
161
156
|
style={{
|
|
@@ -185,7 +180,7 @@ const AgentNode = memo(({ data, selected }) => {
|
|
|
185
180
|
)}
|
|
186
181
|
</div>
|
|
187
182
|
<div className="flex items-center gap-2">
|
|
188
|
-
<div className={`flex-1 ${BAR_H} rounded-sm overflow-hidden`}
|
|
183
|
+
<div className={`flex-1 ${BAR_H} rounded-sm overflow-hidden bg-[rgba(51,175,188,0.15)]`}>
|
|
189
184
|
<div className="h-full rounded-sm transition-all duration-500" style={{ width: `${Math.max(contextPct, 1)}%`, background: ctxColor }} />
|
|
190
185
|
</div>
|
|
191
186
|
<span className="text-[9px] font-mono font-medium" style={{ color: ctxColor }}>{contextPct}%</span>
|
|
@@ -196,7 +191,7 @@ const AgentNode = memo(({ data, selected }) => {
|
|
|
196
191
|
<div className="px-3 pt-1 pb-1">
|
|
197
192
|
<span className="text-[9px] font-mono text-[#505862] uppercase tracking-wider">Quality</span>
|
|
198
193
|
<div className="flex items-center gap-2 mt-1">
|
|
199
|
-
<div className={`flex-1 ${BAR_H} rounded-sm overflow-hidden`}
|
|
194
|
+
<div className={`flex-1 ${BAR_H} rounded-sm overflow-hidden bg-[rgba(51,175,188,0.15)]`}>
|
|
200
195
|
<div className="h-full rounded-sm transition-all duration-500" style={{ width: `${qScore != null ? Math.max(qScore, 1) : 0}%`, background: qColor || '#505862' }} />
|
|
201
196
|
</div>
|
|
202
197
|
<span className="text-[9px] font-mono font-medium" style={{ color: qColor || '#505862' }}>{qScore != null ? qScore : '—'}</span>
|
|
@@ -207,7 +202,7 @@ const AgentNode = memo(({ data, selected }) => {
|
|
|
207
202
|
<div className="px-3 pt-1 pb-1">
|
|
208
203
|
<span className="text-[9px] font-mono text-[#505862] uppercase tracking-wider">Efficiency</span>
|
|
209
204
|
<div className="flex items-center gap-2 mt-1">
|
|
210
|
-
<div className={`flex-1 ${BAR_H} rounded-sm overflow-hidden`}
|
|
205
|
+
<div className={`flex-1 ${BAR_H} rounded-sm overflow-hidden bg-[rgba(51,175,188,0.15)]`}>
|
|
211
206
|
<div className="h-full rounded-sm transition-all duration-500" style={{ width: `${effPct != null ? Math.max(effPct, 1) : 0}%`, background: effColor || '#505862' }} />
|
|
212
207
|
</div>
|
|
213
208
|
<span className="text-[9px] font-mono font-medium" style={{ color: effColor || '#505862' }}>{effPct != null ? `${effPct}%` : '—'}</span>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
-
import { useState, useRef
|
|
2
|
+
import { useState, useRef } from 'react';
|
|
3
3
|
import { useGrooveStore } from '../../stores/groove';
|
|
4
4
|
import { Badge } from '../ui/badge';
|
|
5
5
|
import { AgentFeed } from './agent-feed';
|
|
6
6
|
import { AgentConfig } from './agent-config';
|
|
7
7
|
import { AgentTelemetry } from './agent-telemetry';
|
|
8
8
|
import { AgentMdFiles } from './agent-mdfiles';
|
|
9
|
-
import { MessageSquare, Settings, Activity, FileText, Pencil, Check, X
|
|
9
|
+
import { MessageSquare, Settings, Activity, FileText, Pencil, Check, X } from 'lucide-react';
|
|
10
10
|
import { fmtNum, fmtUptime } from '../../lib/format';
|
|
11
11
|
import { cn } from '../../lib/cn';
|
|
12
12
|
import { roleColor } from '../../lib/status';
|
|
@@ -77,56 +77,6 @@ function InlineName({ agent }) {
|
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
function useRoutingSuggestion(agentId, isAlive) {
|
|
81
|
-
const [suggestion, setSuggestion] = useState(null);
|
|
82
|
-
const [dismissed, setDismissed] = useState(false);
|
|
83
|
-
|
|
84
|
-
useEffect(() => {
|
|
85
|
-
if (!agentId || !isAlive || dismissed) { setSuggestion(null); return; }
|
|
86
|
-
let cancelled = false;
|
|
87
|
-
async function poll() {
|
|
88
|
-
try {
|
|
89
|
-
const res = await fetch(`/api/agents/${agentId}/routing/suggestion`);
|
|
90
|
-
if (cancelled) return;
|
|
91
|
-
if (res.status === 204 || !res.ok) { setSuggestion(null); return; }
|
|
92
|
-
const data = await res.json();
|
|
93
|
-
setSuggestion(data);
|
|
94
|
-
} catch { setSuggestion(null); }
|
|
95
|
-
}
|
|
96
|
-
poll();
|
|
97
|
-
const id = setInterval(poll, 30000);
|
|
98
|
-
return () => { cancelled = true; clearInterval(id); };
|
|
99
|
-
}, [agentId, isAlive, dismissed]);
|
|
100
|
-
|
|
101
|
-
const dismiss = useCallback(() => setDismissed(true), []);
|
|
102
|
-
const reset = useCallback(() => setDismissed(false), []);
|
|
103
|
-
|
|
104
|
-
return { suggestion: dismissed ? null : suggestion, dismiss, reset };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function DownshiftPill({ suggestion, onAccept, onDismiss }) {
|
|
108
|
-
if (!suggestion) return null;
|
|
109
|
-
const { suggestedModel } = suggestion;
|
|
110
|
-
return (
|
|
111
|
-
<div className="flex items-center gap-1 px-1.5 py-0.5 rounded bg-success/10 border border-success/20 text-2xs font-mono animate-in fade-in slide-in-from-left-1 duration-200">
|
|
112
|
-
<TrendingDown size={10} className="text-success flex-shrink-0" />
|
|
113
|
-
<span className="text-success/90 truncate max-w-[80px]">{suggestedModel.name}</span>
|
|
114
|
-
<button
|
|
115
|
-
onClick={onAccept}
|
|
116
|
-
className="px-1 py-px rounded bg-success/20 text-success font-semibold hover:bg-success/30 transition-colors cursor-pointer"
|
|
117
|
-
>
|
|
118
|
-
Switch
|
|
119
|
-
</button>
|
|
120
|
-
<button
|
|
121
|
-
onClick={onDismiss}
|
|
122
|
-
className="p-0.5 text-text-4 hover:text-text-1 cursor-pointer"
|
|
123
|
-
>
|
|
124
|
-
<X size={8} />
|
|
125
|
-
</button>
|
|
126
|
-
</div>
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
80
|
export function AgentPanel() {
|
|
131
81
|
const detailPanel = useGrooveStore((s) => s.detailPanel);
|
|
132
82
|
const agents = useGrooveStore((s) => s.agents);
|
|
@@ -141,7 +91,6 @@ export function AgentPanel() {
|
|
|
141
91
|
else if (cachedAgentRef.current && cachedAgentRef.current.id !== agentId) cachedAgentRef.current = null;
|
|
142
92
|
const agent = liveAgent || cachedAgentRef.current;
|
|
143
93
|
const isAlive = liveAgent?.status === 'running' || liveAgent?.status === 'starting';
|
|
144
|
-
const { suggestion, dismiss: dismissSuggestion } = useRoutingSuggestion(agentId, isAlive);
|
|
145
94
|
|
|
146
95
|
if (!agent) return null;
|
|
147
96
|
if (activeTeamId && agent.teamId && agent.teamId !== activeTeamId) return null;
|
|
@@ -151,17 +100,6 @@ export function AgentPanel() {
|
|
|
151
100
|
const uptime = spawned ? Math.floor((Date.now() - new Date(spawned).getTime()) / 1000) : 0;
|
|
152
101
|
const colors = roleColor(agent.role);
|
|
153
102
|
|
|
154
|
-
async function acceptSuggestion() {
|
|
155
|
-
if (!suggestion) return;
|
|
156
|
-
try {
|
|
157
|
-
await api.patch(`/agents/${agent.id}`, { model: suggestion.suggestedModel.id });
|
|
158
|
-
addToast('success', `Model → ${suggestion.suggestedModel.name}`);
|
|
159
|
-
dismissSuggestion();
|
|
160
|
-
} catch (err) {
|
|
161
|
-
addToast('error', 'Model switch failed', err.message);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
103
|
return (
|
|
166
104
|
<div className="flex flex-col h-full">
|
|
167
105
|
{/* ── Header ─────────────────────────────────────────── */}
|
|
@@ -194,12 +132,6 @@ export function AgentPanel() {
|
|
|
194
132
|
)}
|
|
195
133
|
<span className="text-text-4">·</span>
|
|
196
134
|
<span>{fmtUptime(uptime)}</span>
|
|
197
|
-
{suggestion && (
|
|
198
|
-
<>
|
|
199
|
-
<span className="text-text-4">·</span>
|
|
200
|
-
<DownshiftPill suggestion={suggestion} onAccept={acceptSuggestion} onDismiss={dismissSuggestion} />
|
|
201
|
-
</>
|
|
202
|
-
)}
|
|
203
135
|
</div>
|
|
204
136
|
</div>
|
|
205
137
|
|