@swarmclawai/swarmclaw 0.7.7 → 0.8.0
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/README.md +12 -14
- package/next.config.ts +13 -2
- package/package.json +4 -2
- package/src/app/api/agents/[id]/thread/route.ts +9 -0
- package/src/app/api/agents/route.ts +4 -0
- package/src/app/api/agents/thread-route.test.ts +133 -0
- package/src/app/api/approvals/route.test.ts +148 -0
- package/src/app/api/canvas/[sessionId]/route.ts +3 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -2
- package/src/app/api/chats/[id]/devserver/route.ts +48 -7
- package/src/app/api/chats/[id]/messages/route.ts +42 -18
- package/src/app/api/chats/[id]/route.ts +1 -1
- package/src/app/api/chats/[id]/stop/route.ts +5 -4
- package/src/app/api/chats/route.ts +23 -2
- package/src/app/api/clawhub/install/route.ts +28 -8
- package/src/app/api/connectors/[id]/route.ts +46 -3
- package/src/app/api/connectors/route.ts +12 -8
- package/src/app/api/external-agents/route.test.ts +165 -0
- package/src/app/api/gateways/[id]/health/route.ts +27 -12
- package/src/app/api/gateways/[id]/route.ts +2 -0
- package/src/app/api/gateways/health-route.test.ts +135 -0
- package/src/app/api/gateways/route.ts +2 -0
- package/src/app/api/mcp-servers/route.test.ts +130 -0
- package/src/app/api/openclaw/deploy/route.ts +38 -5
- package/src/app/api/plugins/install/route.ts +46 -6
- package/src/app/api/plugins/marketplace/route.ts +48 -15
- package/src/app/api/preview-server/route.ts +26 -11
- package/src/app/api/projects/[id]/route.ts +6 -2
- package/src/app/api/projects/route.ts +4 -3
- package/src/app/api/schedules/[id]/run/route.ts +4 -0
- package/src/app/api/schedules/route.test.ts +86 -0
- package/src/app/api/schedules/route.ts +6 -1
- package/src/app/api/secrets/[id]/route.ts +1 -0
- package/src/app/api/secrets/route.ts +2 -1
- package/src/app/api/settings/route.ts +2 -0
- package/src/app/api/setup/check-provider/route.test.ts +19 -0
- package/src/app/api/setup/check-provider/route.ts +40 -10
- package/src/app/api/skills/[id]/route.ts +12 -0
- package/src/app/api/skills/import/route.ts +14 -12
- package/src/app/api/skills/route.ts +13 -1
- package/src/app/api/tasks/[id]/route.ts +10 -1
- package/src/app/api/tasks/import/github/route.test.ts +65 -0
- package/src/app/api/tasks/import/github/route.ts +337 -0
- package/src/app/api/wallets/[id]/approve/route.ts +17 -3
- package/src/app/api/wallets/[id]/route.ts +79 -33
- package/src/app/api/wallets/[id]/send/route.ts +19 -33
- package/src/app/api/wallets/route.ts +78 -61
- package/src/app/api/webhooks/[id]/route.ts +33 -6
- package/src/app/api/webhooks/route.test.ts +272 -0
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-card.tsx +9 -2
- package/src/components/agents/agent-chat-list.tsx +18 -2
- package/src/components/agents/agent-list.tsx +1 -0
- package/src/components/agents/agent-sheet.tsx +257 -38
- package/src/components/agents/inspector-panel.tsx +41 -0
- package/src/components/canvas/canvas-panel.tsx +236 -65
- package/src/components/chat/chat-area.tsx +36 -19
- package/src/components/chat/chat-card.tsx +36 -13
- package/src/components/chat/chat-header.tsx +48 -16
- package/src/components/chat/chat-list.tsx +28 -4
- package/src/components/chat/checkpoint-timeline.tsx +50 -34
- package/src/components/chat/delegation-banner.test.ts +14 -1
- package/src/components/chat/delegation-banner.tsx +1 -1
- package/src/components/chat/message-bubble.tsx +208 -145
- package/src/components/chat/message-list.tsx +48 -19
- package/src/components/chatrooms/chatroom-message.tsx +2 -2
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
- package/src/components/connectors/connector-health.tsx +1 -1
- package/src/components/connectors/connector-list.tsx +7 -2
- package/src/components/connectors/connector-sheet.tsx +337 -148
- package/src/components/gateways/gateway-sheet.tsx +2 -2
- package/src/components/layout/app-layout.tsx +40 -23
- package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
- package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
- package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
- package/src/components/plugins/plugin-list.tsx +45 -9
- package/src/components/plugins/plugin-sheet.tsx +55 -7
- package/src/components/projects/project-detail.tsx +217 -0
- package/src/components/projects/project-sheet.tsx +176 -4
- package/src/components/providers/provider-list.tsx +2 -1
- package/src/components/providers/provider-sheet.tsx +21 -2
- package/src/components/schedules/schedule-card.tsx +25 -1
- package/src/components/schedules/schedule-sheet.tsx +44 -2
- package/src/components/secrets/secret-sheet.tsx +21 -2
- package/src/components/shared/agent-switch-dialog.tsx +12 -1
- package/src/components/shared/bottom-sheet.tsx +13 -3
- package/src/components/shared/command-palette.tsx +8 -1
- package/src/components/shared/confirm-dialog.tsx +19 -4
- package/src/components/shared/connector-platform-icon.test.ts +28 -0
- package/src/components/shared/connector-platform-icon.tsx +39 -6
- package/src/components/shared/settings/plugin-manager.tsx +29 -6
- package/src/components/shared/settings/section-capability-policy.tsx +45 -3
- package/src/components/shared/settings/section-voice.tsx +11 -3
- package/src/components/skills/skill-list.tsx +25 -0
- package/src/components/skills/skill-sheet.tsx +84 -12
- package/src/components/tasks/approvals-panel.tsx +289 -34
- package/src/components/tasks/task-board.tsx +410 -25
- package/src/components/tasks/task-card.tsx +66 -8
- package/src/components/tasks/task-sheet.tsx +16 -4
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
- package/src/components/wallets/wallet-panel.tsx +435 -90
- package/src/components/wallets/wallet-section.tsx +198 -48
- package/src/components/webhooks/webhook-sheet.tsx +22 -2
- package/src/lib/approval-display.ts +20 -0
- package/src/lib/canvas-content.ts +198 -0
- package/src/lib/chat-artifact-summary.ts +165 -0
- package/src/lib/chat-display.test.ts +91 -0
- package/src/lib/chat-display.ts +58 -0
- package/src/lib/chat-streaming-state.test.ts +47 -1
- package/src/lib/chat-streaming-state.ts +42 -0
- package/src/lib/ollama-model.ts +10 -0
- package/src/lib/openclaw-endpoint.test.ts +8 -0
- package/src/lib/openclaw-endpoint.ts +6 -1
- package/src/lib/plugin-install-cors.ts +46 -0
- package/src/lib/plugin-sources.test.ts +43 -0
- package/src/lib/plugin-sources.ts +77 -0
- package/src/lib/providers/ollama.ts +16 -6
- package/src/lib/providers/openclaw.test.ts +54 -0
- package/src/lib/providers/openclaw.ts +127 -11
- package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
- package/src/lib/schedule-dedupe.test.ts +66 -1
- package/src/lib/schedule-dedupe.ts +169 -12
- package/src/lib/schedule-origin.test.ts +20 -0
- package/src/lib/schedule-origin.ts +15 -0
- package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
- package/src/lib/server/agent-availability.ts +16 -0
- package/src/lib/server/agent-runtime-config.ts +12 -4
- package/src/lib/server/agent-thread-session.test.ts +51 -0
- package/src/lib/server/agent-thread-session.ts +7 -0
- package/src/lib/server/approval-match.ts +205 -0
- package/src/lib/server/approvals-auto-approve.test.ts +538 -1
- package/src/lib/server/approvals.ts +214 -1
- package/src/lib/server/assistant-control.test.ts +29 -0
- package/src/lib/server/assistant-control.ts +23 -0
- package/src/lib/server/build-llm.test.ts +79 -0
- package/src/lib/server/build-llm.ts +14 -4
- package/src/lib/server/canvas-content.test.ts +32 -0
- package/src/lib/server/canvas-content.ts +6 -0
- package/src/lib/server/capability-router.test.ts +33 -0
- package/src/lib/server/capability-router.ts +80 -19
- package/src/lib/server/chat-execution-advanced.test.ts +651 -0
- package/src/lib/server/chat-execution-disabled.test.ts +94 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
- package/src/lib/server/chat-execution.ts +378 -73
- package/src/lib/server/clawhub-client.test.ts +14 -8
- package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
- package/src/lib/server/connectors/manager.test.ts +1147 -0
- package/src/lib/server/connectors/manager.ts +461 -137
- package/src/lib/server/connectors/pairing.ts +26 -5
- package/src/lib/server/connectors/types.ts +2 -0
- package/src/lib/server/connectors/whatsapp.test.ts +134 -0
- package/src/lib/server/connectors/whatsapp.ts +271 -47
- package/src/lib/server/context-manager.ts +6 -1
- package/src/lib/server/daemon-state.ts +84 -47
- package/src/lib/server/data-dir.test.ts +37 -0
- package/src/lib/server/data-dir.ts +20 -1
- package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
- package/src/lib/server/devserver-launch.test.ts +60 -0
- package/src/lib/server/devserver-launch.ts +85 -0
- package/src/lib/server/elevenlabs.test.ts +247 -1
- package/src/lib/server/elevenlabs.ts +147 -43
- package/src/lib/server/ethereum.ts +590 -0
- package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
- package/src/lib/server/eval/agent-regression.test.ts +18 -1
- package/src/lib/server/eval/agent-regression.ts +383 -11
- package/src/lib/server/evm-swap.ts +475 -0
- package/src/lib/server/execution-log.ts +1 -0
- package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
- package/src/lib/server/heartbeat-service.ts +20 -11
- package/src/lib/server/heartbeat-wake.test.ts +112 -0
- package/src/lib/server/heartbeat-wake.ts +338 -57
- package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
- package/src/lib/server/main-agent-loop.test.ts +260 -0
- package/src/lib/server/main-agent-loop.ts +559 -14
- package/src/lib/server/mcp-client.test.ts +16 -0
- package/src/lib/server/mcp-client.ts +25 -0
- package/src/lib/server/memory-integration.test.ts +719 -0
- package/src/lib/server/memory-policy.test.ts +43 -0
- package/src/lib/server/memory-policy.ts +132 -0
- package/src/lib/server/memory-tiers.test.ts +60 -0
- package/src/lib/server/memory-tiers.ts +16 -0
- package/src/lib/server/ollama-runtime.ts +58 -0
- package/src/lib/server/openclaw-deploy.test.ts +109 -1
- package/src/lib/server/openclaw-deploy.ts +557 -81
- package/src/lib/server/openclaw-gateway.test.ts +131 -0
- package/src/lib/server/openclaw-gateway.ts +10 -4
- package/src/lib/server/openclaw-health.test.ts +35 -0
- package/src/lib/server/openclaw-health.ts +215 -47
- package/src/lib/server/orchestrator-lg.ts +3 -2
- package/src/lib/server/orchestrator.ts +2 -0
- package/src/lib/server/plugins-advanced.test.ts +351 -0
- package/src/lib/server/plugins.ts +211 -6
- package/src/lib/server/project-context.ts +162 -0
- package/src/lib/server/project-utils.ts +150 -0
- package/src/lib/server/queue-advanced.test.ts +528 -0
- package/src/lib/server/queue-followups.test.ts +409 -2
- package/src/lib/server/queue-reconcile.test.ts +128 -0
- package/src/lib/server/queue.ts +527 -68
- package/src/lib/server/scheduler.ts +29 -1
- package/src/lib/server/session-note.test.ts +36 -0
- package/src/lib/server/session-note.ts +42 -0
- package/src/lib/server/session-run-manager.ts +83 -4
- package/src/lib/server/session-tools/canvas.ts +14 -12
- package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
- package/src/lib/server/session-tools/connector.test.ts +138 -0
- package/src/lib/server/session-tools/connector.ts +366 -54
- package/src/lib/server/session-tools/context.ts +17 -3
- package/src/lib/server/session-tools/crud.ts +484 -84
- package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
- package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
- package/src/lib/server/session-tools/delegate.ts +102 -10
- package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
- package/src/lib/server/session-tools/discovery.ts +80 -12
- package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
- package/src/lib/server/session-tools/file.ts +43 -4
- package/src/lib/server/session-tools/human-loop.ts +35 -5
- package/src/lib/server/session-tools/index.ts +44 -9
- package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
- package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
- package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
- package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
- package/src/lib/server/session-tools/memory.test.ts +93 -0
- package/src/lib/server/session-tools/memory.ts +554 -75
- package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
- package/src/lib/server/session-tools/platform-access.test.ts +58 -0
- package/src/lib/server/session-tools/platform.ts +60 -19
- package/src/lib/server/session-tools/plugin-creator.ts +57 -1
- package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
- package/src/lib/server/session-tools/schedule.ts +6 -1
- package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
- package/src/lib/server/session-tools/shell.ts +22 -3
- package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
- package/src/lib/server/session-tools/wallet.ts +1374 -139
- package/src/lib/server/session-tools/web-inputs.test.ts +178 -0
- package/src/lib/server/session-tools/web.ts +621 -70
- package/src/lib/server/skill-discovery.ts +128 -0
- package/src/lib/server/skill-eligibility.test.ts +84 -0
- package/src/lib/server/skill-eligibility.ts +95 -0
- package/src/lib/server/skill-prompt-budget.test.ts +102 -0
- package/src/lib/server/skill-prompt-budget.ts +125 -0
- package/src/lib/server/skills-normalize.test.ts +54 -0
- package/src/lib/server/skills-normalize.ts +372 -26
- package/src/lib/server/solana.ts +214 -29
- package/src/lib/server/storage.ts +65 -36
- package/src/lib/server/stream-agent-chat.test.ts +437 -2
- package/src/lib/server/stream-agent-chat.ts +957 -79
- package/src/lib/server/system-events.ts +1 -1
- package/src/lib/server/tool-aliases.ts +2 -0
- package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
- package/src/lib/server/tool-capability-policy.test.ts +24 -0
- package/src/lib/server/tool-capability-policy.ts +29 -1
- package/src/lib/server/tool-loop-detection.test.ts +105 -0
- package/src/lib/server/tool-loop-detection.ts +260 -0
- package/src/lib/server/tool-planning.test.ts +44 -0
- package/src/lib/server/tool-planning.ts +271 -0
- package/src/lib/server/wallet-execution.test.ts +198 -0
- package/src/lib/server/wallet-portfolio.test.ts +98 -0
- package/src/lib/server/wallet-portfolio.ts +724 -0
- package/src/lib/server/wallet-service.test.ts +57 -0
- package/src/lib/server/wallet-service.ts +213 -0
- package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
- package/src/lib/server/watch-jobs.ts +17 -2
- package/src/lib/server/workspace-context.ts +111 -0
- package/src/lib/skill-save-payload.test.ts +39 -0
- package/src/lib/skill-save-payload.ts +37 -0
- package/src/lib/tasks.ts +28 -0
- package/src/lib/tool-definitions.ts +2 -1
- package/src/lib/tool-event-summary.test.ts +30 -0
- package/src/lib/tool-event-summary.ts +37 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/wallet-transactions.test.ts +75 -0
- package/src/lib/wallet-transactions.ts +43 -0
- package/src/lib/wallet.test.ts +17 -0
- package/src/lib/wallet.ts +183 -0
- package/src/proxy.test.ts +31 -0
- package/src/proxy.ts +34 -2
- package/src/stores/use-chat-store.ts +15 -1
- package/src/types/index.ts +249 -14
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState, useCallback } from 'react'
|
|
4
|
+
import ReactMarkdown from 'react-markdown'
|
|
4
5
|
import { useWs } from '@/hooks/use-ws'
|
|
5
6
|
import { api } from '@/lib/api-client'
|
|
7
|
+
import { normalizeCanvasContent } from '@/lib/canvas-content'
|
|
8
|
+
import type { CanvasContent, CanvasDocument } from '@/types'
|
|
6
9
|
|
|
7
10
|
interface CanvasPanelProps {
|
|
8
11
|
sessionId: string
|
|
@@ -10,86 +13,254 @@ interface CanvasPanelProps {
|
|
|
10
13
|
onClose: () => void
|
|
11
14
|
}
|
|
12
15
|
|
|
16
|
+
const THEME_STYLES: Record<NonNullable<CanvasDocument['theme']>, { accent: string; chip: string }> = {
|
|
17
|
+
slate: { accent: 'text-sky-300', chip: 'bg-sky-500/10 text-sky-300 border-sky-500/20' },
|
|
18
|
+
sky: { accent: 'text-sky-300', chip: 'bg-sky-500/10 text-sky-300 border-sky-500/20' },
|
|
19
|
+
emerald: { accent: 'text-emerald-300', chip: 'bg-emerald-500/10 text-emerald-300 border-emerald-500/20' },
|
|
20
|
+
amber: { accent: 'text-amber-300', chip: 'bg-amber-500/10 text-amber-300 border-amber-500/20' },
|
|
21
|
+
rose: { accent: 'text-rose-300', chip: 'bg-rose-500/10 text-rose-300 border-rose-500/20' },
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function toneClass(tone?: string): string {
|
|
25
|
+
switch (tone) {
|
|
26
|
+
case 'positive': return 'text-emerald-300'
|
|
27
|
+
case 'negative': return 'text-rose-300'
|
|
28
|
+
case 'warning': return 'text-amber-300'
|
|
29
|
+
default: return 'text-text'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function intentClass(intent?: string): string {
|
|
34
|
+
switch (intent) {
|
|
35
|
+
case 'primary': return 'bg-sky-500 text-white border-sky-400/30'
|
|
36
|
+
case 'success': return 'bg-emerald-500 text-white border-emerald-400/30'
|
|
37
|
+
case 'danger': return 'bg-rose-500 text-white border-rose-400/30'
|
|
38
|
+
default: return 'bg-white/[0.03] text-text-2 border-white/[0.08]'
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function StructuredCanvasView({ document }: { document: CanvasDocument }) {
|
|
43
|
+
const theme = THEME_STYLES[document.theme || 'slate']
|
|
44
|
+
return (
|
|
45
|
+
<div className="h-full overflow-y-auto bg-bg px-5 py-5">
|
|
46
|
+
<div className="max-w-4xl mx-auto space-y-4">
|
|
47
|
+
{(document.title || document.subtitle) && (
|
|
48
|
+
<div className="rounded-[18px] border border-white/[0.08] bg-white/[0.03] px-5 py-4">
|
|
49
|
+
{document.title && <h2 className={`font-display text-[22px] font-700 tracking-[-0.03em] ${theme.accent}`}>{document.title}</h2>}
|
|
50
|
+
{document.subtitle && <p className="mt-1 text-[13px] text-text-3/70">{document.subtitle}</p>}
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
53
|
+
|
|
54
|
+
{document.blocks.map((block, index) => {
|
|
55
|
+
if (block.type === 'markdown') {
|
|
56
|
+
return (
|
|
57
|
+
<section key={`${block.type}-${index}`} className="rounded-[18px] border border-white/[0.08] bg-white/[0.03] px-5 py-4">
|
|
58
|
+
{block.title && <div className={`mb-3 text-[11px] font-700 uppercase tracking-[0.08em] ${theme.accent}`}>{block.title}</div>}
|
|
59
|
+
<div className="max-w-none text-[14px] leading-6 text-text-2/90 [&_h1]:font-display [&_h1]:text-[24px] [&_h1]:text-text [&_h2]:font-display [&_h2]:text-[20px] [&_h2]:text-text [&_h3]:font-display [&_h3]:text-[18px] [&_h3]:text-text [&_p]:my-3 [&_ul]:my-3 [&_ul]:pl-5 [&_li]:my-1 [&_code]:rounded [&_code]:bg-black/[0.2] [&_code]:px-1.5 [&_code]:py-0.5">
|
|
60
|
+
<ReactMarkdown>{block.markdown}</ReactMarkdown>
|
|
61
|
+
</div>
|
|
62
|
+
</section>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (block.type === 'metrics') {
|
|
67
|
+
return (
|
|
68
|
+
<section key={`${block.type}-${index}`} className="rounded-[18px] border border-white/[0.08] bg-white/[0.03] px-5 py-4">
|
|
69
|
+
{block.title && <div className={`mb-3 text-[11px] font-700 uppercase tracking-[0.08em] ${theme.accent}`}>{block.title}</div>}
|
|
70
|
+
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-3">
|
|
71
|
+
{block.items.map((item) => (
|
|
72
|
+
<div key={item.label} className="rounded-[14px] border border-white/[0.08] bg-black/[0.14] px-4 py-3">
|
|
73
|
+
<div className="text-[11px] uppercase tracking-[0.08em] text-text-3/60">{item.label}</div>
|
|
74
|
+
<div className={`mt-1 text-[24px] font-display font-700 tracking-[-0.03em] ${toneClass(item.tone)}`}>{item.value}</div>
|
|
75
|
+
{item.detail && <div className="mt-1 text-[12px] text-text-3/65">{item.detail}</div>}
|
|
76
|
+
</div>
|
|
77
|
+
))}
|
|
78
|
+
</div>
|
|
79
|
+
</section>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (block.type === 'cards') {
|
|
84
|
+
return (
|
|
85
|
+
<section key={`${block.type}-${index}`} className="rounded-[18px] border border-white/[0.08] bg-white/[0.03] px-5 py-4">
|
|
86
|
+
{block.title && <div className={`mb-3 text-[11px] font-700 uppercase tracking-[0.08em] ${theme.accent}`}>{block.title}</div>}
|
|
87
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
88
|
+
{block.items.map((item) => (
|
|
89
|
+
<div key={item.title} className="rounded-[14px] border border-white/[0.08] bg-black/[0.14] px-4 py-3">
|
|
90
|
+
<div className={`text-[15px] font-700 ${toneClass(item.tone)}`}>{item.title}</div>
|
|
91
|
+
{item.body && <p className="mt-2 text-[13px] leading-6 text-text-2/85 whitespace-pre-wrap">{item.body}</p>}
|
|
92
|
+
{item.meta && <div className="mt-3 text-[11px] text-text-3/60">{item.meta}</div>}
|
|
93
|
+
</div>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
</section>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (block.type === 'table') {
|
|
101
|
+
return (
|
|
102
|
+
<section key={`${block.type}-${index}`} className="rounded-[18px] border border-white/[0.08] bg-white/[0.03] px-5 py-4 overflow-hidden">
|
|
103
|
+
{block.title && <div className={`mb-3 text-[11px] font-700 uppercase tracking-[0.08em] ${theme.accent}`}>{block.title}</div>}
|
|
104
|
+
<div className="overflow-x-auto rounded-[12px] border border-white/[0.08]">
|
|
105
|
+
<table className="min-w-full text-left text-[13px]">
|
|
106
|
+
<thead className="bg-black/[0.18]">
|
|
107
|
+
<tr>
|
|
108
|
+
{block.table.columns.map((column) => (
|
|
109
|
+
<th key={column} className="px-3 py-2.5 font-700 text-text-2">{column}</th>
|
|
110
|
+
))}
|
|
111
|
+
</tr>
|
|
112
|
+
</thead>
|
|
113
|
+
<tbody>
|
|
114
|
+
{block.table.rows.map((row, rowIndex) => (
|
|
115
|
+
<tr key={rowIndex} className="border-t border-white/[0.06]">
|
|
116
|
+
{row.map((cell, cellIndex) => (
|
|
117
|
+
<td key={cellIndex} className="px-3 py-2.5 text-text-3/80">{cell == null ? '—' : String(cell)}</td>
|
|
118
|
+
))}
|
|
119
|
+
</tr>
|
|
120
|
+
))}
|
|
121
|
+
</tbody>
|
|
122
|
+
</table>
|
|
123
|
+
</div>
|
|
124
|
+
{block.table.caption && <div className="mt-2 text-[11px] text-text-3/60">{block.table.caption}</div>}
|
|
125
|
+
</section>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (block.type === 'code') {
|
|
130
|
+
return (
|
|
131
|
+
<section key={`${block.type}-${index}`} className="rounded-[18px] border border-white/[0.08] bg-white/[0.03] px-5 py-4">
|
|
132
|
+
{block.title && <div className={`mb-3 text-[11px] font-700 uppercase tracking-[0.08em] ${theme.accent}`}>{block.title}</div>}
|
|
133
|
+
<pre className="overflow-x-auto rounded-[14px] border border-white/[0.08] bg-black/[0.25] p-4 text-[12px] leading-6 text-text-2">
|
|
134
|
+
<code>{block.code}</code>
|
|
135
|
+
</pre>
|
|
136
|
+
{block.language && <div className={`mt-2 inline-flex rounded-full border px-2 py-1 text-[10px] font-700 uppercase tracking-[0.08em] ${theme.chip}`}>{block.language}</div>}
|
|
137
|
+
</section>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (block.type === 'actions') {
|
|
142
|
+
return (
|
|
143
|
+
<section key={`${block.type}-${index}`} className="rounded-[18px] border border-white/[0.08] bg-white/[0.03] px-5 py-4">
|
|
144
|
+
{block.title && <div className={`mb-3 text-[11px] font-700 uppercase tracking-[0.08em] ${theme.accent}`}>{block.title}</div>}
|
|
145
|
+
<div className="flex flex-wrap gap-2">
|
|
146
|
+
{block.items.map((item) => (
|
|
147
|
+
item.href ? (
|
|
148
|
+
<a
|
|
149
|
+
key={item.label}
|
|
150
|
+
href={item.href}
|
|
151
|
+
target="_blank"
|
|
152
|
+
rel="noreferrer"
|
|
153
|
+
className={`inline-flex items-center rounded-[12px] border px-3 py-2 text-[12px] font-700 transition-all hover:brightness-110 ${intentClass(item.intent)}`}
|
|
154
|
+
>
|
|
155
|
+
{item.label}
|
|
156
|
+
</a>
|
|
157
|
+
) : (
|
|
158
|
+
<div key={item.label} className={`inline-flex items-center rounded-[12px] border px-3 py-2 text-[12px] font-700 ${intentClass(item.intent)}`}>
|
|
159
|
+
{item.label}
|
|
160
|
+
</div>
|
|
161
|
+
)
|
|
162
|
+
))}
|
|
163
|
+
</div>
|
|
164
|
+
{block.items.some((item) => item.note) && (
|
|
165
|
+
<div className="mt-3 space-y-1">
|
|
166
|
+
{block.items.filter((item) => item.note).map((item) => (
|
|
167
|
+
<div key={`${item.label}-note`} className="text-[11px] text-text-3/60">{item.label}: {item.note}</div>
|
|
168
|
+
))}
|
|
169
|
+
</div>
|
|
170
|
+
)}
|
|
171
|
+
</section>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return null
|
|
176
|
+
})}
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
13
182
|
export function CanvasPanel({ sessionId, agentName, onClose }: CanvasPanelProps) {
|
|
14
|
-
const [content, setContent] = useState<
|
|
183
|
+
const [content, setContent] = useState<CanvasContent>(null)
|
|
184
|
+
const [loaded, setLoaded] = useState(false)
|
|
15
185
|
|
|
16
186
|
const loadCanvas = useCallback(async () => {
|
|
17
187
|
try {
|
|
18
|
-
const res = await api<{ content:
|
|
19
|
-
setContent(res.content)
|
|
20
|
-
} catch {
|
|
188
|
+
const res = await api<{ content: CanvasContent }>('GET', `/canvas/${sessionId}`)
|
|
189
|
+
setContent(normalizeCanvasContent(res.content))
|
|
190
|
+
} catch {
|
|
191
|
+
setContent(null)
|
|
192
|
+
} finally {
|
|
193
|
+
setLoaded(true)
|
|
194
|
+
}
|
|
21
195
|
}, [sessionId])
|
|
22
196
|
|
|
23
|
-
useEffect(() => { loadCanvas() }, [loadCanvas])
|
|
197
|
+
useEffect(() => { loadCanvas() }, [loadCanvas])
|
|
24
198
|
useWs(`canvas:${sessionId}`, loadCanvas, 10_000)
|
|
25
199
|
|
|
26
|
-
|
|
27
|
-
<div className="flex
|
|
28
|
-
<
|
|
29
|
-
<
|
|
30
|
-
|
|
200
|
+
const header = (
|
|
201
|
+
<div className="flex items-center gap-3 px-4 py-3 border-b border-white/[0.06] shrink-0">
|
|
202
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-accent-bright shrink-0">
|
|
203
|
+
<rect x="2" y="3" width="20" height="14" rx="2" /><path d="M8 21h8" /><path d="M12 17v4" />
|
|
204
|
+
</svg>
|
|
205
|
+
<span className="text-[13px] font-600 text-text flex-1 truncate">
|
|
206
|
+
Canvas{agentName ? ` — ${agentName}` : ''}
|
|
207
|
+
</span>
|
|
208
|
+
<button
|
|
209
|
+
onClick={loadCanvas}
|
|
210
|
+
className="p-1.5 rounded-[6px] hover:bg-white/[0.06] transition-colors cursor-pointer border-none bg-transparent text-text-3 hover:text-text-2"
|
|
211
|
+
title="Refresh"
|
|
212
|
+
>
|
|
213
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
214
|
+
<polyline points="23 4 23 10 17 10" /><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" />
|
|
31
215
|
</svg>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
>
|
|
41
|
-
|
|
42
|
-
<path d="M18 6L6 18M6 6l12 12" />
|
|
43
|
-
</svg>
|
|
44
|
-
</button>
|
|
45
|
-
</div>
|
|
46
|
-
<div className="flex-1 flex items-center justify-center">
|
|
47
|
-
<div className="text-center">
|
|
48
|
-
<div className="w-8 h-8 rounded-full border-2 border-text-3/20 border-t-accent-bright animate-spin mx-auto mb-3" />
|
|
49
|
-
<span className="text-[13px] text-text-3">Loading canvas...</span>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
216
|
+
</button>
|
|
217
|
+
<button
|
|
218
|
+
onClick={onClose}
|
|
219
|
+
className="p-1.5 rounded-[6px] hover:bg-white/[0.06] transition-colors cursor-pointer border-none bg-transparent text-text-3 hover:text-text-2"
|
|
220
|
+
title="Close canvas"
|
|
221
|
+
>
|
|
222
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
223
|
+
<path d="M18 6L6 18M6 6l12 12" />
|
|
224
|
+
</svg>
|
|
225
|
+
</button>
|
|
52
226
|
</div>
|
|
53
227
|
)
|
|
54
228
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
<
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
</
|
|
65
|
-
<button
|
|
66
|
-
onClick={loadCanvas}
|
|
67
|
-
className="p-1.5 rounded-[6px] hover:bg-white/[0.06] transition-colors cursor-pointer border-none bg-transparent text-text-3 hover:text-text-2"
|
|
68
|
-
title="Refresh"
|
|
69
|
-
>
|
|
70
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
71
|
-
<polyline points="23 4 23 10 17 10" /><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" />
|
|
72
|
-
</svg>
|
|
73
|
-
</button>
|
|
74
|
-
<button
|
|
75
|
-
onClick={onClose}
|
|
76
|
-
className="p-1.5 rounded-[6px] hover:bg-white/[0.06] transition-colors cursor-pointer border-none bg-transparent text-text-3 hover:text-text-2"
|
|
77
|
-
title="Close canvas"
|
|
78
|
-
>
|
|
79
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
80
|
-
<path d="M18 6L6 18M6 6l12 12" />
|
|
81
|
-
</svg>
|
|
82
|
-
</button>
|
|
229
|
+
if (!loaded) {
|
|
230
|
+
return (
|
|
231
|
+
<div className="flex flex-col h-full border-l border-white/[0.06] bg-bg min-w-[400px]">
|
|
232
|
+
{header}
|
|
233
|
+
<div className="flex-1 flex items-center justify-center">
|
|
234
|
+
<div className="text-center">
|
|
235
|
+
<div className="w-8 h-8 rounded-full border-2 border-text-3/20 border-t-accent-bright animate-spin mx-auto mb-3" />
|
|
236
|
+
<span className="text-[13px] text-text-3">Loading canvas...</span>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
83
239
|
</div>
|
|
240
|
+
)
|
|
241
|
+
}
|
|
84
242
|
|
|
85
|
-
|
|
243
|
+
return (
|
|
244
|
+
<div className="flex flex-col h-full border-l border-white/[0.06] bg-bg min-w-[400px]">
|
|
245
|
+
{header}
|
|
86
246
|
<div className="flex-1 overflow-hidden">
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
247
|
+
{!content ? (
|
|
248
|
+
<div className="h-full flex items-center justify-center text-center px-6">
|
|
249
|
+
<div>
|
|
250
|
+
<div className="text-[14px] font-600 text-text-2">No canvas content yet</div>
|
|
251
|
+
<p className="mt-1 text-[12px] text-text-3/60">Agents can present HTML or structured documents here.</p>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
) : typeof content === 'string' ? (
|
|
255
|
+
<iframe
|
|
256
|
+
sandbox="allow-scripts allow-same-origin"
|
|
257
|
+
srcDoc={content}
|
|
258
|
+
className="w-full h-full border-none bg-white"
|
|
259
|
+
title="Agent Canvas"
|
|
260
|
+
/>
|
|
261
|
+
) : (
|
|
262
|
+
<StructuredCanvasView document={content} />
|
|
263
|
+
)}
|
|
93
264
|
</div>
|
|
94
265
|
</div>
|
|
95
266
|
)
|
|
@@ -136,10 +136,12 @@ export function ChatArea() {
|
|
|
136
136
|
if (cancelled || useAppStore.getState().currentSessionId !== requestedSessionId) return
|
|
137
137
|
setMessagesLoading(false)
|
|
138
138
|
})
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
|
|
140
|
+
const sessionAtLoad = useAppStore.getState().sessions[requestedSessionId]
|
|
141
|
+
if (sessionAtLoad?.active) {
|
|
141
142
|
useChatStore.setState({ streaming: true, streamingSessionId: requestedSessionId, streamText: '' })
|
|
142
143
|
}
|
|
144
|
+
|
|
143
145
|
// Refresh active state from server so returning to a session restores typing indicator.
|
|
144
146
|
loadSessions().then(() => {
|
|
145
147
|
if (cancelled || useAppStore.getState().currentSessionId !== requestedSessionId) return
|
|
@@ -148,6 +150,7 @@ export function ChatArea() {
|
|
|
148
150
|
useChatStore.setState({ streaming: true, streamingSessionId: requestedSessionId, streamText: '' })
|
|
149
151
|
}
|
|
150
152
|
}).catch((err) => console.error('Failed to refresh messages:', err))
|
|
153
|
+
|
|
151
154
|
devServer(requestedSessionId, 'status').then((r) => {
|
|
152
155
|
if (cancelled || useAppStore.getState().currentSessionId !== requestedSessionId) return
|
|
153
156
|
setDevServer(r.running ? r : null)
|
|
@@ -155,23 +158,31 @@ export function ChatArea() {
|
|
|
155
158
|
if (cancelled || useAppStore.getState().currentSessionId !== requestedSessionId) return
|
|
156
159
|
setDevServer(null)
|
|
157
160
|
})
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
} else {
|
|
161
|
+
|
|
162
|
+
return () => {
|
|
163
|
+
cancelled = true
|
|
164
|
+
}
|
|
165
|
+
}, [loadSessions, sessionId, setDevServer, setMessages])
|
|
166
|
+
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (!sessionId) return
|
|
169
|
+
let cancelled = false
|
|
170
|
+
if (!sessionHasBrowserPlugin) {
|
|
169
171
|
setBrowserActive(false)
|
|
172
|
+
return
|
|
170
173
|
}
|
|
174
|
+
checkBrowser(sessionId).then((r) => {
|
|
175
|
+
if (cancelled || useAppStore.getState().currentSessionId !== sessionId) return
|
|
176
|
+
setBrowserActive(r.active)
|
|
177
|
+
}).catch((err) => {
|
|
178
|
+
if (cancelled || useAppStore.getState().currentSessionId !== sessionId) return
|
|
179
|
+
console.error('Browser check failed:', err)
|
|
180
|
+
setBrowserActive(false)
|
|
181
|
+
})
|
|
171
182
|
return () => {
|
|
172
183
|
cancelled = true
|
|
173
184
|
}
|
|
174
|
-
}, [
|
|
185
|
+
}, [sessionHasBrowserPlugin, sessionId])
|
|
175
186
|
|
|
176
187
|
// Auto-poll messages for sessions that are actively running on the server
|
|
177
188
|
const isServerActive = session?.active === true
|
|
@@ -216,10 +227,16 @@ export function ChatArea() {
|
|
|
216
227
|
shouldPollMessages ? 2000 : undefined,
|
|
217
228
|
)
|
|
218
229
|
|
|
219
|
-
//
|
|
230
|
+
// Keep the local typing indicator aligned with the server's active state
|
|
220
231
|
useEffect(() => {
|
|
221
232
|
if (!sessionId) return
|
|
222
233
|
const state = useChatStore.getState()
|
|
234
|
+
if (isServerActive) {
|
|
235
|
+
if (!state.streaming && !state.streamText) {
|
|
236
|
+
useChatStore.setState({ streaming: true, streamingSessionId: sessionId, streamText: '' })
|
|
237
|
+
}
|
|
238
|
+
return
|
|
239
|
+
}
|
|
223
240
|
if (
|
|
224
241
|
!isServerActive
|
|
225
242
|
&& state.streaming
|
|
@@ -230,7 +247,7 @@ export function ChatArea() {
|
|
|
230
247
|
fetchMessages(sessionId).then(setMessages).catch(() => {})
|
|
231
248
|
useChatStore.setState({ streaming: false, streamingSessionId: null, streamText: '' })
|
|
232
249
|
}
|
|
233
|
-
}, [isServerActive, sessionId])
|
|
250
|
+
}, [isServerActive, sessionId, setMessages])
|
|
234
251
|
|
|
235
252
|
// Poll browser status while session has browser tools
|
|
236
253
|
const hasBrowserTool = session?.plugins?.includes('browser')
|
|
@@ -255,7 +272,7 @@ export function ChatArea() {
|
|
|
255
272
|
if (!sessionId) return
|
|
256
273
|
await devServer(sessionId, 'stop')
|
|
257
274
|
setDevServer(null)
|
|
258
|
-
}, [sessionId])
|
|
275
|
+
}, [sessionId, setDevServer])
|
|
259
276
|
|
|
260
277
|
const handleClear = useCallback(async () => {
|
|
261
278
|
setConfirmClear(false)
|
|
@@ -263,7 +280,7 @@ export function ChatArea() {
|
|
|
263
280
|
await clearMessages(sessionId)
|
|
264
281
|
setMessages([])
|
|
265
282
|
loadSessions()
|
|
266
|
-
}, [sessionId])
|
|
283
|
+
}, [loadSessions, sessionId, setMessages])
|
|
267
284
|
|
|
268
285
|
const handleDelete = useCallback(async () => {
|
|
269
286
|
setConfirmDelete(false)
|
|
@@ -271,7 +288,7 @@ export function ChatArea() {
|
|
|
271
288
|
await deleteChat(sessionId)
|
|
272
289
|
removeSessionFromStore(sessionId)
|
|
273
290
|
setCurrentSession(null)
|
|
274
|
-
}, [sessionId])
|
|
291
|
+
}, [removeSessionFromStore, sessionId, setCurrentSession])
|
|
275
292
|
|
|
276
293
|
const handlePrompt = useCallback((text: string) => {
|
|
277
294
|
sendMessage(text)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { useState } from 'react'
|
|
3
4
|
import { DEFAULT_HEARTBEAT_INTERVAL_SEC } from '@/lib/heartbeat-defaults'
|
|
4
5
|
import type { Session } from '@/types'
|
|
5
6
|
import { api } from '@/lib/api-client'
|
|
6
7
|
import { useAppStore } from '@/stores/use-app-store'
|
|
7
8
|
import { useChatStore } from '@/stores/use-chat-store'
|
|
9
|
+
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
8
10
|
import { ConnectorPlatformBadge, getSessionConnector } from '@/components/shared/connector-platform-icon'
|
|
9
11
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
10
12
|
import { toast } from 'sonner'
|
|
@@ -44,18 +46,26 @@ export function ChatCard({ session, active, onClick }: Props) {
|
|
|
44
46
|
const streamPhase = useChatStore((s) => s.streamPhase)
|
|
45
47
|
const streamToolName = useChatStore((s) => s.streamToolName)
|
|
46
48
|
const lastReadTimestamps = useAppStore((s) => s.lastReadTimestamps)
|
|
49
|
+
const [confirmDelete, setConfirmDelete] = useState(false)
|
|
50
|
+
const [deleting, setDeleting] = useState(false)
|
|
47
51
|
const isTyping = streamingSessionId === session.id
|
|
48
52
|
|
|
49
|
-
const
|
|
53
|
+
const handleDeleteClick = (e: React.MouseEvent) => {
|
|
50
54
|
e.stopPropagation()
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
setConfirmDelete(true)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const handleDelete = async () => {
|
|
59
|
+
setDeleting(true)
|
|
53
60
|
try {
|
|
54
61
|
await api('DELETE', `/chats/${session.id}`)
|
|
55
62
|
removeSession(session.id)
|
|
56
63
|
toast.success('Session deleted')
|
|
57
64
|
} catch (err: unknown) {
|
|
58
65
|
toast.error(err instanceof Error ? err.message : 'Failed to delete session')
|
|
66
|
+
} finally {
|
|
67
|
+
setDeleting(false)
|
|
68
|
+
setConfirmDelete(false)
|
|
59
69
|
}
|
|
60
70
|
}
|
|
61
71
|
|
|
@@ -84,14 +94,15 @@ export function ChatCard({ session, active, onClick }: Props) {
|
|
|
84
94
|
&& agent?.heartbeatEnabled !== false
|
|
85
95
|
|
|
86
96
|
return (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
<>
|
|
98
|
+
<div
|
|
99
|
+
onClick={onClick}
|
|
100
|
+
className={`group/card relative py-3.5 px-4 cursor-pointer rounded-[14px]
|
|
101
|
+
transition-all duration-200 active:scale-[0.98]
|
|
102
|
+
${active
|
|
103
|
+
? 'bg-accent-soft border border-accent-bright/10'
|
|
104
|
+
: 'bg-transparent border border-transparent hover:bg-white/[0.02] hover:border-white/[0.03]'}`}
|
|
105
|
+
>
|
|
95
106
|
{active && (
|
|
96
107
|
<div className="absolute left-0 top-3.5 bottom-3.5 w-[2.5px] rounded-full bg-accent-bright" />
|
|
97
108
|
)}
|
|
@@ -134,7 +145,7 @@ export function ChatCard({ session, active, onClick }: Props) {
|
|
|
134
145
|
{timeAgo(session.lastActiveAt)}
|
|
135
146
|
</span>
|
|
136
147
|
<button
|
|
137
|
-
onClick={
|
|
148
|
+
onClick={handleDeleteClick}
|
|
138
149
|
className="shrink-0 opacity-0 group-hover/card:opacity-100 transition-opacity duration-150
|
|
139
150
|
text-text-3 hover:text-red-400 p-0.5 -mr-1 cursor-pointer bg-transparent border-none"
|
|
140
151
|
title="Delete chat"
|
|
@@ -163,6 +174,18 @@ export function ChatCard({ session, active, onClick }: Props) {
|
|
|
163
174
|
) : (
|
|
164
175
|
<div className="text-[13px] text-text-2/50 truncate mt-1 leading-relaxed">{preview}</div>
|
|
165
176
|
)}
|
|
166
|
-
|
|
177
|
+
</div>
|
|
178
|
+
<ConfirmDialog
|
|
179
|
+
open={confirmDelete}
|
|
180
|
+
title="Delete Chat?"
|
|
181
|
+
message={`Delete chat session "${session.name}"?`}
|
|
182
|
+
confirmLabel={deleting ? 'Deleting...' : 'Delete'}
|
|
183
|
+
confirmDisabled={deleting}
|
|
184
|
+
cancelDisabled={deleting}
|
|
185
|
+
danger
|
|
186
|
+
onConfirm={() => { void handleDelete() }}
|
|
187
|
+
onCancel={() => { if (!deleting) setConfirmDelete(false) }}
|
|
188
|
+
/>
|
|
189
|
+
</>
|
|
167
190
|
)
|
|
168
191
|
}
|