@swarmclawai/swarmclaw 0.6.7 → 0.7.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 +82 -39
- package/next.config.ts +31 -6
- package/package.json +3 -2
- package/src/app/api/agents/[id]/thread/route.ts +1 -0
- package/src/app/api/agents/route.ts +19 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/eval/run/route.ts +37 -0
- package/src/app/api/eval/scenarios/route.ts +24 -0
- package/src/app/api/eval/suite/route.ts +29 -0
- package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
- package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
- package/src/app/api/memory/graph/route.ts +46 -0
- package/src/app/api/memory/route.ts +36 -5
- package/src/app/api/notifications/route.ts +3 -0
- package/src/app/api/plugins/install/route.ts +57 -5
- package/src/app/api/plugins/marketplace/route.ts +73 -22
- package/src/app/api/plugins/route.ts +61 -1
- package/src/app/api/plugins/ui/route.ts +34 -0
- package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
- package/src/app/api/sessions/[id]/restore/route.ts +36 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/souls/[id]/route.ts +65 -0
- package/src/app/api/souls/route.ts +70 -0
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +16 -3
- package/src/app/api/tasks/route.ts +10 -2
- package/src/app/api/usage/route.ts +9 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +37 -0
- package/src/components/activity/activity-feed.tsx +9 -2
- package/src/components/agents/agent-avatar.tsx +5 -1
- package/src/components/agents/agent-card.tsx +55 -9
- package/src/components/agents/agent-sheet.tsx +112 -34
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/agents/soul-library-picker.tsx +84 -13
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/activity-moment.tsx +2 -0
- package/src/components/chat/chat-area.tsx +11 -0
- package/src/components/chat/chat-header.tsx +69 -25
- package/src/components/chat/chat-tool-toggles.tsx +2 -2
- package/src/components/chat/checkpoint-timeline.tsx +112 -0
- package/src/components/chat/code-block.tsx +3 -1
- package/src/components/chat/exec-approval-card.tsx +8 -1
- package/src/components/chat/message-bubble.tsx +164 -4
- package/src/components/chat/message-list.tsx +46 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/session-debug-panel.tsx +106 -84
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/task-approval-card.tsx +78 -0
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-call-bubble.tsx +3 -0
- package/src/components/chat/tool-request-banner.tsx +39 -20
- package/src/components/chatrooms/chatroom-list.tsx +11 -4
- package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
- package/src/components/connectors/connector-list.tsx +33 -11
- package/src/components/connectors/connector-sheet.tsx +37 -7
- package/src/components/home/home-view.tsx +54 -24
- package/src/components/input/chat-input.tsx +22 -1
- package/src/components/knowledge/knowledge-list.tsx +17 -18
- package/src/components/knowledge/knowledge-sheet.tsx +9 -5
- package/src/components/layout/app-layout.tsx +87 -19
- package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
- package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
- package/src/components/memory/memory-browser.tsx +73 -45
- package/src/components/memory/memory-graph-view.tsx +203 -0
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +214 -60
- package/src/components/plugins/plugin-sheet.tsx +119 -24
- package/src/components/projects/project-list.tsx +17 -9
- package/src/components/providers/provider-list.tsx +21 -6
- package/src/components/providers/provider-sheet.tsx +42 -25
- package/src/components/runs/run-list.tsx +17 -13
- package/src/components/schedules/schedule-card.tsx +10 -3
- package/src/components/schedules/schedule-list.tsx +2 -2
- package/src/components/schedules/schedule-sheet.tsx +28 -9
- package/src/components/secrets/secret-sheet.tsx +7 -2
- package/src/components/secrets/secrets-list.tsx +18 -5
- package/src/components/sessions/new-session-sheet.tsx +183 -376
- package/src/components/sessions/session-card.tsx +10 -2
- package/src/components/settings/gateway-connection-panel.tsx +9 -8
- package/src/components/shared/command-palette.tsx +13 -5
- package/src/components/shared/empty-state.tsx +20 -8
- package/src/components/shared/hint-tip.tsx +31 -0
- package/src/components/shared/notification-center.tsx +134 -86
- package/src/components/shared/profile-sheet.tsx +4 -0
- package/src/components/shared/settings/plugin-manager.tsx +360 -135
- package/src/components/shared/settings/section-capability-policy.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
- package/src/components/skills/clawhub-browser.tsx +1 -0
- package/src/components/skills/skill-list.tsx +31 -12
- package/src/components/skills/skill-sheet.tsx +20 -7
- package/src/components/tasks/approvals-panel.tsx +224 -0
- package/src/components/tasks/task-board.tsx +20 -12
- package/src/components/tasks/task-card.tsx +21 -7
- package/src/components/tasks/task-column.tsx +4 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +130 -1
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/usage/metrics-dashboard.tsx +72 -48
- package/src/components/wallets/wallet-panel.tsx +65 -41
- package/src/components/wallets/wallet-section.tsx +9 -3
- package/src/components/webhooks/webhook-list.tsx +21 -12
- package/src/components/webhooks/webhook-sheet.tsx +13 -3
- package/src/lib/approval-display.test.ts +45 -0
- package/src/lib/approval-display.ts +62 -0
- package/src/lib/clipboard.ts +38 -0
- package/src/lib/memory.ts +8 -0
- package/src/lib/providers/claude-cli.ts +5 -3
- package/src/lib/providers/index.ts +67 -21
- package/src/lib/runtime-loop.ts +3 -2
- package/src/lib/server/approvals.ts +150 -0
- package/src/lib/server/chat-execution.ts +319 -74
- package/src/lib/server/chatroom-helpers.ts +63 -5
- package/src/lib/server/chatroom-orchestration.ts +74 -0
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/context-manager.ts +132 -50
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +112 -1
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/eval/runner.ts +126 -0
- package/src/lib/server/eval/scenarios.ts +218 -0
- package/src/lib/server/eval/scorer.ts +96 -0
- package/src/lib/server/eval/store.ts +37 -0
- package/src/lib/server/eval/types.ts +48 -0
- package/src/lib/server/execution-log.ts +12 -8
- package/src/lib/server/guardian.ts +34 -0
- package/src/lib/server/heartbeat-service.ts +53 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/langgraph-checkpoint.ts +10 -0
- package/src/lib/server/link-understanding.ts +55 -0
- package/src/lib/server/llm-response-cache.test.ts +102 -0
- package/src/lib/server/llm-response-cache.ts +227 -0
- package/src/lib/server/main-agent-loop.ts +115 -16
- package/src/lib/server/main-session.ts +6 -3
- package/src/lib/server/mcp-conformance.test.ts +18 -0
- package/src/lib/server/mcp-conformance.ts +233 -0
- package/src/lib/server/memory-db.ts +193 -19
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/mmr.ts +73 -0
- package/src/lib/server/orchestrator-lg.ts +7 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +662 -132
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/query-expansion.ts +57 -0
- package/src/lib/server/queue.ts +280 -11
- package/src/lib/server/runtime-settings.ts +9 -0
- package/src/lib/server/session-run-manager.test.ts +23 -0
- package/src/lib/server/session-run-manager.ts +32 -2
- package/src/lib/server/session-tools/canvas.ts +85 -50
- package/src/lib/server/session-tools/chatroom.ts +130 -127
- package/src/lib/server/session-tools/connector.ts +233 -454
- package/src/lib/server/session-tools/context-mgmt.ts +87 -105
- package/src/lib/server/session-tools/crud.ts +84 -7
- package/src/lib/server/session-tools/delegate.ts +351 -752
- package/src/lib/server/session-tools/discovery.ts +198 -0
- package/src/lib/server/session-tools/edit_file.ts +82 -0
- package/src/lib/server/session-tools/file-send.test.ts +39 -0
- package/src/lib/server/session-tools/file.ts +257 -425
- package/src/lib/server/session-tools/git.ts +87 -47
- package/src/lib/server/session-tools/http.ts +95 -33
- package/src/lib/server/session-tools/index.ts +217 -138
- package/src/lib/server/session-tools/memory.ts +154 -239
- package/src/lib/server/session-tools/monitor.ts +126 -0
- package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
- package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
- package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
- package/src/lib/server/session-tools/platform.ts +86 -0
- package/src/lib/server/session-tools/plugin-creator.ts +239 -0
- package/src/lib/server/session-tools/sample-ui.ts +97 -0
- package/src/lib/server/session-tools/sandbox.ts +175 -148
- package/src/lib/server/session-tools/schedule.ts +78 -0
- package/src/lib/server/session-tools/session-info.ts +104 -410
- package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
- package/src/lib/server/session-tools/shell.ts +171 -143
- package/src/lib/server/session-tools/subagent.ts +77 -77
- package/src/lib/server/session-tools/wallet.ts +182 -106
- package/src/lib/server/session-tools/web.ts +181 -327
- package/src/lib/server/storage.ts +36 -0
- package/src/lib/server/stream-agent-chat.ts +348 -242
- package/src/lib/server/task-quality-gate.test.ts +44 -0
- package/src/lib/server/task-quality-gate.ts +67 -0
- package/src/lib/server/task-validation.test.ts +78 -0
- package/src/lib/server/task-validation.ts +67 -2
- package/src/lib/server/tool-aliases.ts +68 -0
- package/src/lib/server/tool-capability-policy.ts +24 -5
- package/src/lib/server/tool-retry.ts +62 -0
- package/src/lib/server/transcript-repair.ts +72 -0
- package/src/lib/setup-defaults.ts +1 -0
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +24 -23
- package/src/lib/validation/schemas.ts +13 -0
- package/src/lib/view-routes.ts +2 -23
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +155 -10
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import type { BoardTask } from '@/types'
|
|
5
|
+
import { api } from '@/lib/api-client'
|
|
6
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
7
|
+
import { toast } from 'sonner'
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
task: BoardTask
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function TaskApprovalCard({ task }: Props) {
|
|
14
|
+
const [resolving, setResolving] = useState(false)
|
|
15
|
+
const loadTasks = useAppStore((s) => s.loadTasks)
|
|
16
|
+
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
17
|
+
|
|
18
|
+
const handleResolve = async (approved: boolean) => {
|
|
19
|
+
setResolving(true)
|
|
20
|
+
try {
|
|
21
|
+
await api('POST', `/tasks/${task.id}/approve`, { approved })
|
|
22
|
+
toast.success(approved ? 'Tool execution approved' : 'Tool execution rejected')
|
|
23
|
+
await loadTasks()
|
|
24
|
+
await loadSessions()
|
|
25
|
+
} catch (err: unknown) {
|
|
26
|
+
toast.error(err instanceof Error ? err.message : 'Failed to submit decision')
|
|
27
|
+
} finally {
|
|
28
|
+
setResolving(false)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!task.pendingApproval) return null
|
|
33
|
+
|
|
34
|
+
const { toolName, args } = task.pendingApproval
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="my-2 rounded-[12px] border border-amber-500/20 bg-amber-500/[0.04] p-4 animate-in fade-in slide-in-from-bottom-2 duration-300">
|
|
38
|
+
<div className="flex items-center gap-2 mb-3">
|
|
39
|
+
<div className="w-5 h-5 rounded-full bg-amber-500/20 flex items-center justify-center">
|
|
40
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-amber-400">
|
|
41
|
+
<path d="M12 9v2m0 4h.01" />
|
|
42
|
+
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
|
43
|
+
</svg>
|
|
44
|
+
</div>
|
|
45
|
+
<span className="text-[12px] font-700 text-amber-400 uppercase tracking-wider">Orchestration Intervention Required</span>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<p className="text-[13px] text-text-2 mb-3 font-500">
|
|
49
|
+
Agent requested to use <span className="text-amber-400 font-600 font-mono">{toolName}</span>:
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
<div className="bg-black/30 rounded-[10px] border border-white/[0.04] p-3 mb-4 overflow-x-auto max-h-[200px] overflow-y-auto">
|
|
53
|
+
<pre className="text-[11px] font-mono text-text-2/80 leading-relaxed whitespace-pre-wrap break-all">
|
|
54
|
+
{JSON.stringify(args, null, 2)}
|
|
55
|
+
</pre>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div className="flex items-center gap-2">
|
|
59
|
+
<button
|
|
60
|
+
onClick={() => handleResolve(true)}
|
|
61
|
+
disabled={resolving}
|
|
62
|
+
className="flex-1 px-4 py-2 rounded-[10px] bg-emerald-500 text-[#000] text-[12px] font-700 hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-50"
|
|
63
|
+
style={{ fontFamily: 'inherit' }}
|
|
64
|
+
>
|
|
65
|
+
{resolving ? 'Applying...' : 'Approve'}
|
|
66
|
+
</button>
|
|
67
|
+
<button
|
|
68
|
+
onClick={() => handleResolve(false)}
|
|
69
|
+
disabled={resolving}
|
|
70
|
+
className="px-4 py-2 rounded-[10px] bg-white/[0.04] border border-white/[0.08] text-text-3 text-[12px] font-600 hover:bg-white/[0.08] active:scale-[0.98] transition-all disabled:opacity-50"
|
|
71
|
+
style={{ fontFamily: 'inherit' }}
|
|
72
|
+
>
|
|
73
|
+
Reject
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
@@ -40,31 +40,64 @@ export function ThinkingIndicator({ assistantName, agentAvatarSeed, agentAvatarU
|
|
|
40
40
|
const streamToolName = useChatStore((s) => s.streamToolName)
|
|
41
41
|
const thinkingText = useChatStore((s) => s.thinkingText)
|
|
42
42
|
const thinkingStartTime = useChatStore((s) => s.thinkingStartTime)
|
|
43
|
+
const agentStatus = useChatStore((s) => s.agentStatus)
|
|
43
44
|
|
|
44
45
|
const statusText = streamPhase === 'tool' && streamToolName
|
|
45
46
|
? `Using ${streamToolName}...`
|
|
46
47
|
: 'Thinking...'
|
|
47
48
|
|
|
48
49
|
const hasThinkingContent = thinkingText.trim().length > 0
|
|
50
|
+
const hasMission = !!agentStatus?.goal
|
|
49
51
|
|
|
50
52
|
return (
|
|
51
53
|
<div className="flex flex-col items-start relative pl-[44px]"
|
|
52
|
-
style={{ animation: 'msg-in-left 0.
|
|
54
|
+
style={{ animation: 'msg-in-left 0.4s var(--ease-spring) both' }}>
|
|
53
55
|
<div className="absolute left-[4px] top-0">
|
|
54
56
|
{agentName ? <AgentAvatar seed={agentAvatarSeed || null} avatarUrl={agentAvatarUrl} name={agentName} size={28} /> : <AiAvatar size="sm" mood={streamPhase === 'tool' ? 'tool' : 'thinking'} />}
|
|
55
57
|
</div>
|
|
58
|
+
|
|
56
59
|
<div className="flex items-center gap-2.5 mb-2 px-1">
|
|
57
60
|
<span className="text-[12px] font-600 text-text-3">{assistantName || 'Claude'}</span>
|
|
61
|
+
{agentStatus?.status && (
|
|
62
|
+
<span className={`px-1.5 py-0.5 rounded-[4px] text-[9px] font-700 uppercase tracking-wider ${
|
|
63
|
+
agentStatus.status === 'progress' ? 'bg-blue-500/10 text-blue-400' :
|
|
64
|
+
agentStatus.status === 'ok' ? 'bg-emerald-500/10 text-emerald-400' :
|
|
65
|
+
agentStatus.status === 'blocked' ? 'bg-red-500/10 text-red-400' :
|
|
66
|
+
'bg-white/[0.06] text-text-3'
|
|
67
|
+
}`} style={{ animation: 'spring-in 0.3s var(--ease-spring)' }}>
|
|
68
|
+
{agentStatus.status}
|
|
69
|
+
</span>
|
|
70
|
+
)}
|
|
58
71
|
</div>
|
|
59
72
|
|
|
73
|
+
{hasMission && (
|
|
74
|
+
<div className="mb-2 w-full max-w-[85%] md:max-w-[72%] p-3 rounded-[12px] border border-accent-bright/10 bg-accent-bright/[0.02]"
|
|
75
|
+
style={{ animation: 'fade-up 0.4s var(--ease-spring)' }}>
|
|
76
|
+
<div className="text-[10px] font-700 text-accent-bright/60 uppercase tracking-widest mb-1.5 flex items-center gap-2">
|
|
77
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/40" />
|
|
78
|
+
Active Mission
|
|
79
|
+
</div>
|
|
80
|
+
<p className="text-[13px] font-500 text-text-2 leading-snug">{agentStatus.goal}</p>
|
|
81
|
+
{agentStatus.nextAction && (
|
|
82
|
+
<div className="mt-2 pt-2 border-t border-white/[0.04]">
|
|
83
|
+
<span className="text-[10px] font-600 text-text-3/40 uppercase block mb-0.5">Next Action</span>
|
|
84
|
+
<p className="text-[11px] text-text-3/80 italic">“{agentStatus.nextAction}”</p>
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
|
|
60
90
|
{hasThinkingContent ? (
|
|
61
91
|
<details className="group/think w-full max-w-[85%] md:max-w-[72%]">
|
|
62
|
-
<summary className="bubble-ai px-5 py-3.5 cursor-pointer list-none [&::-webkit-details-marker]:hidden">
|
|
63
|
-
|
|
92
|
+
<summary className="bubble-ai px-5 py-3.5 cursor-pointer list-none [&::-webkit-details-marker]:hidden relative overflow-hidden group-open/think:rounded-b-none border border-transparent hover:border-white/[0.04] transition-all">
|
|
93
|
+
{/* Thinking pulse background */}
|
|
94
|
+
<div className="absolute inset-0 bg-accent-bright/5 opacity-0 group-hover/think:opacity-100 transition-opacity" style={{ animation: 'pulse-subtle 2s ease-in-out infinite' }} />
|
|
95
|
+
|
|
96
|
+
<div className="flex items-center gap-3 relative z-10">
|
|
64
97
|
<div className="flex gap-2">
|
|
65
|
-
<span className="w-[6px] h-[6px] rounded-full bg-accent-bright/60" style={{ animation: 'dot-bounce 1.2s ease-in-out infinite' }} />
|
|
66
|
-
<span className="w-[6px] h-[6px] rounded-full bg-accent-bright/60" style={{ animation: 'dot-bounce 1.2s ease-in-out infinite 0.15s' }} />
|
|
67
|
-
<span className="w-[6px] h-[6px] rounded-full bg-accent-bright/60" style={{ animation: 'dot-bounce 1.2s ease-in-out infinite 0.3s' }} />
|
|
98
|
+
<span className="w-[6px] h-[6px] rounded-full bg-accent-bright/60 shadow-[0_0_8px_rgba(129,140,248,0.4)]" style={{ animation: 'dot-bounce 1.2s ease-in-out infinite' }} />
|
|
99
|
+
<span className="w-[6px] h-[6px] rounded-full bg-accent-bright/60 shadow-[0_0_8px_rgba(129,140,248,0.4)]" style={{ animation: 'dot-bounce 1.2s ease-in-out infinite 0.15s' }} />
|
|
100
|
+
<span className="w-[6px] h-[6px] rounded-full bg-accent-bright/60 shadow-[0_0_8px_rgba(129,140,248,0.4)]" style={{ animation: 'dot-bounce 1.2s ease-in-out infinite 0.3s' }} />
|
|
68
101
|
</div>
|
|
69
102
|
<span className="text-[12px] text-text-3/60 font-mono">{statusText}</span>
|
|
70
103
|
<ElapsedTimer startTime={thinkingStartTime} />
|
|
@@ -77,7 +110,7 @@ export function ThinkingIndicator({ assistantName, agentAvatarSeed, agentAvatarU
|
|
|
77
110
|
</svg>
|
|
78
111
|
</div>
|
|
79
112
|
</summary>
|
|
80
|
-
<div className="
|
|
113
|
+
<div className="px-4 py-3 rounded-b-[12px] bg-bg/60 border-x border-b border-white/[0.04] max-h-[300px] overflow-y-auto">
|
|
81
114
|
<div className="msg-content text-[13px] leading-[1.6] text-text-3/80">
|
|
82
115
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
|
83
116
|
{thinkingText}
|
|
@@ -86,12 +119,15 @@ export function ThinkingIndicator({ assistantName, agentAvatarSeed, agentAvatarU
|
|
|
86
119
|
</div>
|
|
87
120
|
</details>
|
|
88
121
|
) : (
|
|
89
|
-
<div className="bubble-ai px-6 py-5">
|
|
90
|
-
|
|
122
|
+
<div className="bubble-ai px-6 py-5 relative overflow-hidden">
|
|
123
|
+
{/* Thinking glow effect */}
|
|
124
|
+
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-accent-bright/5 to-transparent" style={{ animation: 'shimmer-bar 3s linear infinite' }} />
|
|
125
|
+
|
|
126
|
+
<div className="flex items-center gap-3 relative z-10">
|
|
91
127
|
<div className="flex gap-2">
|
|
92
|
-
<span className="w-[6px] h-[6px] rounded-full bg-accent-bright/60" style={{ animation: 'dot-bounce 1.2s ease-in-out infinite' }} />
|
|
93
|
-
<span className="w-[6px] h-[6px] rounded-full bg-accent-bright/60" style={{ animation: 'dot-bounce 1.2s ease-in-out infinite 0.15s' }} />
|
|
94
|
-
<span className="w-[6px] h-[6px] rounded-full bg-accent-bright/60" style={{ animation: 'dot-bounce 1.2s ease-in-out infinite 0.3s' }} />
|
|
128
|
+
<span className="w-[6px] h-[6px] rounded-full bg-accent-bright/60 shadow-[0_0_8px_rgba(129,140,248,0.4)]" style={{ animation: 'dot-bounce 1.2s ease-in-out infinite' }} />
|
|
129
|
+
<span className="w-[6px] h-[6px] rounded-full bg-accent-bright/60 shadow-[0_0_8px_rgba(129,140,248,0.4)]" style={{ animation: 'dot-bounce 1.2s ease-in-out infinite 0.15s' }} />
|
|
130
|
+
<span className="w-[6px] h-[6px] rounded-full bg-accent-bright/60 shadow-[0_0_8px_rgba(129,140,248,0.4)]" style={{ animation: 'dot-bounce 1.2s ease-in-out infinite 0.3s' }} />
|
|
95
131
|
</div>
|
|
96
132
|
<span className="text-[12px] text-text-3/60 font-mono">{statusText}</span>
|
|
97
133
|
<ElapsedTimer startTime={thinkingStartTime} />
|
|
@@ -29,6 +29,7 @@ const TOOL_COLORS: Record<string, string> = {
|
|
|
29
29
|
search_history_tool: '#8B5CF6',
|
|
30
30
|
manage_tasks: '#EC4899',
|
|
31
31
|
manage_schedules: '#EC4899',
|
|
32
|
+
schedule_wake: '#F59E0B',
|
|
32
33
|
manage_agents: '#EC4899',
|
|
33
34
|
manage_skills: '#EC4899',
|
|
34
35
|
manage_documents: '#EC4899',
|
|
@@ -81,6 +82,7 @@ export const TOOL_LABELS: Record<string, string> = {
|
|
|
81
82
|
search_history_tool: 'Search History',
|
|
82
83
|
manage_tasks: 'Tasks',
|
|
83
84
|
manage_schedules: 'Schedules',
|
|
85
|
+
schedule_wake: 'Set Reminder',
|
|
84
86
|
manage_agents: 'Agents',
|
|
85
87
|
manage_skills: 'Skills',
|
|
86
88
|
manage_documents: 'Documents',
|
|
@@ -118,6 +120,7 @@ export const TOOL_DESCRIPTIONS: Record<string, string> = {
|
|
|
118
120
|
search_history_tool: 'Search chat history for relevant prior context',
|
|
119
121
|
manage_tasks: 'Create, update, and manage tasks on the board',
|
|
120
122
|
manage_schedules: 'Create and manage cron schedules',
|
|
123
|
+
schedule_wake: 'Set a timer to wake up in this chat later',
|
|
121
124
|
manage_agents: 'Create and configure other agents',
|
|
122
125
|
manage_skills: 'Create and manage agent skills',
|
|
123
126
|
manage_documents: 'Upload and search indexed documents',
|
|
@@ -15,22 +15,36 @@ export function ToolRequestBanner({ text, toolOutputs = [] }: Props) {
|
|
|
15
15
|
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
16
16
|
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
17
17
|
const sessions = useAppStore((s) => s.sessions)
|
|
18
|
+
const serverApprovals = useAppStore((s) => s.approvals)
|
|
19
|
+
const loadApprovals = useAppStore((s) => s.loadApprovals)
|
|
18
20
|
const [granted, setGranted] = useState<Set<string>>(new Set())
|
|
19
21
|
const [denied, setDenied] = useState<Set<string>>(new Set())
|
|
20
22
|
const continueSentRef = useRef(false)
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
// Resolve matching server-side tool_access approval when user grants/denies inline
|
|
25
|
+
const resolveMatchingApproval = (toolId: string, approved: boolean) => {
|
|
26
|
+
const match = Object.values(serverApprovals).find(
|
|
27
|
+
(a) => a.status === 'pending' && a.category === 'tool_access'
|
|
28
|
+
&& (a.data?.toolId === toolId || a.data?.pluginId === toolId)
|
|
29
|
+
)
|
|
30
|
+
if (match) {
|
|
31
|
+
api('POST', '/approvals', { id: match.id, approved }).then(() => loadApprovals()).catch(() => { /* best effort */ })
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const pluginRequests: { pluginId: string; reason: string }[] = []
|
|
23
36
|
const seen = new Set<string>()
|
|
24
37
|
|
|
25
38
|
function extractFromText(t: string) {
|
|
26
39
|
try {
|
|
27
|
-
const jsonMatches = t.match(/\{"type"\s*:\s*"tool_request"[^}]*\}/g)
|
|
40
|
+
const jsonMatches = t.match(/\{"type"\s*:\s*"(?:tool_request|plugin_request)"[^}]*\}/g)
|
|
28
41
|
if (jsonMatches) {
|
|
29
42
|
for (const jm of jsonMatches) {
|
|
30
43
|
const parsed = JSON.parse(jm)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
const pluginId = parsed.pluginId || parsed.toolId
|
|
45
|
+
if ((parsed.type === 'tool_request' || parsed.type === 'plugin_request') && pluginId && !seen.has(pluginId)) {
|
|
46
|
+
seen.add(pluginId)
|
|
47
|
+
pluginRequests.push({ pluginId, reason: parsed.reason || '' })
|
|
34
48
|
}
|
|
35
49
|
}
|
|
36
50
|
}
|
|
@@ -41,7 +55,7 @@ export function ToolRequestBanner({ text, toolOutputs = [] }: Props) {
|
|
|
41
55
|
extractFromText(text)
|
|
42
56
|
for (const output of toolOutputs) extractFromText(output)
|
|
43
57
|
|
|
44
|
-
if (
|
|
58
|
+
if (pluginRequests.length === 0) return null
|
|
45
59
|
|
|
46
60
|
const sid = currentSessionId
|
|
47
61
|
const session = sid ? sessions[sid] : null
|
|
@@ -59,17 +73,20 @@ export function ToolRequestBanner({ text, toolOutputs = [] }: Props) {
|
|
|
59
73
|
const newGranted = new Set(granted).add(toolId)
|
|
60
74
|
setGranted(newGranted)
|
|
61
75
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
// Resolve matching server-side approval so approvals page stays in sync
|
|
77
|
+
resolveMatchingApproval(toolId, true)
|
|
78
|
+
|
|
79
|
+
// Notify agent that access was granted with a precise message (not a vague "Continue")
|
|
80
|
+
const allGranted = pluginRequests.every(
|
|
81
|
+
(r) => newGranted.has(r.pluginId) || updated.includes(r.pluginId),
|
|
65
82
|
)
|
|
66
83
|
if (allGranted && !continueSentRef.current) {
|
|
67
84
|
continueSentRef.current = true
|
|
68
|
-
|
|
85
|
+
const grantedNames = pluginRequests.map((r) => TOOL_LABELS[r.pluginId] || r.pluginId).join(', ')
|
|
69
86
|
setTimeout(() => {
|
|
70
87
|
const { streaming, sendMessage } = useChatStore.getState()
|
|
71
88
|
if (!streaming) {
|
|
72
|
-
sendMessage(
|
|
89
|
+
sendMessage(`Access granted for: ${grantedNames}. You now have these tools available — proceed with your task.`)
|
|
73
90
|
}
|
|
74
91
|
}, 300)
|
|
75
92
|
}
|
|
@@ -77,24 +94,26 @@ export function ToolRequestBanner({ text, toolOutputs = [] }: Props) {
|
|
|
77
94
|
|
|
78
95
|
const handleDeny = (toolId: string) => {
|
|
79
96
|
setDenied((prev) => new Set(prev).add(toolId))
|
|
97
|
+
// Resolve matching server-side approval
|
|
98
|
+
resolveMatchingApproval(toolId, false)
|
|
80
99
|
const label = TOOL_LABELS[toolId] || toolId
|
|
81
100
|
setTimeout(() => {
|
|
82
101
|
const { streaming, sendMessage } = useChatStore.getState()
|
|
83
102
|
if (!streaming) {
|
|
84
|
-
sendMessage(`
|
|
103
|
+
sendMessage(`Plugin access denied for ${label} — proceed without it.`)
|
|
85
104
|
}
|
|
86
105
|
}, 200)
|
|
87
106
|
}
|
|
88
107
|
|
|
89
108
|
return (
|
|
90
109
|
<div className="max-w-[85%] md:max-w-[72%] flex flex-col gap-2 mt-2">
|
|
91
|
-
{
|
|
92
|
-
const isGranted = granted.has(
|
|
93
|
-
const isDenied = denied.has(
|
|
94
|
-
const label = TOOL_LABELS[
|
|
110
|
+
{pluginRequests.map(({ pluginId, reason }) => {
|
|
111
|
+
const isGranted = granted.has(pluginId) || (session?.tools || []).includes(pluginId)
|
|
112
|
+
const isDenied = denied.has(pluginId)
|
|
113
|
+
const label = TOOL_LABELS[pluginId] || pluginId
|
|
95
114
|
return (
|
|
96
115
|
<div
|
|
97
|
-
key={
|
|
116
|
+
key={pluginId}
|
|
98
117
|
className="flex items-center gap-3 px-4 py-3 rounded-[12px] border border-amber-500/20 bg-amber-500/[0.06]"
|
|
99
118
|
style={{ animation: 'fade-in 0.2s ease' }}
|
|
100
119
|
>
|
|
@@ -103,7 +122,7 @@ export function ToolRequestBanner({ text, toolOutputs = [] }: Props) {
|
|
|
103
122
|
</svg>
|
|
104
123
|
<div className="flex-1 min-w-0">
|
|
105
124
|
<p className="text-[12px] text-text-2 font-600">
|
|
106
|
-
Requesting access to <span className="text-amber-400">{label}</span>
|
|
125
|
+
Requesting plugin access to <span className="text-amber-400">{label}</span>
|
|
107
126
|
</p>
|
|
108
127
|
{reason && <p className="text-[11px] text-text-3/60 mt-0.5 truncate">{reason}</p>}
|
|
109
128
|
</div>
|
|
@@ -114,14 +133,14 @@ export function ToolRequestBanner({ text, toolOutputs = [] }: Props) {
|
|
|
114
133
|
) : (
|
|
115
134
|
<div className="flex gap-1.5 shrink-0">
|
|
116
135
|
<button
|
|
117
|
-
onClick={() => handleGrant(
|
|
136
|
+
onClick={() => handleGrant(pluginId)}
|
|
118
137
|
className="px-3 py-1.5 rounded-[8px] bg-amber-500/20 hover:bg-amber-500/30 text-amber-300 text-[11px] font-600 border-none cursor-pointer transition-colors"
|
|
119
138
|
style={{ fontFamily: 'inherit' }}
|
|
120
139
|
>
|
|
121
140
|
Grant
|
|
122
141
|
</button>
|
|
123
142
|
<button
|
|
124
|
-
onClick={() => handleDeny(
|
|
143
|
+
onClick={() => handleDeny(pluginId)}
|
|
125
144
|
className="px-3 py-1.5 rounded-[8px] bg-red-500/15 hover:bg-red-500/25 text-red-400 text-[11px] font-600 border-none cursor-pointer transition-colors"
|
|
126
145
|
style={{ fontFamily: 'inherit' }}
|
|
127
146
|
>
|
|
@@ -63,7 +63,7 @@ export function ChatroomList() {
|
|
|
63
63
|
) : (
|
|
64
64
|
<div className="p-3 space-y-1">
|
|
65
65
|
{sorted.length > 2 && (
|
|
66
|
-
<div className="flex items-center gap-1 px-1 pb-2">
|
|
66
|
+
<div className="flex items-center gap-1 px-1 pb-2" style={{ animation: 'fade-up 0.4s var(--ease-spring)' }}>
|
|
67
67
|
{(['all', 'active', 'recent'] as const).map((f) => (
|
|
68
68
|
<button
|
|
69
69
|
key={f}
|
|
@@ -79,7 +79,7 @@ export function ChatroomList() {
|
|
|
79
79
|
))}
|
|
80
80
|
</div>
|
|
81
81
|
)}
|
|
82
|
-
{filtered.map((chatroom) => {
|
|
82
|
+
{filtered.map((chatroom, idx) => {
|
|
83
83
|
const isActive = chatroom.id === currentChatroomId
|
|
84
84
|
const memberNames = chatroom.agentIds
|
|
85
85
|
.map((id) => agents[id]?.name)
|
|
@@ -91,11 +91,15 @@ export function ChatroomList() {
|
|
|
91
91
|
<button
|
|
92
92
|
key={chatroom.id}
|
|
93
93
|
onClick={() => setCurrentChatroom(chatroom.id)}
|
|
94
|
-
className={`w-full text-left py-3.5 px-4 rounded-[14px] transition-all cursor-pointer group border border-transparent ${
|
|
94
|
+
className={`w-full text-left py-3.5 px-4 rounded-[14px] transition-all cursor-pointer group border border-transparent relative overflow-hidden ${
|
|
95
95
|
isActive
|
|
96
96
|
? 'bg-accent-soft/60'
|
|
97
|
-
: 'hover:bg-white/[0.04]'
|
|
97
|
+
: 'hover:bg-white/[0.04] hover:scale-[1.01]'
|
|
98
98
|
}`}
|
|
99
|
+
style={{
|
|
100
|
+
animation: 'fade-up 0.4s var(--ease-spring) both',
|
|
101
|
+
animationDelay: `${idx * 0.03}s`
|
|
102
|
+
}}
|
|
99
103
|
>
|
|
100
104
|
<div className="flex items-center gap-2 mb-0.5">
|
|
101
105
|
<div className="w-7 h-7 rounded-full bg-accent-soft flex items-center justify-center shrink-0">
|
|
@@ -106,6 +110,9 @@ export function ChatroomList() {
|
|
|
106
110
|
<span className={`text-[13px] font-600 truncate ${isActive ? 'text-accent-bright' : 'text-text'}`}>
|
|
107
111
|
{chatroom.name}
|
|
108
112
|
</span>
|
|
113
|
+
{isActive && (
|
|
114
|
+
<div className="absolute left-0 top-3 bottom-3 w-1 rounded-r-full bg-accent-bright" style={{ animation: 'spring-in 0.3s var(--ease-spring)' }} />
|
|
115
|
+
)}
|
|
109
116
|
<span className="label-mono ml-auto shrink-0">
|
|
110
117
|
{chatroom.agentIds.length} agents
|
|
111
118
|
</span>
|
|
@@ -202,13 +202,15 @@ export function ChatroomSheet() {
|
|
|
202
202
|
}
|
|
203
203
|
if (editing) {
|
|
204
204
|
await updateChatroom(editing.id, payload)
|
|
205
|
-
toast.success('Chatroom
|
|
205
|
+
toast.success('Chatroom updated successfully')
|
|
206
206
|
} else {
|
|
207
207
|
const chatroom = await createChatroom(payload)
|
|
208
208
|
setCurrentChatroom(chatroom.id)
|
|
209
|
-
toast.success('Chatroom created')
|
|
209
|
+
toast.success('Chatroom created successfully')
|
|
210
210
|
}
|
|
211
211
|
setChatroomSheetOpen(false)
|
|
212
|
+
} catch (err: unknown) {
|
|
213
|
+
toast.error(err instanceof Error ? err.message : 'Failed to save chatroom')
|
|
212
214
|
} finally {
|
|
213
215
|
setSaving(false)
|
|
214
216
|
}
|
|
@@ -216,11 +218,14 @@ export function ChatroomSheet() {
|
|
|
216
218
|
|
|
217
219
|
const handleDelete = async () => {
|
|
218
220
|
if (!editing || saving) return
|
|
221
|
+
if (!confirm(`Delete chatroom "${editing.name}"?`)) return
|
|
219
222
|
setSaving(true)
|
|
220
223
|
try {
|
|
221
224
|
await deleteChatroom(editing.id)
|
|
222
225
|
toast.success('Chatroom deleted')
|
|
223
226
|
setChatroomSheetOpen(false)
|
|
227
|
+
} catch (err: unknown) {
|
|
228
|
+
toast.error(err instanceof Error ? err.message : 'Failed to delete chatroom')
|
|
224
229
|
} finally {
|
|
225
230
|
setSaving(false)
|
|
226
231
|
}
|
|
@@ -31,6 +31,10 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
31
31
|
const [reconnecting, setReconnecting] = useState<string | null>(null)
|
|
32
32
|
const [loaded, setLoaded] = useState(false)
|
|
33
33
|
const [error, setError] = useState<string | null>(null)
|
|
34
|
+
const openConnector = useCallback((id: string | null) => {
|
|
35
|
+
setEditingConnectorId(id)
|
|
36
|
+
setConnectorSheetOpen(true)
|
|
37
|
+
}, [setEditingConnectorId, setConnectorSheetOpen])
|
|
34
38
|
|
|
35
39
|
const refresh = useCallback(async () => {
|
|
36
40
|
await Promise.all([loadConnectors(), loadAgents(), loadChatrooms()])
|
|
@@ -95,7 +99,7 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
95
99
|
<div className="flex-1 flex flex-col items-center justify-center px-6 py-12 text-center">
|
|
96
100
|
<p className="text-[13px] text-text-3">No connectors configured yet.</p>
|
|
97
101
|
<button
|
|
98
|
-
onClick={() =>
|
|
102
|
+
onClick={() => openConnector(null)}
|
|
99
103
|
className="mt-3 text-[13px] text-accent-bright hover:underline cursor-pointer bg-transparent border-none"
|
|
100
104
|
>
|
|
101
105
|
+ Add Connector
|
|
@@ -113,7 +117,7 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
113
117
|
{error}
|
|
114
118
|
</div>
|
|
115
119
|
)}
|
|
116
|
-
{list.map((c) => {
|
|
120
|
+
{list.map((c, idx) => {
|
|
117
121
|
const agent = c.agentId ? agents[c.agentId] : null
|
|
118
122
|
const chatroom = c.chatroomId ? chatrooms[c.chatroomId] : null
|
|
119
123
|
const isRunning = c.status === 'running'
|
|
@@ -121,8 +125,12 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
121
125
|
return (
|
|
122
126
|
<button
|
|
123
127
|
key={c.id}
|
|
124
|
-
onClick={() =>
|
|
128
|
+
onClick={() => openConnector(c.id)}
|
|
125
129
|
className="w-full flex items-center gap-3 px-5 py-2.5 hover:bg-white/[0.02] transition-colors cursor-pointer bg-transparent border-none text-left"
|
|
130
|
+
style={{
|
|
131
|
+
animation: 'fade-up 0.4s var(--ease-spring) both',
|
|
132
|
+
animationDelay: `${idx * 0.03}s`
|
|
133
|
+
}}
|
|
126
134
|
>
|
|
127
135
|
<ConnectorPlatformIcon platform={c.platform} size={16} />
|
|
128
136
|
<div className="flex-1 min-w-0">
|
|
@@ -133,7 +141,8 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
133
141
|
</div>
|
|
134
142
|
<span className={`shrink-0 w-2 h-2 rounded-full ${
|
|
135
143
|
isRunning ? 'bg-green-400' : c.status === 'error' ? 'bg-red-400' : 'bg-white/20'
|
|
136
|
-
}`}
|
|
144
|
+
}`}
|
|
145
|
+
style={isRunning ? { animation: 'pulse-subtle 2s infinite' } : undefined} />
|
|
137
146
|
</button>
|
|
138
147
|
)
|
|
139
148
|
})}
|
|
@@ -150,7 +159,7 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
150
159
|
</div>
|
|
151
160
|
)}
|
|
152
161
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
153
|
-
{list.map((c) => {
|
|
162
|
+
{list.map((c, idx) => {
|
|
154
163
|
const platformLabel = getConnectorPlatformLabel(c.platform)
|
|
155
164
|
const agent = c.agentId ? agents[c.agentId] : null
|
|
156
165
|
const chatroom = c.chatroomId ? chatrooms[c.chatroomId] : null
|
|
@@ -164,11 +173,23 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
164
173
|
const lastMsg = c.presence?.lastMessageAt
|
|
165
174
|
|
|
166
175
|
return (
|
|
167
|
-
<
|
|
176
|
+
<div
|
|
168
177
|
key={c.id}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
178
|
+
role="button"
|
|
179
|
+
tabIndex={0}
|
|
180
|
+
onClick={() => openConnector(c.id)}
|
|
181
|
+
onKeyDown={(e) => {
|
|
182
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
183
|
+
e.preventDefault()
|
|
184
|
+
openConnector(c.id)
|
|
185
|
+
}
|
|
186
|
+
}}
|
|
187
|
+
className="group relative flex flex-col rounded-[14px] border border-white/[0.06] bg-surface p-4 cursor-pointer transition-all hover:border-white/[0.12] hover:bg-white/[0.02] hover:scale-[1.01] text-left w-full"
|
|
188
|
+
style={{
|
|
189
|
+
fontFamily: 'inherit',
|
|
190
|
+
animation: 'spring-in 0.5s var(--ease-spring) both',
|
|
191
|
+
animationDelay: `${idx * 0.05}s`
|
|
192
|
+
}}
|
|
172
193
|
>
|
|
173
194
|
{/* Header: platform badge + status */}
|
|
174
195
|
<div className="flex items-center gap-3 mb-3">
|
|
@@ -178,7 +199,8 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
178
199
|
<span className="text-[14px] font-600 text-text truncate">{c.name}</span>
|
|
179
200
|
<span className={`shrink-0 w-2 h-2 rounded-full ${
|
|
180
201
|
isRunning ? 'bg-green-400' : c.status === 'error' ? 'bg-red-400' : 'bg-white/20'
|
|
181
|
-
}`}
|
|
202
|
+
}`}
|
|
203
|
+
style={isRunning ? { animation: 'pulse-subtle 2s infinite' } : c.status === 'error' ? { animation: 'ai-shake 0.5s' } : undefined} />
|
|
182
204
|
</div>
|
|
183
205
|
<span className="text-[11px] text-text-3 block">
|
|
184
206
|
{isRunning ? 'Connected' : c.status === 'error' ? 'Error' : 'Stopped'}
|
|
@@ -264,7 +286,7 @@ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
264
286
|
)}
|
|
265
287
|
</div>
|
|
266
288
|
</div>
|
|
267
|
-
</
|
|
289
|
+
</div>
|
|
268
290
|
)
|
|
269
291
|
})}
|
|
270
292
|
</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useState, useEffect, useCallback } from 'react'
|
|
3
|
+
import { useState, useEffect, useCallback, useMemo } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
6
6
|
import { api } from '@/lib/api-client'
|
|
@@ -12,6 +12,7 @@ import { AgentPickerList } from '@/components/shared/agent-picker-list'
|
|
|
12
12
|
import { ChatroomPickerList } from '@/components/shared/chatroom-picker-list'
|
|
13
13
|
import { SheetFooter } from '@/components/shared/sheet-footer'
|
|
14
14
|
import { SectionLabel } from '@/components/shared/section-label'
|
|
15
|
+
import { HintTip } from '@/components/shared/hint-tip'
|
|
15
16
|
import { useChatroomStore } from '@/stores/use-chatroom-store'
|
|
16
17
|
import { ConnectorHealth } from '@/components/connectors/connector-health'
|
|
17
18
|
|
|
@@ -241,6 +242,29 @@ const COMMON_CONFIG_FIELDS: { key: string; label: string; placeholder: string; h
|
|
|
241
242
|
export function ConnectorSheet() {
|
|
242
243
|
const open = useAppStore((s) => s.connectorSheetOpen)
|
|
243
244
|
const setOpen = useAppStore((s) => s.setConnectorSheetOpen)
|
|
245
|
+
// ... (existing state)
|
|
246
|
+
const [dynamicPlatforms, setDynamicPlatforms] = useState<Array<{ id: string; name: string; description?: string }>>([])
|
|
247
|
+
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
if (open) {
|
|
250
|
+
api<Array<{ id: string; name: string; description?: string }>>('GET', '/plugins/ui?type=connectors').then(list => {
|
|
251
|
+
setDynamicPlatforms(list || [])
|
|
252
|
+
}).catch(() => {})
|
|
253
|
+
}
|
|
254
|
+
}, [open])
|
|
255
|
+
|
|
256
|
+
const ALL_PLATFORMS = useMemo(() => {
|
|
257
|
+
const plugins = dynamicPlatforms.map(p => ({
|
|
258
|
+
id: p.id,
|
|
259
|
+
label: p.name,
|
|
260
|
+
color: '#10B981',
|
|
261
|
+
setupSteps: [p.description || 'Follow the plugin instructions for setup.'],
|
|
262
|
+
tokenLabel: 'Plugin Token',
|
|
263
|
+
tokenHelp: 'Secret key required by this plugin connector.',
|
|
264
|
+
configFields: [],
|
|
265
|
+
}))
|
|
266
|
+
return [...PLATFORMS, ...plugins]
|
|
267
|
+
}, [dynamicPlatforms])
|
|
244
268
|
const editingId = useAppStore((s) => s.editingConnectorId)
|
|
245
269
|
const setEditingId = useAppStore((s) => s.setEditingConnectorId)
|
|
246
270
|
const connectors = useAppStore((s) => s.connectors)
|
|
@@ -392,7 +416,7 @@ export function ConnectorSheet() {
|
|
|
392
416
|
setEditingId(null)
|
|
393
417
|
}
|
|
394
418
|
|
|
395
|
-
const platformConfig =
|
|
419
|
+
const platformConfig = ALL_PLATFORMS.find((p) => p.id === platform) || ALL_PLATFORMS[0]
|
|
396
420
|
const agentList = Object.values(agents).sort((a, b) => a.name.localeCompare(b.name))
|
|
397
421
|
const credList = Object.values(credentials)
|
|
398
422
|
|
|
@@ -412,17 +436,17 @@ export function ConnectorSheet() {
|
|
|
412
436
|
<div className="mb-8">
|
|
413
437
|
<SectionLabel>Platform</SectionLabel>
|
|
414
438
|
<div className="grid grid-cols-2 gap-3">
|
|
415
|
-
{
|
|
439
|
+
{ALL_PLATFORMS.map((p) => (
|
|
416
440
|
<button
|
|
417
441
|
key={p.id}
|
|
418
|
-
onClick={() => { setPlatform(p.id); setShowSetup(false) }}
|
|
442
|
+
onClick={() => { setPlatform(p.id as ConnectorPlatform); setShowSetup(false) }}
|
|
419
443
|
className={`flex items-center gap-3 p-4 rounded-[14px] cursor-pointer transition-all duration-200 border text-left
|
|
420
444
|
${platform === p.id
|
|
421
445
|
? 'bg-white/[0.04] border-white/[0.15] shadow-[0_0_20px_rgba(255,255,255,0.02)]'
|
|
422
446
|
: 'bg-transparent border-white/[0.04] hover:border-white/[0.08] hover:bg-white/[0.01]'}`}
|
|
423
447
|
style={{ fontFamily: 'inherit' }}
|
|
424
448
|
>
|
|
425
|
-
<ConnectorPlatformBadge platform={p.id} size={40} iconSize={18} />
|
|
449
|
+
<ConnectorPlatformBadge platform={p.id as ConnectorPlatform} size={40} iconSize={18} />
|
|
426
450
|
<div>
|
|
427
451
|
<div className={`text-[14px] font-600 ${platform === p.id ? 'text-text' : 'text-text-2'}`}>{p.label}</div>
|
|
428
452
|
<div className="text-[11px] text-text-3 mt-0.5">
|
|
@@ -438,7 +462,7 @@ export function ConnectorSheet() {
|
|
|
438
462
|
{/* Editing: show platform badge */}
|
|
439
463
|
{editing && (
|
|
440
464
|
<div className="mb-6 flex items-center gap-3">
|
|
441
|
-
<ConnectorPlatformBadge platform={platformConfig.id} size={40} iconSize={18} />
|
|
465
|
+
<ConnectorPlatformBadge platform={platformConfig.id as ConnectorPlatform} size={40} iconSize={18} />
|
|
442
466
|
<div>
|
|
443
467
|
<div className="text-[14px] font-600 text-text">{platformConfig.label}</div>
|
|
444
468
|
<div className="flex items-center gap-2 mt-0.5">
|
|
@@ -643,12 +667,18 @@ export function ConnectorSheet() {
|
|
|
643
667
|
{/* Platform-specific config */}
|
|
644
668
|
{[...platformConfig.configFields, ...COMMON_CONFIG_FIELDS].map((field) => {
|
|
645
669
|
const isTagField = field.key === 'allowedJids' || field.key === 'channelIds' || field.key === 'chatIds' || field.key === 'allowFrom'
|
|
670
|
+
const fieldHint: Record<string, string> = {
|
|
671
|
+
channelIds: "Find these in your platform's developer settings. Leave empty to allow all channels",
|
|
672
|
+
chatIds: "Find these in your platform's developer settings. Leave empty to allow all channels",
|
|
673
|
+
allowedJids: "Phone numbers in international format (e.g. 447xxx). Leave empty to allow all",
|
|
674
|
+
}
|
|
646
675
|
if (isTagField) {
|
|
647
676
|
const tags = (config[field.key] || '').split(',').map((s) => s.trim()).filter(Boolean)
|
|
648
677
|
return (
|
|
649
678
|
<div key={field.key} className="mb-6">
|
|
650
|
-
<label className="
|
|
679
|
+
<label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
651
680
|
{field.label} <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
|
|
681
|
+
{fieldHint[field.key] && <HintTip text={fieldHint[field.key]} />}
|
|
652
682
|
</label>
|
|
653
683
|
{field.help && <p className="text-[12px] text-text-3/60 mb-2">{field.help}</p>}
|
|
654
684
|
<div className="flex flex-wrap gap-2 mb-2">
|