@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,311 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
6
|
+
import { api } from '@/lib/api-client'
|
|
7
|
+
import type { PluginMeta, MarketplacePlugin } from '@/types'
|
|
8
|
+
|
|
9
|
+
export function PluginSheet() {
|
|
10
|
+
const open = useAppStore((s) => s.pluginSheetOpen)
|
|
11
|
+
const setOpen = useAppStore((s) => s.setPluginSheetOpen)
|
|
12
|
+
const editingFilename = useAppStore((s) => s.editingPluginFilename)
|
|
13
|
+
const setEditingFilename = useAppStore((s) => s.setEditingPluginFilename)
|
|
14
|
+
const plugins = useAppStore((s) => s.plugins)
|
|
15
|
+
const loadPlugins = useAppStore((s) => s.loadPlugins)
|
|
16
|
+
|
|
17
|
+
const [tab, setTab] = useState<'marketplace' | 'url'>('marketplace')
|
|
18
|
+
const [marketplace, setMarketplace] = useState<MarketplacePlugin[]>([])
|
|
19
|
+
const [loading, setLoading] = useState(false)
|
|
20
|
+
const [installing, setInstalling] = useState<string | null>(null)
|
|
21
|
+
const [urlInput, setUrlInput] = useState('')
|
|
22
|
+
const [urlFilename, setUrlFilename] = useState('')
|
|
23
|
+
const [urlStatus, setUrlStatus] = useState<{ ok: boolean; message: string } | null>(null)
|
|
24
|
+
const [deleting, setDeleting] = useState(false)
|
|
25
|
+
const [search, setSearch] = useState('')
|
|
26
|
+
const [activeTag, setActiveTag] = useState<string | null>(null)
|
|
27
|
+
const [sort, setSort] = useState<'name' | 'downloads'>('downloads')
|
|
28
|
+
|
|
29
|
+
const editing = editingFilename ? plugins[editingFilename] : null
|
|
30
|
+
|
|
31
|
+
const loadMarketplace = useCallback(async () => {
|
|
32
|
+
setLoading(true)
|
|
33
|
+
try {
|
|
34
|
+
const data = await api<MarketplacePlugin[]>('GET', '/plugins/marketplace')
|
|
35
|
+
if (Array.isArray(data)) setMarketplace(data)
|
|
36
|
+
} catch { /* ignore */ }
|
|
37
|
+
setLoading(false)
|
|
38
|
+
}, [])
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (open && !editingFilename && tab === 'marketplace') loadMarketplace()
|
|
42
|
+
}, [open, editingFilename, tab])
|
|
43
|
+
|
|
44
|
+
const handleClose = () => {
|
|
45
|
+
setOpen(false)
|
|
46
|
+
setEditingFilename(null)
|
|
47
|
+
setUrlInput('')
|
|
48
|
+
setUrlFilename('')
|
|
49
|
+
setUrlStatus(null)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const togglePlugin = async (filename: string, enabled: boolean) => {
|
|
53
|
+
await api('POST', '/plugins', { filename, enabled })
|
|
54
|
+
loadPlugins()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const deletePlugin = async (filename: string) => {
|
|
58
|
+
setDeleting(true)
|
|
59
|
+
try {
|
|
60
|
+
await api('DELETE', `/plugins/${encodeURIComponent(filename)}`)
|
|
61
|
+
await loadPlugins()
|
|
62
|
+
handleClose()
|
|
63
|
+
} catch { /* ignore */ }
|
|
64
|
+
setDeleting(false)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const installFromMarketplace = async (p: MarketplacePlugin) => {
|
|
68
|
+
setInstalling(p.id)
|
|
69
|
+
try {
|
|
70
|
+
await api('POST', '/plugins/install', { url: p.url, filename: `${p.id}.js` })
|
|
71
|
+
await loadPlugins()
|
|
72
|
+
} catch { /* ignore */ }
|
|
73
|
+
setInstalling(null)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const installFromUrl = async () => {
|
|
77
|
+
if (!urlInput || !urlFilename) return
|
|
78
|
+
setUrlStatus(null)
|
|
79
|
+
setInstalling('url')
|
|
80
|
+
try {
|
|
81
|
+
await api('POST', '/plugins/install', { url: urlInput, filename: urlFilename })
|
|
82
|
+
await loadPlugins()
|
|
83
|
+
setUrlStatus({ ok: true, message: 'Installed successfully' })
|
|
84
|
+
setUrlInput('')
|
|
85
|
+
setUrlFilename('')
|
|
86
|
+
} catch (err: any) {
|
|
87
|
+
setUrlStatus({ ok: false, message: err.message || 'Install failed' })
|
|
88
|
+
}
|
|
89
|
+
setInstalling(null)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const installedFilenames = new Set(Object.keys(plugins))
|
|
93
|
+
|
|
94
|
+
const tabClass = (t: string) =>
|
|
95
|
+
`py-2.5 px-4 rounded-[10px] text-center cursor-pointer transition-all text-[12px] font-600 border
|
|
96
|
+
${tab === t
|
|
97
|
+
? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
|
|
98
|
+
: 'bg-bg border-white/[0.06] text-text-3 hover:bg-surface-2'}`
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<BottomSheet open={open} onClose={handleClose}>
|
|
102
|
+
{editing ? (
|
|
103
|
+
<div className="space-y-5">
|
|
104
|
+
<div className="py-3 px-4 rounded-[14px] bg-surface border border-white/[0.06]">
|
|
105
|
+
<div className="flex items-center gap-2 mb-1">
|
|
106
|
+
<span className="text-[14px] font-600 text-text">{editing.name}</span>
|
|
107
|
+
{editing.openclaw && <span className="text-[9px] font-600 text-emerald-400 bg-emerald-400/10 px-1.5 py-0.5 rounded-full">OpenClaw</span>}
|
|
108
|
+
</div>
|
|
109
|
+
<div className="text-[11px] font-mono text-text-3">{editing.filename}</div>
|
|
110
|
+
{editing.description && <div className="text-[11px] text-text-3/60 mt-1">{editing.description}</div>}
|
|
111
|
+
{editing.author && <div className="text-[10px] text-text-3/70 mt-1">by {editing.author}</div>}
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div className="flex items-center justify-between py-3 px-4 rounded-[14px] bg-surface border border-white/[0.06]">
|
|
115
|
+
<span className="text-[13px] font-600 text-text">Enabled</span>
|
|
116
|
+
<div
|
|
117
|
+
onClick={() => togglePlugin(editing.filename, !editing.enabled)}
|
|
118
|
+
className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer shrink-0
|
|
119
|
+
${editing.enabled ? 'bg-[#6366F1]' : 'bg-white/[0.08]'}`}
|
|
120
|
+
>
|
|
121
|
+
<div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
|
|
122
|
+
${editing.enabled ? 'left-[22px]' : 'left-0.5'}`} />
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<button
|
|
127
|
+
onClick={() => deletePlugin(editing.filename)}
|
|
128
|
+
disabled={deleting}
|
|
129
|
+
className="w-full py-2.5 rounded-[10px] text-[13px] font-600 bg-red-500/10 text-red-400 border border-red-500/20
|
|
130
|
+
hover:bg-red-500/20 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-default"
|
|
131
|
+
style={{ fontFamily: 'inherit' }}
|
|
132
|
+
>
|
|
133
|
+
{deleting ? 'Deleting...' : 'Delete Plugin'}
|
|
134
|
+
</button>
|
|
135
|
+
</div>
|
|
136
|
+
) : (
|
|
137
|
+
<div>
|
|
138
|
+
<div className="flex gap-2 mb-5">
|
|
139
|
+
<button onClick={() => setTab('marketplace')} className={tabClass('marketplace')} style={{ fontFamily: 'inherit' }}>
|
|
140
|
+
Marketplace
|
|
141
|
+
</button>
|
|
142
|
+
<button onClick={() => setTab('url')} className={tabClass('url')} style={{ fontFamily: 'inherit' }}>
|
|
143
|
+
Install from URL
|
|
144
|
+
</button>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
{tab === 'marketplace' && (
|
|
148
|
+
loading
|
|
149
|
+
? <p className="text-[12px] text-text-3/70">Loading marketplace...</p>
|
|
150
|
+
: marketplace.length === 0
|
|
151
|
+
? <p className="text-[12px] text-text-3/70">No plugins available</p>
|
|
152
|
+
: (() => {
|
|
153
|
+
const allTags = Array.from(new Set(marketplace.flatMap((p) => p.tags))).sort()
|
|
154
|
+
const q = search.toLowerCase()
|
|
155
|
+
const filtered = marketplace
|
|
156
|
+
.filter((p) => {
|
|
157
|
+
if (q && !p.name.toLowerCase().includes(q) && !p.description.toLowerCase().includes(q) && !p.tags.some((t) => t.toLowerCase().includes(q))) return false
|
|
158
|
+
if (activeTag && !p.tags.includes(activeTag)) return false
|
|
159
|
+
return true
|
|
160
|
+
})
|
|
161
|
+
.sort((a, b) => sort === 'downloads' ? b.downloads - a.downloads : a.name.localeCompare(b.name))
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div className="space-y-3">
|
|
165
|
+
{/* Search */}
|
|
166
|
+
<input
|
|
167
|
+
value={search}
|
|
168
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
169
|
+
placeholder="Search plugins..."
|
|
170
|
+
className="w-full px-3 py-2.5 rounded-[10px] bg-bg border border-white/[0.06] text-[12px] text-text placeholder:text-text-3/50 outline-none focus:border-accent-bright/30"
|
|
171
|
+
style={{ fontFamily: 'inherit' }}
|
|
172
|
+
/>
|
|
173
|
+
|
|
174
|
+
{/* Tags + Sort */}
|
|
175
|
+
<div className="flex items-center gap-1.5 flex-wrap">
|
|
176
|
+
<button
|
|
177
|
+
onClick={() => setActiveTag(null)}
|
|
178
|
+
className={`px-2 py-1 rounded-[6px] text-[10px] font-600 cursor-pointer transition-all border-none ${
|
|
179
|
+
!activeTag ? 'bg-accent-soft text-accent-bright' : 'bg-white/[0.03] text-text-3/60 hover:text-text-3'
|
|
180
|
+
}`}
|
|
181
|
+
>
|
|
182
|
+
All
|
|
183
|
+
</button>
|
|
184
|
+
{allTags.map((t) => (
|
|
185
|
+
<button
|
|
186
|
+
key={t}
|
|
187
|
+
onClick={() => setActiveTag(activeTag === t ? null : t)}
|
|
188
|
+
className={`px-2 py-1 rounded-[6px] text-[10px] font-600 cursor-pointer transition-all border-none ${
|
|
189
|
+
activeTag === t ? 'bg-accent-soft text-accent-bright' : 'bg-white/[0.03] text-text-3/60 hover:text-text-3'
|
|
190
|
+
}`}
|
|
191
|
+
>
|
|
192
|
+
{t}
|
|
193
|
+
</button>
|
|
194
|
+
))}
|
|
195
|
+
<div className="flex-1" />
|
|
196
|
+
<select
|
|
197
|
+
value={sort}
|
|
198
|
+
onChange={(e) => setSort(e.target.value as 'name' | 'downloads')}
|
|
199
|
+
className="px-2 py-1 rounded-[6px] bg-bg border border-white/[0.06] text-[10px] text-text-3 outline-none cursor-pointer appearance-none"
|
|
200
|
+
style={{ fontFamily: 'inherit' }}
|
|
201
|
+
>
|
|
202
|
+
<option value="downloads">Popular</option>
|
|
203
|
+
<option value="name">A-Z</option>
|
|
204
|
+
</select>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
{/* Results */}
|
|
208
|
+
{filtered.length === 0 ? (
|
|
209
|
+
<p className="text-[12px] text-text-3/50 text-center py-4">No plugins match your search</p>
|
|
210
|
+
) : (
|
|
211
|
+
<div className="space-y-2.5">
|
|
212
|
+
{filtered.map((p) => {
|
|
213
|
+
const isInstalled = installedFilenames.has(`${p.id}.js`)
|
|
214
|
+
return (
|
|
215
|
+
<div key={p.id} className="py-3.5 px-4 rounded-[14px] bg-surface border border-white/[0.06]">
|
|
216
|
+
<div className="flex items-start gap-3">
|
|
217
|
+
<div className="flex-1 min-w-0">
|
|
218
|
+
<div className="flex items-center gap-2">
|
|
219
|
+
<span className="text-[14px] font-600 text-text">{p.name}</span>
|
|
220
|
+
<span className="text-[10px] font-mono text-text-3/70">v{p.version}</span>
|
|
221
|
+
{p.openclaw && <span className="text-[9px] font-600 text-emerald-400 bg-emerald-400/10 px-1.5 py-0.5 rounded-full">OpenClaw</span>}
|
|
222
|
+
</div>
|
|
223
|
+
<div className="text-[11px] text-text-3/60 mt-1">{p.description}</div>
|
|
224
|
+
<div className="flex items-center gap-2 mt-2">
|
|
225
|
+
<span className="text-[10px] text-text-3/70">by {p.author}</span>
|
|
226
|
+
<span className="text-[10px] text-text-3/50">·</span>
|
|
227
|
+
{p.tags.slice(0, 3).map((t) => (
|
|
228
|
+
<button
|
|
229
|
+
key={t}
|
|
230
|
+
onClick={() => setActiveTag(activeTag === t ? null : t)}
|
|
231
|
+
className={`text-[9px] font-600 px-1.5 py-0.5 rounded-full cursor-pointer transition-all border-none ${
|
|
232
|
+
activeTag === t ? 'text-accent-bright bg-accent-soft' : 'text-text-3/50 bg-white/[0.04] hover:text-text-3'
|
|
233
|
+
}`}
|
|
234
|
+
>
|
|
235
|
+
{t}
|
|
236
|
+
</button>
|
|
237
|
+
))}
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
<button
|
|
241
|
+
onClick={() => !isInstalled && installFromMarketplace(p)}
|
|
242
|
+
disabled={isInstalled || installing === p.id}
|
|
243
|
+
className={`shrink-0 py-2 px-4 rounded-[10px] text-[12px] font-600 transition-all cursor-pointer
|
|
244
|
+
${isInstalled
|
|
245
|
+
? 'bg-white/[0.04] text-text-3/70 cursor-default'
|
|
246
|
+
: installing === p.id
|
|
247
|
+
? 'bg-accent-soft text-accent-bright animate-pulse'
|
|
248
|
+
: 'bg-accent-soft text-accent-bright hover:bg-accent-soft/80 border border-accent-bright/20'}`}
|
|
249
|
+
style={{ fontFamily: 'inherit' }}
|
|
250
|
+
>
|
|
251
|
+
{isInstalled ? 'Installed' : installing === p.id ? 'Installing...' : 'Install'}
|
|
252
|
+
</button>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
)
|
|
256
|
+
})}
|
|
257
|
+
</div>
|
|
258
|
+
)}
|
|
259
|
+
</div>
|
|
260
|
+
)
|
|
261
|
+
})()
|
|
262
|
+
)}
|
|
263
|
+
|
|
264
|
+
{tab === 'url' && (
|
|
265
|
+
<div className="p-5 rounded-[14px] bg-surface border border-white/[0.06]">
|
|
266
|
+
<div className="mb-4">
|
|
267
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Plugin URL</label>
|
|
268
|
+
<input
|
|
269
|
+
type="url"
|
|
270
|
+
value={urlInput}
|
|
271
|
+
onChange={(e) => setUrlInput(e.target.value)}
|
|
272
|
+
placeholder="https://example.com/my-plugin.js"
|
|
273
|
+
className="w-full py-2.5 px-3 rounded-[10px] text-[13px] bg-bg border border-white/[0.06] text-text placeholder:text-text-3/60 outline-none focus:border-accent-bright/30"
|
|
274
|
+
style={{ fontFamily: 'inherit' }}
|
|
275
|
+
/>
|
|
276
|
+
</div>
|
|
277
|
+
<div className="mb-4">
|
|
278
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Save as filename</label>
|
|
279
|
+
<input
|
|
280
|
+
type="text"
|
|
281
|
+
value={urlFilename}
|
|
282
|
+
onChange={(e) => setUrlFilename(e.target.value)}
|
|
283
|
+
placeholder="my-plugin.js"
|
|
284
|
+
className="w-full py-2.5 px-3 rounded-[10px] text-[13px] bg-bg border border-white/[0.06] text-text placeholder:text-text-3/60 outline-none focus:border-accent-bright/30"
|
|
285
|
+
style={{ fontFamily: 'inherit' }}
|
|
286
|
+
/>
|
|
287
|
+
</div>
|
|
288
|
+
<button
|
|
289
|
+
onClick={installFromUrl}
|
|
290
|
+
disabled={!urlInput || !urlFilename || installing === 'url'}
|
|
291
|
+
className="w-full py-2.5 rounded-[10px] text-[13px] font-600 bg-accent-soft text-accent-bright border border-accent-bright/20
|
|
292
|
+
hover:bg-accent-soft/80 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-default"
|
|
293
|
+
style={{ fontFamily: 'inherit' }}
|
|
294
|
+
>
|
|
295
|
+
{installing === 'url' ? 'Installing...' : 'Install Plugin'}
|
|
296
|
+
</button>
|
|
297
|
+
{urlStatus && (
|
|
298
|
+
<p className={`text-[11px] mt-3 ${urlStatus.ok ? 'text-emerald-400' : 'text-red-400'}`}>
|
|
299
|
+
{urlStatus.message}
|
|
300
|
+
</p>
|
|
301
|
+
)}
|
|
302
|
+
<p className="text-[10px] text-text-3/60 mt-3">
|
|
303
|
+
Works with SwarmClaw and OpenClaw plugin formats. URL must be HTTPS.
|
|
304
|
+
</p>
|
|
305
|
+
</div>
|
|
306
|
+
)}
|
|
307
|
+
</div>
|
|
308
|
+
)}
|
|
309
|
+
</BottomSheet>
|
|
310
|
+
)
|
|
311
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
|
|
6
|
+
export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
7
|
+
const providers = useAppStore((s) => s.providers)
|
|
8
|
+
const providerConfigs = useAppStore((s) => s.providerConfigs)
|
|
9
|
+
const loadProviders = useAppStore((s) => s.loadProviders)
|
|
10
|
+
const loadProviderConfigs = useAppStore((s) => s.loadProviderConfigs)
|
|
11
|
+
const credentials = useAppStore((s) => s.credentials)
|
|
12
|
+
const loadCredentials = useAppStore((s) => s.loadCredentials)
|
|
13
|
+
const setProviderSheetOpen = useAppStore((s) => s.setProviderSheetOpen)
|
|
14
|
+
const setEditingProviderId = useAppStore((s) => s.setEditingProviderId)
|
|
15
|
+
const [loaded, setLoaded] = useState(false)
|
|
16
|
+
|
|
17
|
+
const refresh = useCallback(async () => {
|
|
18
|
+
await Promise.all([loadProviders(), loadProviderConfigs(), loadCredentials()])
|
|
19
|
+
setLoaded(true)
|
|
20
|
+
}, [loadProviders, loadProviderConfigs, loadCredentials])
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const bootstrap = setTimeout(() => { void refresh() }, 0)
|
|
24
|
+
const poll = setInterval(() => { void loadProviders() }, 20_000)
|
|
25
|
+
return () => {
|
|
26
|
+
clearTimeout(bootstrap)
|
|
27
|
+
clearInterval(poll)
|
|
28
|
+
}
|
|
29
|
+
}, [refresh, loadProviders])
|
|
30
|
+
|
|
31
|
+
const handleEdit = (id: string) => {
|
|
32
|
+
setEditingProviderId(id)
|
|
33
|
+
setProviderSheetOpen(true)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Merge built-in providers with custom configs
|
|
37
|
+
const builtinItems = providers.map((p) => ({
|
|
38
|
+
id: p.id,
|
|
39
|
+
name: p.name,
|
|
40
|
+
type: 'builtin' as const,
|
|
41
|
+
models: p.models,
|
|
42
|
+
requiresApiKey: p.requiresApiKey,
|
|
43
|
+
isEnabled: true,
|
|
44
|
+
isConnected: !p.requiresApiKey || Object.values(credentials).some((c) => c.provider === p.id),
|
|
45
|
+
}))
|
|
46
|
+
|
|
47
|
+
const customItems = providerConfigs.map((c) => ({
|
|
48
|
+
id: c.id,
|
|
49
|
+
name: c.name,
|
|
50
|
+
type: 'custom' as const,
|
|
51
|
+
models: c.models,
|
|
52
|
+
requiresApiKey: c.requiresApiKey,
|
|
53
|
+
isEnabled: c.isEnabled,
|
|
54
|
+
isConnected: !c.requiresApiKey || !!c.credentialId,
|
|
55
|
+
}))
|
|
56
|
+
|
|
57
|
+
const allItems = [...builtinItems, ...customItems]
|
|
58
|
+
|
|
59
|
+
if (!loaded) {
|
|
60
|
+
return (
|
|
61
|
+
<div className={`flex-1 flex items-center justify-center ${inSidebar ? 'px-3 pb-4' : 'px-4'}`}>
|
|
62
|
+
<p className="text-[13px] text-text-3">Loading providers...</p>
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className={`flex-1 overflow-y-auto ${inSidebar ? 'px-3 pb-4' : 'px-4'}`}>
|
|
69
|
+
<div className="space-y-2">
|
|
70
|
+
{allItems.map((item) => (
|
|
71
|
+
<button
|
|
72
|
+
key={item.id}
|
|
73
|
+
onClick={() => handleEdit(item.id)}
|
|
74
|
+
className="w-full text-left p-4 rounded-[14px] border transition-all duration-200
|
|
75
|
+
cursor-pointer hover:bg-surface-2 bg-surface border-white/[0.06]"
|
|
76
|
+
>
|
|
77
|
+
<div className="flex items-center justify-between mb-1.5">
|
|
78
|
+
<span className="font-display text-[14px] font-600 text-text truncate">{item.name}</span>
|
|
79
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
80
|
+
<span className={`text-[10px] font-600 px-2 py-0.5 rounded-[5px] uppercase tracking-wider
|
|
81
|
+
${item.type === 'builtin' ? 'bg-white/[0.04] text-text-3' : 'bg-[#6366F1]/10 text-[#6366F1]'}`}>
|
|
82
|
+
{item.type === 'builtin' ? 'Built-in' : 'Custom'}
|
|
83
|
+
</span>
|
|
84
|
+
<span className={`w-2 h-2 rounded-full ${item.isConnected ? 'bg-emerald-400' : 'bg-white/10'}`} />
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
<div className="text-[12px] text-text-3/60 font-mono truncate">
|
|
88
|
+
{item.models.slice(0, 3).join(', ')}
|
|
89
|
+
{item.models.length > 3 && ` +${item.models.length - 3}`}
|
|
90
|
+
</div>
|
|
91
|
+
</button>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
)
|
|
96
|
+
}
|