@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,202 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { useChatStore } from '@/stores/use-chat-store'
|
|
6
|
+
import { SessionCard } from './session-card'
|
|
7
|
+
import { fetchMessages } from '@/lib/sessions'
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
inSidebar?: boolean
|
|
11
|
+
onSelect?: () => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type SessionFilter = 'all' | 'active' | 'human' | 'orchestrated'
|
|
15
|
+
type SortMode = 'lastActive' | 'name' | 'messages'
|
|
16
|
+
|
|
17
|
+
export function SessionList({ inSidebar, onSelect }: Props) {
|
|
18
|
+
const sessions = useAppStore((s) => s.sessions)
|
|
19
|
+
const currentUser = useAppStore((s) => s.currentUser)
|
|
20
|
+
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
21
|
+
const setCurrentSession = useAppStore((s) => s.setCurrentSession)
|
|
22
|
+
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
23
|
+
const loadConnectors = useAppStore((s) => s.loadConnectors)
|
|
24
|
+
const setNewSessionOpen = useAppStore((s) => s.setNewSessionOpen)
|
|
25
|
+
const clearSessions = useAppStore((s) => s.clearSessions)
|
|
26
|
+
const togglePinSession = useAppStore((s) => s.togglePinSession)
|
|
27
|
+
const setMessages = useChatStore((s) => s.setMessages)
|
|
28
|
+
const [search, setSearch] = useState('')
|
|
29
|
+
const [typeFilter, setTypeFilter] = useState<SessionFilter>('all')
|
|
30
|
+
const [sortMode, setSortMode] = useState<SortMode>('lastActive')
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
void loadConnectors()
|
|
34
|
+
}, [loadConnectors])
|
|
35
|
+
|
|
36
|
+
const allUserSessions = useMemo(() => {
|
|
37
|
+
return Object.values(sessions).filter((s) => {
|
|
38
|
+
if (s.name === '__main__') return false
|
|
39
|
+
const owner = (s.user || '').toLowerCase()
|
|
40
|
+
const isPlatformOwned = owner === 'system' || owner === 'connector' || owner === 'swarm'
|
|
41
|
+
const isCurrentUserOwned = !!currentUser && owner === currentUser.toLowerCase()
|
|
42
|
+
const isUnownedLegacy = !owner
|
|
43
|
+
if (!isCurrentUserOwned && !isPlatformOwned && !isUnownedLegacy) return false
|
|
44
|
+
return true
|
|
45
|
+
})
|
|
46
|
+
}, [sessions, currentUser])
|
|
47
|
+
|
|
48
|
+
const filtered = useMemo(() => {
|
|
49
|
+
return allUserSessions
|
|
50
|
+
.filter((s) => {
|
|
51
|
+
if (search && !s.name.toLowerCase().includes(search.toLowerCase())) return false
|
|
52
|
+
if (typeFilter === 'active' && !s.active) return false
|
|
53
|
+
if (typeFilter === 'human' && s.sessionType === 'orchestrated') return false
|
|
54
|
+
if (typeFilter === 'orchestrated' && s.sessionType !== 'orchestrated') return false
|
|
55
|
+
return true
|
|
56
|
+
})
|
|
57
|
+
.sort((a, b) => {
|
|
58
|
+
// Pinned always first
|
|
59
|
+
if (a.pinned && !b.pinned) return -1
|
|
60
|
+
if (!a.pinned && b.pinned) return 1
|
|
61
|
+
// Then by sort mode
|
|
62
|
+
if (sortMode === 'name') return a.name.localeCompare(b.name)
|
|
63
|
+
if (sortMode === 'messages') return (b.messages?.length || 0) - (a.messages?.length || 0)
|
|
64
|
+
return (b.lastActiveAt || 0) - (a.lastActiveAt || 0)
|
|
65
|
+
})
|
|
66
|
+
}, [allUserSessions, search, typeFilter, sortMode])
|
|
67
|
+
|
|
68
|
+
const handleSelect = async (id: string) => {
|
|
69
|
+
setCurrentSession(id)
|
|
70
|
+
if (typeof window !== 'undefined') {
|
|
71
|
+
window.dispatchEvent(new CustomEvent('swarmclaw:scroll-bottom'))
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const msgs = await fetchMessages(id)
|
|
75
|
+
setMessages(msgs)
|
|
76
|
+
} catch {
|
|
77
|
+
setMessages(sessions[id]?.messages || [])
|
|
78
|
+
}
|
|
79
|
+
await loadSessions()
|
|
80
|
+
onSelect?.()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Truly empty — no sessions at all for this user
|
|
84
|
+
if (!allUserSessions.length) {
|
|
85
|
+
return (
|
|
86
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-4 text-text-3 p-8 text-center">
|
|
87
|
+
<div className="w-12 h-12 rounded-[14px] bg-accent-soft flex items-center justify-center mb-1">
|
|
88
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" className="text-accent-bright">
|
|
89
|
+
<path d="M12 2L14.5 9.5L22 12L14.5 14.5L12 22L9.5 14.5L2 12L9.5 9.5L12 2Z" fill="currentColor" />
|
|
90
|
+
</svg>
|
|
91
|
+
</div>
|
|
92
|
+
<p className="font-display text-[15px] font-600 text-text-2">No sessions yet</p>
|
|
93
|
+
<p className="text-[13px] text-text-3/50">Create one to start chatting</p>
|
|
94
|
+
{!inSidebar && (
|
|
95
|
+
<button
|
|
96
|
+
onClick={() => setNewSessionOpen(true)}
|
|
97
|
+
className="mt-3 px-8 py-3 rounded-[14px] border-none bg-[#6366F1] text-white
|
|
98
|
+
text-[14px] font-600 cursor-pointer active:scale-95 transition-all duration-200
|
|
99
|
+
shadow-[0_4px_16px_rgba(99,102,241,0.2)]"
|
|
100
|
+
style={{ fontFamily: 'inherit' }}
|
|
101
|
+
>
|
|
102
|
+
+ New Session
|
|
103
|
+
</button>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<div className="flex-1 flex flex-col overflow-y-auto">
|
|
111
|
+
{/* Filter tabs — always visible when sessions exist */}
|
|
112
|
+
<div className="flex items-center gap-1 px-4 pt-2 pb-1 shrink-0">
|
|
113
|
+
{(['all', 'active', 'human', 'orchestrated'] as SessionFilter[]).map((f) => (
|
|
114
|
+
<button
|
|
115
|
+
key={f}
|
|
116
|
+
onClick={() => setTypeFilter(f)}
|
|
117
|
+
className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 cursor-pointer transition-all
|
|
118
|
+
${typeFilter === f ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`}
|
|
119
|
+
style={{ fontFamily: 'inherit' }}
|
|
120
|
+
>
|
|
121
|
+
{f === 'all' ? 'All' : f === 'active' ? 'Active' : f === 'human' ? 'Human' : 'AI'}
|
|
122
|
+
</button>
|
|
123
|
+
))}
|
|
124
|
+
{filtered.length > 0 && (
|
|
125
|
+
<button
|
|
126
|
+
onClick={async () => {
|
|
127
|
+
if (!window.confirm(`Delete ${filtered.length} session${filtered.length === 1 ? '' : 's'}?`)) return
|
|
128
|
+
await clearSessions(filtered.map((s) => s.id))
|
|
129
|
+
}}
|
|
130
|
+
className="ml-auto p-1.5 rounded-[8px] text-text-3/70 hover:text-red-400 hover:bg-red-400/[0.06]
|
|
131
|
+
cursor-pointer transition-all bg-transparent border-none"
|
|
132
|
+
title="Clear all sessions"
|
|
133
|
+
>
|
|
134
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
135
|
+
<polyline points="3 6 5 6 21 6" /><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
|
|
136
|
+
<path d="M10 11v6" /><path d="M14 11v6" /><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
|
|
137
|
+
</svg>
|
|
138
|
+
</button>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
{/* Search — always visible */}
|
|
143
|
+
<div className="px-4 py-2 shrink-0 flex gap-2">
|
|
144
|
+
<input
|
|
145
|
+
type="text"
|
|
146
|
+
value={search}
|
|
147
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
148
|
+
placeholder="Search..."
|
|
149
|
+
className="flex-1 px-4 py-2.5 rounded-[12px] border border-white/[0.04] bg-surface text-text
|
|
150
|
+
text-[13px] outline-none transition-all duration-200 placeholder:text-text-3/70 focus-glow"
|
|
151
|
+
style={{ fontFamily: 'inherit' }}
|
|
152
|
+
/>
|
|
153
|
+
{/* Sort dropdown */}
|
|
154
|
+
<select
|
|
155
|
+
value={sortMode}
|
|
156
|
+
onChange={(e) => setSortMode(e.target.value as SortMode)}
|
|
157
|
+
aria-label="Sort sessions"
|
|
158
|
+
className="px-2 py-2 rounded-[12px] border border-white/[0.04] bg-surface text-text
|
|
159
|
+
text-[11px] outline-none cursor-pointer"
|
|
160
|
+
style={{ fontFamily: 'inherit' }}
|
|
161
|
+
>
|
|
162
|
+
<option value="lastActive">Recent</option>
|
|
163
|
+
<option value="name">Name</option>
|
|
164
|
+
<option value="messages">Messages</option>
|
|
165
|
+
</select>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
{filtered.length > 0 ? (
|
|
169
|
+
<div className="flex flex-col gap-1 px-2 pb-4">
|
|
170
|
+
{filtered.map((s) => (
|
|
171
|
+
<div key={s.id} className="group/pin relative">
|
|
172
|
+
<SessionCard
|
|
173
|
+
session={s}
|
|
174
|
+
active={s.id === currentSessionId}
|
|
175
|
+
onClick={() => handleSelect(s.id)}
|
|
176
|
+
/>
|
|
177
|
+
<button
|
|
178
|
+
onClick={(e) => { e.stopPropagation(); togglePinSession(s.id) }}
|
|
179
|
+
aria-label={s.pinned ? 'Unpin session' : 'Pin session'}
|
|
180
|
+
className={`absolute top-2 right-2 p-1 rounded-[6px] border-none cursor-pointer transition-all
|
|
181
|
+
${s.pinned
|
|
182
|
+
? 'text-amber-400 bg-amber-400/10 opacity-100'
|
|
183
|
+
: 'text-text-3/50 bg-transparent opacity-0 group-hover/pin:opacity-100 hover:text-text-2 hover:bg-white/[0.04]'}`}
|
|
184
|
+
>
|
|
185
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill={s.pinned ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
186
|
+
<path d="M12 17v5" />
|
|
187
|
+
<path d="M9 2h6l-1 7h4l-8 8 2-8H8z" />
|
|
188
|
+
</svg>
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
))}
|
|
192
|
+
</div>
|
|
193
|
+
) : (
|
|
194
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-3 text-text-3 p-8 text-center">
|
|
195
|
+
<p className="text-[13px] text-text-3/50">
|
|
196
|
+
No {typeFilter === 'orchestrated' ? 'AI' : typeFilter === 'active' ? 'active' : typeFilter} sessions{search ? ` matching "${search}"` : ''}
|
|
197
|
+
</p>
|
|
198
|
+
</div>
|
|
199
|
+
)}
|
|
200
|
+
</div>
|
|
201
|
+
)
|
|
202
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react'
|
|
4
|
+
import { api } from '@/lib/api-client'
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
aiPrompt: string
|
|
8
|
+
setAiPrompt: (v: string) => void
|
|
9
|
+
generating: boolean
|
|
10
|
+
generated: boolean
|
|
11
|
+
genError: string
|
|
12
|
+
onGenerate: () => void
|
|
13
|
+
appSettings?: Record<string, any>
|
|
14
|
+
placeholder: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function AiGenBlock({ aiPrompt, setAiPrompt, generating, generated, genError, onGenerate, placeholder }: Props) {
|
|
18
|
+
const [expanded, setExpanded] = useState(false)
|
|
19
|
+
const [genInfo, setGenInfo] = useState<{ provider: string; model: string } | null>(null)
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (expanded && !genInfo) {
|
|
23
|
+
api<{ provider: string; model: string }>('GET', '/generate/info')
|
|
24
|
+
.then(setGenInfo)
|
|
25
|
+
.catch((err) => console.error('AI gen info fetch failed:', err))
|
|
26
|
+
}
|
|
27
|
+
}, [expanded])
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="mb-10">
|
|
31
|
+
<button
|
|
32
|
+
onClick={() => setExpanded(!expanded)}
|
|
33
|
+
className="flex items-center gap-2.5 px-4 py-3 rounded-[14px] border border-[#6366F1]/15 bg-[#6366F1]/[0.03] hover:bg-[#6366F1]/[0.06] transition-all cursor-pointer w-full text-left"
|
|
34
|
+
style={{ fontFamily: 'inherit' }}
|
|
35
|
+
>
|
|
36
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" className="text-accent-bright shrink-0">
|
|
37
|
+
<path d="M12 2L14.5 9.5L22 12L14.5 14.5L12 22L9.5 14.5L2 12L9.5 9.5L12 2Z" fill="currentColor" />
|
|
38
|
+
</svg>
|
|
39
|
+
<span className="font-display text-[13px] font-600 text-accent-bright flex-1">Generate with AI</span>
|
|
40
|
+
<svg
|
|
41
|
+
width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"
|
|
42
|
+
className={`text-accent-bright/50 transition-transform duration-200 ${expanded ? 'rotate-180' : ''}`}
|
|
43
|
+
>
|
|
44
|
+
<polyline points="6 9 12 15 18 9" />
|
|
45
|
+
</svg>
|
|
46
|
+
</button>
|
|
47
|
+
|
|
48
|
+
{expanded && (
|
|
49
|
+
<div className="mt-3 p-5 rounded-[18px] border border-[#6366F1]/15 bg-[#6366F1]/[0.03]"
|
|
50
|
+
style={{ animation: 'fade-in 0.2s ease' }}>
|
|
51
|
+
<textarea
|
|
52
|
+
value={aiPrompt}
|
|
53
|
+
onChange={(e) => setAiPrompt(e.target.value)}
|
|
54
|
+
placeholder={placeholder}
|
|
55
|
+
rows={2}
|
|
56
|
+
className="w-full px-4 py-3 rounded-[12px] border border-[#6366F1]/10 bg-[#6366F1]/[0.02] text-text text-[14px] outline-none transition-all duration-200 placeholder:text-text-3/70 focus:border-[#6366F1]/30 resize-none"
|
|
57
|
+
style={{ fontFamily: 'inherit' }}
|
|
58
|
+
autoFocus
|
|
59
|
+
/>
|
|
60
|
+
<button
|
|
61
|
+
onClick={onGenerate}
|
|
62
|
+
disabled={generating || !aiPrompt.trim()}
|
|
63
|
+
className="mt-3 px-5 py-2.5 rounded-[12px] border-none bg-[#6366F1] text-white text-[13px] font-600 cursor-pointer disabled:opacity-30 transition-all hover:brightness-110 active:scale-[0.97] shadow-[0_2px_12px_rgba(99,102,241,0.2)]"
|
|
64
|
+
style={{ fontFamily: 'inherit' }}
|
|
65
|
+
>
|
|
66
|
+
{generating ? 'Generating...' : generated ? 'Regenerate' : 'Generate'}
|
|
67
|
+
</button>
|
|
68
|
+
{generated && <span className="ml-3 text-[12px] text-emerald-400/70">Fields populated — edit below</span>}
|
|
69
|
+
{genError && <p className="mt-2 text-[12px] text-red-400/80">{genError}</p>}
|
|
70
|
+
<p className="mt-3 text-[11px] text-text-3/50">
|
|
71
|
+
Using {genInfo ? `${genInfo.model} via ${genInfo.provider}` : 'auto-detected provider'}
|
|
72
|
+
</p>
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
user: string
|
|
5
|
+
size?: 'xs' | 'sm' | 'md' | 'lg'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const sizes = {
|
|
9
|
+
xs: 'w-6 h-6 text-[9px] rounded-[7px]',
|
|
10
|
+
sm: 'w-7 h-7 text-[10px] rounded-[8px]',
|
|
11
|
+
md: 'w-9 h-9 text-[13px] rounded-[10px]',
|
|
12
|
+
lg: 'w-[72px] h-[72px] text-[24px] rounded-[22px]',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Generate a consistent gradient from a username */
|
|
16
|
+
function userGradient(name: string): string {
|
|
17
|
+
let hash = 0
|
|
18
|
+
for (let i = 0; i < name.length; i++) {
|
|
19
|
+
hash = name.charCodeAt(i) + ((hash << 5) - hash)
|
|
20
|
+
}
|
|
21
|
+
const hue = Math.abs(hash) % 360
|
|
22
|
+
return `linear-gradient(135deg, hsl(${hue}, 70%, 35%), hsl(${(hue + 30) % 360}, 75%, 50%))`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function Avatar({ user, size = 'md' }: Props) {
|
|
26
|
+
const initial = (user || '?')[0].toUpperCase()
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
className={`${sizes[size]} flex items-center justify-center font-display font-600 tracking-tight shrink-0 text-white`}
|
|
30
|
+
style={{ background: userGradient(user) }}
|
|
31
|
+
>
|
|
32
|
+
{initial}
|
|
33
|
+
</div>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function AiAvatar({ size = 'md' }: { size?: 'sm' | 'md' }) {
|
|
38
|
+
const s = size === 'sm' ? 'w-6 h-6' : 'w-8 h-8'
|
|
39
|
+
const iconSize = size === 'sm' ? 12 : 16
|
|
40
|
+
return (
|
|
41
|
+
<div className={`${s} rounded-[8px] bg-accent-soft flex items-center justify-center shrink-0`}>
|
|
42
|
+
<svg width={iconSize} height={iconSize} viewBox="0 0 24 24" fill="none" className="text-accent-bright">
|
|
43
|
+
<path d="M12 2L14.5 9.5L22 12L14.5 14.5L12 22L9.5 14.5L2 12L9.5 9.5L12 2Z"
|
|
44
|
+
fill="currentColor" />
|
|
45
|
+
</svg>
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
open: boolean
|
|
7
|
+
onClose: () => void
|
|
8
|
+
children: ReactNode
|
|
9
|
+
wide?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function BottomSheet({ open, onClose, children, wide }: Props) {
|
|
13
|
+
if (!open) return null
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="fixed inset-0 z-100 flex items-center justify-center p-6">
|
|
17
|
+
<div className="absolute inset-0 bg-black/70 backdrop-blur-sm" onClick={onClose} />
|
|
18
|
+
<div
|
|
19
|
+
className={`relative bg-raised w-full ${wide ? 'max-w-[640px]' : 'max-w-[520px]'} max-h-[85vh] flex flex-col
|
|
20
|
+
rounded-[24px] border border-white/[0.06]
|
|
21
|
+
shadow-[0_24px_80px_rgba(0,0,0,0.6),0_0_1px_rgba(255,255,255,0.05)]`}
|
|
22
|
+
style={{ animation: 'modal-in 0.3s cubic-bezier(0.16, 1, 0.3, 1)' }}
|
|
23
|
+
>
|
|
24
|
+
<div className="flex-1 overflow-y-auto px-8 pt-8 pb-8">
|
|
25
|
+
{children}
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
open: boolean
|
|
5
|
+
title: string
|
|
6
|
+
message: string
|
|
7
|
+
confirmLabel?: string
|
|
8
|
+
danger?: boolean
|
|
9
|
+
onConfirm: () => void
|
|
10
|
+
onCancel: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function ConfirmDialog({ open, title, message, confirmLabel = 'Confirm', danger, onConfirm, onCancel }: Props) {
|
|
14
|
+
if (!open) return null
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="fixed inset-0 z-100 flex items-center justify-center p-6">
|
|
18
|
+
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={onCancel} />
|
|
19
|
+
<div className="relative glass rounded-[20px] p-6 w-full max-w-[380px]
|
|
20
|
+
shadow-[0_24px_80px_rgba(0,0,0,0.5)]"
|
|
21
|
+
style={{ animation: 'fade-in 0.2s cubic-bezier(0.16, 1, 0.3, 1)' }}>
|
|
22
|
+
<h3 className="font-display text-[18px] font-700 tracking-[-0.02em] mb-2">{title}</h3>
|
|
23
|
+
<p className="text-[13px] text-text-2 mb-6 leading-relaxed">{message}</p>
|
|
24
|
+
<div className="flex gap-2.5">
|
|
25
|
+
<button
|
|
26
|
+
onClick={onCancel}
|
|
27
|
+
className="flex-1 py-2.5 rounded-[12px] border border-white/[0.06] bg-transparent text-text-2 text-[13px] font-600 cursor-pointer
|
|
28
|
+
hover:bg-surface transition-all duration-200"
|
|
29
|
+
style={{ fontFamily: 'inherit' }}
|
|
30
|
+
>
|
|
31
|
+
Cancel
|
|
32
|
+
</button>
|
|
33
|
+
<button
|
|
34
|
+
onClick={onConfirm}
|
|
35
|
+
className={`flex-1 py-2.5 rounded-[12px] border-none text-[13px] font-600 cursor-pointer active:scale-[0.97] transition-all duration-200
|
|
36
|
+
${danger
|
|
37
|
+
? 'bg-danger text-white shadow-[0_4px_20px_rgba(244,63,94,0.2)]'
|
|
38
|
+
: 'bg-[#6366F1] text-white shadow-[0_4px_20px_rgba(99,102,241,0.2)]'}`}
|
|
39
|
+
style={{ fontFamily: 'inherit' }}
|
|
40
|
+
>
|
|
41
|
+
{confirmLabel}
|
|
42
|
+
</button>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Connector, ConnectorPlatform, Session } from '@/types'
|
|
2
|
+
import { cn } from '@/lib/utils'
|
|
3
|
+
|
|
4
|
+
export const CONNECTOR_PLATFORM_META: Record<ConnectorPlatform, { label: string; color: string }> = {
|
|
5
|
+
discord: { label: 'Discord', color: '#5865F2' },
|
|
6
|
+
telegram: { label: 'Telegram', color: '#229ED9' },
|
|
7
|
+
slack: { label: 'Slack', color: '#4A154B' },
|
|
8
|
+
whatsapp: { label: 'WhatsApp', color: '#25D366' },
|
|
9
|
+
openclaw: { label: 'OpenClaw', color: '#F97316' },
|
|
10
|
+
signal: { label: 'Signal', color: '#3A76F0' },
|
|
11
|
+
teams: { label: 'Teams', color: '#6264A7' },
|
|
12
|
+
googlechat: { label: 'Google Chat', color: '#00AC47' },
|
|
13
|
+
matrix: { label: 'Matrix', color: '#0DBD8B' },
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getConnectorPlatformLabel(platform: ConnectorPlatform): string {
|
|
17
|
+
return CONNECTOR_PLATFORM_META[platform]?.label || platform
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getConnectorIdFromSessionName(sessionName?: string | null): string | null {
|
|
21
|
+
if (!sessionName || !sessionName.startsWith('connector:')) return null
|
|
22
|
+
const parts = sessionName.split(':')
|
|
23
|
+
return parts.length >= 2 && parts[1] ? parts[1] : null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getSessionConnector(
|
|
27
|
+
session: Pick<Session, 'name'>,
|
|
28
|
+
connectors: Record<string, Connector>,
|
|
29
|
+
): Connector | null {
|
|
30
|
+
const connectorId = getConnectorIdFromSessionName(session.name)
|
|
31
|
+
if (!connectorId) return null
|
|
32
|
+
return connectors[connectorId] || null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ConnectorPlatformIconProps {
|
|
36
|
+
platform: ConnectorPlatform
|
|
37
|
+
size?: number
|
|
38
|
+
className?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function ConnectorPlatformIcon({
|
|
42
|
+
platform,
|
|
43
|
+
size = 14,
|
|
44
|
+
className,
|
|
45
|
+
}: ConnectorPlatformIconProps) {
|
|
46
|
+
switch (platform) {
|
|
47
|
+
case 'discord':
|
|
48
|
+
return (
|
|
49
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" className={className}>
|
|
50
|
+
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
|
|
51
|
+
</svg>
|
|
52
|
+
)
|
|
53
|
+
case 'telegram':
|
|
54
|
+
return (
|
|
55
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" className={className}>
|
|
56
|
+
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z" />
|
|
57
|
+
</svg>
|
|
58
|
+
)
|
|
59
|
+
case 'slack':
|
|
60
|
+
return (
|
|
61
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" className={className}>
|
|
62
|
+
<path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z" />
|
|
63
|
+
</svg>
|
|
64
|
+
)
|
|
65
|
+
case 'whatsapp':
|
|
66
|
+
return (
|
|
67
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" className={className}>
|
|
68
|
+
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z" />
|
|
69
|
+
</svg>
|
|
70
|
+
)
|
|
71
|
+
case 'openclaw':
|
|
72
|
+
return (
|
|
73
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
|
|
74
|
+
<path d="M4 17l2-5 2 5" /><path d="M12 17l2-5 2 5" /><path d="M20 17l-2-5-2 5" />
|
|
75
|
+
<path d="M2 7l4-4 3 3" /><path d="M22 7l-4-4-3 3" />
|
|
76
|
+
<line x1="12" y1="3" x2="12" y2="8" />
|
|
77
|
+
</svg>
|
|
78
|
+
)
|
|
79
|
+
default:
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface ConnectorPlatformBadgeProps {
|
|
85
|
+
platform: ConnectorPlatform
|
|
86
|
+
size?: number
|
|
87
|
+
iconSize?: number
|
|
88
|
+
className?: string
|
|
89
|
+
roundedClassName?: string
|
|
90
|
+
title?: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function ConnectorPlatformBadge({
|
|
94
|
+
platform,
|
|
95
|
+
size = 36,
|
|
96
|
+
iconSize,
|
|
97
|
+
className,
|
|
98
|
+
roundedClassName = 'rounded-[10px]',
|
|
99
|
+
title,
|
|
100
|
+
}: ConnectorPlatformBadgeProps) {
|
|
101
|
+
const meta = CONNECTOR_PLATFORM_META[platform]
|
|
102
|
+
const glyphSize = iconSize ?? Math.max(12, Math.floor(size * 0.52))
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<span
|
|
106
|
+
title={title || `${meta.label} connector`}
|
|
107
|
+
className={cn('inline-flex items-center justify-center shrink-0', roundedClassName, className)}
|
|
108
|
+
style={{ width: size, height: size, backgroundColor: meta.color }}
|
|
109
|
+
>
|
|
110
|
+
<ConnectorPlatformIcon platform={platform} size={glyphSize} className="text-white" />
|
|
111
|
+
</span>
|
|
112
|
+
)
|
|
113
|
+
}
|