@swarmclawai/swarmclaw 0.2.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 +577 -0
- package/bin/server-cmd.js +359 -0
- package/bin/swarmclaw.js +29 -0
- package/bin/swarmclaw.mjs +1504 -0
- package/next.config.ts +33 -0
- package/package.json +112 -0
- package/postcss.config.mjs +7 -0
- package/public/branding/swarmclaw-org-avatar.png +0 -0
- package/public/branding/swarmclaw-org-avatar.svg +58 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/connectors.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/new-session-openclaw.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/schedules.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/agents/[id]/route.ts +30 -0
- package/src/app/api/agents/[id]/thread/route.ts +66 -0
- package/src/app/api/agents/generate/route.ts +42 -0
- package/src/app/api/agents/route.ts +33 -0
- package/src/app/api/auth/route.ts +25 -0
- package/src/app/api/claude-skills/route.ts +42 -0
- package/src/app/api/clawhub/install/route.ts +39 -0
- package/src/app/api/clawhub/search/route.ts +11 -0
- package/src/app/api/connectors/[id]/route.ts +79 -0
- package/src/app/api/connectors/route.ts +60 -0
- package/src/app/api/credentials/[id]/route.ts +14 -0
- package/src/app/api/credentials/route.ts +31 -0
- package/src/app/api/daemon/health-check/route.ts +11 -0
- package/src/app/api/daemon/route.ts +22 -0
- package/src/app/api/dirs/pick/route.ts +60 -0
- package/src/app/api/dirs/route.ts +29 -0
- package/src/app/api/documents/[id]/route.ts +47 -0
- package/src/app/api/documents/route.ts +93 -0
- package/src/app/api/files/serve/route.ts +69 -0
- package/src/app/api/generate/info/route.ts +12 -0
- package/src/app/api/generate/route.ts +106 -0
- package/src/app/api/ip/route.ts +6 -0
- package/src/app/api/knowledge/[id]/route.ts +61 -0
- package/src/app/api/knowledge/route.ts +48 -0
- package/src/app/api/knowledge/upload/route.ts +86 -0
- package/src/app/api/logs/route.ts +65 -0
- package/src/app/api/mcp-servers/[id]/route.ts +32 -0
- package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
- package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
- package/src/app/api/mcp-servers/route.ts +27 -0
- package/src/app/api/memory/[id]/route.ts +126 -0
- package/src/app/api/memory/maintenance/route.ts +63 -0
- package/src/app/api/memory/route.ts +111 -0
- package/src/app/api/memory-images/[filename]/route.ts +36 -0
- package/src/app/api/orchestrator/run/route.ts +43 -0
- package/src/app/api/plugins/install/route.ts +58 -0
- package/src/app/api/plugins/marketplace/route.ts +33 -0
- package/src/app/api/plugins/route.ts +21 -0
- package/src/app/api/preview-server/route.ts +339 -0
- package/src/app/api/providers/[id]/models/route.ts +29 -0
- package/src/app/api/providers/[id]/route.ts +34 -0
- package/src/app/api/providers/configs/route.ts +7 -0
- package/src/app/api/providers/ollama/route.ts +30 -0
- package/src/app/api/providers/openclaw/health/route.ts +23 -0
- package/src/app/api/providers/route.ts +28 -0
- package/src/app/api/runs/[id]/route.ts +9 -0
- package/src/app/api/runs/route.ts +13 -0
- package/src/app/api/schedules/[id]/route.ts +28 -0
- package/src/app/api/schedules/[id]/run/route.ts +104 -0
- package/src/app/api/schedules/route.ts +78 -0
- package/src/app/api/secrets/[id]/route.ts +29 -0
- package/src/app/api/secrets/route.ts +42 -0
- package/src/app/api/sessions/[id]/browser/route.ts +13 -0
- package/src/app/api/sessions/[id]/chat/route.ts +96 -0
- package/src/app/api/sessions/[id]/clear/route.ts +19 -0
- package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
- package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
- package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
- package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
- package/src/app/api/sessions/[id]/messages/route.ts +9 -0
- package/src/app/api/sessions/[id]/retry/route.ts +28 -0
- package/src/app/api/sessions/[id]/route.ts +103 -0
- package/src/app/api/sessions/[id]/stop/route.ts +13 -0
- package/src/app/api/sessions/heartbeat/route.ts +26 -0
- package/src/app/api/sessions/route.ts +85 -0
- package/src/app/api/settings/route.ts +58 -0
- package/src/app/api/setup/check-provider/route.ts +326 -0
- package/src/app/api/setup/doctor/route.ts +250 -0
- package/src/app/api/skills/[id]/route.ts +40 -0
- package/src/app/api/skills/import/route.ts +69 -0
- package/src/app/api/skills/route.ts +28 -0
- package/src/app/api/tasks/[id]/route.ts +102 -0
- package/src/app/api/tasks/route.ts +115 -0
- package/src/app/api/tts/route.ts +40 -0
- package/src/app/api/upload/route.ts +18 -0
- package/src/app/api/uploads/[filename]/route.ts +59 -0
- package/src/app/api/usage/route.ts +35 -0
- package/src/app/api/version/route.ts +81 -0
- package/src/app/api/version/update/route.ts +95 -0
- package/src/app/api/webhooks/[id]/history/route.ts +13 -0
- package/src/app/api/webhooks/[id]/route.ts +204 -0
- package/src/app/api/webhooks/route.ts +37 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +370 -0
- package/src/app/layout.tsx +52 -0
- package/src/app/page.tsx +172 -0
- package/src/cli/index.js +1232 -0
- package/src/cli/index.test.js +281 -0
- package/src/cli/index.ts +1158 -0
- package/src/cli/spec.js +284 -0
- package/src/components/agents/agent-card.tsx +219 -0
- package/src/components/agents/agent-chat-list.tsx +165 -0
- package/src/components/agents/agent-list.tsx +110 -0
- package/src/components/agents/agent-sheet.tsx +1220 -0
- package/src/components/auth/access-key-gate.tsx +248 -0
- package/src/components/auth/setup-wizard.tsx +940 -0
- package/src/components/auth/user-picker.tsx +88 -0
- package/src/components/chat/chat-area.tsx +406 -0
- package/src/components/chat/chat-header.tsx +491 -0
- package/src/components/chat/chat-tool-toggles.tsx +161 -0
- package/src/components/chat/code-block.tsx +146 -0
- package/src/components/chat/dev-server-bar.tsx +39 -0
- package/src/components/chat/message-bubble.tsx +486 -0
- package/src/components/chat/message-list.tsx +299 -0
- package/src/components/chat/session-debug-panel.tsx +196 -0
- package/src/components/chat/streaming-bubble.tsx +85 -0
- package/src/components/chat/thinking-indicator.tsx +26 -0
- package/src/components/chat/tool-call-bubble.tsx +438 -0
- package/src/components/chat/tool-request-banner.tsx +103 -0
- package/src/components/connectors/connector-list.tsx +196 -0
- package/src/components/connectors/connector-sheet.tsx +804 -0
- package/src/components/input/chat-input.tsx +235 -0
- package/src/components/knowledge/knowledge-list.tsx +206 -0
- package/src/components/knowledge/knowledge-sheet.tsx +316 -0
- package/src/components/layout/app-layout.tsx +1016 -0
- package/src/components/layout/daemon-indicator.tsx +56 -0
- package/src/components/layout/mobile-header.tsx +31 -0
- package/src/components/layout/network-banner.tsx +17 -0
- package/src/components/layout/update-banner.tsx +130 -0
- package/src/components/logs/log-list.tsx +358 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
- package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
- package/src/components/memory/memory-card.tsx +63 -0
- package/src/components/memory/memory-detail.tsx +339 -0
- package/src/components/memory/memory-list.tsx +198 -0
- package/src/components/memory/memory-sheet.tsx +70 -0
- package/src/components/plugins/plugin-list.tsx +60 -0
- package/src/components/plugins/plugin-sheet.tsx +311 -0
- package/src/components/providers/provider-list.tsx +96 -0
- package/src/components/providers/provider-sheet.tsx +542 -0
- package/src/components/runs/run-list.tsx +231 -0
- package/src/components/schedules/schedule-card.tsx +63 -0
- package/src/components/schedules/schedule-list.tsx +76 -0
- package/src/components/schedules/schedule-sheet.tsx +336 -0
- package/src/components/secrets/secret-sheet.tsx +180 -0
- package/src/components/secrets/secrets-list.tsx +91 -0
- package/src/components/sessions/new-session-sheet.tsx +478 -0
- package/src/components/sessions/session-card.tsx +144 -0
- package/src/components/sessions/session-list.tsx +202 -0
- package/src/components/shared/ai-gen-block.tsx +77 -0
- package/src/components/shared/avatar.tsx +48 -0
- package/src/components/shared/bottom-sheet.tsx +30 -0
- package/src/components/shared/confirm-dialog.tsx +47 -0
- package/src/components/shared/connector-platform-icon.tsx +113 -0
- package/src/components/shared/dir-browser.tsx +285 -0
- package/src/components/shared/dropdown.tsx +55 -0
- package/src/components/shared/icon-button.tsx +25 -0
- package/src/components/shared/settings/plugin-manager.tsx +207 -0
- package/src/components/shared/settings/section-capability-policy.tsx +93 -0
- package/src/components/shared/settings/section-embedding.tsx +99 -0
- package/src/components/shared/settings/section-heartbeat.tsx +168 -0
- package/src/components/shared/settings/section-memory.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +108 -0
- package/src/components/shared/settings/section-providers.tsx +181 -0
- package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
- package/src/components/shared/settings/section-secrets.tsx +132 -0
- package/src/components/shared/settings/section-user-preferences.tsx +24 -0
- package/src/components/shared/settings/section-voice.tsx +53 -0
- package/src/components/shared/settings/settings-sheet.tsx +88 -0
- package/src/components/shared/settings/types.ts +7 -0
- package/src/components/shared/settings/utils.ts +13 -0
- package/src/components/shared/settings-sheet.tsx +1 -0
- package/src/components/shared/skeleton.tsx +19 -0
- package/src/components/shared/usage-badge.tsx +28 -0
- package/src/components/skills/clawhub-browser.tsx +225 -0
- package/src/components/skills/skill-list.tsx +70 -0
- package/src/components/skills/skill-sheet.tsx +254 -0
- package/src/components/tasks/task-board.tsx +96 -0
- package/src/components/tasks/task-card.tsx +179 -0
- package/src/components/tasks/task-column.tsx +73 -0
- package/src/components/tasks/task-list.tsx +118 -0
- package/src/components/tasks/task-sheet.tsx +415 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sonner.tsx +22 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/components/usage/usage-list.tsx +105 -0
- package/src/components/webhooks/webhook-list.tsx +166 -0
- package/src/components/webhooks/webhook-sheet.tsx +402 -0
- package/src/hooks/use-auto-resize.ts +20 -0
- package/src/hooks/use-media-query.ts +21 -0
- package/src/hooks/use-speech-recognition.ts +83 -0
- package/src/instrumentation.ts +8 -0
- package/src/lib/agents.ts +13 -0
- package/src/lib/api-client.ts +100 -0
- package/src/lib/chat.ts +60 -0
- package/src/lib/memory.ts +42 -0
- package/src/lib/openclaw-endpoint.test.ts +48 -0
- package/src/lib/openclaw-endpoint.ts +67 -0
- package/src/lib/provider-config.ts +13 -0
- package/src/lib/providers/anthropic.ts +135 -0
- package/src/lib/providers/claude-cli.ts +202 -0
- package/src/lib/providers/codex-cli.ts +260 -0
- package/src/lib/providers/index.ts +351 -0
- package/src/lib/providers/ollama.ts +131 -0
- package/src/lib/providers/openai.ts +164 -0
- package/src/lib/providers/openclaw.ts +330 -0
- package/src/lib/providers/opencode-cli.ts +164 -0
- package/src/lib/runtime-loop.ts +15 -0
- package/src/lib/schedule-dedupe.test.ts +84 -0
- package/src/lib/schedule-dedupe.ts +174 -0
- package/src/lib/schedule-name.ts +62 -0
- package/src/lib/schedules.ts +16 -0
- package/src/lib/server/agent-registry.ts +70 -0
- package/src/lib/server/api-routes.test.ts +362 -0
- package/src/lib/server/autonomy-contract.ts +200 -0
- package/src/lib/server/build-llm.ts +155 -0
- package/src/lib/server/capability-router.test.ts +21 -0
- package/src/lib/server/capability-router.ts +172 -0
- package/src/lib/server/chat-execution.ts +894 -0
- package/src/lib/server/clawhub-client.test.ts +161 -0
- package/src/lib/server/clawhub-client.ts +26 -0
- package/src/lib/server/connectors/connector-routing.test.ts +243 -0
- package/src/lib/server/connectors/discord.ts +116 -0
- package/src/lib/server/connectors/googlechat.ts +66 -0
- package/src/lib/server/connectors/manager.ts +559 -0
- package/src/lib/server/connectors/matrix.ts +78 -0
- package/src/lib/server/connectors/media.ts +149 -0
- package/src/lib/server/connectors/openclaw.test.ts +375 -0
- package/src/lib/server/connectors/openclaw.ts +1132 -0
- package/src/lib/server/connectors/signal.ts +183 -0
- package/src/lib/server/connectors/slack.ts +258 -0
- package/src/lib/server/connectors/teams.ts +94 -0
- package/src/lib/server/connectors/telegram.ts +221 -0
- package/src/lib/server/connectors/types.ts +62 -0
- package/src/lib/server/connectors/whatsapp.ts +349 -0
- package/src/lib/server/context-manager.ts +232 -0
- package/src/lib/server/cost.ts +31 -0
- package/src/lib/server/daemon-state.ts +354 -0
- package/src/lib/server/data-dir.ts +3 -0
- package/src/lib/server/embeddings.ts +111 -0
- package/src/lib/server/execution-log.ts +257 -0
- package/src/lib/server/gateway/protocol.test.ts +54 -0
- package/src/lib/server/gateway/protocol.ts +114 -0
- package/src/lib/server/heartbeat-service.ts +366 -0
- package/src/lib/server/knowledge-db.test.ts +441 -0
- package/src/lib/server/logger.ts +47 -0
- package/src/lib/server/main-agent-loop.ts +1017 -0
- package/src/lib/server/mcp-client.test.ts +342 -0
- package/src/lib/server/mcp-client.ts +130 -0
- package/src/lib/server/memory-db.ts +1078 -0
- package/src/lib/server/memory-graph.test.ts +153 -0
- package/src/lib/server/memory-graph.ts +138 -0
- package/src/lib/server/openclaw-health.ts +245 -0
- package/src/lib/server/orchestrator-lg.ts +431 -0
- package/src/lib/server/orchestrator.ts +364 -0
- package/src/lib/server/playwright-proxy.mjs +70 -0
- package/src/lib/server/plugins.ts +229 -0
- package/src/lib/server/process-manager.ts +327 -0
- package/src/lib/server/provider-health.ts +113 -0
- package/src/lib/server/queue.ts +859 -0
- package/src/lib/server/runtime-settings.ts +119 -0
- package/src/lib/server/scheduler.ts +196 -0
- package/src/lib/server/session-mailbox.ts +129 -0
- package/src/lib/server/session-run-manager.ts +512 -0
- package/src/lib/server/session-tools/connector.ts +124 -0
- package/src/lib/server/session-tools/context-mgmt.ts +103 -0
- package/src/lib/server/session-tools/context.ts +114 -0
- package/src/lib/server/session-tools/crud.ts +673 -0
- package/src/lib/server/session-tools/delegate.ts +708 -0
- package/src/lib/server/session-tools/file.ts +264 -0
- package/src/lib/server/session-tools/index.ts +164 -0
- package/src/lib/server/session-tools/memory.ts +230 -0
- package/src/lib/server/session-tools/session-info.ts +422 -0
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
- package/src/lib/server/session-tools/shell.ts +171 -0
- package/src/lib/server/session-tools/web.ts +408 -0
- package/src/lib/server/session-tools.ts +9 -0
- package/src/lib/server/skills-normalize.ts +130 -0
- package/src/lib/server/storage-mcp.test.ts +161 -0
- package/src/lib/server/storage.ts +670 -0
- package/src/lib/server/stream-agent-chat.ts +571 -0
- package/src/lib/server/task-reports.ts +122 -0
- package/src/lib/server/task-result.ts +161 -0
- package/src/lib/server/task-validation.test.ts +27 -0
- package/src/lib/server/task-validation.ts +90 -0
- package/src/lib/server/tool-capability-policy.test.ts +58 -0
- package/src/lib/server/tool-capability-policy.ts +262 -0
- package/src/lib/sessions.ts +68 -0
- package/src/lib/tasks.ts +20 -0
- package/src/lib/tts.ts +42 -0
- package/src/lib/upload.ts +10 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +43 -0
- package/src/stores/use-app-store.ts +468 -0
- package/src/stores/use-chat-store.ts +323 -0
- package/src/types/index.ts +621 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { api } from '@/lib/api-client'
|
|
6
|
+
|
|
7
|
+
export function UserPicker() {
|
|
8
|
+
const setUser = useAppStore((s) => s.setUser)
|
|
9
|
+
const [name, setName] = useState('')
|
|
10
|
+
|
|
11
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
12
|
+
e.preventDefault()
|
|
13
|
+
const trimmed = name.trim()
|
|
14
|
+
if (!trimmed) return
|
|
15
|
+
const userName = trimmed.toLowerCase()
|
|
16
|
+
// Save server-side so it persists across devices
|
|
17
|
+
try {
|
|
18
|
+
await api('PUT', '/settings', { userName })
|
|
19
|
+
} catch { /* still set locally */ }
|
|
20
|
+
setUser(userName)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="h-full flex flex-col items-center justify-center px-8 bg-bg relative overflow-hidden">
|
|
25
|
+
{/* Atmospheric gradient mesh */}
|
|
26
|
+
<div className="absolute inset-0 pointer-events-none">
|
|
27
|
+
<div className="absolute top-[30%] left-[50%] -translate-x-1/2 -translate-y-1/2 w-[600px] h-[400px]"
|
|
28
|
+
style={{
|
|
29
|
+
background: 'radial-gradient(ellipse at center, rgba(99,102,241,0.06) 0%, transparent 70%)',
|
|
30
|
+
animation: 'glow-pulse 6s ease-in-out infinite',
|
|
31
|
+
}} />
|
|
32
|
+
<div className="absolute bottom-[20%] left-[30%] w-[300px] h-[300px]"
|
|
33
|
+
style={{
|
|
34
|
+
background: 'radial-gradient(circle, rgba(236,72,153,0.03) 0%, transparent 70%)',
|
|
35
|
+
animation: 'glow-pulse 8s ease-in-out infinite 2s',
|
|
36
|
+
}} />
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div className="relative max-w-[420px] w-full text-center"
|
|
40
|
+
style={{ animation: 'fade-in 0.6s cubic-bezier(0.16, 1, 0.3, 1)' }}>
|
|
41
|
+
|
|
42
|
+
{/* Sparkle icon */}
|
|
43
|
+
<div className="flex justify-center mb-6">
|
|
44
|
+
<div className="relative w-12 h-12">
|
|
45
|
+
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" className="text-accent-bright"
|
|
46
|
+
style={{ animation: 'sparkle-spin 8s linear infinite' }}>
|
|
47
|
+
<path d="M24 4L27.5 18.5L42 24L27.5 29.5L24 44L20.5 29.5L6 24L20.5 18.5L24 4Z"
|
|
48
|
+
fill="currentColor" opacity="0.9" />
|
|
49
|
+
</svg>
|
|
50
|
+
<div className="absolute inset-0 blur-xl bg-accent-bright/20" />
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<h1 className="font-display text-[42px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
|
|
55
|
+
Welcome
|
|
56
|
+
</h1>
|
|
57
|
+
<p className="text-[15px] text-text-2 mb-10">
|
|
58
|
+
What should we call you?
|
|
59
|
+
</p>
|
|
60
|
+
|
|
61
|
+
<form onSubmit={handleSubmit} className="flex flex-col items-center gap-5">
|
|
62
|
+
<input
|
|
63
|
+
type="text"
|
|
64
|
+
value={name}
|
|
65
|
+
onChange={(e) => setName(e.target.value)}
|
|
66
|
+
placeholder="Your name"
|
|
67
|
+
autoFocus
|
|
68
|
+
className="w-full max-w-[280px] px-6 py-4 rounded-[16px] border border-white/[0.08] bg-surface
|
|
69
|
+
text-text text-[18px] text-center font-display font-600 outline-none
|
|
70
|
+
transition-all duration-200 placeholder:text-text-3/70
|
|
71
|
+
focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
|
|
72
|
+
style={{ fontFamily: 'inherit' }}
|
|
73
|
+
/>
|
|
74
|
+
<button
|
|
75
|
+
type="submit"
|
|
76
|
+
disabled={!name.trim()}
|
|
77
|
+
className="px-12 py-4 rounded-[16px] border-none bg-[#6366F1] text-white text-[16px] font-display font-600
|
|
78
|
+
cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
|
|
79
|
+
shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
|
|
80
|
+
style={{ fontFamily: 'inherit' }}
|
|
81
|
+
>
|
|
82
|
+
Get Started
|
|
83
|
+
</button>
|
|
84
|
+
</form>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useCallback, useState, useRef } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { useChatStore } from '@/stores/use-chat-store'
|
|
6
|
+
import { fetchMessages, clearMessages, deleteSession, devServer, checkBrowser, stopBrowser } from '@/lib/sessions'
|
|
7
|
+
import { uploadImage } from '@/lib/upload'
|
|
8
|
+
import { deleteAgent } from '@/lib/agents'
|
|
9
|
+
import { useMediaQuery } from '@/hooks/use-media-query'
|
|
10
|
+
import { ChatHeader } from './chat-header'
|
|
11
|
+
import { DevServerBar } from './dev-server-bar'
|
|
12
|
+
import { MessageList } from './message-list'
|
|
13
|
+
import { SessionDebugPanel } from './session-debug-panel'
|
|
14
|
+
import { ChatInput } from '@/components/input/chat-input'
|
|
15
|
+
import { Dropdown, DropdownItem } from '@/components/shared/dropdown'
|
|
16
|
+
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
17
|
+
import { speak } from '@/lib/tts'
|
|
18
|
+
|
|
19
|
+
const PROMPT_SUGGESTIONS = [
|
|
20
|
+
{ text: 'List all my sessions and agents', icon: 'book', gradient: 'from-[#6366F1]/10 to-[#818CF8]/5' },
|
|
21
|
+
{ text: 'Help me set up a new connector', icon: 'link', gradient: 'from-[#EC4899]/10 to-[#F472B6]/5' },
|
|
22
|
+
{ text: 'Create a new agent for me', icon: 'bot', gradient: 'from-[#34D399]/10 to-[#6EE7B7]/5' },
|
|
23
|
+
{ text: 'Schedule a recurring task', icon: 'check', gradient: 'from-[#F59E0B]/10 to-[#FBBF24]/5' },
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
export function ChatArea() {
|
|
27
|
+
const session = useAppStore((s) => {
|
|
28
|
+
const id = s.currentSessionId
|
|
29
|
+
return id ? s.sessions[id] : null
|
|
30
|
+
})
|
|
31
|
+
const sessionId = useAppStore((s) => s.currentSessionId)
|
|
32
|
+
const currentUser = useAppStore((s) => s.currentUser)
|
|
33
|
+
const setCurrentSession = useAppStore((s) => s.setCurrentSession)
|
|
34
|
+
const removeSessionFromStore = useAppStore((s) => s.removeSession)
|
|
35
|
+
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
36
|
+
const appSettings = useAppStore((s) => s.appSettings)
|
|
37
|
+
const { messages, setMessages, streaming, streamingSessionId, sendMessage, stopStreaming, devServer: devServerStatus, setDevServer, debugOpen, setDebugOpen, ttsEnabled } = useChatStore()
|
|
38
|
+
const isDesktop = useMediaQuery('(min-width: 768px)')
|
|
39
|
+
|
|
40
|
+
const agents = useAppStore((s) => s.agents)
|
|
41
|
+
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
42
|
+
const setEditingAgentId = useAppStore((s) => s.setEditingAgentId)
|
|
43
|
+
const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
|
|
44
|
+
|
|
45
|
+
const [menuOpen, setMenuOpen] = useState(false)
|
|
46
|
+
const [confirmDelete, setConfirmDelete] = useState(false)
|
|
47
|
+
const [confirmClear, setConfirmClear] = useState(false)
|
|
48
|
+
const [confirmDeleteAgent, setConfirmDeleteAgent] = useState(false)
|
|
49
|
+
const [browserActive, setBrowserActive] = useState(false)
|
|
50
|
+
const [isDragging, setIsDragging] = useState(false)
|
|
51
|
+
const dragCounter = useRef(0)
|
|
52
|
+
const setPendingImage = useChatStore((s) => s.setPendingImage)
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!sessionId) return
|
|
56
|
+
const chatState = useChatStore.getState()
|
|
57
|
+
const preserveLocalStream = chatState.streaming && chatState.streamingSessionId === sessionId
|
|
58
|
+
// Clear stale state from the previous session, but keep active local stream state for this session.
|
|
59
|
+
setMessages([])
|
|
60
|
+
if (!preserveLocalStream) {
|
|
61
|
+
useChatStore.setState({ streaming: false, streamingSessionId: null, streamText: '', toolEvents: [] })
|
|
62
|
+
}
|
|
63
|
+
fetchMessages(sessionId).then(setMessages).catch((err) => {
|
|
64
|
+
console.error('Failed to load messages:', err)
|
|
65
|
+
setMessages(session?.messages || [])
|
|
66
|
+
})
|
|
67
|
+
// If server reports session is still active, show streaming state
|
|
68
|
+
if (session?.active) {
|
|
69
|
+
useChatStore.setState({ streaming: true, streamingSessionId: sessionId, streamText: '' })
|
|
70
|
+
}
|
|
71
|
+
// Refresh active state from server so returning to a session restores typing indicator.
|
|
72
|
+
loadSessions().then(() => {
|
|
73
|
+
const refreshed = useAppStore.getState().sessions[sessionId]
|
|
74
|
+
if (refreshed?.active) {
|
|
75
|
+
useChatStore.setState({ streaming: true, streamingSessionId: sessionId, streamText: '' })
|
|
76
|
+
}
|
|
77
|
+
}).catch((err) => console.error('Failed to refresh messages:', err))
|
|
78
|
+
devServer(sessionId, 'status').then((r) => {
|
|
79
|
+
setDevServer(r.running ? r : null)
|
|
80
|
+
}).catch(() => setDevServer(null))
|
|
81
|
+
// Check browser status
|
|
82
|
+
if (session?.tools?.includes('browser')) {
|
|
83
|
+
checkBrowser(sessionId).then((r) => setBrowserActive(r.active)).catch((err) => { console.error('Browser check failed:', err); setBrowserActive(false) })
|
|
84
|
+
} else {
|
|
85
|
+
setBrowserActive(false)
|
|
86
|
+
}
|
|
87
|
+
}, [sessionId])
|
|
88
|
+
|
|
89
|
+
// Auto-poll messages for orchestrated or server-active sessions
|
|
90
|
+
const isOrchestrated = session?.sessionType === 'orchestrated'
|
|
91
|
+
const isServerActive = session?.active === true
|
|
92
|
+
const isOngoingMonitored = appSettings.loopMode === 'ongoing' && !!session?.tools?.length
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (!sessionId || (!isOrchestrated && !isServerActive && !isOngoingMonitored)) return
|
|
95
|
+
const interval = setInterval(async () => {
|
|
96
|
+
try {
|
|
97
|
+
const msgs = await fetchMessages(sessionId)
|
|
98
|
+
if (msgs.length > messages.length) {
|
|
99
|
+
const newMsgs = msgs.slice(messages.length)
|
|
100
|
+
setMessages(msgs)
|
|
101
|
+
if (ttsEnabled && typeof document !== 'undefined' && document.visibilityState === 'visible') {
|
|
102
|
+
const latestAssistant = [...newMsgs].reverse().find((m) => {
|
|
103
|
+
if (m.role !== 'assistant') return false
|
|
104
|
+
const isHeartbeat = m.kind === 'heartbeat' || /^\s*HEARTBEAT_OK\b/i.test(m.text || '')
|
|
105
|
+
return !isHeartbeat && !!m.text?.trim()
|
|
106
|
+
})
|
|
107
|
+
if (latestAssistant?.text) {
|
|
108
|
+
void speak(latestAssistant.text)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Check if session is still active on the server
|
|
113
|
+
if (isServerActive) {
|
|
114
|
+
await loadSessions()
|
|
115
|
+
}
|
|
116
|
+
} catch (err) { console.error('Failed to refresh messages:', err) }
|
|
117
|
+
}, 2000)
|
|
118
|
+
return () => clearInterval(interval)
|
|
119
|
+
}, [sessionId, isOrchestrated, isServerActive, isOngoingMonitored, messages.length])
|
|
120
|
+
|
|
121
|
+
// When server-active flag drops, stop the streaming indicator
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (!sessionId) return
|
|
124
|
+
const state = useChatStore.getState()
|
|
125
|
+
if (
|
|
126
|
+
!isServerActive
|
|
127
|
+
&& state.streaming
|
|
128
|
+
&& (state.streamingSessionId === sessionId || state.streamingSessionId == null)
|
|
129
|
+
&& !state.streamText
|
|
130
|
+
) {
|
|
131
|
+
// Server finished but we weren't the ones streaming — clear the indicator
|
|
132
|
+
fetchMessages(sessionId).then(setMessages).catch(() => {})
|
|
133
|
+
useChatStore.setState({ streaming: false, streamingSessionId: null, streamText: '' })
|
|
134
|
+
}
|
|
135
|
+
}, [isServerActive, sessionId])
|
|
136
|
+
|
|
137
|
+
// Poll browser status while session has browser tools
|
|
138
|
+
const hasBrowserTool = session?.tools?.includes('browser')
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
if (!sessionId || !hasBrowserTool) return
|
|
141
|
+
const interval = setInterval(() => {
|
|
142
|
+
checkBrowser(sessionId).then((r) => setBrowserActive(r.active)).catch(() => {})
|
|
143
|
+
}, 5000)
|
|
144
|
+
return () => clearInterval(interval)
|
|
145
|
+
}, [sessionId, hasBrowserTool])
|
|
146
|
+
|
|
147
|
+
const handleStopBrowser = useCallback(async () => {
|
|
148
|
+
if (!sessionId) return
|
|
149
|
+
await stopBrowser(sessionId)
|
|
150
|
+
setBrowserActive(false)
|
|
151
|
+
}, [sessionId])
|
|
152
|
+
|
|
153
|
+
const handleStopDevServer = useCallback(async () => {
|
|
154
|
+
if (!sessionId) return
|
|
155
|
+
await devServer(sessionId, 'stop')
|
|
156
|
+
setDevServer(null)
|
|
157
|
+
}, [sessionId])
|
|
158
|
+
|
|
159
|
+
const handleClear = useCallback(async () => {
|
|
160
|
+
setConfirmClear(false)
|
|
161
|
+
if (!sessionId) return
|
|
162
|
+
await clearMessages(sessionId)
|
|
163
|
+
setMessages([])
|
|
164
|
+
loadSessions()
|
|
165
|
+
}, [sessionId])
|
|
166
|
+
|
|
167
|
+
const handleDelete = useCallback(async () => {
|
|
168
|
+
setConfirmDelete(false)
|
|
169
|
+
if (!sessionId) return
|
|
170
|
+
await deleteSession(sessionId)
|
|
171
|
+
removeSessionFromStore(sessionId)
|
|
172
|
+
setCurrentSession(null)
|
|
173
|
+
}, [sessionId])
|
|
174
|
+
|
|
175
|
+
const handleBack = useCallback(() => {
|
|
176
|
+
setCurrentSession(null)
|
|
177
|
+
}, [])
|
|
178
|
+
|
|
179
|
+
const handlePrompt = useCallback((text: string) => {
|
|
180
|
+
sendMessage(text)
|
|
181
|
+
}, [sendMessage])
|
|
182
|
+
|
|
183
|
+
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
184
|
+
e.preventDefault()
|
|
185
|
+
e.stopPropagation()
|
|
186
|
+
}, [])
|
|
187
|
+
|
|
188
|
+
const handleDragEnter = useCallback((e: React.DragEvent) => {
|
|
189
|
+
e.preventDefault()
|
|
190
|
+
e.stopPropagation()
|
|
191
|
+
dragCounter.current++
|
|
192
|
+
if (e.dataTransfer.types.includes('Files')) setIsDragging(true)
|
|
193
|
+
}, [])
|
|
194
|
+
|
|
195
|
+
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
|
196
|
+
e.preventDefault()
|
|
197
|
+
e.stopPropagation()
|
|
198
|
+
dragCounter.current--
|
|
199
|
+
if (dragCounter.current === 0) setIsDragging(false)
|
|
200
|
+
}, [])
|
|
201
|
+
|
|
202
|
+
const handleDrop = useCallback(async (e: React.DragEvent) => {
|
|
203
|
+
e.preventDefault()
|
|
204
|
+
e.stopPropagation()
|
|
205
|
+
dragCounter.current = 0
|
|
206
|
+
setIsDragging(false)
|
|
207
|
+
const file = e.dataTransfer.files?.[0]
|
|
208
|
+
if (!file) return
|
|
209
|
+
try {
|
|
210
|
+
const result = await uploadImage(file)
|
|
211
|
+
setPendingImage({ file, path: result.path, url: result.url })
|
|
212
|
+
} catch {
|
|
213
|
+
// ignore
|
|
214
|
+
}
|
|
215
|
+
}, [setPendingImage])
|
|
216
|
+
|
|
217
|
+
if (!session) return null
|
|
218
|
+
|
|
219
|
+
const streamingForThisSession = streaming && (!streamingSessionId || streamingSessionId === session.id)
|
|
220
|
+
const isMainChat = session.name === '__main__'
|
|
221
|
+
const isEmpty = !messages.length && !streamingForThisSession
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<div
|
|
225
|
+
className="flex-1 flex flex-col h-full min-h-0 relative"
|
|
226
|
+
onDragOver={handleDragOver}
|
|
227
|
+
onDragEnter={handleDragEnter}
|
|
228
|
+
onDragLeave={handleDragLeave}
|
|
229
|
+
onDrop={handleDrop}
|
|
230
|
+
>
|
|
231
|
+
{isDesktop && (
|
|
232
|
+
<ChatHeader
|
|
233
|
+
session={session}
|
|
234
|
+
streaming={streamingForThisSession}
|
|
235
|
+
onStop={stopStreaming}
|
|
236
|
+
onMenuToggle={() => setMenuOpen(!menuOpen)}
|
|
237
|
+
onBack={handleBack}
|
|
238
|
+
browserActive={browserActive}
|
|
239
|
+
onStopBrowser={handleStopBrowser}
|
|
240
|
+
/>
|
|
241
|
+
)}
|
|
242
|
+
{!isDesktop && (
|
|
243
|
+
<ChatHeader
|
|
244
|
+
session={session}
|
|
245
|
+
streaming={streamingForThisSession}
|
|
246
|
+
onStop={stopStreaming}
|
|
247
|
+
onMenuToggle={() => setMenuOpen(!menuOpen)}
|
|
248
|
+
mobile
|
|
249
|
+
browserActive={browserActive}
|
|
250
|
+
onStopBrowser={handleStopBrowser}
|
|
251
|
+
/>
|
|
252
|
+
)}
|
|
253
|
+
<DevServerBar status={devServerStatus} onStop={handleStopDevServer} />
|
|
254
|
+
|
|
255
|
+
{isEmpty ? (
|
|
256
|
+
<div className="flex-1 flex flex-col items-center justify-center px-6 pb-4 relative">
|
|
257
|
+
{/* Atmospheric background glow */}
|
|
258
|
+
<div className="absolute inset-0 pointer-events-none overflow-hidden">
|
|
259
|
+
<div className="absolute top-[20%] left-[50%] -translate-x-1/2 w-[500px] h-[300px]"
|
|
260
|
+
style={{
|
|
261
|
+
background: 'radial-gradient(ellipse at center, rgba(99,102,241,0.05) 0%, transparent 70%)',
|
|
262
|
+
animation: 'glow-pulse 6s ease-in-out infinite',
|
|
263
|
+
}} />
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<div className="relative max-w-[560px] w-full text-center mb-10"
|
|
267
|
+
style={{ animation: 'fade-in 0.5s cubic-bezier(0.16, 1, 0.3, 1)' }}>
|
|
268
|
+
{/* Sparkle */}
|
|
269
|
+
<div className="flex justify-center mb-5">
|
|
270
|
+
<div className="relative">
|
|
271
|
+
<svg width="32" height="32" viewBox="0 0 48 48" fill="none" className="text-accent-bright"
|
|
272
|
+
style={{ animation: 'sparkle-spin 8s linear infinite' }}>
|
|
273
|
+
<path d="M24 4L27.5 18.5L42 24L27.5 29.5L24 44L20.5 29.5L6 24L20.5 18.5L24 4Z"
|
|
274
|
+
fill="currentColor" opacity="0.8" />
|
|
275
|
+
</svg>
|
|
276
|
+
<div className="absolute inset-0 blur-lg bg-accent-bright/20" />
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
|
|
280
|
+
<h1 className="font-display text-[28px] md:text-[36px] font-800 leading-[1.1] tracking-[-0.04em] mb-3">
|
|
281
|
+
Hi{currentUser ? ', ' : ' '}<span className="text-accent-bright">{currentUser || 'there'}</span>
|
|
282
|
+
<br />
|
|
283
|
+
<span className="text-text-2">How can I help?</span>
|
|
284
|
+
</h1>
|
|
285
|
+
<p className="text-[13px] text-text-3 mt-2">
|
|
286
|
+
Pick a prompt or type your own below
|
|
287
|
+
</p>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
<div className="relative grid grid-cols-2 md:grid-cols-4 gap-3 max-w-[640px] w-full mb-6">
|
|
291
|
+
{PROMPT_SUGGESTIONS.map((prompt, i) => (
|
|
292
|
+
<button
|
|
293
|
+
key={prompt.text}
|
|
294
|
+
onClick={() => handlePrompt(prompt.text)}
|
|
295
|
+
className={`suggestion-card p-4 rounded-[14px] border border-white/[0.04] bg-gradient-to-br ${prompt.gradient}
|
|
296
|
+
text-left cursor-pointer flex flex-col gap-3 min-h-[110px] active:scale-[0.97]`}
|
|
297
|
+
style={{ fontFamily: 'inherit', animation: `fade-in 0.4s cubic-bezier(0.16, 1, 0.3, 1) ${i * 0.07 + 0.15}s both` }}
|
|
298
|
+
>
|
|
299
|
+
<PromptIcon type={prompt.icon} />
|
|
300
|
+
<span className="text-[12px] text-text-2/80 leading-snug flex-1">{prompt.text}</span>
|
|
301
|
+
</button>
|
|
302
|
+
))}
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
) : (
|
|
306
|
+
<MessageList messages={messages} streaming={streamingForThisSession} />
|
|
307
|
+
)}
|
|
308
|
+
|
|
309
|
+
<SessionDebugPanel
|
|
310
|
+
messages={messages}
|
|
311
|
+
open={debugOpen}
|
|
312
|
+
onClose={() => setDebugOpen(false)}
|
|
313
|
+
/>
|
|
314
|
+
|
|
315
|
+
<ChatInput
|
|
316
|
+
streaming={streamingForThisSession}
|
|
317
|
+
onSend={sendMessage}
|
|
318
|
+
onStop={stopStreaming}
|
|
319
|
+
/>
|
|
320
|
+
|
|
321
|
+
<Dropdown open={menuOpen} onClose={() => setMenuOpen(false)}>
|
|
322
|
+
{session.agentId && agents[session.agentId] && (
|
|
323
|
+
<DropdownItem onClick={() => { setMenuOpen(false); setEditingAgentId(session.agentId!); setAgentSheetOpen(true) }}>
|
|
324
|
+
Edit Agent
|
|
325
|
+
</DropdownItem>
|
|
326
|
+
)}
|
|
327
|
+
<DropdownItem onClick={() => { setMenuOpen(false); setConfirmClear(true) }}>
|
|
328
|
+
Clear History
|
|
329
|
+
</DropdownItem>
|
|
330
|
+
{session.agentId && agents[session.agentId] && !isMainChat && (
|
|
331
|
+
<DropdownItem danger onClick={() => { setMenuOpen(false); setConfirmDeleteAgent(true) }}>
|
|
332
|
+
Delete Agent
|
|
333
|
+
</DropdownItem>
|
|
334
|
+
)}
|
|
335
|
+
{!isMainChat && (
|
|
336
|
+
<DropdownItem danger onClick={() => { setMenuOpen(false); setConfirmDelete(true) }}>
|
|
337
|
+
Delete Session
|
|
338
|
+
</DropdownItem>
|
|
339
|
+
)}
|
|
340
|
+
</Dropdown>
|
|
341
|
+
|
|
342
|
+
<ConfirmDialog
|
|
343
|
+
open={confirmClear}
|
|
344
|
+
title="Clear History"
|
|
345
|
+
message="This will delete all messages in this session. This cannot be undone."
|
|
346
|
+
confirmLabel="Clear"
|
|
347
|
+
danger
|
|
348
|
+
onConfirm={handleClear}
|
|
349
|
+
onCancel={() => setConfirmClear(false)}
|
|
350
|
+
/>
|
|
351
|
+
<ConfirmDialog
|
|
352
|
+
open={confirmDelete}
|
|
353
|
+
title="Delete Session"
|
|
354
|
+
message={`Delete "${session.name}"? This cannot be undone.`}
|
|
355
|
+
confirmLabel="Delete"
|
|
356
|
+
danger
|
|
357
|
+
onConfirm={handleDelete}
|
|
358
|
+
onCancel={() => setConfirmDelete(false)}
|
|
359
|
+
/>
|
|
360
|
+
{session.agentId && agents[session.agentId] && (
|
|
361
|
+
<ConfirmDialog
|
|
362
|
+
open={confirmDeleteAgent}
|
|
363
|
+
title="Delete Agent"
|
|
364
|
+
message={`Delete agent "${agents[session.agentId].name}"? This cannot be undone.`}
|
|
365
|
+
confirmLabel="Delete"
|
|
366
|
+
danger
|
|
367
|
+
onConfirm={async () => {
|
|
368
|
+
setConfirmDeleteAgent(false)
|
|
369
|
+
await deleteAgent(session.agentId!)
|
|
370
|
+
await loadAgents()
|
|
371
|
+
}}
|
|
372
|
+
onCancel={() => setConfirmDeleteAgent(false)}
|
|
373
|
+
/>
|
|
374
|
+
)}
|
|
375
|
+
|
|
376
|
+
{isDragging && (
|
|
377
|
+
<div className="absolute inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm pointer-events-none">
|
|
378
|
+
<div className="px-8 py-6 rounded-[20px] border-2 border-dashed border-accent-bright/50 bg-surface/80 text-center">
|
|
379
|
+
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-accent-bright mx-auto mb-3">
|
|
380
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
381
|
+
<polyline points="17 8 12 3 7 8" />
|
|
382
|
+
<line x1="12" y1="3" x2="12" y2="15" />
|
|
383
|
+
</svg>
|
|
384
|
+
<p className="text-[15px] font-600 text-text">Drop file to attach</p>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
)}
|
|
388
|
+
</div>
|
|
389
|
+
)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function PromptIcon({ type }: { type: string }) {
|
|
393
|
+
const cls = "w-5 h-5"
|
|
394
|
+
switch (type) {
|
|
395
|
+
case 'book':
|
|
396
|
+
return <svg className={cls} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" style={{ color: '#818CF8' }}><rect x="3" y="3" width="7" height="7" rx="1" /><rect x="14" y="3" width="7" height="7" rx="1" /><rect x="3" y="14" width="7" height="7" rx="1" /><rect x="14" y="14" width="7" height="7" rx="1" /></svg>
|
|
397
|
+
case 'link':
|
|
398
|
+
return <svg className={cls} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" style={{ color: '#F472B6' }}><path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3" /><line x1="8" y1="12" x2="16" y2="12" /></svg>
|
|
399
|
+
case 'bot':
|
|
400
|
+
return <svg className={cls} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" style={{ color: '#34D399' }}><path d="M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h1a7 7 0 0 1 7 7v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-3a7 7 0 0 1 7-7h1V5.73c-.6-.34-1-.99-1-1.73a2 2 0 0 1 2-2z" /><circle cx="9" cy="13" r="1.25" fill="currentColor" /><circle cx="15" cy="13" r="1.25" fill="currentColor" /><path d="M10 17h4" /></svg>
|
|
401
|
+
case 'check':
|
|
402
|
+
return <svg className={cls} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" style={{ color: '#FBBF24' }}><circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /></svg>
|
|
403
|
+
default:
|
|
404
|
+
return null
|
|
405
|
+
}
|
|
406
|
+
}
|