@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,478 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { useChatStore } from '@/stores/use-chat-store'
|
|
6
|
+
import { createSession, createCredential } from '@/lib/sessions'
|
|
7
|
+
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
8
|
+
import { DirBrowser } from '@/components/shared/dir-browser'
|
|
9
|
+
import { TOOL_LABELS, TOOL_DESCRIPTIONS } from '@/components/chat/tool-call-bubble'
|
|
10
|
+
import type { ProviderType, SessionTool } from '@/types'
|
|
11
|
+
|
|
12
|
+
export function NewSessionSheet() {
|
|
13
|
+
const open = useAppStore((s) => s.newSessionOpen)
|
|
14
|
+
const setOpen = useAppStore((s) => s.setNewSessionOpen)
|
|
15
|
+
|
|
16
|
+
const [name, setName] = useState('')
|
|
17
|
+
const [selectedDir, setSelectedDir] = useState<string | null>(null)
|
|
18
|
+
const [selectedFile, setSelectedFile] = useState<string | null>(null)
|
|
19
|
+
const [provider, setProvider] = useState<ProviderType>('claude-cli')
|
|
20
|
+
const [model, setModel] = useState('')
|
|
21
|
+
const [credentialId, setCredentialId] = useState<string | null>(null)
|
|
22
|
+
const [endpoint, setEndpoint] = useState('http://localhost:11434')
|
|
23
|
+
const [addingKey, setAddingKey] = useState(false)
|
|
24
|
+
const [newKeyName, setNewKeyName] = useState('')
|
|
25
|
+
const [newKeyValue, setNewKeyValue] = useState('')
|
|
26
|
+
const [ollamaMode, setOllamaMode] = useState<'local' | 'cloud'>('local')
|
|
27
|
+
const [selectedAgentId, setSelectedAgentId] = useState<string | null>(null)
|
|
28
|
+
const [selectedTools, setSelectedTools] = useState<SessionTool[]>([])
|
|
29
|
+
|
|
30
|
+
const providers = useAppStore((s) => s.providers)
|
|
31
|
+
const loadProviders = useAppStore((s) => s.loadProviders)
|
|
32
|
+
const credentials = useAppStore((s) => s.credentials)
|
|
33
|
+
const loadCredentials = useAppStore((s) => s.loadCredentials)
|
|
34
|
+
const agents = useAppStore((s) => s.agents)
|
|
35
|
+
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
36
|
+
const currentUser = useAppStore((s) => s.currentUser)
|
|
37
|
+
const updateSessionInStore = useAppStore((s) => s.updateSessionInStore)
|
|
38
|
+
const setCurrentSession = useAppStore((s) => s.setCurrentSession)
|
|
39
|
+
const setMessages = useChatStore((s) => s.setMessages)
|
|
40
|
+
|
|
41
|
+
const currentProvider = providers.find((p) => p.id === provider)
|
|
42
|
+
const providerCredentials = Object.values(credentials).filter((c) => c.provider === provider)
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (open) {
|
|
46
|
+
loadProviders()
|
|
47
|
+
loadCredentials()
|
|
48
|
+
loadAgents()
|
|
49
|
+
setName('')
|
|
50
|
+
setSelectedDir(null)
|
|
51
|
+
setSelectedFile(null)
|
|
52
|
+
setProvider('claude-cli')
|
|
53
|
+
setModel('')
|
|
54
|
+
setCredentialId(null)
|
|
55
|
+
setEndpoint('http://localhost:11434')
|
|
56
|
+
setAddingKey(false)
|
|
57
|
+
setNewKeyName('')
|
|
58
|
+
setNewKeyValue('')
|
|
59
|
+
setOllamaMode('local')
|
|
60
|
+
// Auto-select last used agent, or default agent if no history
|
|
61
|
+
const agentsList = Object.values(agents)
|
|
62
|
+
const lastAgentId = typeof window !== 'undefined' ? localStorage.getItem('swarmclaw-last-agent') : null
|
|
63
|
+
const lastAgent = lastAgentId ? agentsList.find((a: any) => a.id === lastAgentId) : null
|
|
64
|
+
const defaultAgent = lastAgent || agentsList.find((a: any) => a.id === 'default') || agentsList[0]
|
|
65
|
+
if (defaultAgent) {
|
|
66
|
+
setSelectedAgentId((defaultAgent as any).id)
|
|
67
|
+
setProvider((defaultAgent as any).provider || 'claude-cli')
|
|
68
|
+
setModel((defaultAgent as any).model || '')
|
|
69
|
+
setCredentialId((defaultAgent as any).credentialId || null)
|
|
70
|
+
if ((defaultAgent as any).apiEndpoint) setEndpoint((defaultAgent as any).apiEndpoint)
|
|
71
|
+
} else {
|
|
72
|
+
setSelectedAgentId(null)
|
|
73
|
+
}
|
|
74
|
+
setSelectedTools([])
|
|
75
|
+
}
|
|
76
|
+
}, [open])
|
|
77
|
+
|
|
78
|
+
// Derive model, endpoint, and credential from provider + ollamaMode (consolidated)
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
// Set model from provider defaults
|
|
81
|
+
if (currentProvider?.models.length) {
|
|
82
|
+
setModel(currentProvider.models[0])
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Reset ollama mode for non-ollama providers
|
|
86
|
+
if (provider !== 'ollama') {
|
|
87
|
+
setOllamaMode('local')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Derive endpoint
|
|
91
|
+
if (provider === 'ollama') {
|
|
92
|
+
setEndpoint(ollamaMode === 'local' ? 'http://localhost:11434' : '')
|
|
93
|
+
} else if (currentProvider?.defaultEndpoint) {
|
|
94
|
+
setEndpoint(currentProvider.defaultEndpoint)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Derive credential
|
|
98
|
+
const needsKey = currentProvider?.requiresApiKey || (provider === 'ollama' && ollamaMode === 'cloud')
|
|
99
|
+
if (needsKey && providerCredentials.length > 0) {
|
|
100
|
+
setCredentialId(providerCredentials[0].id)
|
|
101
|
+
} else {
|
|
102
|
+
setCredentialId(null)
|
|
103
|
+
}
|
|
104
|
+
}, [provider, providers, ollamaMode, providerCredentials.length])
|
|
105
|
+
|
|
106
|
+
const handleAddKey = async () => {
|
|
107
|
+
if (!newKeyValue.trim()) return
|
|
108
|
+
const cred = await createCredential(provider, newKeyName || `${provider} key`, newKeyValue)
|
|
109
|
+
await loadCredentials()
|
|
110
|
+
setCredentialId(cred.id)
|
|
111
|
+
setAddingKey(false)
|
|
112
|
+
setNewKeyName('')
|
|
113
|
+
setNewKeyValue('')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const onClose = () => setOpen(false)
|
|
117
|
+
|
|
118
|
+
const handleSelectAgent = (agentId: string | null) => {
|
|
119
|
+
setSelectedAgentId(agentId)
|
|
120
|
+
if (agentId && agents[agentId]) {
|
|
121
|
+
const p = agents[agentId]
|
|
122
|
+
setProvider(p.provider)
|
|
123
|
+
setModel(p.model)
|
|
124
|
+
setCredentialId(p.credentialId || null)
|
|
125
|
+
if (p.apiEndpoint) setEndpoint(p.apiEndpoint)
|
|
126
|
+
if (!name) setName(p.name)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const handleCreate = async () => {
|
|
131
|
+
const sessionName = name.trim() || 'New Session'
|
|
132
|
+
const cwd = selectedDir || ''
|
|
133
|
+
const resolvedCredentialId = currentProvider?.requiresApiKey
|
|
134
|
+
? credentialId
|
|
135
|
+
: (currentProvider?.optionalApiKey && ollamaMode === 'cloud') ? credentialId : null
|
|
136
|
+
const agent = selectedAgentId ? agents[selectedAgentId] : null
|
|
137
|
+
const agentTools = agent?.tools || (selectedTools.length ? selectedTools : undefined)
|
|
138
|
+
const s = await createSession(
|
|
139
|
+
sessionName, cwd || (agent ? '~' : ''), currentUser!,
|
|
140
|
+
agent?.provider || provider,
|
|
141
|
+
agent?.model || model || undefined,
|
|
142
|
+
agent?.credentialId || resolvedCredentialId,
|
|
143
|
+
selectedAgentId ? (agent?.apiEndpoint || null) : (currentProvider?.requiresEndpoint ? endpoint : null),
|
|
144
|
+
selectedAgentId ? 'human' : undefined,
|
|
145
|
+
selectedAgentId,
|
|
146
|
+
agentTools || undefined,
|
|
147
|
+
selectedFile,
|
|
148
|
+
)
|
|
149
|
+
// Remember agent selection for next time
|
|
150
|
+
if (selectedAgentId) {
|
|
151
|
+
localStorage.setItem('swarmclaw-last-agent', selectedAgentId)
|
|
152
|
+
} else {
|
|
153
|
+
localStorage.removeItem('swarmclaw-last-agent')
|
|
154
|
+
}
|
|
155
|
+
updateSessionInStore(s)
|
|
156
|
+
setCurrentSession(s.id)
|
|
157
|
+
setMessages([])
|
|
158
|
+
onClose()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const canCreate = () => {
|
|
162
|
+
if (!selectedAgentId) {
|
|
163
|
+
if (currentProvider?.requiresApiKey && !credentialId) return false
|
|
164
|
+
if (provider === 'ollama' && ollamaMode === 'cloud' && !credentialId) return false
|
|
165
|
+
if (provider === 'claude-cli' && !selectedDir) return false
|
|
166
|
+
}
|
|
167
|
+
return true
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const inputClass = "w-full px-4 py-3.5 rounded-[14px] border border-white/[0.08] bg-surface text-text text-[15px] outline-none transition-all duration-200 placeholder:text-text-3/50 focus-glow"
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<BottomSheet open={open} onClose={onClose} wide>
|
|
174
|
+
{/* Header */}
|
|
175
|
+
<div className="mb-10">
|
|
176
|
+
<h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">New Session</h2>
|
|
177
|
+
<p className="text-[14px] text-text-3">Configure your AI session</p>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
{/* Name */}
|
|
181
|
+
<div className="mb-8">
|
|
182
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
|
|
183
|
+
Session Name
|
|
184
|
+
</label>
|
|
185
|
+
<input
|
|
186
|
+
type="text"
|
|
187
|
+
value={name}
|
|
188
|
+
onChange={(e) => setName(e.target.value)}
|
|
189
|
+
placeholder="e.g. Fix login bug"
|
|
190
|
+
className={inputClass}
|
|
191
|
+
style={{ fontFamily: 'inherit' }}
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
{/* Agent (optional) */}
|
|
196
|
+
{Object.keys(agents).length > 0 && (
|
|
197
|
+
<div className="mb-8">
|
|
198
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
|
|
199
|
+
Agent <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
|
|
200
|
+
</label>
|
|
201
|
+
<select
|
|
202
|
+
value={selectedAgentId || ''}
|
|
203
|
+
onChange={(e) => handleSelectAgent(e.target.value || null)}
|
|
204
|
+
className={`${inputClass} appearance-none cursor-pointer`}
|
|
205
|
+
style={{ fontFamily: 'inherit' }}
|
|
206
|
+
>
|
|
207
|
+
<option value="">None — manual configuration</option>
|
|
208
|
+
{Object.values(agents).map((p) => (
|
|
209
|
+
<option key={p.id} value={p.id}>{p.name}{p.isOrchestrator ? ' (Orchestrator)' : ''}</option>
|
|
210
|
+
))}
|
|
211
|
+
</select>
|
|
212
|
+
</div>
|
|
213
|
+
)}
|
|
214
|
+
|
|
215
|
+
{/* Provider/Model/Key/Endpoint — only show when no agent selected */}
|
|
216
|
+
{!selectedAgentId && (
|
|
217
|
+
<>
|
|
218
|
+
{/* Provider */}
|
|
219
|
+
<div className="mb-8">
|
|
220
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
|
|
221
|
+
Provider
|
|
222
|
+
</label>
|
|
223
|
+
<div className="grid grid-cols-3 gap-3">
|
|
224
|
+
{providers.map((p) => (
|
|
225
|
+
<button
|
|
226
|
+
key={p.id}
|
|
227
|
+
onClick={() => setProvider(p.id)}
|
|
228
|
+
className={`py-3.5 px-4 rounded-[14px] text-center cursor-pointer transition-all duration-200
|
|
229
|
+
active:scale-[0.97] text-[14px] font-600 border
|
|
230
|
+
${provider === p.id
|
|
231
|
+
? 'bg-accent-soft border-accent-bright/25 text-accent-bright shadow-[0_0_20px_rgba(99,102,241,0.1)]'
|
|
232
|
+
: 'bg-surface border-white/[0.06] text-text-2 hover:bg-surface-2 hover:border-white/[0.08]'}`}
|
|
233
|
+
style={{ fontFamily: 'inherit' }}
|
|
234
|
+
>
|
|
235
|
+
{p.name}
|
|
236
|
+
</button>
|
|
237
|
+
))}
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
{/* Ollama Mode Toggle */}
|
|
242
|
+
{provider === 'ollama' && (
|
|
243
|
+
<div className="mb-8">
|
|
244
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
|
|
245
|
+
Mode
|
|
246
|
+
</label>
|
|
247
|
+
<div className="flex p-1 rounded-[14px] bg-surface border border-white/[0.06]">
|
|
248
|
+
{(['local', 'cloud'] as const).map((mode) => (
|
|
249
|
+
<button
|
|
250
|
+
key={mode}
|
|
251
|
+
onClick={() => setOllamaMode(mode)}
|
|
252
|
+
className={`flex-1 py-3 rounded-[12px] text-center cursor-pointer transition-all duration-200
|
|
253
|
+
text-[14px] font-600 capitalize
|
|
254
|
+
${ollamaMode === mode
|
|
255
|
+
? 'bg-accent-soft text-accent-bright shadow-[0_0_20px_rgba(99,102,241,0.1)]'
|
|
256
|
+
: 'bg-transparent text-text-3 hover:text-text-2'}`}
|
|
257
|
+
style={{ fontFamily: 'inherit' }}
|
|
258
|
+
>
|
|
259
|
+
{mode}
|
|
260
|
+
</button>
|
|
261
|
+
))}
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
)}
|
|
265
|
+
|
|
266
|
+
{/* Model */}
|
|
267
|
+
{currentProvider && currentProvider.models.length > 0 && (
|
|
268
|
+
<div className="mb-8">
|
|
269
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
|
|
270
|
+
Model
|
|
271
|
+
</label>
|
|
272
|
+
<select
|
|
273
|
+
value={model}
|
|
274
|
+
onChange={(e) => setModel(e.target.value)}
|
|
275
|
+
className={`${inputClass} appearance-none cursor-pointer`}
|
|
276
|
+
style={{ fontFamily: 'inherit' }}
|
|
277
|
+
>
|
|
278
|
+
{currentProvider.models.map((m) => (
|
|
279
|
+
<option key={m} value={m}>{m}</option>
|
|
280
|
+
))}
|
|
281
|
+
</select>
|
|
282
|
+
</div>
|
|
283
|
+
)}
|
|
284
|
+
|
|
285
|
+
{/* API Key */}
|
|
286
|
+
{(currentProvider?.requiresApiKey || (currentProvider?.optionalApiKey && ollamaMode === 'cloud')) && (
|
|
287
|
+
<div className="mb-8">
|
|
288
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
|
|
289
|
+
API Key
|
|
290
|
+
</label>
|
|
291
|
+
{providerCredentials.length > 0 && !addingKey ? (
|
|
292
|
+
<select
|
|
293
|
+
value={credentialId || ''}
|
|
294
|
+
onChange={(e) => {
|
|
295
|
+
if (e.target.value === '__add__') {
|
|
296
|
+
setAddingKey(true)
|
|
297
|
+
} else {
|
|
298
|
+
setCredentialId(e.target.value)
|
|
299
|
+
}
|
|
300
|
+
}}
|
|
301
|
+
className={`${inputClass} appearance-none cursor-pointer`}
|
|
302
|
+
style={{ fontFamily: 'inherit' }}
|
|
303
|
+
>
|
|
304
|
+
{providerCredentials.map((c) => (
|
|
305
|
+
<option key={c.id} value={c.id}>{c.name}</option>
|
|
306
|
+
))}
|
|
307
|
+
<option value="__add__">+ Add new key...</option>
|
|
308
|
+
</select>
|
|
309
|
+
) : (
|
|
310
|
+
<div className="space-y-3 p-5 rounded-[16px] bg-surface-2 border border-white/[0.06]">
|
|
311
|
+
<input
|
|
312
|
+
type="text"
|
|
313
|
+
value={newKeyName}
|
|
314
|
+
onChange={(e) => setNewKeyName(e.target.value)}
|
|
315
|
+
placeholder="Key name (optional)"
|
|
316
|
+
className={inputClass}
|
|
317
|
+
style={{ fontFamily: 'inherit' }}
|
|
318
|
+
/>
|
|
319
|
+
<input
|
|
320
|
+
type="password"
|
|
321
|
+
value={newKeyValue}
|
|
322
|
+
onChange={(e) => setNewKeyValue(e.target.value)}
|
|
323
|
+
placeholder="sk-..."
|
|
324
|
+
className={inputClass}
|
|
325
|
+
style={{ fontFamily: 'inherit' }}
|
|
326
|
+
/>
|
|
327
|
+
<div className="flex gap-3 pt-2">
|
|
328
|
+
{providerCredentials.length > 0 && (
|
|
329
|
+
<button
|
|
330
|
+
onClick={() => setAddingKey(false)}
|
|
331
|
+
className="flex-1 py-3 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[14px] font-600 cursor-pointer hover:bg-surface-2 transition-colors"
|
|
332
|
+
style={{ fontFamily: 'inherit' }}
|
|
333
|
+
>
|
|
334
|
+
Cancel
|
|
335
|
+
</button>
|
|
336
|
+
)}
|
|
337
|
+
<button
|
|
338
|
+
onClick={handleAddKey}
|
|
339
|
+
disabled={!newKeyValue.trim()}
|
|
340
|
+
className="flex-1 py-3 rounded-[14px] border-none bg-[#6366F1] text-white text-[14px] font-600 cursor-pointer disabled:opacity-30 transition-all hover:brightness-110"
|
|
341
|
+
style={{ fontFamily: 'inherit' }}
|
|
342
|
+
>
|
|
343
|
+
Save Key
|
|
344
|
+
</button>
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
)}
|
|
348
|
+
</div>
|
|
349
|
+
)}
|
|
350
|
+
|
|
351
|
+
{/* Endpoint — show for providers that require it (Ollama local, OpenClaw) */}
|
|
352
|
+
{currentProvider?.requiresEndpoint && (provider === 'openclaw' || (provider === 'ollama' && ollamaMode === 'local')) && (
|
|
353
|
+
<div className="mb-8">
|
|
354
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
|
|
355
|
+
{provider === 'openclaw' ? 'OpenClaw Endpoint' : 'Endpoint'}
|
|
356
|
+
</label>
|
|
357
|
+
<input
|
|
358
|
+
type="text"
|
|
359
|
+
value={endpoint}
|
|
360
|
+
onChange={(e) => setEndpoint(e.target.value)}
|
|
361
|
+
placeholder={currentProvider.defaultEndpoint || 'http://localhost:11434'}
|
|
362
|
+
className={`${inputClass} font-mono text-[14px]`}
|
|
363
|
+
/>
|
|
364
|
+
{provider === 'openclaw' && (
|
|
365
|
+
<p className="text-[11px] text-text-3/60 mt-2">
|
|
366
|
+
The /v1 endpoint of your remote OpenClaw instance
|
|
367
|
+
</p>
|
|
368
|
+
)}
|
|
369
|
+
</div>
|
|
370
|
+
)}
|
|
371
|
+
{/* Tools */}
|
|
372
|
+
{provider !== 'claude-cli' && (
|
|
373
|
+
<div className="mb-8">
|
|
374
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
375
|
+
Tools <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
|
|
376
|
+
</label>
|
|
377
|
+
<p className="text-[12px] text-text-3/60 mb-3">Allow this model to execute commands and access files in the session directory.</p>
|
|
378
|
+
<div className="flex flex-wrap gap-2.5">
|
|
379
|
+
{([
|
|
380
|
+
{ id: 'shell' as SessionTool, label: 'Shell' },
|
|
381
|
+
{ id: 'files' as SessionTool, label: 'Files' },
|
|
382
|
+
{ id: 'edit_file' as SessionTool, label: 'Edit File' },
|
|
383
|
+
{ id: 'web_search' as SessionTool, label: 'Web Search' },
|
|
384
|
+
{ id: 'web_fetch' as SessionTool, label: 'Web Fetch' },
|
|
385
|
+
{ id: 'claude_code' as SessionTool, label: 'Claude Code' },
|
|
386
|
+
{ id: 'codex_cli' as SessionTool, label: 'Codex CLI' },
|
|
387
|
+
{ id: 'opencode_cli' as SessionTool, label: 'OpenCode CLI' },
|
|
388
|
+
]).map(({ id, label }) => {
|
|
389
|
+
const active = selectedTools.includes(id)
|
|
390
|
+
return (
|
|
391
|
+
<button
|
|
392
|
+
key={id}
|
|
393
|
+
onClick={() => {
|
|
394
|
+
setSelectedTools((prev) =>
|
|
395
|
+
active ? prev.filter((t) => t !== id) : [...prev, id],
|
|
396
|
+
)
|
|
397
|
+
}}
|
|
398
|
+
className={`px-4 py-2.5 rounded-[12px] text-[13px] font-600 border cursor-pointer transition-all duration-200 active:scale-[0.97]
|
|
399
|
+
${active
|
|
400
|
+
? 'bg-accent-soft border-accent-bright/25 text-accent-bright shadow-[0_0_20px_rgba(99,102,241,0.1)]'
|
|
401
|
+
: 'bg-surface border-white/[0.06] text-text-3 hover:bg-surface-2 hover:border-white/[0.08]'}`}
|
|
402
|
+
style={{ fontFamily: 'inherit' }}
|
|
403
|
+
>
|
|
404
|
+
{label}
|
|
405
|
+
</button>
|
|
406
|
+
)
|
|
407
|
+
})}
|
|
408
|
+
</div>
|
|
409
|
+
</div>
|
|
410
|
+
)}
|
|
411
|
+
</>
|
|
412
|
+
)}
|
|
413
|
+
|
|
414
|
+
{/* Summary when agent selected */}
|
|
415
|
+
{selectedAgentId && agents[selectedAgentId] && (
|
|
416
|
+
<div className="mb-8 px-4 py-3 rounded-[14px] bg-surface border border-white/[0.06]">
|
|
417
|
+
<span className="text-[13px] text-text-3">
|
|
418
|
+
Using <span className="text-text-2 font-600">{agents[selectedAgentId].provider}</span>
|
|
419
|
+
{' / '}
|
|
420
|
+
<span className="text-text-2 font-600">{agents[selectedAgentId].model}</span>
|
|
421
|
+
{agents[selectedAgentId].tools?.length ? (
|
|
422
|
+
<> + {agents[selectedAgentId].tools!.map((tool, i) => (
|
|
423
|
+
<span key={tool}>
|
|
424
|
+
{i > 0 && ', '}
|
|
425
|
+
<span className="text-sky-400/70 font-600 cursor-help" title={TOOL_DESCRIPTIONS[tool] || tool}>
|
|
426
|
+
{TOOL_LABELS[tool] || tool.replace(/_/g, ' ')}
|
|
427
|
+
</span>
|
|
428
|
+
</span>
|
|
429
|
+
))}</>
|
|
430
|
+
) : null}
|
|
431
|
+
</span>
|
|
432
|
+
</div>
|
|
433
|
+
)}
|
|
434
|
+
|
|
435
|
+
{/* Project */}
|
|
436
|
+
<div className="mb-10">
|
|
437
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
|
|
438
|
+
Directory {provider !== 'claude-cli' && <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>}
|
|
439
|
+
</label>
|
|
440
|
+
<DirBrowser
|
|
441
|
+
value={selectedDir}
|
|
442
|
+
file={selectedFile}
|
|
443
|
+
onChange={(dir, file) => {
|
|
444
|
+
setSelectedDir(dir)
|
|
445
|
+
setSelectedFile(file ?? null)
|
|
446
|
+
if (!name) {
|
|
447
|
+
const dirName = dir.split('/').pop() || ''
|
|
448
|
+
setName(dirName)
|
|
449
|
+
}
|
|
450
|
+
}}
|
|
451
|
+
onClear={() => { setSelectedDir(null); setSelectedFile(null) }}
|
|
452
|
+
/>
|
|
453
|
+
</div>
|
|
454
|
+
|
|
455
|
+
{/* Actions */}
|
|
456
|
+
<div className="flex gap-3 pt-2 border-t border-white/[0.04]">
|
|
457
|
+
<button
|
|
458
|
+
onClick={onClose}
|
|
459
|
+
className="flex-1 py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[15px] font-600 cursor-pointer
|
|
460
|
+
hover:bg-surface-2 transition-all duration-200"
|
|
461
|
+
style={{ fontFamily: 'inherit' }}
|
|
462
|
+
>
|
|
463
|
+
Cancel
|
|
464
|
+
</button>
|
|
465
|
+
<button
|
|
466
|
+
onClick={handleCreate}
|
|
467
|
+
disabled={!canCreate()}
|
|
468
|
+
className="flex-1 py-3.5 rounded-[14px] border-none bg-[#6366F1] text-white text-[15px] font-600 cursor-pointer
|
|
469
|
+
active:scale-[0.97] disabled:opacity-30 transition-all duration-200
|
|
470
|
+
shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110"
|
|
471
|
+
style={{ fontFamily: 'inherit' }}
|
|
472
|
+
>
|
|
473
|
+
Create Session
|
|
474
|
+
</button>
|
|
475
|
+
</div>
|
|
476
|
+
</BottomSheet>
|
|
477
|
+
)
|
|
478
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { Session } from '@/types'
|
|
4
|
+
import { api } from '@/lib/api-client'
|
|
5
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
6
|
+
import { useChatStore } from '@/stores/use-chat-store'
|
|
7
|
+
import { ConnectorPlatformBadge, getSessionConnector } from '@/components/shared/connector-platform-icon'
|
|
8
|
+
|
|
9
|
+
function timeAgo(ts: number): string {
|
|
10
|
+
if (!ts) return ''
|
|
11
|
+
const s = Math.floor((Date.now() - ts) / 1000)
|
|
12
|
+
if (s < 60) return 'now'
|
|
13
|
+
if (s < 3600) return Math.floor(s / 60) + 'm'
|
|
14
|
+
if (s < 86400) return Math.floor(s / 3600) + 'h'
|
|
15
|
+
return Math.floor(s / 86400) + 'd'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function shortPath(p: string): string {
|
|
19
|
+
return (p || '').replace(/^\/Users\/\w+/, '~')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const PROVIDER_LABELS: Record<string, string> = {
|
|
23
|
+
'claude-cli': '',
|
|
24
|
+
openai: 'GPT',
|
|
25
|
+
ollama: 'OLL',
|
|
26
|
+
anthropic: 'ANT',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface Props {
|
|
30
|
+
session: Session
|
|
31
|
+
active?: boolean
|
|
32
|
+
onClick: () => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function SessionCard({ session, active, onClick }: Props) {
|
|
36
|
+
const removeSession = useAppStore((s) => s.removeSession)
|
|
37
|
+
const appSettings = useAppStore((s) => s.appSettings)
|
|
38
|
+
const agents = useAppStore((s) => s.agents)
|
|
39
|
+
const connectors = useAppStore((s) => s.connectors)
|
|
40
|
+
const streamingSessionId = useChatStore((s) => s.streamingSessionId)
|
|
41
|
+
const isTyping = streamingSessionId === session.id
|
|
42
|
+
|
|
43
|
+
const handleDelete = async (e: React.MouseEvent) => {
|
|
44
|
+
e.stopPropagation()
|
|
45
|
+
await api('DELETE', `/sessions/${session.id}`)
|
|
46
|
+
removeSession(session.id)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const last = session.messages?.length
|
|
50
|
+
? session.messages[session.messages.length - 1]
|
|
51
|
+
: null
|
|
52
|
+
const preview = last
|
|
53
|
+
? (last.role === 'user' ? 'You: ' : '') + last.text.slice(0, 70)
|
|
54
|
+
: 'No messages'
|
|
55
|
+
const providerLabel = PROVIDER_LABELS[session.provider] || session.provider
|
|
56
|
+
const agent = session.agentId ? agents[session.agentId] : null
|
|
57
|
+
const connector = getSessionConnector(session, connectors)
|
|
58
|
+
const loopIsOngoing = appSettings.loopMode === 'ongoing'
|
|
59
|
+
const intervalRaw = session.heartbeatIntervalSec ?? agent?.heartbeatIntervalSec ?? appSettings.heartbeatIntervalSec ?? 120
|
|
60
|
+
const intervalNum = typeof intervalRaw === 'number' ? intervalRaw : Number.parseInt(String(intervalRaw), 10)
|
|
61
|
+
const intervalEnabled = Number.isFinite(intervalNum) ? intervalNum > 0 : true
|
|
62
|
+
const heartbeatEnabled =
|
|
63
|
+
loopIsOngoing
|
|
64
|
+
&& (session.tools?.length ?? 0) > 0
|
|
65
|
+
&& intervalEnabled
|
|
66
|
+
&& (session.heartbeatEnabled === true || (session.heartbeatEnabled !== false && agent?.heartbeatEnabled !== false))
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
onClick={onClick}
|
|
71
|
+
className={`group/card relative py-3.5 px-4 cursor-pointer rounded-[14px]
|
|
72
|
+
transition-all duration-200 active:scale-[0.98]
|
|
73
|
+
${active
|
|
74
|
+
? 'bg-accent-soft border border-accent-bright/10'
|
|
75
|
+
: 'bg-transparent border border-transparent hover:bg-white/[0.02] hover:border-white/[0.03]'}`}
|
|
76
|
+
>
|
|
77
|
+
{active && (
|
|
78
|
+
<div className="absolute left-0 top-3.5 bottom-3.5 w-[2.5px] rounded-full bg-accent-bright" />
|
|
79
|
+
)}
|
|
80
|
+
<div className="flex items-center gap-2.5">
|
|
81
|
+
{session.active && (
|
|
82
|
+
<span className="inline-block w-[6px] h-[6px] rounded-full bg-success shrink-0"
|
|
83
|
+
style={{ animation: 'pulse 2s ease-in-out infinite' }} />
|
|
84
|
+
)}
|
|
85
|
+
{heartbeatEnabled && (
|
|
86
|
+
<span
|
|
87
|
+
className="inline-flex items-center justify-center w-[10px] h-[10px] rounded-full bg-emerald-400/15 border border-emerald-400/30 shrink-0"
|
|
88
|
+
title="Heartbeat enabled"
|
|
89
|
+
>
|
|
90
|
+
<span className="w-[4px] h-[4px] rounded-full bg-emerald-400" />
|
|
91
|
+
</span>
|
|
92
|
+
)}
|
|
93
|
+
{connector && (
|
|
94
|
+
<ConnectorPlatformBadge
|
|
95
|
+
platform={connector.platform}
|
|
96
|
+
size={16}
|
|
97
|
+
iconSize={9}
|
|
98
|
+
roundedClassName="rounded-[5px]"
|
|
99
|
+
title={`${connector.name} (${connector.platform})`}
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
102
|
+
<span className="font-display text-[14px] font-600 truncate flex-1 tracking-[-0.01em]">{session.name}</span>
|
|
103
|
+
{session.sessionType === 'orchestrated' && (
|
|
104
|
+
<span className="shrink-0 text-[10px] font-600 uppercase tracking-wider text-amber-400/80 bg-amber-400/[0.08] px-2 py-0.5 rounded-[6px]">
|
|
105
|
+
AI
|
|
106
|
+
</span>
|
|
107
|
+
)}
|
|
108
|
+
{providerLabel && (
|
|
109
|
+
<span className="shrink-0 text-[10px] font-600 uppercase tracking-wider text-text-3/70 bg-white/[0.03] px-2 py-0.5 rounded-[6px]">
|
|
110
|
+
{providerLabel}
|
|
111
|
+
</span>
|
|
112
|
+
)}
|
|
113
|
+
<span className="text-[11px] text-text-3/70 shrink-0 tabular-nums font-mono">
|
|
114
|
+
{timeAgo(session.lastActiveAt)}
|
|
115
|
+
</span>
|
|
116
|
+
<button
|
|
117
|
+
onClick={handleDelete}
|
|
118
|
+
className="shrink-0 opacity-0 group-hover/card:opacity-100 transition-opacity duration-150
|
|
119
|
+
text-text-3 hover:text-red-400 p-0.5 -mr-1 cursor-pointer bg-transparent border-none"
|
|
120
|
+
title="Delete session"
|
|
121
|
+
>
|
|
122
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
123
|
+
<line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
|
|
124
|
+
</svg>
|
|
125
|
+
</button>
|
|
126
|
+
</div>
|
|
127
|
+
<div className="text-[12px] text-text-3/70 font-mono mt-1.5 truncate">
|
|
128
|
+
{shortPath(session.cwd)}
|
|
129
|
+
</div>
|
|
130
|
+
{isTyping ? (
|
|
131
|
+
<div className="text-[13px] text-accent-bright/70 truncate mt-1 leading-relaxed flex items-center gap-1.5">
|
|
132
|
+
<span className="flex gap-0.5">
|
|
133
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:0ms]" />
|
|
134
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:150ms]" />
|
|
135
|
+
<span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:300ms]" />
|
|
136
|
+
</span>
|
|
137
|
+
Typing...
|
|
138
|
+
</div>
|
|
139
|
+
) : (
|
|
140
|
+
<div className="text-[13px] text-text-2/50 truncate mt-1 leading-relaxed">{preview}</div>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
)
|
|
144
|
+
}
|