groove-dev 0.27.140 → 0.27.141
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/integrations-registry.json +12 -44
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +82 -16
- package/node_modules/@groove-dev/daemon/src/integrations.js +10 -0
- package/node_modules/@groove-dev/daemon/src/journalist.js +169 -0
- package/node_modules/@groove-dev/daemon/src/keeper.js +3 -3
- package/node_modules/@groove-dev/daemon/src/model-lab.js +11 -0
- package/node_modules/@groove-dev/daemon/src/process.js +76 -0
- package/node_modules/@groove-dev/daemon/src/validate.js +8 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-A4e1gIDh.css +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-P1hsM27-.js +8696 -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/components/agents/agent-chat.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +7 -2
- package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +5 -4
- package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +160 -12
- package/node_modules/@groove-dev/gui/src/components/editor/ai-panel.jsx +77 -6
- package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +2 -49
- package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +15 -4
- package/node_modules/@groove-dev/gui/src/components/keeper/global-modals.jsx +10 -10
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +152 -3
- package/node_modules/@groove-dev/gui/src/components/marketplace/integration-wizard.jsx +223 -18
- package/node_modules/@groove-dev/gui/src/stores/groove.js +110 -32
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +114 -56
- package/node_modules/@groove-dev/gui/src/views/memory.jsx +9 -9
- package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +1 -6
- package/node_modules/@groove-dev/gui/src/views/models.jsx +658 -565
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/integrations-registry.json +12 -44
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +82 -16
- package/packages/daemon/src/integrations.js +10 -0
- package/packages/daemon/src/journalist.js +169 -0
- package/packages/daemon/src/keeper.js +3 -3
- package/packages/daemon/src/model-lab.js +11 -0
- package/packages/daemon/src/process.js +76 -0
- package/packages/daemon/src/validate.js +8 -0
- package/packages/gui/dist/assets/index-A4e1gIDh.css +1 -0
- package/packages/gui/dist/assets/index-P1hsM27-.js +8696 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/agents/agent-chat.jsx +3 -3
- package/packages/gui/src/components/agents/agent-file-tree.jsx +7 -2
- package/packages/gui/src/components/agents/code-review.jsx +5 -4
- package/packages/gui/src/components/agents/workspace-mode.jsx +160 -12
- package/packages/gui/src/components/editor/ai-panel.jsx +77 -6
- package/packages/gui/src/components/editor/file-tree.jsx +2 -49
- package/packages/gui/src/components/editor/terminal.jsx +15 -4
- package/packages/gui/src/components/keeper/global-modals.jsx +10 -10
- package/packages/gui/src/components/layout/terminal-panel.jsx +152 -3
- package/packages/gui/src/components/marketplace/integration-wizard.jsx +223 -18
- package/packages/gui/src/stores/groove.js +110 -32
- package/packages/gui/src/views/agents.jsx +114 -56
- package/packages/gui/src/views/memory.jsx +9 -9
- package/packages/gui/src/views/model-lab.jsx +1 -6
- package/packages/gui/src/views/models.jsx +658 -565
- package/plan_files/keeper-manual.md +53 -42
- package/node_modules/@groove-dev/gui/dist/assets/index-BV9CAiw1.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-DK6UIz0n.js +0 -8698
- package/packages/gui/dist/assets/index-BV9CAiw1.css +0 -1
- package/packages/gui/dist/assets/index-DK6UIz0n.js +0 -8698
|
@@ -213,6 +213,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
213
213
|
|
|
214
214
|
// ── Editor (Cursor-style) ────────────────────────────────
|
|
215
215
|
editorSelectedAgent: null,
|
|
216
|
+
editorPendingSnippet: null,
|
|
216
217
|
editorViewMode: 'code',
|
|
217
218
|
editorAiPanelOpen: false,
|
|
218
219
|
editorAiPanelWidth: Number(localStorage.getItem('groove:editorAiPanelWidth')) || 360,
|
|
@@ -665,6 +666,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
665
666
|
case 'lab:runtime:added':
|
|
666
667
|
case 'lab:runtime:updated':
|
|
667
668
|
case 'lab:runtime:removed':
|
|
669
|
+
case 'llama:server:stopped':
|
|
668
670
|
get().fetchLabRuntimes();
|
|
669
671
|
break;
|
|
670
672
|
|
|
@@ -1320,7 +1322,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
1320
1322
|
},
|
|
1321
1323
|
|
|
1322
1324
|
async _handleKeeperCommand(agentId, message, command) {
|
|
1323
|
-
const rest = message.replace(/\[\w+[-\w]*\]/i, '').trim();
|
|
1325
|
+
const rest = message.replace(/\[\w+[-\w]*\]|\b(?:save|append|update|delete|view|doc|link|read|instruct)\b/i, '').trim();
|
|
1324
1326
|
const tags = (rest.match(/#[\w/.-]+/g) || []).map(t => t.replace(/^#/, ''));
|
|
1325
1327
|
|
|
1326
1328
|
const addSystemMsg = (text) => {
|
|
@@ -1335,23 +1337,21 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
1335
1337
|
}
|
|
1336
1338
|
|
|
1337
1339
|
case 'save': {
|
|
1338
|
-
if (tags.length === 0) { addSystemMsg('Usage:
|
|
1340
|
+
if (tags.length === 0) { addSystemMsg('Usage: save #tag your message here'); return true; }
|
|
1339
1341
|
const content = rest.replace(/#[\w/.-]+/g, '').trim();
|
|
1340
|
-
if (!content) { addSystemMsg('Usage:
|
|
1341
|
-
get().addChatMessage(agentId, 'user', message, false);
|
|
1342
|
+
if (!content) { addSystemMsg('Usage: save #tag your message here'); return true; }
|
|
1342
1343
|
await get().saveKeeperItem(tags[0], content);
|
|
1343
1344
|
addSystemMsg(`Saved to #${tags[0]}`);
|
|
1344
|
-
return
|
|
1345
|
+
return { passthrough: content };
|
|
1345
1346
|
}
|
|
1346
1347
|
|
|
1347
1348
|
case 'append': {
|
|
1348
|
-
if (tags.length === 0) { addSystemMsg('Usage:
|
|
1349
|
+
if (tags.length === 0) { addSystemMsg('Usage: append #tag content to add'); return true; }
|
|
1349
1350
|
const content = rest.replace(/#[\w/.-]+/g, '').trim();
|
|
1350
|
-
if (!content) { addSystemMsg('Usage:
|
|
1351
|
-
get().addChatMessage(agentId, 'user', message, false);
|
|
1351
|
+
if (!content) { addSystemMsg('Usage: append #tag content to add'); return true; }
|
|
1352
1352
|
await get().appendKeeperItem(tags[0], content);
|
|
1353
1353
|
addSystemMsg(`Appended to #${tags[0]}`);
|
|
1354
|
-
return
|
|
1354
|
+
return { passthrough: content };
|
|
1355
1355
|
}
|
|
1356
1356
|
|
|
1357
1357
|
case 'update': {
|
|
@@ -2031,7 +2031,6 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
2031
2031
|
...(tlc?.reasoningEffort != null && { teamReasoningEffort: tlc.reasoningEffort }),
|
|
2032
2032
|
...(tlc?.temperature != null && { teamTemperature: tlc.temperature }),
|
|
2033
2033
|
...(tlc?.verbosity != null && { teamVerbosity: tlc.verbosity }),
|
|
2034
|
-
...(tlc?.mode && { mode: tlc.mode }),
|
|
2035
2034
|
};
|
|
2036
2035
|
const result = await api.post('/recommended-team/launch', body);
|
|
2037
2036
|
const totalOk = (result.launched || 0) + (result.reused || 0);
|
|
@@ -2685,10 +2684,13 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
2685
2684
|
|
|
2686
2685
|
async instructAgent(id, message) {
|
|
2687
2686
|
// ── Keeper command interception ─────────────────────────
|
|
2688
|
-
const keeperCmd = message.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]/i);
|
|
2687
|
+
const keeperCmd = message.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]|\b(save|append|update|delete|view|doc|link|read)\b(?=\s+#[\w/.-])/i);
|
|
2689
2688
|
if (keeperCmd) {
|
|
2690
|
-
const handled = await get()._handleKeeperCommand(id, message, keeperCmd[1].toLowerCase());
|
|
2691
|
-
if (handled) return { status: 'keeper_handled' };
|
|
2689
|
+
const handled = await get()._handleKeeperCommand(id, message, (keeperCmd[1] || keeperCmd[2]).toLowerCase());
|
|
2690
|
+
if (handled === true) return { status: 'keeper_handled' };
|
|
2691
|
+
if (handled?.passthrough) {
|
|
2692
|
+
message = handled.passthrough;
|
|
2693
|
+
}
|
|
2692
2694
|
}
|
|
2693
2695
|
|
|
2694
2696
|
get().addChatMessage(id, 'user', message, false);
|
|
@@ -3226,10 +3228,27 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3226
3228
|
set({ editorQuickSearchOpen: open });
|
|
3227
3229
|
},
|
|
3228
3230
|
|
|
3231
|
+
attachSnippet(snippet) {
|
|
3232
|
+
set({ editorPendingSnippet: snippet });
|
|
3233
|
+
if (!get().editorAiPanelOpen) {
|
|
3234
|
+
set({ editorAiPanelOpen: true });
|
|
3235
|
+
}
|
|
3236
|
+
},
|
|
3237
|
+
|
|
3238
|
+
clearSnippet() {
|
|
3239
|
+
set({ editorPendingSnippet: null });
|
|
3240
|
+
},
|
|
3241
|
+
|
|
3229
3242
|
async sendCodeToAgent(agentId, instruction, filePath, lineStart, lineEnd, selectedCode) {
|
|
3230
3243
|
if (!agentId) return;
|
|
3231
|
-
|
|
3232
|
-
|
|
3244
|
+
get().attachSnippet({
|
|
3245
|
+
type: 'code',
|
|
3246
|
+
instruction,
|
|
3247
|
+
filePath,
|
|
3248
|
+
lineStart,
|
|
3249
|
+
lineEnd,
|
|
3250
|
+
code: selectedCode,
|
|
3251
|
+
});
|
|
3233
3252
|
},
|
|
3234
3253
|
|
|
3235
3254
|
async fetchGitStatus() {
|
|
@@ -3844,11 +3863,17 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3844
3863
|
const payload = line.slice(6);
|
|
3845
3864
|
if (payload === '[DONE]') continue;
|
|
3846
3865
|
try {
|
|
3847
|
-
const
|
|
3848
|
-
|
|
3866
|
+
const parsed = JSON.parse(payload);
|
|
3867
|
+
|
|
3868
|
+
// Support both raw OpenAI format (piped) and legacy wrapper format
|
|
3869
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
3870
|
+
const reasoningText = delta?.reasoning_content || (parsed.type === 'reasoning' ? parsed.content : null);
|
|
3871
|
+
const contentText = delta?.content || (parsed.type === 'token' ? parsed.content : null);
|
|
3872
|
+
|
|
3873
|
+
if (reasoningText) {
|
|
3849
3874
|
if (!firstTokenTime) firstTokenTime = performance.now();
|
|
3850
3875
|
tokenCount++;
|
|
3851
|
-
fullReasoning +=
|
|
3876
|
+
fullReasoning += reasoningText;
|
|
3852
3877
|
set((s) => {
|
|
3853
3878
|
const sessions = s.labSessions.map((sess) => {
|
|
3854
3879
|
if (sess.id !== sessionId) return sess;
|
|
@@ -3859,10 +3884,10 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3859
3884
|
return { labSessions: sessions };
|
|
3860
3885
|
});
|
|
3861
3886
|
}
|
|
3862
|
-
if (
|
|
3887
|
+
if (contentText) {
|
|
3863
3888
|
if (!firstTokenTime) firstTokenTime = performance.now();
|
|
3864
3889
|
tokenCount++;
|
|
3865
|
-
fullContent +=
|
|
3890
|
+
fullContent += contentText;
|
|
3866
3891
|
set((s) => {
|
|
3867
3892
|
const sessions = s.labSessions.map((sess) => {
|
|
3868
3893
|
if (sess.id !== sessionId) return sess;
|
|
@@ -3873,11 +3898,13 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3873
3898
|
return { labSessions: sessions };
|
|
3874
3899
|
});
|
|
3875
3900
|
}
|
|
3876
|
-
|
|
3901
|
+
|
|
3902
|
+
// Handle done event (legacy wrapper) or finish_reason (raw OpenAI)
|
|
3903
|
+
if (parsed.type === 'done' && parsed.metrics) {
|
|
3877
3904
|
const elapsed = performance.now() - startTime;
|
|
3878
3905
|
const ttft = firstTokenTime ? firstTokenTime - startTime : null;
|
|
3879
3906
|
const tps = tokenCount > 0 && elapsed > 0 ? (tokenCount / (elapsed / 1000)) : null;
|
|
3880
|
-
const msgMetrics = { ttft, tokensPerSec: tps, tokens: tokenCount, generationTime: elapsed, ...
|
|
3907
|
+
const msgMetrics = { ttft, tokensPerSec: tps, tokens: tokenCount, generationTime: elapsed, ...parsed.metrics };
|
|
3881
3908
|
|
|
3882
3909
|
set((s) => {
|
|
3883
3910
|
const tpsHist = [...s.labMetrics.tokensPerSecHistory, tps].slice(-10);
|
|
@@ -3891,15 +3918,15 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3891
3918
|
labSessions: sessions,
|
|
3892
3919
|
labMetrics: {
|
|
3893
3920
|
ttft, tokensPerSec: tps, tokensPerSecHistory: tpsHist,
|
|
3894
|
-
memory:
|
|
3895
|
-
totalTokens: s.labMetrics.totalTokens + (
|
|
3896
|
-
generationTime:
|
|
3921
|
+
memory: parsed.metrics.memoryUsage || s.labMetrics.memory,
|
|
3922
|
+
totalTokens: s.labMetrics.totalTokens + (parsed.metrics.totalTokens || tokenCount),
|
|
3923
|
+
generationTime: parsed.metrics.generationTime || elapsed,
|
|
3897
3924
|
},
|
|
3898
3925
|
};
|
|
3899
3926
|
});
|
|
3900
3927
|
}
|
|
3901
|
-
if (
|
|
3902
|
-
throw new Error(
|
|
3928
|
+
if (parsed.type === 'error') {
|
|
3929
|
+
throw new Error(parsed.error || 'Inference error');
|
|
3903
3930
|
}
|
|
3904
3931
|
} catch (e) {
|
|
3905
3932
|
if (e.message && e.message !== 'Inference error' && !e.message.startsWith('HTTP ')) continue;
|
|
@@ -3908,14 +3935,24 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3908
3935
|
}
|
|
3909
3936
|
}
|
|
3910
3937
|
|
|
3911
|
-
//
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3938
|
+
// Compute final metrics from client-side timing
|
|
3939
|
+
const elapsed = performance.now() - startTime;
|
|
3940
|
+
const ttft = firstTokenTime ? firstTokenTime - startTime : null;
|
|
3941
|
+
const tps = tokenCount > 0 && elapsed > 0 ? (tokenCount / (elapsed / 1000)) : null;
|
|
3942
|
+
if (tokenCount > 0) {
|
|
3916
3943
|
set((s) => {
|
|
3917
3944
|
const tpsHist = [...s.labMetrics.tokensPerSecHistory, tps].slice(-10);
|
|
3945
|
+
const sessions = s.labSessions.map((sess) => {
|
|
3946
|
+
if (sess.id !== sessionId) return sess;
|
|
3947
|
+
const msgs = [...sess.messages];
|
|
3948
|
+
const last = msgs[msgs.length - 1];
|
|
3949
|
+
if (!last?.metrics) {
|
|
3950
|
+
msgs[msgs.length - 1] = { ...last, metrics: { ttft, tokensPerSec: tps, tokens: tokenCount, generationTime: elapsed } };
|
|
3951
|
+
}
|
|
3952
|
+
return { ...sess, messages: msgs };
|
|
3953
|
+
});
|
|
3918
3954
|
return {
|
|
3955
|
+
labSessions: sessions,
|
|
3919
3956
|
labMetrics: { ...s.labMetrics, ttft, tokensPerSec: tps, tokensPerSecHistory: tpsHist, totalTokens: s.labMetrics.totalTokens + tokenCount, generationTime: elapsed },
|
|
3920
3957
|
};
|
|
3921
3958
|
});
|
|
@@ -4040,4 +4077,45 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
4040
4077
|
return false;
|
|
4041
4078
|
}
|
|
4042
4079
|
},
|
|
4080
|
+
|
|
4081
|
+
// ── Integration Agent Install ────────────────────────────
|
|
4082
|
+
|
|
4083
|
+
async installViaExistingAgent(integration, agentId) {
|
|
4084
|
+
const message = buildIntegrationPrompt(integration);
|
|
4085
|
+
await get().instructAgent(agentId, message);
|
|
4086
|
+
get().setActiveView('agents');
|
|
4087
|
+
get().selectAgent(agentId);
|
|
4088
|
+
},
|
|
4089
|
+
|
|
4090
|
+
async spawnIntegrationTeam(integration) {
|
|
4091
|
+
const team = await get().createTeam(integration.name);
|
|
4092
|
+
const prompt = buildIntegrationPrompt(integration);
|
|
4093
|
+
const agent = await get().spawnAgent({ role: 'planner', prompt, teamId: team.id });
|
|
4094
|
+
get().setActiveView('agents');
|
|
4095
|
+
get().selectAgent(agent.id);
|
|
4096
|
+
return agent;
|
|
4097
|
+
},
|
|
4043
4098
|
}));
|
|
4099
|
+
|
|
4100
|
+
function buildIntegrationPrompt(integration) {
|
|
4101
|
+
const lines = [
|
|
4102
|
+
`Set up the "${integration.name}" integration for this project.`,
|
|
4103
|
+
'',
|
|
4104
|
+
];
|
|
4105
|
+
if (integration.description) lines.push(`**Description:** ${integration.description}`);
|
|
4106
|
+
if (integration.npmPackage) lines.push(`**npm package:** ${integration.npmPackage}`);
|
|
4107
|
+
if (integration.authType) lines.push(`**Auth type:** ${integration.authType}`);
|
|
4108
|
+
if (integration.envKeys?.length) {
|
|
4109
|
+
lines.push('', '**Environment keys required:**');
|
|
4110
|
+
for (const k of integration.envKeys) {
|
|
4111
|
+
lines.push(`- \`${k.key}\` — ${k.label}${k.required ? ' (required)' : ''}`);
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
if (integration.setupSteps?.length) {
|
|
4115
|
+
lines.push('', '**Setup steps:**');
|
|
4116
|
+
integration.setupSteps.forEach((step, i) => lines.push(`${i + 1}. ${step}`));
|
|
4117
|
+
}
|
|
4118
|
+
if (integration.setupUrl) lines.push(``, `**Setup URL:** ${integration.setupUrl}`);
|
|
4119
|
+
if (integration.agentInstructions) lines.push('', `**Agent instructions:** ${integration.agentInstructions}`);
|
|
4120
|
+
return lines.join('\n');
|
|
4121
|
+
}
|
|
@@ -10,7 +10,7 @@ import { RootNode } from '../components/agents/root-node';
|
|
|
10
10
|
import { cn } from '../lib/cn';
|
|
11
11
|
import { Button } from '../components/ui/button';
|
|
12
12
|
import { Badge } from '../components/ui/badge';
|
|
13
|
-
import { Plus, Users, UserPlus, Zap, X, Check, Rocket, Server, Monitor, Code2, TestTube, Shield, Pencil, Copy, Trash2, ChevronDown, ChevronLeft, ChevronRight, FolderOpen, Eye, Settings2, Search, GripVertical, Cloud, FileText, Database, Megaphone, Calculator, UserCheck, Headphones, BarChart3, Pen, Presentation, Globe, MessageCircle, Save, Layers,
|
|
13
|
+
import { Plus, Users, UserPlus, Zap, X, Check, Rocket, Server, Monitor, Code2, TestTube, Shield, Pencil, Copy, Trash2, ChevronDown, ChevronLeft, ChevronRight, FolderOpen, Eye, Settings2, Search, GripVertical, Cloud, FileText, Database, Megaphone, Calculator, UserCheck, Headphones, BarChart3, Pen, Presentation, Globe, MessageCircle, Save, Layers, LayoutGrid, Activity, Gauge, Cpu } from 'lucide-react';
|
|
14
14
|
import { PreviewWorkspace } from '../components/preview/preview-workspace';
|
|
15
15
|
import { WorkspaceMode } from '../components/agents/workspace-mode';
|
|
16
16
|
import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuSeparator } from '../components/ui/context-menu';
|
|
@@ -1258,7 +1258,7 @@ function RecommendedTeamCard() {
|
|
|
1258
1258
|
const [tsModel, setTsModel] = useState(teamLaunchConfig?.model || '');
|
|
1259
1259
|
const [tsReasoning, setTsReasoning] = useState(teamLaunchConfig?.reasoningEffort ?? 50);
|
|
1260
1260
|
const [tsTemp, setTsTemp] = useState(teamLaunchConfig?.temperature ?? 0.5);
|
|
1261
|
-
const [
|
|
1261
|
+
const [expandedAgent, setExpandedAgent] = useState(null);
|
|
1262
1262
|
|
|
1263
1263
|
useEffect(() => {
|
|
1264
1264
|
fetchProviders().then((list) => {
|
|
@@ -1283,6 +1283,15 @@ function RecommendedTeamCard() {
|
|
|
1283
1283
|
setEditedAgents(next);
|
|
1284
1284
|
}
|
|
1285
1285
|
|
|
1286
|
+
function handleAgentField(i, updates) {
|
|
1287
|
+
if (typeof updates === 'string') {
|
|
1288
|
+
const [field, value] = [updates, arguments[2]];
|
|
1289
|
+
setEditedAgents((prev) => (prev ?? agentEdits).map((a, idx) => idx === i ? { ...a, [field]: value } : a));
|
|
1290
|
+
} else {
|
|
1291
|
+
setEditedAgents((prev) => (prev ?? agentEdits).map((a, idx) => idx === i ? { ...a, ...updates } : a));
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1286
1295
|
function handleTsProviderChange(id) {
|
|
1287
1296
|
setTsProvider(id);
|
|
1288
1297
|
const p = providers.find((x) => x.id === id);
|
|
@@ -1298,7 +1307,6 @@ function RecommendedTeamCard() {
|
|
|
1298
1307
|
...(tsProvider && { provider: tsProvider, model: tsModel }),
|
|
1299
1308
|
reasoningEffort: tsReasoning,
|
|
1300
1309
|
...(showTemp && { temperature: tsTemp }),
|
|
1301
|
-
mode: tsMode,
|
|
1302
1310
|
},
|
|
1303
1311
|
});
|
|
1304
1312
|
try {
|
|
@@ -1376,41 +1384,6 @@ function RecommendedTeamCard() {
|
|
|
1376
1384
|
formatValue={(v) => v.toFixed(2)}
|
|
1377
1385
|
/>
|
|
1378
1386
|
)}
|
|
1379
|
-
{/* Build Mode */}
|
|
1380
|
-
<div className="space-y-1">
|
|
1381
|
-
<label className="text-2xs text-text-3 font-sans">Build Mode</label>
|
|
1382
|
-
<div className="flex rounded-md bg-surface-4 border border-border-subtle p-0.5">
|
|
1383
|
-
<button
|
|
1384
|
-
onClick={() => setTsMode('sandbox')}
|
|
1385
|
-
className={cn(
|
|
1386
|
-
'flex-1 flex items-center justify-center gap-1.5 rounded px-2 py-1.5 text-xs font-sans transition-all cursor-pointer',
|
|
1387
|
-
tsMode === 'sandbox'
|
|
1388
|
-
? 'bg-surface-2 text-text-0 font-semibold shadow-sm'
|
|
1389
|
-
: 'text-text-3 hover:text-text-1',
|
|
1390
|
-
)}
|
|
1391
|
-
>
|
|
1392
|
-
<Box size={11} />
|
|
1393
|
-
Sandbox
|
|
1394
|
-
</button>
|
|
1395
|
-
<button
|
|
1396
|
-
onClick={() => setTsMode('production')}
|
|
1397
|
-
className={cn(
|
|
1398
|
-
'flex-1 flex items-center justify-center gap-1.5 rounded px-2 py-1.5 text-xs font-sans transition-all cursor-pointer',
|
|
1399
|
-
tsMode === 'production'
|
|
1400
|
-
? 'bg-surface-2 text-text-0 font-semibold shadow-sm'
|
|
1401
|
-
: 'text-text-3 hover:text-text-1',
|
|
1402
|
-
)}
|
|
1403
|
-
>
|
|
1404
|
-
<HardDrive size={11} />
|
|
1405
|
-
Production
|
|
1406
|
-
</button>
|
|
1407
|
-
</div>
|
|
1408
|
-
<p className="text-2xs text-text-4 font-sans">
|
|
1409
|
-
{tsMode === 'sandbox'
|
|
1410
|
-
? 'Files live in a team directory, removable with the team'
|
|
1411
|
-
: 'Files live in the project directory, persist forever'}
|
|
1412
|
-
</p>
|
|
1413
|
-
</div>
|
|
1414
1387
|
</div>
|
|
1415
1388
|
)}
|
|
1416
1389
|
</div>
|
|
@@ -1419,31 +1392,116 @@ function RecommendedTeamCard() {
|
|
|
1419
1392
|
{agentEdits.map((a, i) => {
|
|
1420
1393
|
const Icon = ROLE_ICONS[a.role] || Code2;
|
|
1421
1394
|
const nameValid = !a.name || NAME_RE.test(a.name);
|
|
1395
|
+
const isExpanded = expandedAgent === i;
|
|
1396
|
+
const agentProvider = providers.find((p) => p.id === (a.provider || tsProvider));
|
|
1397
|
+
const agentModels = (agentProvider?.models || []).filter((m) => m.type !== 'image' && !m.disabled);
|
|
1422
1398
|
return (
|
|
1423
|
-
<div key={i} className="
|
|
1424
|
-
<
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1399
|
+
<div key={i} className="rounded-md bg-surface-4 border border-border-subtle overflow-hidden">
|
|
1400
|
+
<div
|
|
1401
|
+
className="flex items-center gap-2 px-2.5 py-1.5 cursor-pointer hover:bg-surface-5/50 transition-colors"
|
|
1402
|
+
onClick={() => setExpandedAgent(isExpanded ? null : i)}
|
|
1403
|
+
>
|
|
1404
|
+
<Icon size={12} className="text-text-2 shrink-0" />
|
|
1405
|
+
<input
|
|
1406
|
+
type="text"
|
|
1407
|
+
value={a.name}
|
|
1408
|
+
onChange={(e) => handleNameChange(i, e.target.value)}
|
|
1409
|
+
onClick={(e) => e.stopPropagation()}
|
|
1410
|
+
placeholder={a.role}
|
|
1411
|
+
className={cn(
|
|
1412
|
+
'flex-1 min-w-0 bg-transparent text-xs font-mono text-text-0 outline-none placeholder:text-text-4',
|
|
1413
|
+
!nameValid && 'text-red-400',
|
|
1414
|
+
)}
|
|
1415
|
+
maxLength={64}
|
|
1416
|
+
spellCheck={false}
|
|
1417
|
+
/>
|
|
1418
|
+
{a.provider && a.provider !== tsProvider && (
|
|
1419
|
+
<span className="text-2xs text-accent font-mono shrink-0">{a.provider}</span>
|
|
1433
1420
|
)}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1421
|
+
{a.scope?.length > 0 && (
|
|
1422
|
+
<span className="text-2xs text-text-4 font-mono shrink-0 truncate max-w-[120px]">
|
|
1423
|
+
{a.scope[0]}{a.scope.length > 1 ? ` +${a.scope.length - 1}` : ''}
|
|
1424
|
+
</span>
|
|
1425
|
+
)}
|
|
1426
|
+
<ChevronDown size={10} className={cn('text-text-4 shrink-0 transition-transform duration-200', !isExpanded && '-rotate-90')} />
|
|
1427
|
+
</div>
|
|
1428
|
+
{isExpanded && (
|
|
1429
|
+
<div className="px-2.5 pb-2.5 pt-1 space-y-2.5 border-t border-border-subtle">
|
|
1430
|
+
<div className="flex gap-2">
|
|
1431
|
+
<div className="flex-1 space-y-1">
|
|
1432
|
+
<label className="flex items-center gap-1 text-2xs text-text-3 font-sans"><Cpu size={10} />Provider</label>
|
|
1433
|
+
<Select value={a.provider || ''} onValueChange={(id) => {
|
|
1434
|
+
const p = providers.find((x) => x.id === id);
|
|
1435
|
+
const pModels = (p?.models || []).filter((m) => m.type !== 'image' && !m.disabled);
|
|
1436
|
+
handleAgentField(i, { provider: id, model: pModels[0]?.id || '' });
|
|
1437
|
+
}}>
|
|
1438
|
+
<SelectTrigger placeholder="Team default" className="bg-surface-3 h-7 text-xs" />
|
|
1439
|
+
<SelectContent>
|
|
1440
|
+
<SelectItem value="">Team default</SelectItem>
|
|
1441
|
+
{providers.map((p) => (
|
|
1442
|
+
<SelectItem key={p.id} value={p.id}>{p.displayName || p.name || p.id}</SelectItem>
|
|
1443
|
+
))}
|
|
1444
|
+
</SelectContent>
|
|
1445
|
+
</Select>
|
|
1446
|
+
</div>
|
|
1447
|
+
<div className="flex-1 space-y-1">
|
|
1448
|
+
<label className="text-2xs text-text-3 font-sans">Model</label>
|
|
1449
|
+
<Select value={a.model || ''} onValueChange={(v) => handleAgentField(i, 'model', v)}>
|
|
1450
|
+
<SelectTrigger placeholder="Auto" className="bg-surface-3 h-7 text-xs" />
|
|
1451
|
+
<SelectContent>
|
|
1452
|
+
<SelectItem value="">Auto</SelectItem>
|
|
1453
|
+
{agentModels.map((m) => (
|
|
1454
|
+
<SelectItem key={m.id} value={m.id}>{m.name || m.id}</SelectItem>
|
|
1455
|
+
))}
|
|
1456
|
+
</SelectContent>
|
|
1457
|
+
</Select>
|
|
1458
|
+
</div>
|
|
1459
|
+
</div>
|
|
1460
|
+
<div className="space-y-1">
|
|
1461
|
+
<label className="flex items-center gap-1 text-2xs text-text-3 font-sans"><Activity size={10} />Model Routing</label>
|
|
1462
|
+
<div className="flex bg-surface-3 rounded-md p-0.5 border border-border-subtle">
|
|
1463
|
+
{[{ value: 'fixed', label: 'Fixed' }, { value: 'auto', label: 'Auto' }, { value: 'auto-floor', label: 'Auto + Floor' }].map((opt) => (
|
|
1464
|
+
<button
|
|
1465
|
+
key={opt.value}
|
|
1466
|
+
onClick={() => handleAgentField(i, 'routingMode', opt.value)}
|
|
1467
|
+
className={cn(
|
|
1468
|
+
'flex-1 px-2 py-1 text-2xs font-semibold font-sans rounded transition-all cursor-pointer',
|
|
1469
|
+
(a.routingMode || 'auto') === opt.value
|
|
1470
|
+
? 'bg-accent/15 text-accent shadow-sm'
|
|
1471
|
+
: 'text-text-3 hover:text-text-1',
|
|
1472
|
+
)}
|
|
1473
|
+
>
|
|
1474
|
+
{opt.label}
|
|
1475
|
+
</button>
|
|
1476
|
+
))}
|
|
1477
|
+
</div>
|
|
1478
|
+
</div>
|
|
1479
|
+
<div className="space-y-1">
|
|
1480
|
+
<label className="flex items-center gap-1 text-2xs text-text-3 font-sans"><Gauge size={10} />Effort Level</label>
|
|
1481
|
+
<div className="flex bg-surface-3 rounded-md p-0.5 border border-border-subtle">
|
|
1482
|
+
{[{ value: 'min', label: 'Min' }, { value: 'low', label: 'Low' }, { value: 'default', label: 'Default' }, { value: 'high', label: 'High' }, { value: 'max', label: 'Max' }].map((opt) => (
|
|
1483
|
+
<button
|
|
1484
|
+
key={opt.value}
|
|
1485
|
+
onClick={() => handleAgentField(i, 'effort', opt.value)}
|
|
1486
|
+
className={cn(
|
|
1487
|
+
'flex-1 px-1.5 py-1 text-2xs font-semibold font-sans rounded transition-all cursor-pointer',
|
|
1488
|
+
(a.effort || 'default') === opt.value
|
|
1489
|
+
? 'bg-accent/15 text-accent shadow-sm'
|
|
1490
|
+
: 'text-text-3 hover:text-text-1',
|
|
1491
|
+
)}
|
|
1492
|
+
>
|
|
1493
|
+
{opt.label}
|
|
1494
|
+
</button>
|
|
1495
|
+
))}
|
|
1496
|
+
</div>
|
|
1497
|
+
</div>
|
|
1498
|
+
</div>
|
|
1441
1499
|
)}
|
|
1442
1500
|
</div>
|
|
1443
1501
|
);
|
|
1444
1502
|
})}
|
|
1445
1503
|
|
|
1446
|
-
{recommendedTeam.projectDir &&
|
|
1504
|
+
{recommendedTeam.projectDir && (
|
|
1447
1505
|
<div className="flex items-center gap-1.5 text-2xs text-text-2 font-mono pt-0.5">
|
|
1448
1506
|
<span className="text-text-4">Project:</span>
|
|
1449
1507
|
<span className="text-accent">{recommendedTeam.projectDir}/</span>
|
|
@@ -7,15 +7,15 @@ import { Dialog, DialogContent } from '../components/ui/dialog';
|
|
|
7
7
|
import { BookOpen, Plus, Search, Trash2, Pencil, ChevronRight, Hash, FolderOpen, Clock, Save, Link2, FileText, Sparkles, HelpCircle } from 'lucide-react';
|
|
8
8
|
|
|
9
9
|
const COMMANDS = [
|
|
10
|
-
{ cmd: '
|
|
11
|
-
{ cmd: '
|
|
12
|
-
{ cmd: '
|
|
13
|
-
{ cmd: '
|
|
14
|
-
{ cmd: '
|
|
15
|
-
{ cmd: '
|
|
16
|
-
{ cmd: '
|
|
17
|
-
{ cmd: '
|
|
18
|
-
{ cmd: '[instruct]', args: '',
|
|
10
|
+
{ cmd: 'save', args: '#tag', desc: 'Save the message and send it to the agent' },
|
|
11
|
+
{ cmd: 'append', args: '#tag', desc: 'Add to an existing memory and send to agent' },
|
|
12
|
+
{ cmd: 'update', args: '#tag', desc: 'Open the editor to modify a memory in place' },
|
|
13
|
+
{ cmd: 'delete', args: '#tag', desc: 'Remove a memory permanently' },
|
|
14
|
+
{ cmd: 'view', args: '#tag', desc: 'Read a memory in the viewer' },
|
|
15
|
+
{ cmd: 'read', args: '#tag1 #tag2 ...', desc: 'Send memory content to the agent — chat stays clean' },
|
|
16
|
+
{ cmd: 'doc', args: '#tag', desc: 'AI synthesizes the full conversation into a document' },
|
|
17
|
+
{ cmd: 'link', args: '#tag path/to/doc', desc: 'Link a memory to a NORTHSTAR or external document' },
|
|
18
|
+
{ cmd: '[instruct]', args: '', desc: 'Show this command reference' },
|
|
19
19
|
];
|
|
20
20
|
|
|
21
21
|
function formatRelative(iso) {
|
|
@@ -171,17 +171,12 @@ export default function ModelLabView() {
|
|
|
171
171
|
<PanelToggle collapsed={false} onClick={() => setLeftCollapsed(true)} side="left" />
|
|
172
172
|
</div>
|
|
173
173
|
<ScrollArea className="flex-1 min-h-0">
|
|
174
|
-
<div className="px-4 pb-4 space-y-5">
|
|
174
|
+
<div className="px-4 pb-4 space-y-5 divide-y divide-border-subtle [&>*]:pt-5 [&>*:first-child]:pt-0">
|
|
175
175
|
<LaunchModel />
|
|
176
|
-
<div className="h-px bg-border-subtle" />
|
|
177
176
|
<RuntimeConfig />
|
|
178
|
-
<div className="h-px bg-border-subtle" />
|
|
179
177
|
<ModelSelector />
|
|
180
|
-
<div className="h-px bg-border-subtle" />
|
|
181
178
|
<ParameterPanel />
|
|
182
|
-
<div className="h-px bg-border-subtle" />
|
|
183
179
|
<PresetManager />
|
|
184
|
-
<div className="h-px bg-border-subtle" />
|
|
185
180
|
<SystemPromptEditor />
|
|
186
181
|
</div>
|
|
187
182
|
</ScrollArea>
|