@swarmclawai/swarmclaw 0.4.0 → 0.5.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 +21 -4
- package/bin/server-cmd.js +28 -19
- package/next.config.ts +13 -0
- package/package.json +3 -1
- package/src/app/api/agents/[id]/route.ts +39 -22
- package/src/app/api/agents/[id]/thread/route.ts +2 -2
- package/src/app/api/agents/route.ts +3 -2
- package/src/app/api/agents/trash/route.ts +44 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/connectors/[id]/route.ts +17 -7
- package/src/app/api/connectors/[id]/webhook/route.ts +103 -0
- package/src/app/api/connectors/route.ts +6 -3
- package/src/app/api/credentials/[id]/route.ts +2 -1
- package/src/app/api/credentials/route.ts +2 -2
- package/src/app/api/documents/route.ts +2 -2
- package/src/app/api/files/serve/route.ts +8 -0
- package/src/app/api/knowledge/[id]/route.ts +5 -4
- package/src/app/api/knowledge/upload/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/route.ts +11 -14
- package/src/app/api/mcp-servers/[id]/test/route.ts +2 -1
- package/src/app/api/mcp-servers/[id]/tools/route.ts +2 -1
- package/src/app/api/mcp-servers/route.ts +2 -2
- package/src/app/api/memory/[id]/route.ts +9 -8
- package/src/app/api/memory/route.ts +2 -2
- package/src/app/api/memory-images/[filename]/route.ts +2 -1
- package/src/app/api/openclaw/agent-files/route.ts +57 -0
- package/src/app/api/openclaw/approvals/route.ts +46 -0
- package/src/app/api/openclaw/config-sync/route.ts +33 -0
- package/src/app/api/openclaw/cron/route.ts +52 -0
- package/src/app/api/openclaw/directory/route.ts +27 -0
- package/src/app/api/openclaw/discover/route.ts +62 -0
- package/src/app/api/openclaw/dotenv-keys/route.ts +18 -0
- package/src/app/api/openclaw/exec-config/route.ts +41 -0
- package/src/app/api/openclaw/gateway/route.ts +72 -0
- package/src/app/api/openclaw/history/route.ts +109 -0
- package/src/app/api/openclaw/media/route.ts +53 -0
- package/src/app/api/openclaw/models/route.ts +12 -0
- package/src/app/api/openclaw/permissions/route.ts +39 -0
- package/src/app/api/openclaw/sandbox-env/route.ts +69 -0
- package/src/app/api/openclaw/skills/install/route.ts +32 -0
- package/src/app/api/openclaw/skills/remove/route.ts +24 -0
- package/src/app/api/openclaw/skills/route.ts +82 -0
- package/src/app/api/openclaw/sync/route.ts +31 -0
- package/src/app/api/orchestrator/run/route.ts +2 -2
- package/src/app/api/projects/[id]/route.ts +55 -0
- package/src/app/api/projects/route.ts +27 -0
- package/src/app/api/providers/[id]/models/route.ts +2 -1
- package/src/app/api/providers/[id]/route.ts +13 -15
- package/src/app/api/providers/route.ts +2 -2
- package/src/app/api/schedules/[id]/route.ts +16 -18
- package/src/app/api/schedules/[id]/run/route.ts +4 -3
- package/src/app/api/schedules/route.ts +2 -2
- package/src/app/api/secrets/[id]/route.ts +16 -17
- package/src/app/api/secrets/route.ts +2 -2
- package/src/app/api/sessions/[id]/clear/route.ts +2 -1
- package/src/app/api/sessions/[id]/deploy/route.ts +2 -1
- package/src/app/api/sessions/[id]/devserver/route.ts +2 -1
- package/src/app/api/sessions/[id]/edit-resend/route.ts +22 -0
- package/src/app/api/sessions/[id]/fork/route.ts +44 -0
- package/src/app/api/sessions/[id]/messages/route.ts +20 -2
- package/src/app/api/sessions/[id]/retry/route.ts +2 -1
- package/src/app/api/sessions/[id]/route.ts +14 -4
- package/src/app/api/sessions/route.ts +8 -4
- package/src/app/api/skills/[id]/route.ts +23 -21
- package/src/app/api/skills/import/route.ts +2 -2
- package/src/app/api/skills/route.ts +2 -2
- package/src/app/api/tasks/[id]/approve/route.ts +2 -1
- package/src/app/api/tasks/[id]/route.ts +6 -5
- package/src/app/api/tasks/route.ts +2 -2
- package/src/app/api/tts/stream/route.ts +48 -0
- package/src/app/api/upload/route.ts +2 -2
- package/src/app/api/uploads/[filename]/route.ts +4 -1
- package/src/app/api/webhooks/[id]/route.ts +29 -31
- package/src/app/api/webhooks/route.ts +2 -2
- package/src/app/globals.css +14 -0
- package/src/app/layout.tsx +5 -20
- package/src/app/page.tsx +3 -24
- package/src/cli/index.js +60 -0
- package/src/cli/index.ts +1 -1
- package/src/cli/spec.js +42 -0
- package/src/components/agents/agent-avatar.tsx +45 -0
- package/src/components/agents/agent-card.tsx +19 -5
- package/src/components/agents/agent-chat-list.tsx +31 -24
- package/src/components/agents/agent-files-editor.tsx +185 -0
- package/src/components/agents/agent-list.tsx +84 -3
- package/src/components/agents/agent-sheet.tsx +147 -14
- package/src/components/agents/cron-job-form.tsx +137 -0
- package/src/components/agents/exec-config-panel.tsx +147 -0
- package/src/components/agents/inspector-panel.tsx +310 -0
- package/src/components/agents/openclaw-skills-panel.tsx +230 -0
- package/src/components/agents/permission-preset-selector.tsx +79 -0
- package/src/components/agents/personality-builder.tsx +111 -0
- package/src/components/agents/sandbox-env-panel.tsx +72 -0
- package/src/components/agents/skill-install-dialog.tsx +102 -0
- package/src/components/agents/trash-list.tsx +109 -0
- package/src/components/chat/chat-area.tsx +41 -6
- package/src/components/chat/chat-header.tsx +305 -29
- package/src/components/chat/chat-preview-panel.tsx +113 -0
- package/src/components/chat/exec-approval-card.tsx +89 -0
- package/src/components/chat/message-bubble.tsx +218 -36
- package/src/components/chat/message-list.tsx +135 -31
- package/src/components/chat/streaming-bubble.tsx +59 -10
- package/src/components/chat/suggestions-bar.tsx +74 -0
- package/src/components/chat/thinking-indicator.tsx +20 -6
- package/src/components/chat/tool-call-bubble.tsx +98 -19
- package/src/components/chat/tool-request-banner.tsx +20 -2
- package/src/components/chat/trace-block.tsx +103 -0
- package/src/components/chat/voice-overlay.tsx +80 -0
- package/src/components/connectors/connector-list.tsx +6 -2
- package/src/components/connectors/connector-sheet.tsx +31 -7
- package/src/components/layout/app-layout.tsx +47 -25
- package/src/components/projects/project-list.tsx +123 -0
- package/src/components/projects/project-sheet.tsx +135 -0
- package/src/components/schedules/schedule-list.tsx +3 -1
- package/src/components/sessions/new-session-sheet.tsx +6 -6
- package/src/components/sessions/session-card.tsx +1 -1
- package/src/components/sessions/session-list.tsx +7 -7
- package/src/components/settings/gateway-connection-panel.tsx +278 -0
- package/src/components/shared/avatar.tsx +13 -2
- package/src/components/shared/connector-platform-icon.tsx +4 -0
- package/src/components/shared/settings/section-heartbeat.tsx +1 -1
- package/src/components/shared/settings/section-orchestrator.tsx +1 -2
- package/src/components/shared/settings/section-web-search.tsx +56 -0
- package/src/components/shared/settings/settings-page.tsx +74 -0
- package/src/components/skills/skill-list.tsx +2 -1
- package/src/components/tasks/task-board.tsx +1 -1
- package/src/components/tasks/task-list.tsx +5 -2
- package/src/components/tasks/task-sheet.tsx +12 -12
- package/src/hooks/use-continuous-speech.ts +181 -0
- package/src/hooks/use-openclaw-gateway.ts +63 -0
- package/src/hooks/use-view-router.ts +52 -0
- package/src/hooks/use-voice-conversation.ts +80 -0
- package/src/lib/id.ts +6 -0
- package/src/lib/notification-sounds.ts +58 -0
- package/src/lib/personality-parser.ts +97 -0
- package/src/lib/projects.ts +13 -0
- package/src/lib/provider-sets.ts +5 -0
- package/src/lib/providers/anthropic.ts +14 -1
- package/src/lib/providers/index.ts +6 -0
- package/src/lib/providers/ollama.ts +9 -1
- package/src/lib/providers/openai.ts +9 -1
- package/src/lib/providers/openclaw.ts +28 -2
- package/src/lib/runtime-loop.ts +2 -2
- package/src/lib/server/api-routes.test.ts +5 -6
- package/src/lib/server/build-llm.ts +17 -4
- package/src/lib/server/chat-execution.ts +82 -6
- package/src/lib/server/collection-helpers.ts +54 -0
- package/src/lib/server/connectors/bluebubbles.test.ts +217 -0
- package/src/lib/server/connectors/bluebubbles.ts +360 -0
- package/src/lib/server/connectors/connector-routing.test.ts +1 -1
- package/src/lib/server/connectors/googlechat.ts +51 -8
- package/src/lib/server/connectors/manager.ts +424 -13
- package/src/lib/server/connectors/media.ts +2 -2
- package/src/lib/server/connectors/openclaw.ts +65 -0
- package/src/lib/server/connectors/pairing.test.ts +99 -0
- package/src/lib/server/connectors/pairing.ts +256 -0
- package/src/lib/server/connectors/signal.ts +1 -0
- package/src/lib/server/connectors/teams.ts +5 -5
- package/src/lib/server/connectors/types.ts +10 -0
- package/src/lib/server/daemon-state.ts +11 -0
- package/src/lib/server/execution-log.ts +3 -3
- package/src/lib/server/heartbeat-service.ts +1 -1
- package/src/lib/server/knowledge-db.test.ts +2 -33
- package/src/lib/server/main-agent-loop.ts +8 -9
- package/src/lib/server/main-session.ts +21 -0
- package/src/lib/server/memory-db.ts +6 -6
- package/src/lib/server/openclaw-approvals.ts +105 -0
- package/src/lib/server/openclaw-config-sync.ts +107 -0
- package/src/lib/server/openclaw-exec-config.ts +52 -0
- package/src/lib/server/openclaw-gateway.ts +291 -0
- package/src/lib/server/openclaw-history-merge.ts +36 -0
- package/src/lib/server/openclaw-models.ts +56 -0
- package/src/lib/server/openclaw-permission-presets.ts +64 -0
- package/src/lib/server/openclaw-sync.ts +497 -0
- package/src/lib/server/orchestrator-lg.ts +30 -9
- package/src/lib/server/orchestrator.ts +4 -4
- package/src/lib/server/process-manager.ts +2 -2
- package/src/lib/server/queue.ts +24 -11
- package/src/lib/server/scheduler.ts +2 -2
- package/src/lib/server/session-mailbox.ts +2 -2
- package/src/lib/server/session-run-manager.ts +2 -2
- package/src/lib/server/session-tools/connector.ts +53 -6
- package/src/lib/server/session-tools/crud.ts +3 -3
- package/src/lib/server/session-tools/delegate.ts +22 -6
- package/src/lib/server/session-tools/file.ts +192 -19
- package/src/lib/server/session-tools/index.ts +4 -2
- package/src/lib/server/session-tools/memory.ts +2 -2
- package/src/lib/server/session-tools/openclaw-nodes.ts +112 -0
- package/src/lib/server/session-tools/sandbox.ts +33 -0
- package/src/lib/server/session-tools/search-providers.ts +277 -0
- package/src/lib/server/session-tools/session-info.ts +2 -2
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +2 -2
- package/src/lib/server/session-tools/shell.ts +1 -1
- package/src/lib/server/session-tools/web.ts +53 -72
- package/src/lib/server/storage.ts +74 -11
- package/src/lib/server/stream-agent-chat.ts +53 -4
- package/src/lib/server/suggestions.ts +20 -0
- package/src/lib/server/task-result.test.ts +44 -0
- package/src/lib/server/task-result.ts +14 -0
- package/src/lib/server/ws-hub.ts +14 -0
- package/src/lib/tool-definitions.ts +5 -3
- package/src/lib/tts-stream.ts +130 -0
- package/src/lib/view-routes.ts +28 -0
- package/src/proxy.ts +3 -0
- package/src/stores/use-app-store.ts +80 -1
- package/src/stores/use-approval-store.ts +78 -0
- package/src/stores/use-chat-store.ts +162 -6
- package/src/types/index.ts +154 -3
- package/tsconfig.json +13 -4
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useMemo } from 'react'
|
|
4
4
|
import type { ToolEvent } from '@/stores/use-chat-store'
|
|
5
|
+
import { useChatStore } from '@/stores/use-chat-store'
|
|
5
6
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
7
|
|
|
7
8
|
const TOOL_COLORS: Record<string, string> = {
|
|
@@ -14,6 +15,8 @@ const TOOL_COLORS: Record<string, string> = {
|
|
|
14
15
|
delete_file: '#EF4444',
|
|
15
16
|
edit_file: '#10B981',
|
|
16
17
|
send_file: '#10B981',
|
|
18
|
+
create_document: '#10B981',
|
|
19
|
+
create_spreadsheet: '#10B981',
|
|
17
20
|
web_search: '#3B82F6',
|
|
18
21
|
web_fetch: '#3B82F6',
|
|
19
22
|
delegate_to_agent: '#6366F1',
|
|
@@ -60,6 +63,8 @@ export const TOOL_LABELS: Record<string, string> = {
|
|
|
60
63
|
delete_file: 'Delete File',
|
|
61
64
|
edit_file: 'Edit File',
|
|
62
65
|
send_file: 'Send File',
|
|
66
|
+
create_document: 'Create Document',
|
|
67
|
+
create_spreadsheet: 'Create Spreadsheet',
|
|
63
68
|
web_search: 'Web Search',
|
|
64
69
|
web_fetch: 'Web Fetch',
|
|
65
70
|
claude_code: 'Claude Code',
|
|
@@ -79,7 +84,7 @@ export const TOOL_LABELS: Record<string, string> = {
|
|
|
79
84
|
manage_documents: 'Documents',
|
|
80
85
|
manage_webhooks: 'Webhooks',
|
|
81
86
|
manage_connectors: 'Connectors',
|
|
82
|
-
manage_sessions: '
|
|
87
|
+
manage_sessions: 'Chats',
|
|
83
88
|
memory: 'Memory',
|
|
84
89
|
browser: 'Browser',
|
|
85
90
|
}
|
|
@@ -94,6 +99,8 @@ export const TOOL_DESCRIPTIONS: Record<string, string> = {
|
|
|
94
99
|
delete_file: 'Delete files or directories (when explicitly enabled)',
|
|
95
100
|
edit_file: 'Edit existing files with find-and-replace',
|
|
96
101
|
send_file: 'Send files to the user (images, PDFs, videos, documents, etc.)',
|
|
102
|
+
create_document: 'Render markdown content into PDF, HTML, or image',
|
|
103
|
+
create_spreadsheet: 'Create Excel or CSV files from structured data',
|
|
97
104
|
web_search: 'Search the web for information',
|
|
98
105
|
web_fetch: 'Fetch and read web page content',
|
|
99
106
|
claude_code: 'Enable delegation to Claude Code CLI',
|
|
@@ -103,7 +110,7 @@ export const TOOL_DESCRIPTIONS: Record<string, string> = {
|
|
|
103
110
|
delegate_to_claude_code: 'Delegate complex coding tasks to Claude Code',
|
|
104
111
|
delegate_to_codex_cli: 'Delegate complex coding tasks to Codex CLI',
|
|
105
112
|
delegate_to_opencode_cli: 'Delegate complex coding tasks to OpenCode CLI',
|
|
106
|
-
whoami_tool: 'Reveal the current
|
|
113
|
+
whoami_tool: 'Reveal the current agent and chat context',
|
|
107
114
|
connector_message_tool: 'Send proactive outbound messages via running connectors',
|
|
108
115
|
search_history_tool: 'Search chat history for relevant prior context',
|
|
109
116
|
manage_tasks: 'Create, update, and manage tasks on the board',
|
|
@@ -113,7 +120,7 @@ export const TOOL_DESCRIPTIONS: Record<string, string> = {
|
|
|
113
120
|
manage_documents: 'Upload and search indexed documents',
|
|
114
121
|
manage_webhooks: 'Register and manage inbound webhooks',
|
|
115
122
|
manage_connectors: 'Manage chat platform connectors (Slack, Discord, etc.)',
|
|
116
|
-
manage_sessions: 'Create and manage
|
|
123
|
+
manage_sessions: 'Create and manage agent chats',
|
|
117
124
|
memory: 'Store and recall information across conversations',
|
|
118
125
|
browser: 'Browse the web, take screenshots, and interact with pages',
|
|
119
126
|
}
|
|
@@ -245,6 +252,61 @@ function extractMedia(output: string): { images: string[]; videos: string[]; pdf
|
|
|
245
252
|
return { images, videos, pdfs, files, cleanText }
|
|
246
253
|
}
|
|
247
254
|
|
|
255
|
+
import type { AppSettings } from '@/types'
|
|
256
|
+
|
|
257
|
+
/** Settings keys that can be quick-fixed from error output */
|
|
258
|
+
const TIMEOUT_SETTINGS: Array<{ pattern: RegExp; settingKey: keyof AppSettings; label: string; increment: number }> = [
|
|
259
|
+
{ pattern: /Claude Code CLI timed out/i, settingKey: 'claudeCodeTimeoutSec', label: 'Claude Code Timeout', increment: 600 },
|
|
260
|
+
{ pattern: /Codex CLI timed out|OpenCode CLI timed out/i, settingKey: 'cliProcessTimeoutSec', label: 'CLI Process Timeout', increment: 600 },
|
|
261
|
+
{ pattern: /command timed out|shell.*timed out/i, settingKey: 'shellCommandTimeoutSec', label: 'Shell Timeout', increment: 60 },
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
/** Inline quick-fix button for timeout errors */
|
|
265
|
+
function TimeoutQuickFix({ event }: { event: ToolEvent }) {
|
|
266
|
+
const [applied, setApplied] = useState(false)
|
|
267
|
+
if (event.status !== 'error' || !event.output) return null
|
|
268
|
+
|
|
269
|
+
const match = TIMEOUT_SETTINGS.find((s) => s.pattern.test(event.output || ''))
|
|
270
|
+
if (!match) return null
|
|
271
|
+
|
|
272
|
+
const handleIncrease = async (e: React.MouseEvent) => {
|
|
273
|
+
e.stopPropagation()
|
|
274
|
+
const store = useAppStore.getState()
|
|
275
|
+
const current = (store.appSettings[match.settingKey] as number) || 0
|
|
276
|
+
const newValue = current + match.increment
|
|
277
|
+
await store.updateSettings({ [match.settingKey]: newValue })
|
|
278
|
+
setApplied(true)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (applied) {
|
|
282
|
+
const store = useAppStore.getState()
|
|
283
|
+
const val = store.appSettings[match.settingKey] as number
|
|
284
|
+
return (
|
|
285
|
+
<div className="flex items-center gap-2 mt-2 text-[12px] text-emerald-400 font-500">
|
|
286
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><polyline points="20 6 9 17 4 12" /></svg>
|
|
287
|
+
{match.label} increased to {val}s
|
|
288
|
+
</div>
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<button
|
|
294
|
+
type="button"
|
|
295
|
+
onClick={handleIncrease}
|
|
296
|
+
className="mt-2 flex items-center gap-1.5 px-3 py-1.5 rounded-[8px] text-[12px] font-600
|
|
297
|
+
bg-amber-500/10 border border-amber-500/20 text-amber-400 hover:bg-amber-500/20
|
|
298
|
+
cursor-pointer transition-all"
|
|
299
|
+
style={{ fontFamily: 'inherit' }}
|
|
300
|
+
>
|
|
301
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
302
|
+
<circle cx="12" cy="12" r="10" />
|
|
303
|
+
<polyline points="12 6 12 12 16 14" />
|
|
304
|
+
</svg>
|
|
305
|
+
Increase {match.label}
|
|
306
|
+
</button>
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
|
|
248
310
|
export function ToolCallBubble({ event }: { event: ToolEvent }) {
|
|
249
311
|
const [expanded, setExpanded] = useState(false)
|
|
250
312
|
const [imgExpanded, setImgExpanded] = useState(false)
|
|
@@ -332,7 +394,7 @@ export function ToolCallBubble({ event }: { event: ToolEvent }) {
|
|
|
332
394
|
role="link"
|
|
333
395
|
tabIndex={0}
|
|
334
396
|
onClick={handleAgentClick}
|
|
335
|
-
onKeyDown={(e) => e.key === 'Enter' && handleAgentClick(e as
|
|
397
|
+
onKeyDown={(e) => e.key === 'Enter' && handleAgentClick(e as unknown as React.MouseEvent)}
|
|
336
398
|
className="text-accent-bright hover:underline cursor-pointer font-600"
|
|
337
399
|
>
|
|
338
400
|
{delegationInfo.agentName}
|
|
@@ -377,6 +439,7 @@ export function ToolCallBubble({ event }: { event: ToolEvent }) {
|
|
|
377
439
|
{formattedCleanOutput}
|
|
378
440
|
</pre>
|
|
379
441
|
)}
|
|
442
|
+
<TimeoutQuickFix event={event} />
|
|
380
443
|
</>
|
|
381
444
|
)}
|
|
382
445
|
</div>
|
|
@@ -391,23 +454,39 @@ export function ToolCallBubble({ event }: { event: ToolEvent }) {
|
|
|
391
454
|
<img
|
|
392
455
|
src={src}
|
|
393
456
|
alt={`Screenshot ${i + 1}`}
|
|
457
|
+
loading="lazy"
|
|
394
458
|
className={`rounded-[10px] border border-white/10 cursor-pointer transition-all duration-200 hover:border-white/25 ${imgExpanded ? 'max-w-full' : 'max-w-[400px]'}`}
|
|
395
459
|
onClick={(e) => { e.stopPropagation(); setImgExpanded(!imgExpanded) }}
|
|
396
460
|
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
|
|
397
461
|
/>
|
|
398
|
-
<
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
<
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
462
|
+
<div className="absolute top-2 right-2 flex gap-1 opacity-0 group-hover/img:opacity-100 transition-opacity">
|
|
463
|
+
<button
|
|
464
|
+
onClick={(e) => {
|
|
465
|
+
e.stopPropagation()
|
|
466
|
+
useChatStore.getState().setPreviewContent({ type: 'image', url: src, title: `${label} — Screenshot` })
|
|
467
|
+
}}
|
|
468
|
+
className="bg-black/60 backdrop-blur-sm rounded-[8px] p-1.5 hover:bg-black/80 border-none cursor-pointer"
|
|
469
|
+
title="Open in side panel"
|
|
470
|
+
>
|
|
471
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round">
|
|
472
|
+
<rect x="3" y="3" width="18" height="18" rx="2" />
|
|
473
|
+
<line x1="12" y1="3" x2="12" y2="21" />
|
|
474
|
+
</svg>
|
|
475
|
+
</button>
|
|
476
|
+
<a
|
|
477
|
+
href={src}
|
|
478
|
+
download
|
|
479
|
+
onClick={(e) => e.stopPropagation()}
|
|
480
|
+
className="bg-black/60 backdrop-blur-sm rounded-[8px] p-1.5 hover:bg-black/80"
|
|
481
|
+
title="Download"
|
|
482
|
+
>
|
|
483
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round">
|
|
484
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
485
|
+
<polyline points="7 10 12 15 17 10" />
|
|
486
|
+
<line x1="12" y1="15" x2="12" y2="3" />
|
|
487
|
+
</svg>
|
|
488
|
+
</a>
|
|
489
|
+
</div>
|
|
411
490
|
</div>
|
|
412
491
|
))}
|
|
413
492
|
</div>
|
|
@@ -417,7 +496,7 @@ export function ToolCallBubble({ event }: { event: ToolEvent }) {
|
|
|
417
496
|
{media.videos.length > 0 && (
|
|
418
497
|
<div className="mt-2 flex flex-col gap-2">
|
|
419
498
|
{media.videos.map((src, i) => (
|
|
420
|
-
<video key={i} src={src} controls playsInline className="max-w-full rounded-[10px] border border-white/10" />
|
|
499
|
+
<video key={i} src={src} controls playsInline preload="none" className="max-w-full rounded-[10px] border border-white/10" />
|
|
421
500
|
))}
|
|
422
501
|
</div>
|
|
423
502
|
)}
|
|
@@ -427,7 +506,7 @@ export function ToolCallBubble({ event }: { event: ToolEvent }) {
|
|
|
427
506
|
<div className="mt-2 flex flex-col gap-2">
|
|
428
507
|
{media.pdfs.map((file, i) => (
|
|
429
508
|
<div key={i} className="rounded-[10px] border border-white/10 overflow-hidden">
|
|
430
|
-
<iframe src={file.url} className="w-full h-[400px] bg-white" title={file.name} />
|
|
509
|
+
<iframe src={file.url} loading="lazy" className="w-full h-[400px] bg-white" title={file.name} />
|
|
431
510
|
<a
|
|
432
511
|
href={file.url}
|
|
433
512
|
download
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useState } from 'react'
|
|
3
|
+
import { useState, useRef } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { useChatStore } from '@/stores/use-chat-store'
|
|
5
6
|
import { api } from '@/lib/api-client'
|
|
6
7
|
import { TOOL_LABELS } from '@/lib/tool-definitions'
|
|
7
8
|
|
|
@@ -15,6 +16,7 @@ export function ToolRequestBanner({ text, toolOutputs = [] }: Props) {
|
|
|
15
16
|
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
16
17
|
const sessions = useAppStore((s) => s.sessions)
|
|
17
18
|
const [granted, setGranted] = useState<Set<string>>(new Set())
|
|
19
|
+
const continueSentRef = useRef(false)
|
|
18
20
|
|
|
19
21
|
const toolRequests: { toolId: string; reason: string }[] = []
|
|
20
22
|
const seen = new Set<string>()
|
|
@@ -53,7 +55,23 @@ export function ToolRequestBanner({ text, toolOutputs = [] }: Props) {
|
|
|
53
55
|
const updated = [...currentTools, toolId]
|
|
54
56
|
await api('PUT', `/sessions/${sid}`, { tools: updated })
|
|
55
57
|
await loadSessions()
|
|
56
|
-
|
|
58
|
+
const newGranted = new Set(granted).add(toolId)
|
|
59
|
+
setGranted(newGranted)
|
|
60
|
+
|
|
61
|
+
// Auto-continue: once all requested tools are granted, send a follow-up message
|
|
62
|
+
const allGranted = toolRequests.every(
|
|
63
|
+
(r) => newGranted.has(r.toolId) || updated.includes(r.toolId),
|
|
64
|
+
)
|
|
65
|
+
if (allGranted && !continueSentRef.current) {
|
|
66
|
+
continueSentRef.current = true
|
|
67
|
+
// Small delay to let the session update propagate
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
const { streaming, sendMessage } = useChatStore.getState()
|
|
70
|
+
if (!streaming) {
|
|
71
|
+
sendMessage('Continue')
|
|
72
|
+
}
|
|
73
|
+
}, 300)
|
|
74
|
+
}
|
|
57
75
|
}
|
|
58
76
|
|
|
59
77
|
return (
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import type { ChatTraceBlock } from '@/types'
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
trace: ChatTraceBlock
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function TraceBlock({ trace }: Props) {
|
|
11
|
+
const [collapsed, setCollapsed] = useState(trace.collapsed !== false)
|
|
12
|
+
|
|
13
|
+
const bgColor = trace.type === 'thinking'
|
|
14
|
+
? 'bg-purple-500/[0.04] border-purple-500/10'
|
|
15
|
+
: trace.type === 'tool-call'
|
|
16
|
+
? 'bg-sky-500/[0.04] border-sky-500/10'
|
|
17
|
+
: 'bg-emerald-500/[0.04] border-emerald-500/10'
|
|
18
|
+
|
|
19
|
+
const labelColor = trace.type === 'thinking'
|
|
20
|
+
? 'text-purple-400/70'
|
|
21
|
+
: trace.type === 'tool-call'
|
|
22
|
+
? 'text-sky-400/70'
|
|
23
|
+
: 'text-emerald-400/70'
|
|
24
|
+
|
|
25
|
+
const icon = trace.type === 'thinking'
|
|
26
|
+
? '...'
|
|
27
|
+
: trace.type === 'tool-call'
|
|
28
|
+
? '>'
|
|
29
|
+
: '<'
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className={`my-1 rounded-[8px] border ${bgColor} overflow-hidden`}>
|
|
33
|
+
<button
|
|
34
|
+
onClick={() => setCollapsed(!collapsed)}
|
|
35
|
+
className={`w-full flex items-center gap-2 px-3 py-1.5 text-left cursor-pointer border-none bg-transparent transition-colors hover:bg-white/[0.02] ${labelColor}`}
|
|
36
|
+
style={{ fontFamily: 'inherit' }}
|
|
37
|
+
>
|
|
38
|
+
<span className="font-mono text-[10px] w-4 shrink-0">{collapsed ? '+' : '-'}</span>
|
|
39
|
+
<span className="font-mono text-[10px] shrink-0">{icon}</span>
|
|
40
|
+
<span className="text-[11px] font-600 truncate">
|
|
41
|
+
{trace.label || trace.type.replace('-', ' ')}
|
|
42
|
+
</span>
|
|
43
|
+
</button>
|
|
44
|
+
{!collapsed && (
|
|
45
|
+
<div className="px-3 pb-2">
|
|
46
|
+
<pre className={`text-[11px] leading-relaxed whitespace-pre-wrap break-words m-0 ${
|
|
47
|
+
trace.type === 'thinking'
|
|
48
|
+
? 'text-text-3/60 italic'
|
|
49
|
+
: 'text-text-3/70 font-mono'
|
|
50
|
+
}`}>
|
|
51
|
+
{trace.content.length > 2000
|
|
52
|
+
? trace.content.slice(0, 2000) + '\n... (truncated)'
|
|
53
|
+
: trace.content}
|
|
54
|
+
</pre>
|
|
55
|
+
</div>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Parse message text with [[prefix]] markers into text and trace blocks */
|
|
62
|
+
export function parseTraceBlocks(text: string): Array<{ type: 'text'; content: string } | ChatTraceBlock> {
|
|
63
|
+
const blocks: Array<{ type: 'text'; content: string } | ChatTraceBlock> = []
|
|
64
|
+
const regex = /\[\[(thinking|tool|tool-result|trace|meta)\]\]([\s\S]*?)(?=\[\[(thinking|tool|tool-result|trace|meta)\]\]|$)/g
|
|
65
|
+
|
|
66
|
+
let lastEnd = 0
|
|
67
|
+
let match: RegExpExecArray | null
|
|
68
|
+
|
|
69
|
+
while ((match = regex.exec(text)) !== null) {
|
|
70
|
+
// Add any text before this match
|
|
71
|
+
if (match.index > lastEnd) {
|
|
72
|
+
const before = text.slice(lastEnd, match.index).trim()
|
|
73
|
+
if (before) blocks.push({ type: 'text', content: before })
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const prefix = match[1]
|
|
77
|
+
const content = match[2].trim()
|
|
78
|
+
if (content) {
|
|
79
|
+
if (prefix === 'thinking' || prefix === 'trace') {
|
|
80
|
+
blocks.push({ type: 'thinking', content, collapsed: true })
|
|
81
|
+
} else if (prefix === 'tool') {
|
|
82
|
+
const firstLine = content.split('\n')[0] || ''
|
|
83
|
+
blocks.push({ type: 'tool-call', content, label: firstLine.slice(0, 60), collapsed: true })
|
|
84
|
+
} else if (prefix === 'tool-result') {
|
|
85
|
+
blocks.push({ type: 'tool-result', content, collapsed: true })
|
|
86
|
+
}
|
|
87
|
+
// meta is ignored
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
lastEnd = match.index + match[0].length
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Add remaining text
|
|
94
|
+
if (lastEnd === 0) {
|
|
95
|
+
// No trace markers found
|
|
96
|
+
if (text.trim()) blocks.push({ type: 'text', content: text })
|
|
97
|
+
} else if (lastEnd < text.length) {
|
|
98
|
+
const remaining = text.slice(lastEnd).trim()
|
|
99
|
+
if (remaining) blocks.push({ type: 'text', content: remaining })
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return blocks
|
|
103
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { VoiceConversationState } from '@/hooks/use-voice-conversation'
|
|
4
|
+
|
|
5
|
+
interface VoiceOverlayProps {
|
|
6
|
+
state: VoiceConversationState
|
|
7
|
+
interimText: string
|
|
8
|
+
transcript: string
|
|
9
|
+
onStop: () => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const STATE_LABELS: Record<VoiceConversationState, string> = {
|
|
13
|
+
idle: '',
|
|
14
|
+
listening: 'Listening...',
|
|
15
|
+
processing: 'Processing...',
|
|
16
|
+
speaking: 'Speaking...',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function VoiceOverlay({ state, interimText, transcript, onStop }: VoiceOverlayProps) {
|
|
20
|
+
if (state === 'idle') return null
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="absolute inset-0 z-20 flex flex-col items-center justify-center gap-4 bg-bg/90 backdrop-blur-sm">
|
|
24
|
+
{/* Animated indicator */}
|
|
25
|
+
<div className="relative">
|
|
26
|
+
<div className={`w-20 h-20 rounded-full flex items-center justify-center ${
|
|
27
|
+
state === 'listening'
|
|
28
|
+
? 'bg-accent/20 animate-pulse'
|
|
29
|
+
: state === 'speaking'
|
|
30
|
+
? 'bg-green-500/20'
|
|
31
|
+
: 'bg-yellow-500/20'
|
|
32
|
+
}`}>
|
|
33
|
+
<div className={`w-12 h-12 rounded-full flex items-center justify-center ${
|
|
34
|
+
state === 'listening'
|
|
35
|
+
? 'bg-accent/30'
|
|
36
|
+
: state === 'speaking'
|
|
37
|
+
? 'bg-green-500/30'
|
|
38
|
+
: 'bg-yellow-500/30'
|
|
39
|
+
}`}>
|
|
40
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className={
|
|
41
|
+
state === 'listening' ? 'text-accent-bright' : state === 'speaking' ? 'text-green-400' : 'text-yellow-400'
|
|
42
|
+
}>
|
|
43
|
+
{state === 'speaking' ? (
|
|
44
|
+
<>
|
|
45
|
+
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" />
|
|
46
|
+
<path d="M15.54 8.46a5 5 0 0 1 0 7.07" />
|
|
47
|
+
<path d="M19.07 4.93a10 10 0 0 1 0 14.14" />
|
|
48
|
+
</>
|
|
49
|
+
) : (
|
|
50
|
+
<>
|
|
51
|
+
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" />
|
|
52
|
+
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
|
|
53
|
+
<line x1="12" x2="12" y1="19" y2="22" />
|
|
54
|
+
</>
|
|
55
|
+
)}
|
|
56
|
+
</svg>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div className="text-[14px] font-500 text-text-2">{STATE_LABELS[state]}</div>
|
|
62
|
+
|
|
63
|
+
{/* Transcript display */}
|
|
64
|
+
{(transcript || interimText) && (
|
|
65
|
+
<div className="max-w-md px-6 text-center">
|
|
66
|
+
{transcript && <p className="text-[14px] text-text-1 mb-1">{transcript}</p>}
|
|
67
|
+
{interimText && <p className="text-[13px] text-text-3/60 italic">{interimText}</p>}
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
|
|
71
|
+
{/* Stop button */}
|
|
72
|
+
<button
|
|
73
|
+
onClick={onStop}
|
|
74
|
+
className="mt-2 px-5 py-2 rounded-lg bg-red-500/10 text-red-400 text-[13px] font-600 hover:bg-red-500/20 transition-colors"
|
|
75
|
+
>
|
|
76
|
+
Stop Voice
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
@@ -104,8 +104,12 @@ export function ConnectorList({ inSidebar: _inSidebar }: { inSidebar?: boolean }
|
|
|
104
104
|
const agent = agents[c.agentId]
|
|
105
105
|
const isRunning = c.status === 'running'
|
|
106
106
|
const isToggling = toggling === c.id
|
|
107
|
-
// Can only toggle if connector has credentials (or
|
|
108
|
-
const hasCredentials = c.platform === 'whatsapp'
|
|
107
|
+
// Can only toggle if connector has credentials (or uses non-token auth modes).
|
|
108
|
+
const hasCredentials = c.platform === 'whatsapp'
|
|
109
|
+
|| c.platform === 'openclaw'
|
|
110
|
+
|| c.platform === 'signal'
|
|
111
|
+
|| (c.platform === 'bluebubbles' && (!!c.credentialId || !!c.config?.password))
|
|
112
|
+
|| !!c.credentialId
|
|
109
113
|
return (
|
|
110
114
|
<div
|
|
111
115
|
key={c.id}
|
|
@@ -114,7 +114,7 @@ const PLATFORMS: {
|
|
|
114
114
|
tokenHelp: 'Required when your OpenClaw gateway is auth-protected',
|
|
115
115
|
configFields: [
|
|
116
116
|
{ key: 'wsUrl', label: 'WebSocket URL', placeholder: 'ws://localhost:18789', help: 'OpenClaw gateway WebSocket endpoint (root URL, not /ws)' },
|
|
117
|
-
{ key: 'sessionKey', label: '
|
|
117
|
+
{ key: 'sessionKey', label: 'Chat Key Filter', placeholder: 'main', help: 'Optional. If set, only inbound events for this OpenClaw session are processed.' },
|
|
118
118
|
{ key: 'nodeId', label: 'Client Label', placeholder: 'swarmclaw', help: 'Optional display label shown in OpenClaw presence metadata.' },
|
|
119
119
|
{ key: 'role', label: 'Gateway Role', placeholder: 'operator', help: 'Optional role claim for connect handshake. Default is operator.' },
|
|
120
120
|
{ key: 'scopes', label: 'Scopes (CSV)', placeholder: 'operator.read,operator.write', help: 'Optional comma-separated scopes for OpenClaw connect.' },
|
|
@@ -122,6 +122,28 @@ const PLATFORMS: {
|
|
|
122
122
|
{ key: 'tickIntervalMs', label: 'Tick Interval Override (ms)', placeholder: '30000', help: 'Optional watchdog interval override when policy tick is unavailable.' },
|
|
123
123
|
],
|
|
124
124
|
},
|
|
125
|
+
{
|
|
126
|
+
id: 'bluebubbles',
|
|
127
|
+
label: 'BlueBubbles',
|
|
128
|
+
color: '#2E89FF',
|
|
129
|
+
setupSteps: [
|
|
130
|
+
'Run BlueBubbles server on your macOS host and enable the REST API',
|
|
131
|
+
'Copy the BlueBubbles server password',
|
|
132
|
+
'After saving the connector, point BlueBubbles webhook to /api/connectors/<connector-id>/webhook',
|
|
133
|
+
'Optionally set dmPolicy=pairing to require explicit sender approval for new DMs',
|
|
134
|
+
],
|
|
135
|
+
tokenLabel: 'BlueBubbles Password',
|
|
136
|
+
tokenHelp: 'Server password used for /api/v1/ping and /api/v1/message/text',
|
|
137
|
+
configFields: [
|
|
138
|
+
{ key: 'serverUrl', label: 'Server URL', placeholder: 'http://127.0.0.1:1234', help: 'BlueBubbles server URL (no trailing /api path needed)' },
|
|
139
|
+
{ key: 'chatIds', label: 'Allowed Chat IDs', placeholder: 'iMessage;-;+15551234567', help: 'Optional comma-separated chat IDs/guid fragments. Leave empty for all chats.' },
|
|
140
|
+
{ key: 'dmPolicy', label: 'DM Policy', placeholder: 'open | allowlist | pairing | disabled', help: 'Access policy for direct-message senders. Default: open.' },
|
|
141
|
+
{ key: 'allowFrom', label: 'Allowed Sender IDs', placeholder: '+15551234567,test@example.com', help: 'Optional comma-separated sender IDs for allowlist/pairing mode.' },
|
|
142
|
+
{ key: 'outboundTarget', label: 'Default Outbound Target', placeholder: 'iMessage;-;+15551234567', help: 'Used when proactive sends omit "to".' },
|
|
143
|
+
{ key: 'webhookSecret', label: 'Webhook Secret', placeholder: 'optional-shared-secret', help: 'Optional secret required by /api/connectors/{id}/webhook (header: x-connector-secret or ?secret=...)' },
|
|
144
|
+
{ key: 'timeoutMs', label: 'Request Timeout (ms)', placeholder: '10000', help: 'Optional BlueBubbles API timeout in milliseconds.' },
|
|
145
|
+
],
|
|
146
|
+
},
|
|
125
147
|
{
|
|
126
148
|
id: 'matrix',
|
|
127
149
|
label: 'Matrix',
|
|
@@ -146,13 +168,14 @@ const PLATFORMS: {
|
|
|
146
168
|
setupSteps: [
|
|
147
169
|
'Create a Google Cloud project and enable the Google Chat API',
|
|
148
170
|
'Create a service account and download the JSON key file',
|
|
149
|
-
'In Google Chat Admin, configure
|
|
171
|
+
'In Google Chat Admin, configure event delivery to /api/connectors/<connector-id>/webhook',
|
|
150
172
|
'Paste the full service account JSON as the bot token',
|
|
151
173
|
],
|
|
152
174
|
tokenLabel: 'Service Account JSON',
|
|
153
175
|
tokenHelp: 'Paste the full service account JSON key file contents',
|
|
154
176
|
configFields: [
|
|
155
177
|
{ key: 'spaceIds', label: 'Space IDs', placeholder: 'spaces/AAAA123', help: 'Comma-separated Google Chat space IDs' },
|
|
178
|
+
{ key: 'webhookSecret', label: 'Webhook Secret', placeholder: 'optional-shared-secret', help: 'Optional secret required by /api/connectors/{id}/webhook (header: x-connector-secret or ?secret=...)' },
|
|
156
179
|
],
|
|
157
180
|
},
|
|
158
181
|
{
|
|
@@ -163,13 +186,14 @@ const PLATFORMS: {
|
|
|
163
186
|
'Register a bot in the Azure Bot Framework portal',
|
|
164
187
|
'Note the Microsoft App ID and generate an App Secret',
|
|
165
188
|
'Set up a public HTTPS endpoint for webhook delivery',
|
|
166
|
-
'
|
|
189
|
+
'After saving the connector, point Azure to /api/connectors/<connector-id>/webhook',
|
|
167
190
|
],
|
|
168
191
|
tokenLabel: 'App Secret',
|
|
169
192
|
tokenHelp: 'Microsoft App Secret from Azure Bot registration',
|
|
170
193
|
configFields: [
|
|
171
194
|
{ key: 'appId', label: 'Microsoft App ID', placeholder: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', help: 'Azure Bot Framework App ID' },
|
|
172
|
-
{ key: 'notifyUrl', label: 'Notify URL', placeholder: 'https://your-server.com/api/
|
|
195
|
+
{ key: 'notifyUrl', label: 'Notify URL', placeholder: 'https://your-server.com/api/connectors/<id>/webhook', help: 'Public HTTPS endpoint for receiving messages (informational)' },
|
|
196
|
+
{ key: 'webhookSecret', label: 'Webhook Secret', placeholder: 'optional-shared-secret', help: 'Optional secret required by /api/connectors/{id}/webhook (header: x-connector-secret or ?secret=...)' },
|
|
173
197
|
],
|
|
174
198
|
},
|
|
175
199
|
{
|
|
@@ -366,7 +390,7 @@ export function ConnectorSheet() {
|
|
|
366
390
|
<div>
|
|
367
391
|
<div className={`text-[14px] font-600 ${platform === p.id ? 'text-text' : 'text-text-2'}`}>{p.label}</div>
|
|
368
392
|
<div className="text-[11px] text-text-3 mt-0.5">
|
|
369
|
-
{p.id === 'whatsapp' ? 'QR code pairing' : p.id === 'openclaw' ? 'WebSocket gateway' : p.id === 'signal' ? 'signal-cli binary' : p.id === 'matrix' ? 'Access token' : p.id === 'googlechat' ? 'Service account' : p.id === 'teams' ? 'Bot Framework' : 'Bot token'}
|
|
393
|
+
{p.id === 'whatsapp' ? 'QR code pairing' : p.id === 'openclaw' ? 'WebSocket gateway' : p.id === 'bluebubbles' ? 'iMessage bridge' : p.id === 'signal' ? 'signal-cli binary' : p.id === 'matrix' ? 'Access token' : p.id === 'googlechat' ? 'Service account' : p.id === 'teams' ? 'Bot Framework' : 'Bot token'}
|
|
370
394
|
</div>
|
|
371
395
|
</div>
|
|
372
396
|
</button>
|
|
@@ -552,7 +576,7 @@ export function ConnectorSheet() {
|
|
|
552
576
|
|
|
553
577
|
{/* Platform-specific config */}
|
|
554
578
|
{platformConfig.configFields.map((field) => {
|
|
555
|
-
const isTagField = field.key === 'allowedJids' || field.key === 'channelIds' || field.key === 'chatIds'
|
|
579
|
+
const isTagField = field.key === 'allowedJids' || field.key === 'channelIds' || field.key === 'chatIds' || field.key === 'allowFrom'
|
|
556
580
|
if (isTagField) {
|
|
557
581
|
const tags = (config[field.key] || '').split(',').map((s) => s.trim()).filter(Boolean)
|
|
558
582
|
return (
|
|
@@ -732,7 +756,7 @@ export function ConnectorSheet() {
|
|
|
732
756
|
</div>
|
|
733
757
|
<p className="text-[11px] text-text-3">
|
|
734
758
|
{waHasCreds
|
|
735
|
-
? 'Reconnecting with saved
|
|
759
|
+
? 'Reconnecting with saved credentials, this should only take a moment'
|
|
736
760
|
: 'Connecting to WhatsApp, QR code will appear shortly'}
|
|
737
761
|
</p>
|
|
738
762
|
{waHasCreds && (
|