groove-dev 0.27.142 → 0.27.144
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 +1086 -6532
- 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 +889 -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-BcoF6_eF.js +1012 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.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/{packages/gui/src/app.jsx → node_modules/@groove-dev/gui/src/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 +144 -31
- package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
- 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/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/editor/selection-menu.jsx +2 -0
- 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 +195 -14
- package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +286 -102
- 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/lib/cron.js +64 -0
- package/node_modules/@groove-dev/gui/src/lib/status.js +24 -24
- package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +34 -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 +452 -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 +227 -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 +17 -6
- package/node_modules/@groove-dev/gui/src/views/models.jsx +410 -509
- 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/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/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 +889 -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-BcoF6_eF.js +1012 -0
- package/packages/gui/dist/assets/index-Dd7qhiEd.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/{node_modules/@groove-dev/gui/src/app.jsx → packages/gui/src/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 +144 -31
- package/packages/gui/src/components/agents/agent-node.jsx +8 -13
- 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/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/editor/selection-menu.jsx +2 -0
- 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 +195 -14
- package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
- 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/lib/cron.js +64 -0
- package/packages/gui/src/lib/status.js +24 -24
- package/packages/gui/src/lib/theme-hex.js +1 -0
- package/packages/gui/src/stores/groove.js +34 -3144
- package/packages/gui/src/stores/helpers.js +10 -0
- package/packages/gui/src/stores/slices/agents-slice.js +452 -0
- package/packages/gui/src/stores/slices/automations-slice.js +96 -0
- package/packages/gui/src/stores/slices/chat-slice.js +227 -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 +17 -6
- package/packages/gui/src/views/models.jsx +410 -509
- 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-Bjd91ufV.js +0 -984
- package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +0 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -322
- 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-Bjd91ufV.js +0 -984
- package/packages/gui/dist/assets/index-BqdwIFn4.css +0 -1
- package/packages/gui/src/components/agents/agent-chat.jsx +0 -322
- package/packages/gui/src/views/preview.jsx +0 -6
- package/packages/gui/src/views/subscription-panel.jsx +0 -327
- package/test.py +0 -571
|
@@ -5,7 +5,7 @@ import { HEX } from '../../lib/theme-hex';
|
|
|
5
5
|
import { Tooltip } from '../ui/tooltip';
|
|
6
6
|
import { HelpCircle } from 'lucide-react';
|
|
7
7
|
|
|
8
|
-
function MiniSparkline({ data, color = HEX.
|
|
8
|
+
function MiniSparkline({ data, color = HEX.text3, width = 72, height = 22 }) {
|
|
9
9
|
if (!data || data.length < 2) return <div style={{ width, height }} />;
|
|
10
10
|
const vals = data.map((d) => d.v);
|
|
11
11
|
const min = Math.min(...vals);
|
|
@@ -34,7 +34,7 @@ function MiniSparkline({ data, color = HEX.accent, width = 72, height = 22 }) {
|
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const KpiCard = memo(function KpiCard({ label, value, sparkData, color = HEX.
|
|
37
|
+
const KpiCard = memo(function KpiCard({ label, value, sparkData, color = HEX.text3, hint, className }) {
|
|
38
38
|
return (
|
|
39
39
|
<div className={cn(
|
|
40
40
|
'flex items-center gap-2.5 px-3 py-2.5 min-w-0',
|
|
@@ -59,7 +59,7 @@ const KpiCard = memo(function KpiCard({ label, value, sparkData, color = HEX.acc
|
|
|
59
59
|
|
|
60
60
|
export function KpiStrip({ kpis }) {
|
|
61
61
|
return (
|
|
62
|
-
<div className="flex flex-wrap border-b border-border
|
|
62
|
+
<div className="flex flex-wrap border-b border-border bg-surface-0">
|
|
63
63
|
{kpis.map((kpi) => (
|
|
64
64
|
<KpiCard
|
|
65
65
|
key={kpi.label}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { memo } from 'react';
|
|
3
|
-
import { HEX } from '../../lib/theme-hex';
|
|
3
|
+
import { HEX, hexAlpha } from '../../lib/theme-hex';
|
|
4
4
|
import { fmtNum, fmtPct } from '../../lib/format';
|
|
5
5
|
|
|
6
|
-
const TIER_COLORS = { heavy: HEX.
|
|
6
|
+
const TIER_COLORS = { heavy: HEX.text0, medium: HEX.text2, light: HEX.text4 };
|
|
7
7
|
const TIER_LABELS = { heavy: 'Heavy', medium: 'Medium', light: 'Light' };
|
|
8
8
|
|
|
9
9
|
const DONUT_SIZE = 80;
|
|
@@ -126,7 +126,7 @@ const RoutingChart = memo(function RoutingChart({ routing, agentBreakdown }) {
|
|
|
126
126
|
<div className="h-0.5 bg-surface-4 rounded-sm overflow-hidden">
|
|
127
127
|
<div
|
|
128
128
|
className="h-full rounded-sm transition-all duration-500"
|
|
129
|
-
style={{ width: `${Math.max(barPct, 2)}%`, background: HEX.
|
|
129
|
+
style={{ width: `${Math.max(barPct, 2)}%`, background: HEX.text2 }}
|
|
130
130
|
/>
|
|
131
131
|
</div>
|
|
132
132
|
</div>
|
|
@@ -41,7 +41,7 @@ export const TeamBurnPanel = memo(function TeamBurnPanel({ teams = [] }) {
|
|
|
41
41
|
<div className="h-0.5 bg-surface-2 rounded-sm overflow-hidden">
|
|
42
42
|
<div
|
|
43
43
|
className="h-full rounded-sm transition-all"
|
|
44
|
-
style={{ width: `${pct}%`, background: HEX.
|
|
44
|
+
style={{ width: `${pct}%`, background: HEX.text3 }}
|
|
45
45
|
/>
|
|
46
46
|
</div>
|
|
47
47
|
</div>
|
|
@@ -144,7 +144,7 @@ const TokenChart = memo(function TokenChart({ data }) {
|
|
|
144
144
|
const hasCacheData = caches.some((c) => c > 0);
|
|
145
145
|
if (hasCacheData) {
|
|
146
146
|
ctx.beginPath();
|
|
147
|
-
ctx.strokeStyle = hexAlpha(HEX.
|
|
147
|
+
ctx.strokeStyle = hexAlpha(HEX.text2, 0.5);
|
|
148
148
|
ctx.lineWidth = 1;
|
|
149
149
|
ctx.lineJoin = 'round';
|
|
150
150
|
ctx.setLineDash([2, 3]);
|
|
@@ -164,7 +164,7 @@ const TokenChart = memo(function TokenChart({ data }) {
|
|
|
164
164
|
const ly = 14;
|
|
165
165
|
|
|
166
166
|
if (hasCacheData) {
|
|
167
|
-
ctx.fillStyle = hexAlpha(HEX.
|
|
167
|
+
ctx.fillStyle = hexAlpha(HEX.text2, 0.5);
|
|
168
168
|
ctx.fillText('Cache %', rx, ly);
|
|
169
169
|
rx -= ctx.measureText('Cache %').width + 4;
|
|
170
170
|
ctx.beginPath(); ctx.arc(rx, ly - 3, 2.5, 0, Math.PI * 2); ctx.fill();
|
|
@@ -203,8 +203,8 @@ const TokenChart = memo(function TokenChart({ data }) {
|
|
|
203
203
|
// Tooltip
|
|
204
204
|
const lines = [
|
|
205
205
|
{ label: 'Burn', value: `${fmtNum(d.burnRate)}/m`, color: HEX.accent },
|
|
206
|
-
{ label: 'Cache', value: fmtPct(d.cacheHitRate * 100), color: HEX.
|
|
207
|
-
{ label: 'Agents', value: `${d.running}/${d.agents}`, color: HEX.
|
|
206
|
+
{ label: 'Cache', value: fmtPct(d.cacheHitRate * 100), color: HEX.text2 },
|
|
207
|
+
{ label: 'Agents', value: `${d.running}/${d.agents}`, color: HEX.text3 },
|
|
208
208
|
];
|
|
209
209
|
const tooltipW = 104;
|
|
210
210
|
const tooltipH = lines.length * 16 + 12;
|
|
@@ -18,6 +18,7 @@ export function SelectionMenu({ x, y, filePath, lineStart, lineEnd, selectedCode
|
|
|
18
18
|
const sendCodeToAgent = useGrooveStore((s) => s.sendCodeToAgent);
|
|
19
19
|
const toggleAiPanel = useGrooveStore((s) => s.toggleAiPanel);
|
|
20
20
|
const aiPanelOpen = useGrooveStore((s) => s.editorAiPanelOpen);
|
|
21
|
+
const selectAgent = useGrooveStore((s) => s.selectAgent);
|
|
21
22
|
|
|
22
23
|
useEffect(() => {
|
|
23
24
|
function handleClick(e) {
|
|
@@ -51,6 +52,7 @@ export function SelectionMenu({ x, y, filePath, lineStart, lineEnd, selectedCode
|
|
|
51
52
|
if (!agentId) return;
|
|
52
53
|
sendCodeToAgent(agentId, action.instruction, filePath, lineStart, lineEnd, selectedCode);
|
|
53
54
|
if (!aiPanelOpen) toggleAiPanel();
|
|
55
|
+
selectAgent(agentId);
|
|
54
56
|
onClose();
|
|
55
57
|
}
|
|
56
58
|
|
|
@@ -4,41 +4,237 @@ import { useGrooveStore } from '../../stores/groove';
|
|
|
4
4
|
import { ScrollArea } from '../ui/scroll-area';
|
|
5
5
|
import { Badge } from '../ui/badge';
|
|
6
6
|
import { cn } from '../../lib/cn';
|
|
7
|
-
import { Send, X,
|
|
7
|
+
import { Send, X, ArrowRight, ChevronDown, AlertCircle, FlaskConical } from 'lucide-react';
|
|
8
8
|
|
|
9
|
+
// ── Inline formatting (bold, code) ──────────────────────────
|
|
10
|
+
function InlineFormat({ text }) {
|
|
11
|
+
if (!text) return null;
|
|
12
|
+
return text.split(/(\*\*[^*]+\*\*|`[^`]+`)/g).map((part, i) => {
|
|
13
|
+
if (part.startsWith('**') && part.endsWith('**'))
|
|
14
|
+
return <strong key={i} className="font-semibold text-text-0">{part.slice(2, -2)}</strong>;
|
|
15
|
+
if (part.startsWith('`') && part.endsWith('`'))
|
|
16
|
+
return <code key={i} className="px-1 py-px rounded bg-accent/8 text-[11px] font-mono text-accent">{part.slice(1, -1)}</code>;
|
|
17
|
+
return <span key={i}>{part}</span>;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ── Structured message renderer ─────────────────────────────
|
|
22
|
+
function StructuredMessage({ text }) {
|
|
23
|
+
if (!text) return null;
|
|
24
|
+
|
|
25
|
+
const blocks = [];
|
|
26
|
+
const lines = text.split('\n');
|
|
27
|
+
let i = 0;
|
|
28
|
+
|
|
29
|
+
while (i < lines.length) {
|
|
30
|
+
const line = lines[i];
|
|
31
|
+
|
|
32
|
+
if (line.trimStart().startsWith('```')) {
|
|
33
|
+
const codeLines = [];
|
|
34
|
+
i++;
|
|
35
|
+
while (i < lines.length && !lines[i].trimStart().startsWith('```')) {
|
|
36
|
+
codeLines.push(lines[i]);
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
i++;
|
|
40
|
+
blocks.push({ type: 'code', content: codeLines.join('\n') });
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (/^#{1,3}\s/.test(line) || /^\*\*[^*]+:\*\*\s*$/.test(line.trim())) {
|
|
45
|
+
const heading = line.replace(/^#+\s*/, '').replace(/^\*\*/, '').replace(/:\*\*\s*$/, ':').trim();
|
|
46
|
+
blocks.push({ type: 'heading', content: heading });
|
|
47
|
+
i++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (/^\s*[-*]\s/.test(line)) {
|
|
52
|
+
const items = [];
|
|
53
|
+
while (i < lines.length && /^\s*[-*]\s/.test(lines[i])) {
|
|
54
|
+
items.push(lines[i].replace(/^\s*[-*]\s+/, '').trim());
|
|
55
|
+
i++;
|
|
56
|
+
}
|
|
57
|
+
blocks.push({ type: 'list', items });
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (/^\s*\d+[\.)]\s/.test(line)) {
|
|
62
|
+
const items = [];
|
|
63
|
+
while (i < lines.length && /^\s*\d+[\.)]\s/.test(lines[i])) {
|
|
64
|
+
items.push(lines[i].replace(/^\s*\d+[\.)]\s+/, '').trim());
|
|
65
|
+
i++;
|
|
66
|
+
}
|
|
67
|
+
blocks.push({ type: 'numbered', items });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!line.trim()) { i++; continue; }
|
|
72
|
+
|
|
73
|
+
if (/^(Note|Warning|Important|IMPORTANT|TODO):/i.test(line.trim())) {
|
|
74
|
+
blocks.push({ type: 'note', content: line.trim() });
|
|
75
|
+
i++;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const paraLines = [];
|
|
80
|
+
while (i < lines.length && lines[i].trim() && !/^#{1,3}\s/.test(lines[i]) && !/^\s*[-*]\s/.test(lines[i]) && !/^\s*\d+[\.)]\s/.test(lines[i]) && !lines[i].trimStart().startsWith('```')) {
|
|
81
|
+
paraLines.push(lines[i].trim());
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
if (paraLines.length > 0) {
|
|
85
|
+
blocks.push({ type: 'para', content: paraLines.join(' ') });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div className="space-y-2">
|
|
91
|
+
{blocks.map((block, idx) => {
|
|
92
|
+
switch (block.type) {
|
|
93
|
+
case 'heading':
|
|
94
|
+
return (
|
|
95
|
+
<div key={idx} className="flex items-center gap-1.5 pt-1.5 first:pt-0">
|
|
96
|
+
<div className="w-1 h-3.5 rounded-full bg-accent/40 flex-shrink-0" />
|
|
97
|
+
<span className="text-[13px] font-semibold text-text-0 font-sans"><InlineFormat text={block.content} /></span>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
case 'list':
|
|
101
|
+
return (
|
|
102
|
+
<div key={idx} className="space-y-1 pl-2">
|
|
103
|
+
{block.items.map((item, j) => (
|
|
104
|
+
<div key={j} className="flex gap-2 text-[13px] text-text-1 font-sans leading-relaxed">
|
|
105
|
+
<span className="text-accent/50 mt-0.5 flex-shrink-0">-</span>
|
|
106
|
+
<span className="min-w-0"><InlineFormat text={item} /></span>
|
|
107
|
+
</div>
|
|
108
|
+
))}
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
case 'numbered':
|
|
112
|
+
return (
|
|
113
|
+
<div key={idx} className="space-y-1 pl-2">
|
|
114
|
+
{block.items.map((item, j) => (
|
|
115
|
+
<div key={j} className="flex gap-2 text-[13px] text-text-1 font-sans leading-relaxed">
|
|
116
|
+
<span className="text-text-4 font-mono w-4 text-right flex-shrink-0">{j + 1}.</span>
|
|
117
|
+
<span className="min-w-0"><InlineFormat text={item} /></span>
|
|
118
|
+
</div>
|
|
119
|
+
))}
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
case 'code':
|
|
123
|
+
return (
|
|
124
|
+
<pre key={idx} className="p-2.5 rounded-md bg-[#0d1117] text-[11px] font-mono text-[#c9d1d9] overflow-x-auto whitespace-pre-wrap border border-white/[0.06] leading-relaxed">
|
|
125
|
+
{block.content}
|
|
126
|
+
</pre>
|
|
127
|
+
);
|
|
128
|
+
case 'note':
|
|
129
|
+
return (
|
|
130
|
+
<div key={idx} className="flex items-start gap-1.5 px-2.5 py-1.5 rounded-md bg-warning/6 border border-warning/12">
|
|
131
|
+
<AlertCircle size={10} className="text-warning mt-0.5 flex-shrink-0" />
|
|
132
|
+
<span className="text-[12px] text-warning/80 font-sans"><InlineFormat text={block.content} /></span>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
case 'para':
|
|
136
|
+
default:
|
|
137
|
+
return <p key={idx} className="text-[13px] text-text-1 font-sans leading-relaxed"><InlineFormat text={block.content} /></p>;
|
|
138
|
+
}
|
|
139
|
+
})}
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── Thinking indicator ──────────────────────────────────────
|
|
145
|
+
const THINKING_MESSAGES = [
|
|
146
|
+
'Checking your system...',
|
|
147
|
+
'Running setup commands...',
|
|
148
|
+
'Working through installation...',
|
|
149
|
+
'Configuring the server...',
|
|
150
|
+
'Making progress...',
|
|
151
|
+
'Almost there...',
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
function LabThinkingIndicator() {
|
|
155
|
+
const [idx, setIdx] = useState(0);
|
|
156
|
+
const [fade, setFade] = useState(true);
|
|
157
|
+
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
const t = setInterval(() => {
|
|
160
|
+
setFade(false);
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
setIdx((i) => (i + 1) % THINKING_MESSAGES.length);
|
|
163
|
+
setFade(true);
|
|
164
|
+
}, 250);
|
|
165
|
+
}, 2800);
|
|
166
|
+
return () => clearInterval(t);
|
|
167
|
+
}, []);
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<div>
|
|
171
|
+
<div className="flex items-center gap-2 mb-1">
|
|
172
|
+
<span className="text-2xs font-semibold text-text-1 font-sans">Lab Assistant</span>
|
|
173
|
+
<span className="text-2xs text-accent font-mono">working</span>
|
|
174
|
+
</div>
|
|
175
|
+
<div className="border-l border-accent/40 pl-3.5 py-1 flex items-center gap-2.5">
|
|
176
|
+
<div className="relative w-3.5 h-3.5 flex-shrink-0">
|
|
177
|
+
<span className="absolute inset-0 rounded-full border border-transparent border-t-accent animate-spin" style={{ animationDuration: '0.9s' }} />
|
|
178
|
+
</div>
|
|
179
|
+
<span
|
|
180
|
+
className="text-[13px] font-sans text-text-3 transition-opacity duration-[250ms]"
|
|
181
|
+
style={{ opacity: fade ? 1 : 0 }}
|
|
182
|
+
>
|
|
183
|
+
{THINKING_MESSAGES[idx]}
|
|
184
|
+
</span>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── Message components ──────────────────────────────────────
|
|
9
191
|
function AssistantMessage({ msg }) {
|
|
192
|
+
const [collapsed, setCollapsed] = useState(msg.text?.length > 800);
|
|
193
|
+
const isLong = msg.text?.length > 800;
|
|
194
|
+
|
|
10
195
|
return (
|
|
11
196
|
<div className="animate-chat-fade-in">
|
|
12
|
-
<div className="flex items-center gap-
|
|
13
|
-
<
|
|
14
|
-
<Bot size={10} className="text-text-3" />
|
|
15
|
-
</div>
|
|
16
|
-
<span className="text-2xs text-text-3 font-sans font-medium">Lab Assistant</span>
|
|
197
|
+
<div className="flex items-center gap-2 mb-1">
|
|
198
|
+
<span className="text-2xs font-semibold text-text-1 font-sans">Lab Assistant</span>
|
|
17
199
|
</div>
|
|
18
|
-
<div className="
|
|
19
|
-
<
|
|
20
|
-
'text-xs font-sans whitespace-pre-wrap break-words leading-relaxed',
|
|
21
|
-
msg.error ? 'text-danger' : 'text-text-1',
|
|
22
|
-
)}>
|
|
23
|
-
{msg.text}
|
|
24
|
-
</div>
|
|
200
|
+
<div className="border-l border-accent pl-3.5 py-1">
|
|
201
|
+
<StructuredMessage text={collapsed ? msg.text.slice(0, 800) + '...' : msg.text} />
|
|
25
202
|
</div>
|
|
203
|
+
{collapsed && (
|
|
204
|
+
<button
|
|
205
|
+
onClick={() => setCollapsed(false)}
|
|
206
|
+
className="ml-3.5 mt-1.5 flex items-center gap-1.5 text-[11px] text-accent/70 hover:text-accent font-sans font-medium cursor-pointer transition-colors"
|
|
207
|
+
>
|
|
208
|
+
<ChevronDown size={11} />
|
|
209
|
+
Show full response
|
|
210
|
+
</button>
|
|
211
|
+
)}
|
|
212
|
+
{isLong && !collapsed && (
|
|
213
|
+
<button
|
|
214
|
+
onClick={() => setCollapsed(true)}
|
|
215
|
+
className="ml-3.5 mt-1.5 flex items-center gap-1.5 text-[11px] text-accent/70 hover:text-accent font-sans font-medium cursor-pointer transition-colors"
|
|
216
|
+
>
|
|
217
|
+
<ChevronDown size={11} className="rotate-180" />
|
|
218
|
+
Collapse
|
|
219
|
+
</button>
|
|
220
|
+
)}
|
|
26
221
|
</div>
|
|
27
222
|
);
|
|
28
223
|
}
|
|
29
224
|
|
|
30
225
|
function UserMessage({ msg }) {
|
|
31
226
|
return (
|
|
32
|
-
<div className="flex justify-end animate-chat-fade-in">
|
|
33
|
-
<div className="max-w-[
|
|
34
|
-
<div className="px-3.5 py-2 bg-
|
|
35
|
-
<p className="text-
|
|
227
|
+
<div className="flex justify-end pl-8 animate-chat-fade-in">
|
|
228
|
+
<div className="max-w-[90%]">
|
|
229
|
+
<div className="px-3.5 py-2.5 rounded-lg border bg-info/10 border-info/25">
|
|
230
|
+
<p className="text-[13px] text-text-0 font-sans whitespace-pre-wrap break-words leading-relaxed">{msg.text}</p>
|
|
36
231
|
</div>
|
|
37
232
|
</div>
|
|
38
233
|
</div>
|
|
39
234
|
);
|
|
40
235
|
}
|
|
41
236
|
|
|
237
|
+
// ── Main component ──────────────────────────────────────────
|
|
42
238
|
export function LabAssistant() {
|
|
43
239
|
const agentId = useGrooveStore((s) => s.labAssistantAgentId);
|
|
44
240
|
const backend = useGrooveStore((s) => s.labAssistantBackend);
|
|
@@ -48,10 +244,15 @@ export function LabAssistant() {
|
|
|
48
244
|
const instructAgent = useGrooveStore((s) => s.instructAgent);
|
|
49
245
|
const dismissLabAssistant = useGrooveStore((s) => s.dismissLabAssistant);
|
|
50
246
|
const setLabAssistantMode = useGrooveStore((s) => s.setLabAssistantMode);
|
|
247
|
+
const onLabAssistantComplete = useGrooveStore((s) => s.onLabAssistantComplete);
|
|
248
|
+
const fetchLabRuntimes = useGrooveStore((s) => s.fetchLabRuntimes);
|
|
249
|
+
const activeRuntime = useGrooveStore((s) => s.labActiveRuntime);
|
|
250
|
+
const activeModel = useGrooveStore((s) => s.labActiveModel);
|
|
51
251
|
|
|
52
252
|
const [input, setInput] = useState('');
|
|
53
253
|
const scrollRef = useRef(null);
|
|
54
254
|
const inputRef = useRef(null);
|
|
255
|
+
const completionHandled = useRef(false);
|
|
55
256
|
|
|
56
257
|
const agent = agents.find((a) => a.id === agentId);
|
|
57
258
|
const messages = chatHistory[agentId] || [];
|
|
@@ -59,19 +260,33 @@ export function LabAssistant() {
|
|
|
59
260
|
const isRunning = agent?.status === 'running';
|
|
60
261
|
const isComplete = agent && agent.status !== 'running';
|
|
61
262
|
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
if (isComplete && !completionHandled.current) {
|
|
265
|
+
completionHandled.current = true;
|
|
266
|
+
onLabAssistantComplete();
|
|
267
|
+
}
|
|
268
|
+
if (isRunning) completionHandled.current = false;
|
|
269
|
+
}, [isComplete, isRunning, onLabAssistantComplete]);
|
|
270
|
+
|
|
271
|
+
useEffect(() => {
|
|
272
|
+
if (!isRunning || activeRuntime) return;
|
|
273
|
+
const interval = setInterval(fetchLabRuntimes, 5000);
|
|
274
|
+
return () => clearInterval(interval);
|
|
275
|
+
}, [isRunning, activeRuntime, fetchLabRuntimes]);
|
|
276
|
+
|
|
62
277
|
useEffect(() => {
|
|
63
278
|
if (scrollRef.current) {
|
|
64
279
|
const el = scrollRef.current.querySelector('[data-radix-scroll-area-viewport]');
|
|
65
280
|
if (el) el.scrollTop = el.scrollHeight;
|
|
66
281
|
}
|
|
67
|
-
}, [messages.length, messages[messages.length - 1]?.text]);
|
|
282
|
+
}, [messages.length, messages[messages.length - 1]?.text, isThinking]);
|
|
68
283
|
|
|
69
284
|
const handleSend = useCallback(() => {
|
|
70
285
|
const text = input.trim();
|
|
71
|
-
if (!text || !agentId
|
|
286
|
+
if (!text || !agentId) return;
|
|
72
287
|
setInput('');
|
|
73
288
|
instructAgent(agentId, text);
|
|
74
|
-
}, [input, agentId,
|
|
289
|
+
}, [input, agentId, instructAgent]);
|
|
75
290
|
|
|
76
291
|
function handleKeyDown(e) {
|
|
77
292
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
@@ -84,39 +299,31 @@ export function LabAssistant() {
|
|
|
84
299
|
|
|
85
300
|
return (
|
|
86
301
|
<div className="h-full flex flex-col">
|
|
87
|
-
{/*
|
|
88
|
-
|
|
89
|
-
<div className="flex items-center gap-
|
|
90
|
-
|
|
91
|
-
<span className="
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
</Badge>
|
|
100
|
-
)}
|
|
302
|
+
{/* Streaming status bar */}
|
|
303
|
+
{isRunning && (
|
|
304
|
+
<div className="flex-shrink-0 flex items-center gap-3 px-4 h-8 border-b border-border-subtle bg-surface-1/80">
|
|
305
|
+
<div className="relative flex items-center justify-center w-4 h-4">
|
|
306
|
+
<span className="absolute inset-0 rounded-full bg-accent/15 animate-ping [animation-duration:2s]" />
|
|
307
|
+
<span className="relative w-1.5 h-1.5 rounded-full bg-accent" />
|
|
308
|
+
</div>
|
|
309
|
+
<span className="text-2xs font-sans text-text-2">
|
|
310
|
+
Lab Assistant is setting up <span className="font-medium text-text-1">{backend?.toUpperCase()}</span>
|
|
311
|
+
</span>
|
|
312
|
+
<div className="flex-1" />
|
|
313
|
+
<Badge variant="success" className="text-2xs">running</Badge>
|
|
101
314
|
</div>
|
|
102
|
-
|
|
103
|
-
onClick={dismissLabAssistant}
|
|
104
|
-
className="p-1 text-text-4 hover:text-text-1 transition-colors cursor-pointer"
|
|
105
|
-
>
|
|
106
|
-
<X size={14} />
|
|
107
|
-
</button>
|
|
108
|
-
</div>
|
|
315
|
+
)}
|
|
109
316
|
|
|
110
317
|
{/* Messages */}
|
|
111
318
|
<ScrollArea ref={scrollRef} className="flex-1 min-h-0">
|
|
112
|
-
<div className="px-
|
|
319
|
+
<div className="px-5 py-4 space-y-6">
|
|
113
320
|
{messages.length === 0 && !isThinking ? (
|
|
114
321
|
<div className="flex flex-col items-center justify-center py-20 text-center">
|
|
115
|
-
<div className="w-
|
|
116
|
-
<
|
|
322
|
+
<div className="w-12 h-12 rounded-lg bg-accent/10 flex items-center justify-center mb-3">
|
|
323
|
+
<FlaskConical size={22} className="text-accent" />
|
|
117
324
|
</div>
|
|
118
|
-
<p className="text-sm text-text-
|
|
119
|
-
<p className="text-
|
|
325
|
+
<p className="text-sm text-text-1 font-sans font-medium">Setting up {backend?.toUpperCase()}</p>
|
|
326
|
+
<p className="text-[13px] text-text-3 font-sans mt-1.5">The assistant is starting up and will begin configuring your runtime...</p>
|
|
120
327
|
</div>
|
|
121
328
|
) : (
|
|
122
329
|
messages.map((msg, i) =>
|
|
@@ -128,69 +335,96 @@ export function LabAssistant() {
|
|
|
128
335
|
)
|
|
129
336
|
)}
|
|
130
337
|
|
|
131
|
-
{isThinking &&
|
|
132
|
-
<div className="animate-chat-fade-in">
|
|
133
|
-
<div className="flex items-center gap-1.5 mb-1">
|
|
134
|
-
<div className="w-4 h-4 rounded-sm bg-surface-4 flex items-center justify-center">
|
|
135
|
-
<Bot size={10} className="text-text-3" />
|
|
136
|
-
</div>
|
|
137
|
-
<span className="text-2xs text-text-3 font-sans font-medium">Lab Assistant</span>
|
|
138
|
-
</div>
|
|
139
|
-
<div className="ml-5 flex items-center gap-1.5 py-1">
|
|
140
|
-
<span className="w-1 h-1 rounded-full bg-text-4 animate-pulse" />
|
|
141
|
-
<span className="w-1 h-1 rounded-full bg-text-4 animate-pulse" style={{ animationDelay: '150ms' }} />
|
|
142
|
-
<span className="w-1 h-1 rounded-full bg-text-4 animate-pulse" style={{ animationDelay: '300ms' }} />
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
)}
|
|
338
|
+
{isThinking && <LabThinkingIndicator />}
|
|
146
339
|
</div>
|
|
147
340
|
</ScrollArea>
|
|
148
341
|
|
|
149
342
|
{/* Completion banner */}
|
|
150
343
|
{isComplete && messages.length > 0 && (
|
|
151
|
-
<div className=
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
344
|
+
<div className={cn(
|
|
345
|
+
'flex-shrink-0 px-4 py-3 border-t',
|
|
346
|
+
activeRuntime && activeModel
|
|
347
|
+
? 'bg-success/10 border-success/20'
|
|
348
|
+
: activeRuntime
|
|
349
|
+
? 'bg-warning/10 border-warning/20'
|
|
350
|
+
: 'bg-surface-2 border-border',
|
|
351
|
+
)}>
|
|
352
|
+
{activeRuntime && activeModel ? (
|
|
353
|
+
<div className="flex items-center justify-between">
|
|
354
|
+
<div>
|
|
355
|
+
<span className="text-[13px] font-sans text-success font-semibold">Runtime ready</span>
|
|
356
|
+
<p className="text-xs text-text-2 font-sans mt-0.5">Your model is loaded and ready to chat</p>
|
|
357
|
+
</div>
|
|
358
|
+
<button
|
|
359
|
+
onClick={() => setLabAssistantMode(false)}
|
|
360
|
+
className="flex items-center gap-1.5 px-4 py-2 text-[13px] font-sans font-medium text-surface-0 bg-accent hover:bg-accent/90 rounded-md transition-colors cursor-pointer"
|
|
361
|
+
>
|
|
362
|
+
<ArrowRight size={14} /> Open Playground
|
|
363
|
+
</button>
|
|
364
|
+
</div>
|
|
365
|
+
) : activeRuntime ? (
|
|
366
|
+
<div className="flex items-center justify-between">
|
|
367
|
+
<div>
|
|
368
|
+
<span className="text-[13px] font-sans text-warning font-semibold">Runtime registered</span>
|
|
369
|
+
<p className="text-xs text-text-2 font-sans mt-0.5">Model may still be loading — test the runtime in the sidebar</p>
|
|
370
|
+
</div>
|
|
371
|
+
<button
|
|
372
|
+
onClick={() => setLabAssistantMode(false)}
|
|
373
|
+
className="flex items-center gap-1.5 px-4 py-2 text-[13px] font-sans font-medium text-text-0 bg-surface-3 hover:bg-surface-4 rounded-md transition-colors cursor-pointer"
|
|
374
|
+
>
|
|
375
|
+
<ArrowRight size={14} /> View Playground
|
|
376
|
+
</button>
|
|
377
|
+
</div>
|
|
378
|
+
) : (
|
|
379
|
+
<div className="flex items-center justify-between">
|
|
380
|
+
<div>
|
|
381
|
+
<span className="text-[13px] font-sans text-text-1 font-semibold">Setup finished</span>
|
|
382
|
+
<p className="text-xs text-text-2 font-sans mt-0.5">
|
|
383
|
+
If the model is still downloading, the runtime will appear once the server starts.
|
|
384
|
+
</p>
|
|
385
|
+
</div>
|
|
386
|
+
<button
|
|
387
|
+
onClick={fetchLabRuntimes}
|
|
388
|
+
className="flex items-center gap-1.5 px-4 py-2 text-[13px] font-sans font-medium text-text-0 bg-surface-3 hover:bg-surface-4 rounded-md transition-colors cursor-pointer"
|
|
389
|
+
>
|
|
390
|
+
Check Runtimes
|
|
391
|
+
</button>
|
|
392
|
+
</div>
|
|
393
|
+
)}
|
|
161
394
|
</div>
|
|
162
395
|
)}
|
|
163
396
|
|
|
164
397
|
{/* Input */}
|
|
165
398
|
<div className="flex-shrink-0 px-4 py-3">
|
|
166
|
-
<div className="flex items-end gap-2 bg-surface-1
|
|
399
|
+
<div className="flex items-end gap-2 border rounded-xl bg-surface-0 p-1.5 transition-colors border-border-subtle focus-within:border-accent/30">
|
|
167
400
|
<textarea
|
|
168
401
|
ref={inputRef}
|
|
169
402
|
value={input}
|
|
170
403
|
onChange={(e) => setInput(e.target.value)}
|
|
171
404
|
onKeyDown={handleKeyDown}
|
|
172
|
-
placeholder={isRunning ? '
|
|
173
|
-
disabled={!isRunning}
|
|
405
|
+
placeholder={isRunning ? 'Ask the assistant anything...' : 'Send a message to continue...'}
|
|
174
406
|
rows={1}
|
|
175
407
|
className={cn(
|
|
176
|
-
'flex-1 resize-none bg-transparent px-2 py-1.5',
|
|
177
|
-
'text-
|
|
408
|
+
'flex-1 resize-none bg-transparent px-2.5 py-1.5',
|
|
409
|
+
'text-[13px] text-text-0 font-sans placeholder:text-text-4',
|
|
178
410
|
'focus:outline-none',
|
|
179
|
-
'
|
|
180
|
-
'min-h-[28px] max-h-32',
|
|
411
|
+
'min-h-[32px] max-h-32',
|
|
181
412
|
)}
|
|
182
413
|
style={{ height: 'auto', overflowY: input.split('\n').length > 4 ? 'auto' : 'hidden' }}
|
|
183
414
|
onInput={(e) => { e.target.style.height = 'auto'; e.target.style.height = `${Math.min(e.target.scrollHeight, 128)}px`; }}
|
|
184
415
|
/>
|
|
185
416
|
<button
|
|
186
|
-
disabled={!input.trim()
|
|
417
|
+
disabled={!input.trim()}
|
|
187
418
|
onClick={handleSend}
|
|
188
419
|
className={cn(
|
|
189
|
-
'flex-shrink-0 w-
|
|
190
|
-
|
|
420
|
+
'flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-lg transition-all cursor-pointer',
|
|
421
|
+
'disabled:opacity-15 disabled:cursor-not-allowed',
|
|
422
|
+
input.trim()
|
|
423
|
+
? 'bg-accent/15 text-accent hover:bg-accent/25 border border-accent/25'
|
|
424
|
+
: 'bg-transparent text-text-4',
|
|
191
425
|
)}
|
|
192
426
|
>
|
|
193
|
-
<Send size={
|
|
427
|
+
<Send size={13} />
|
|
194
428
|
</button>
|
|
195
429
|
</div>
|
|
196
430
|
</div>
|