@swarmclawai/swarmclaw 0.3.1 → 0.4.5
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 +33 -13
- package/bin/server-cmd.js +14 -7
- package/bin/swarmclaw.js +3 -1
- package/bin/update-cmd.js +120 -0
- package/next.config.ts +10 -0
- package/package.json +4 -1
- package/src/app/api/agents/[id]/route.ts +20 -18
- package/src/app/api/agents/[id]/thread/route.ts +4 -3
- package/src/app/api/agents/route.ts +8 -3
- package/src/app/api/auth/route.ts +3 -1
- package/src/app/api/claude-skills/route.ts +3 -1
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/connectors/[id]/route.ts +14 -3
- package/src/app/api/connectors/[id]/webhook/route.ts +99 -0
- package/src/app/api/connectors/route.ts +12 -4
- package/src/app/api/credentials/[id]/route.ts +2 -1
- package/src/app/api/credentials/route.ts +5 -3
- package/src/app/api/daemon/route.ts +6 -1
- package/src/app/api/documents/route.ts +2 -2
- package/src/app/api/files/serve/route.ts +8 -0
- package/src/app/api/ip/route.ts +3 -1
- package/src/app/api/knowledge/[id]/route.ts +5 -4
- package/src/app/api/knowledge/upload/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/route.ts +11 -14
- package/src/app/api/mcp-servers/[id]/test/route.ts +2 -1
- package/src/app/api/mcp-servers/[id]/tools/route.ts +2 -1
- package/src/app/api/mcp-servers/route.ts +5 -3
- package/src/app/api/memory/[id]/route.ts +9 -8
- package/src/app/api/memory/route.ts +2 -2
- package/src/app/api/memory-images/[filename]/route.ts +2 -1
- package/src/app/api/openclaw/directory/route.ts +26 -0
- package/src/app/api/openclaw/discover/route.ts +61 -0
- package/src/app/api/openclaw/sync/route.ts +30 -0
- package/src/app/api/orchestrator/graph/route.ts +25 -0
- package/src/app/api/orchestrator/run/route.ts +2 -2
- package/src/app/api/plugins/marketplace/route.ts +3 -1
- package/src/app/api/plugins/route.ts +3 -1
- package/src/app/api/projects/[id]/route.ts +55 -0
- package/src/app/api/projects/route.ts +27 -0
- package/src/app/api/providers/[id]/models/route.ts +2 -1
- package/src/app/api/providers/[id]/route.ts +13 -12
- package/src/app/api/providers/configs/route.ts +3 -1
- package/src/app/api/providers/route.ts +7 -3
- package/src/app/api/schedules/[id]/route.ts +16 -15
- package/src/app/api/schedules/[id]/run/route.ts +4 -3
- package/src/app/api/schedules/route.ts +8 -3
- package/src/app/api/secrets/[id]/route.ts +16 -17
- package/src/app/api/secrets/route.ts +5 -3
- package/src/app/api/sessions/[id]/chat/route.ts +5 -2
- package/src/app/api/sessions/[id]/clear/route.ts +2 -1
- package/src/app/api/sessions/[id]/deploy/route.ts +2 -1
- package/src/app/api/sessions/[id]/devserver/route.ts +2 -1
- package/src/app/api/sessions/[id]/messages/route.ts +2 -1
- package/src/app/api/sessions/[id]/retry/route.ts +2 -1
- package/src/app/api/sessions/[id]/route.ts +2 -1
- package/src/app/api/sessions/route.ts +11 -4
- package/src/app/api/settings/route.ts +3 -1
- package/src/app/api/setup/doctor/route.ts +1 -0
- package/src/app/api/setup/openclaw-device/route.ts +3 -1
- package/src/app/api/skills/[id]/route.ts +23 -21
- package/src/app/api/skills/import/route.ts +2 -2
- package/src/app/api/skills/route.ts +5 -3
- package/src/app/api/tasks/[id]/approve/route.ts +74 -0
- package/src/app/api/tasks/[id]/route.ts +9 -5
- package/src/app/api/tasks/route.ts +5 -2
- package/src/app/api/tts/stream/route.ts +48 -0
- package/src/app/api/upload/route.ts +2 -2
- package/src/app/api/uploads/[filename]/route.ts +4 -1
- package/src/app/api/usage/route.ts +3 -1
- package/src/app/api/version/route.ts +3 -1
- package/src/app/api/webhooks/[id]/route.ts +31 -32
- package/src/app/api/webhooks/route.ts +5 -3
- package/src/app/icon.svg +58 -0
- package/src/app/page.tsx +11 -26
- package/src/cli/index.js +28 -9
- package/src/cli/index.ts +45 -2
- package/src/cli/spec.js +2 -8
- package/src/components/agents/agent-card.tsx +1 -1
- package/src/components/agents/agent-list.tsx +3 -1
- package/src/components/agents/agent-sheet.tsx +166 -81
- package/src/components/chat/chat-area.tsx +71 -34
- package/src/components/chat/chat-header.tsx +141 -29
- package/src/components/chat/chat-tool-toggles.tsx +12 -53
- package/src/components/chat/message-bubble.tsx +110 -42
- package/src/components/chat/tool-call-bubble.tsx +50 -6
- package/src/components/chat/tool-request-banner.tsx +1 -9
- package/src/components/chat/voice-overlay.tsx +80 -0
- package/src/components/connectors/connector-list.tsx +9 -10
- package/src/components/connectors/connector-sheet.tsx +55 -36
- package/src/components/input/chat-input.tsx +72 -56
- package/src/components/knowledge/knowledge-list.tsx +27 -31
- package/src/components/layout/app-layout.tsx +133 -90
- package/src/components/layout/daemon-indicator.tsx +3 -5
- package/src/components/logs/log-list.tsx +5 -9
- package/src/components/mcp-servers/mcp-server-list.tsx +24 -2
- package/src/components/memory/memory-detail.tsx +1 -1
- package/src/components/plugins/plugin-list.tsx +227 -27
- package/src/components/projects/project-list.tsx +122 -0
- package/src/components/projects/project-sheet.tsx +135 -0
- package/src/components/providers/provider-list.tsx +46 -13
- package/src/components/providers/provider-sheet.tsx +0 -45
- package/src/components/runs/run-list.tsx +6 -15
- package/src/components/schedules/schedule-card.tsx +54 -4
- package/src/components/schedules/schedule-list.tsx +9 -4
- package/src/components/schedules/schedule-sheet.tsx +0 -47
- package/src/components/secrets/secrets-list.tsx +20 -2
- package/src/components/sessions/new-session-sheet.tsx +14 -15
- package/src/components/sessions/session-card.tsx +1 -1
- package/src/components/sessions/session-list.tsx +7 -7
- package/src/components/shared/connector-platform-icon.tsx +26 -20
- package/src/components/shared/model-combobox.tsx +148 -0
- package/src/components/shared/settings/section-heartbeat.tsx +8 -40
- package/src/components/shared/settings/section-orchestrator.tsx +9 -11
- package/src/components/shared/settings/section-web-search.tsx +56 -0
- package/src/components/shared/settings/settings-page.tsx +73 -0
- package/src/components/skills/skill-list.tsx +262 -35
- package/src/components/skills/skill-sheet.tsx +0 -45
- package/src/components/tasks/task-board.tsx +3 -6
- package/src/components/tasks/task-card.tsx +43 -1
- package/src/components/tasks/task-list.tsx +8 -7
- package/src/components/tasks/task-sheet.tsx +0 -44
- package/src/components/usage/usage-list.tsx +12 -4
- package/src/hooks/use-continuous-speech.ts +144 -0
- package/src/hooks/use-view-router.ts +52 -0
- package/src/hooks/use-voice-conversation.ts +80 -0
- package/src/hooks/use-ws.ts +66 -0
- package/src/instrumentation.ts +2 -0
- package/src/lib/chat.ts +14 -2
- package/src/lib/id.ts +6 -0
- package/src/lib/projects.ts +13 -0
- package/src/lib/provider-sets.ts +5 -0
- package/src/lib/providers/anthropic.ts +15 -2
- package/src/lib/providers/index.ts +8 -0
- package/src/lib/providers/ollama.ts +10 -2
- package/src/lib/providers/openai.ts +42 -13
- package/src/lib/providers/openclaw.ts +11 -0
- package/src/lib/server/api-routes.test.ts +5 -6
- package/src/lib/server/build-llm.ts +17 -4
- package/src/lib/server/chat-execution.ts +57 -8
- package/src/lib/server/collection-helpers.ts +54 -0
- package/src/lib/server/connectors/bluebubbles.test.ts +208 -0
- package/src/lib/server/connectors/bluebubbles.ts +357 -0
- package/src/lib/server/connectors/connector-routing.test.ts +1 -1
- package/src/lib/server/connectors/googlechat.ts +46 -7
- package/src/lib/server/connectors/manager.ts +401 -6
- package/src/lib/server/connectors/media.ts +2 -2
- package/src/lib/server/connectors/openclaw.ts +64 -0
- package/src/lib/server/connectors/pairing.test.ts +99 -0
- package/src/lib/server/connectors/pairing.ts +256 -0
- package/src/lib/server/connectors/signal.ts +1 -0
- package/src/lib/server/connectors/teams.ts +5 -5
- package/src/lib/server/connectors/types.ts +10 -0
- package/src/lib/server/context-manager.ts +1 -1
- package/src/lib/server/daemon-state.ts +3 -0
- package/src/lib/server/data-dir.ts +1 -0
- package/src/lib/server/execution-log.ts +3 -3
- package/src/lib/server/heartbeat-service.ts +67 -3
- package/src/lib/server/knowledge-db.test.ts +2 -33
- package/src/lib/server/langgraph-checkpoint.ts +274 -0
- package/src/lib/server/main-agent-loop.ts +67 -8
- package/src/lib/server/memory-db.ts +6 -6
- package/src/lib/server/openclaw-approvals.ts +105 -0
- package/src/lib/server/openclaw-sync.ts +496 -0
- package/src/lib/server/orchestrator-lg.ts +422 -20
- package/src/lib/server/orchestrator.ts +29 -9
- package/src/lib/server/process-manager.ts +2 -2
- package/src/lib/server/queue.ts +39 -13
- package/src/lib/server/scheduler.ts +2 -2
- package/src/lib/server/session-mailbox.ts +2 -2
- package/src/lib/server/session-run-manager.ts +8 -3
- package/src/lib/server/session-tools/connector.ts +51 -4
- package/src/lib/server/session-tools/crud.ts +3 -3
- package/src/lib/server/session-tools/delegate.ts +5 -5
- package/src/lib/server/session-tools/file.ts +176 -3
- package/src/lib/server/session-tools/index.ts +4 -0
- package/src/lib/server/session-tools/memory.ts +2 -2
- package/src/lib/server/session-tools/openclaw-nodes.ts +112 -0
- package/src/lib/server/session-tools/sandbox.ts +197 -0
- package/src/lib/server/session-tools/search-providers.ts +270 -0
- package/src/lib/server/session-tools/session-info.ts +2 -2
- package/src/lib/server/session-tools/web.ts +47 -66
- package/src/lib/server/storage-mcp.test.ts +25 -2
- package/src/lib/server/storage.ts +36 -7
- package/src/lib/server/stream-agent-chat.ts +106 -22
- package/src/lib/server/task-result.test.ts +44 -0
- package/src/lib/server/task-result.ts +14 -0
- package/src/lib/server/task-validation.test.ts +23 -0
- package/src/lib/server/task-validation.ts +5 -3
- package/src/lib/server/ws-hub.ts +85 -0
- package/src/lib/tool-definitions.ts +44 -0
- package/src/lib/tts-stream.ts +130 -0
- package/src/lib/upload.ts +7 -1
- package/src/lib/view-routes.ts +28 -0
- package/src/lib/ws-client.ts +124 -0
- package/src/proxy.ts +3 -0
- package/src/stores/use-app-store.ts +28 -1
- package/src/stores/use-chat-store.ts +42 -14
- package/src/types/index.ts +34 -2
- package/src/app/api/agents/generate/route.ts +0 -42
- package/src/app/api/generate/info/route.ts +0 -12
- package/src/app/api/generate/route.ts +0 -106
- package/src/app/favicon.ico +0 -0
- package/src/components/shared/ai-gen-block.tsx +0 -77
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useState } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { useWs } from '@/hooks/use-ws'
|
|
6
|
+
import { api } from '@/lib/api-client'
|
|
5
7
|
|
|
6
8
|
export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
7
9
|
const providers = useAppStore((s) => s.providers)
|
|
@@ -19,20 +21,26 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
19
21
|
setLoaded(true)
|
|
20
22
|
}, [loadProviders, loadProviderConfigs, loadCredentials])
|
|
21
23
|
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
|
|
24
|
-
const poll = setInterval(() => { void loadProviders() }, 20_000)
|
|
25
|
-
return () => {
|
|
26
|
-
clearTimeout(bootstrap)
|
|
27
|
-
clearInterval(poll)
|
|
28
|
-
}
|
|
29
|
-
}, [refresh, loadProviders])
|
|
24
|
+
useEffect(() => { void refresh() }, [refresh])
|
|
25
|
+
useWs('providers', loadProviders, 20_000)
|
|
30
26
|
|
|
31
27
|
const handleEdit = (id: string) => {
|
|
32
28
|
setEditingProviderId(id)
|
|
33
29
|
setProviderSheetOpen(true)
|
|
34
30
|
}
|
|
35
31
|
|
|
32
|
+
const handleToggle = async (e: React.MouseEvent, id: string, currentEnabled: boolean) => {
|
|
33
|
+
e.stopPropagation()
|
|
34
|
+
await api('PUT', `/providers/${id}`, { isEnabled: !currentEnabled })
|
|
35
|
+
await loadProviderConfigs()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const handleDelete = async (e: React.MouseEvent, id: string) => {
|
|
39
|
+
e.stopPropagation()
|
|
40
|
+
await api('DELETE', `/providers/${id}`)
|
|
41
|
+
await loadProviderConfigs()
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
// Merge built-in providers with custom configs
|
|
37
45
|
const builtinItems = providers.map((p) => ({
|
|
38
46
|
id: p.id,
|
|
@@ -58,15 +66,15 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
58
66
|
|
|
59
67
|
if (!loaded) {
|
|
60
68
|
return (
|
|
61
|
-
<div className={`flex-1 flex items-center justify-center ${inSidebar ? 'px-3 pb-4' : 'px-
|
|
69
|
+
<div className={`flex-1 flex items-center justify-center ${inSidebar ? 'px-3 pb-4' : 'px-5'}`}>
|
|
62
70
|
<p className="text-[13px] text-text-3">Loading providers...</p>
|
|
63
71
|
</div>
|
|
64
72
|
)
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
return (
|
|
68
|
-
<div className={`flex-1 overflow-y-auto ${inSidebar ? 'px-3 pb-4' : 'px-
|
|
69
|
-
<div className=
|
|
76
|
+
<div className={`flex-1 overflow-y-auto ${inSidebar ? 'px-3 pb-4' : 'px-5 pb-6'}`}>
|
|
77
|
+
<div className={inSidebar ? 'space-y-2' : 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3'}>
|
|
70
78
|
{allItems.map((item) => (
|
|
71
79
|
<button
|
|
72
80
|
key={item.id}
|
|
@@ -81,12 +89,37 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
81
89
|
${item.type === 'builtin' ? 'bg-white/[0.04] text-text-3' : 'bg-[#6366F1]/10 text-[#6366F1]'}`}>
|
|
82
90
|
{item.type === 'builtin' ? 'Built-in' : 'Custom'}
|
|
83
91
|
</span>
|
|
92
|
+
{!inSidebar && item.type === 'custom' && (
|
|
93
|
+
<>
|
|
94
|
+
<div
|
|
95
|
+
onClick={(e) => handleToggle(e, item.id, item.isEnabled)}
|
|
96
|
+
className={`w-9 h-5 rounded-full transition-all relative cursor-pointer shrink-0
|
|
97
|
+
${item.isEnabled ? 'bg-[#6366F1]' : 'bg-white/[0.08]'}`}
|
|
98
|
+
>
|
|
99
|
+
<div className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-all
|
|
100
|
+
${item.isEnabled ? 'left-[18px]' : 'left-0.5'}`} />
|
|
101
|
+
</div>
|
|
102
|
+
<button
|
|
103
|
+
onClick={(e) => handleDelete(e, item.id)}
|
|
104
|
+
className="text-text-3/40 hover:text-red-400 transition-colors p-0.5"
|
|
105
|
+
title="Delete provider"
|
|
106
|
+
>
|
|
107
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
108
|
+
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
109
|
+
</svg>
|
|
110
|
+
</button>
|
|
111
|
+
</>
|
|
112
|
+
)}
|
|
84
113
|
<span className={`w-2 h-2 rounded-full ${item.isConnected ? 'bg-emerald-400' : 'bg-white/10'}`} />
|
|
85
114
|
</div>
|
|
86
115
|
</div>
|
|
87
116
|
<div className="text-[12px] text-text-3/60 font-mono truncate">
|
|
88
|
-
{item.models.
|
|
89
|
-
|
|
117
|
+
{!inSidebar ? item.models.join(', ') : (
|
|
118
|
+
<>
|
|
119
|
+
{item.models.slice(0, 3).join(', ')}
|
|
120
|
+
{item.models.length > 3 && ` +${item.models.length - 3}`}
|
|
121
|
+
</>
|
|
122
|
+
)}
|
|
90
123
|
</div>
|
|
91
124
|
</button>
|
|
92
125
|
))}
|
|
@@ -5,7 +5,6 @@ import { useAppStore } from '@/stores/use-app-store'
|
|
|
5
5
|
import { createProviderConfig, updateProviderConfig, deleteProviderConfig } from '@/lib/provider-config'
|
|
6
6
|
import { api } from '@/lib/api-client'
|
|
7
7
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
8
|
-
import { AiGenBlock } from '@/components/shared/ai-gen-block'
|
|
9
8
|
import { toast } from 'sonner'
|
|
10
9
|
|
|
11
10
|
export function ProviderSheet() {
|
|
@@ -41,51 +40,15 @@ export function ProviderSheet() {
|
|
|
41
40
|
const [localLoading, setLocalLoading] = useState(false)
|
|
42
41
|
const [localError, setLocalError] = useState('')
|
|
43
42
|
|
|
44
|
-
// AI generation state
|
|
45
|
-
const [aiPrompt, setAiPrompt] = useState('')
|
|
46
|
-
const [generating, setGenerating] = useState(false)
|
|
47
|
-
const [generated, setGenerated] = useState(false)
|
|
48
|
-
const [genError, setGenError] = useState('')
|
|
49
|
-
const appSettings = useAppStore((s) => s.appSettings)
|
|
50
|
-
const loadSettings = useAppStore((s) => s.loadSettings)
|
|
51
|
-
|
|
52
43
|
// Find editing provider in custom configs OR built-in list
|
|
53
44
|
const editingCustom = editingId ? providerConfigs.find((c) => c.id === editingId) : null
|
|
54
45
|
const editingBuiltin = editingId ? providers.find((p) => p.id === editingId) : null
|
|
55
46
|
const isBuiltin = !!editingBuiltin && !editingCustom
|
|
56
47
|
const editing = editingCustom || editingBuiltin
|
|
57
48
|
|
|
58
|
-
const handleGenerate = async () => {
|
|
59
|
-
if (!aiPrompt.trim()) return
|
|
60
|
-
setGenerating(true)
|
|
61
|
-
setGenError('')
|
|
62
|
-
try {
|
|
63
|
-
const result = await api<{ name?: string; baseUrl?: string; models?: string; requiresApiKey?: boolean; error?: string }>('POST', '/generate', { type: 'provider', prompt: aiPrompt })
|
|
64
|
-
if (result.error) {
|
|
65
|
-
setGenError(result.error)
|
|
66
|
-
} else if (result.name || result.baseUrl) {
|
|
67
|
-
if (result.name) setName(result.name)
|
|
68
|
-
if (result.baseUrl) setBaseUrl(result.baseUrl)
|
|
69
|
-
if (result.models) setModels(result.models)
|
|
70
|
-
if (result.requiresApiKey !== undefined) setRequiresApiKey(result.requiresApiKey)
|
|
71
|
-
setGenerated(true)
|
|
72
|
-
} else {
|
|
73
|
-
setGenError('AI returned empty response — try again')
|
|
74
|
-
}
|
|
75
|
-
} catch (err: unknown) {
|
|
76
|
-
setGenError(err instanceof Error ? err.message : 'Generation failed')
|
|
77
|
-
}
|
|
78
|
-
setGenerating(false)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
49
|
useEffect(() => {
|
|
82
50
|
if (open) {
|
|
83
51
|
loadCredentials()
|
|
84
|
-
loadSettings()
|
|
85
|
-
setAiPrompt('')
|
|
86
|
-
setGenerating(false)
|
|
87
|
-
setGenerated(false)
|
|
88
|
-
setGenError('')
|
|
89
52
|
setNewModel('')
|
|
90
53
|
setLocalModels([])
|
|
91
54
|
setLocalError('')
|
|
@@ -248,14 +211,6 @@ export function ProviderSheet() {
|
|
|
248
211
|
</p>
|
|
249
212
|
</div>
|
|
250
213
|
|
|
251
|
-
{/* AI Generation — only for new custom providers */}
|
|
252
|
-
{isNew && <AiGenBlock
|
|
253
|
-
aiPrompt={aiPrompt} setAiPrompt={setAiPrompt}
|
|
254
|
-
generating={generating} generated={generated} genError={genError}
|
|
255
|
-
onGenerate={handleGenerate} appSettings={appSettings}
|
|
256
|
-
placeholder='Name a provider, e.g. "Groq", "Together AI", "z.ai", "DeepSeek"'
|
|
257
|
-
/>}
|
|
258
|
-
|
|
259
214
|
{/* Name */}
|
|
260
215
|
<div className="mb-8">
|
|
261
216
|
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Name</label>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState,
|
|
3
|
+
import { useEffect, useState, useCallback } from 'react'
|
|
4
4
|
import { api } from '@/lib/api-client'
|
|
5
|
+
import { useWs } from '@/hooks/use-ws'
|
|
5
6
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
6
7
|
import type { SessionRunRecord, SessionRunStatus } from '@/types'
|
|
7
8
|
|
|
@@ -37,7 +38,6 @@ export function RunList() {
|
|
|
37
38
|
const [autoRefresh, setAutoRefresh] = useState(false)
|
|
38
39
|
const [statusFilter, setStatusFilter] = useState<SessionRunStatus | null>(null)
|
|
39
40
|
const [selected, setSelected] = useState<SessionRunRecord | null>(null)
|
|
40
|
-
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
|
41
41
|
|
|
42
42
|
const fetchRuns = useCallback(async () => {
|
|
43
43
|
try {
|
|
@@ -54,16 +54,7 @@ export function RunList() {
|
|
|
54
54
|
fetchRuns()
|
|
55
55
|
}, [fetchRuns])
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
if (!autoRefresh) {
|
|
59
|
-
if (timerRef.current) clearInterval(timerRef.current)
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
timerRef.current = setInterval(fetchRuns, 3000)
|
|
63
|
-
return () => {
|
|
64
|
-
if (timerRef.current) clearInterval(timerRef.current)
|
|
65
|
-
}
|
|
66
|
-
}, [autoRefresh, fetchRuns])
|
|
57
|
+
useWs('runs', fetchRuns, autoRefresh ? 3000 : undefined)
|
|
67
58
|
|
|
68
59
|
const filtered = statusFilter ? runs.filter((r) => r.status === statusFilter) : runs
|
|
69
60
|
|
|
@@ -78,7 +69,7 @@ export function RunList() {
|
|
|
78
69
|
return (
|
|
79
70
|
<div className="flex-1 flex flex-col overflow-hidden">
|
|
80
71
|
{/* Controls */}
|
|
81
|
-
<div className="px-
|
|
72
|
+
<div className="px-5 py-2 space-y-2 shrink-0">
|
|
82
73
|
{/* Status filter + auto-refresh */}
|
|
83
74
|
<div className="flex items-center gap-1.5 flex-wrap">
|
|
84
75
|
<button
|
|
@@ -113,12 +104,12 @@ export function RunList() {
|
|
|
113
104
|
</div>
|
|
114
105
|
|
|
115
106
|
{/* Count */}
|
|
116
|
-
<div className="px-
|
|
107
|
+
<div className="px-5 py-1 text-[10px] text-text-3/60">
|
|
117
108
|
{filtered.length} run{filtered.length !== 1 ? 's' : ''}
|
|
118
109
|
</div>
|
|
119
110
|
|
|
120
111
|
{/* Run list */}
|
|
121
|
-
<div className="flex-1 overflow-y-auto px-
|
|
112
|
+
<div className="flex-1 overflow-y-auto px-4 pb-8">
|
|
122
113
|
{filtered.length === 0 ? (
|
|
123
114
|
<div className="flex items-center justify-center h-32 text-text-3 text-[12px]">
|
|
124
115
|
No runs found
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { Schedule } from '@/types'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { api } from '@/lib/api-client'
|
|
5
6
|
|
|
6
7
|
const STATUS_COLORS: Record<string, string> = {
|
|
7
8
|
active: 'text-emerald-400 bg-emerald-400/[0.08]',
|
|
@@ -24,11 +25,13 @@ function formatNext(ts?: number): string {
|
|
|
24
25
|
|
|
25
26
|
interface Props {
|
|
26
27
|
schedule: Schedule
|
|
28
|
+
inSidebar?: boolean
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
export function ScheduleCard({ schedule }: Props) {
|
|
31
|
+
export function ScheduleCard({ schedule, inSidebar }: Props) {
|
|
30
32
|
const setEditingScheduleId = useAppStore((s) => s.setEditingScheduleId)
|
|
31
33
|
const setScheduleSheetOpen = useAppStore((s) => s.setScheduleSheetOpen)
|
|
34
|
+
const loadSchedules = useAppStore((s) => s.loadSchedules)
|
|
32
35
|
const agents = useAppStore((s) => s.agents)
|
|
33
36
|
|
|
34
37
|
const handleClick = () => {
|
|
@@ -36,8 +39,22 @@ export function ScheduleCard({ schedule }: Props) {
|
|
|
36
39
|
setScheduleSheetOpen(true)
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
const handleToggle = async (e: React.MouseEvent) => {
|
|
43
|
+
e.stopPropagation()
|
|
44
|
+
const newStatus = schedule.status === 'active' ? 'paused' : 'active'
|
|
45
|
+
await api('PUT', `/schedules/${schedule.id}`, { status: newStatus })
|
|
46
|
+
loadSchedules()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const handleDelete = async (e: React.MouseEvent) => {
|
|
50
|
+
e.stopPropagation()
|
|
51
|
+
await api('DELETE', `/schedules/${schedule.id}`)
|
|
52
|
+
loadSchedules()
|
|
53
|
+
}
|
|
54
|
+
|
|
39
55
|
const agent = agents[schedule.agentId]
|
|
40
56
|
const statusClass = STATUS_COLORS[schedule.status] || STATUS_COLORS.paused
|
|
57
|
+
const canToggle = schedule.status === 'active' || schedule.status === 'paused'
|
|
41
58
|
|
|
42
59
|
return (
|
|
43
60
|
<div
|
|
@@ -48,12 +65,45 @@ export function ScheduleCard({ schedule }: Props) {
|
|
|
48
65
|
>
|
|
49
66
|
<div className="flex items-center gap-2.5">
|
|
50
67
|
<span className="font-display text-[14px] font-600 truncate flex-1 tracking-[-0.01em]">{schedule.name}</span>
|
|
51
|
-
<
|
|
52
|
-
{
|
|
53
|
-
|
|
68
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
69
|
+
{!inSidebar && canToggle && (
|
|
70
|
+
<div
|
|
71
|
+
onClick={handleToggle}
|
|
72
|
+
className={`w-9 h-5 rounded-full transition-all relative cursor-pointer shrink-0
|
|
73
|
+
${schedule.status === 'active' ? 'bg-[#6366F1]' : 'bg-white/[0.08]'}`}
|
|
74
|
+
>
|
|
75
|
+
<div className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-all
|
|
76
|
+
${schedule.status === 'active' ? 'left-[18px]' : 'left-0.5'}`} />
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
<span className={`text-[10px] font-600 uppercase tracking-wider px-2 py-0.5 rounded-[6px] ${statusClass}`}>
|
|
80
|
+
{schedule.status}
|
|
81
|
+
</span>
|
|
82
|
+
{!inSidebar && (
|
|
83
|
+
<button
|
|
84
|
+
onClick={handleDelete}
|
|
85
|
+
className="text-text-3/40 hover:text-red-400 transition-colors p-0.5"
|
|
86
|
+
title="Delete"
|
|
87
|
+
>
|
|
88
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
89
|
+
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
90
|
+
</svg>
|
|
91
|
+
</button>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
54
94
|
</div>
|
|
55
95
|
<div className="text-[12px] text-text-3/70 mt-1.5 truncate">
|
|
56
96
|
{agent?.name || 'Unknown agent'} · {schedule.scheduleType}
|
|
97
|
+
{!inSidebar && schedule.scheduleType === 'cron' && schedule.cron && (
|
|
98
|
+
<span className="font-mono text-text-3/50 ml-1">({schedule.cron})</span>
|
|
99
|
+
)}
|
|
100
|
+
{!inSidebar && schedule.scheduleType === 'interval' && schedule.intervalMs && (
|
|
101
|
+
<span className="text-text-3/50 ml-1">
|
|
102
|
+
(every {schedule.intervalMs >= 3600000
|
|
103
|
+
? `${Math.round(schedule.intervalMs / 3600000)}h`
|
|
104
|
+
: `${Math.round(schedule.intervalMs / 60000)}m`})
|
|
105
|
+
</span>
|
|
106
|
+
)}
|
|
57
107
|
</div>
|
|
58
108
|
<div className="text-[11px] text-text-3/60 mt-1">
|
|
59
109
|
Next: {formatNext(schedule.nextRunAt)}
|
|
@@ -12,6 +12,7 @@ export function ScheduleList({ inSidebar }: Props) {
|
|
|
12
12
|
const schedules = useAppStore((s) => s.schedules)
|
|
13
13
|
const loadSchedules = useAppStore((s) => s.loadSchedules)
|
|
14
14
|
const setScheduleSheetOpen = useAppStore((s) => s.setScheduleSheetOpen)
|
|
15
|
+
const activeProjectFilter = useAppStore((s) => s.activeProjectFilter)
|
|
15
16
|
const [search, setSearch] = useState('')
|
|
16
17
|
|
|
17
18
|
useEffect(() => { loadSchedules() }, [])
|
|
@@ -20,10 +21,11 @@ export function ScheduleList({ inSidebar }: Props) {
|
|
|
20
21
|
return Object.values(schedules)
|
|
21
22
|
.filter((s) => {
|
|
22
23
|
if (search && !s.name.toLowerCase().includes(search.toLowerCase())) return false
|
|
24
|
+
if (activeProjectFilter && s.projectId !== activeProjectFilter) return false
|
|
23
25
|
return true
|
|
24
26
|
})
|
|
25
27
|
.sort((a, b) => b.createdAt - a.createdAt)
|
|
26
|
-
}, [schedules, search])
|
|
28
|
+
}, [schedules, search, activeProjectFilter])
|
|
27
29
|
|
|
28
30
|
if (!filtered.length && !search) {
|
|
29
31
|
return (
|
|
@@ -54,7 +56,7 @@ export function ScheduleList({ inSidebar }: Props) {
|
|
|
54
56
|
return (
|
|
55
57
|
<div className="flex-1 overflow-y-auto">
|
|
56
58
|
{(filtered.length > 3 || search) && (
|
|
57
|
-
<div className=
|
|
59
|
+
<div className={inSidebar ? 'px-4 py-2.5' : 'px-5 py-2.5'}>
|
|
58
60
|
<input
|
|
59
61
|
type="text"
|
|
60
62
|
value={search}
|
|
@@ -66,9 +68,12 @@ export function ScheduleList({ inSidebar }: Props) {
|
|
|
66
68
|
/>
|
|
67
69
|
</div>
|
|
68
70
|
)}
|
|
69
|
-
<div className=
|
|
71
|
+
<div className={inSidebar
|
|
72
|
+
? 'flex flex-col gap-1 px-2 pb-4'
|
|
73
|
+
: 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3 px-5 pb-6'
|
|
74
|
+
}>
|
|
70
75
|
{filtered.map((s) => (
|
|
71
|
-
<ScheduleCard key={s.id} schedule={s} />
|
|
76
|
+
<ScheduleCard key={s.id} schedule={s} inSidebar={inSidebar} />
|
|
72
77
|
))}
|
|
73
78
|
</div>
|
|
74
79
|
</div>
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
import { useEffect, useState, useMemo } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { createSchedule, updateSchedule, deleteSchedule } from '@/lib/schedules'
|
|
6
|
-
import { api } from '@/lib/api-client'
|
|
7
6
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
8
|
-
import { AiGenBlock } from '@/components/shared/ai-gen-block'
|
|
9
7
|
import type { ScheduleType, ScheduleStatus } from '@/types'
|
|
10
8
|
import cronstrue from 'cronstrue'
|
|
11
9
|
|
|
@@ -63,49 +61,12 @@ export function ScheduleSheet() {
|
|
|
63
61
|
const [status, setStatus] = useState<ScheduleStatus>('active')
|
|
64
62
|
const [customCron, setCustomCron] = useState(false)
|
|
65
63
|
|
|
66
|
-
// AI generation state
|
|
67
|
-
const [aiPrompt, setAiPrompt] = useState('')
|
|
68
|
-
const [generating, setGenerating] = useState(false)
|
|
69
|
-
const [generated, setGenerated] = useState(false)
|
|
70
|
-
const [genError, setGenError] = useState('')
|
|
71
|
-
const appSettings = useAppStore((s) => s.appSettings)
|
|
72
|
-
const loadSettings = useAppStore((s) => s.loadSettings)
|
|
73
|
-
|
|
74
64
|
const editing = editingId ? schedules[editingId] : null
|
|
75
65
|
const agentList = Object.values(agents)
|
|
76
66
|
|
|
77
|
-
const handleGenerate = async () => {
|
|
78
|
-
if (!aiPrompt.trim()) return
|
|
79
|
-
setGenerating(true)
|
|
80
|
-
setGenError('')
|
|
81
|
-
try {
|
|
82
|
-
const result = await api<{ name?: string; taskPrompt?: string; scheduleType?: ScheduleType; cron?: string; intervalMs?: number; error?: string }>('POST', '/generate', { type: 'schedule', prompt: aiPrompt })
|
|
83
|
-
if (result.error) {
|
|
84
|
-
setGenError(result.error)
|
|
85
|
-
} else if (result.name || result.taskPrompt) {
|
|
86
|
-
if (result.name) setName(result.name)
|
|
87
|
-
if (result.taskPrompt) setTaskPrompt(result.taskPrompt)
|
|
88
|
-
if (result.scheduleType) setScheduleType(result.scheduleType)
|
|
89
|
-
if (result.cron) { setCron(result.cron); setCustomCron(true) }
|
|
90
|
-
if (result.intervalMs) setIntervalMs(result.intervalMs)
|
|
91
|
-
setGenerated(true)
|
|
92
|
-
} else {
|
|
93
|
-
setGenError('AI returned empty response — try again')
|
|
94
|
-
}
|
|
95
|
-
} catch (err: unknown) {
|
|
96
|
-
setGenError(err instanceof Error ? err.message : 'Generation failed')
|
|
97
|
-
}
|
|
98
|
-
setGenerating(false)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
67
|
useEffect(() => {
|
|
102
68
|
if (open) {
|
|
103
69
|
loadAgents()
|
|
104
|
-
loadSettings()
|
|
105
|
-
setAiPrompt('')
|
|
106
|
-
setGenerating(false)
|
|
107
|
-
setGenerated(false)
|
|
108
|
-
setGenError('')
|
|
109
70
|
if (editing) {
|
|
110
71
|
setName(editing.name || '')
|
|
111
72
|
setAgentId(editing.agentId)
|
|
@@ -175,14 +136,6 @@ export function ScheduleSheet() {
|
|
|
175
136
|
<p className="text-[14px] text-text-3">Automate agent tasks on a schedule</p>
|
|
176
137
|
</div>
|
|
177
138
|
|
|
178
|
-
{/* AI Generation */}
|
|
179
|
-
{!editing && <AiGenBlock
|
|
180
|
-
aiPrompt={aiPrompt} setAiPrompt={setAiPrompt}
|
|
181
|
-
generating={generating} generated={generated} genError={genError}
|
|
182
|
-
onGenerate={handleGenerate} appSettings={appSettings}
|
|
183
|
-
placeholder='Describe the schedule, e.g. "Run keyword research every Monday at 9am"'
|
|
184
|
-
/>}
|
|
185
|
-
|
|
186
139
|
<div className="mb-8">
|
|
187
140
|
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Name</label>
|
|
188
141
|
<input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. Daily keyword research" className={inputClass} style={{ fontFamily: 'inherit' }} />
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { api } from '@/lib/api-client'
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
8
|
inSidebar?: boolean
|
|
@@ -18,6 +19,12 @@ export function SecretsList({ inSidebar }: Props) {
|
|
|
18
19
|
loadSecrets()
|
|
19
20
|
}, [])
|
|
20
21
|
|
|
22
|
+
const handleDelete = async (e: React.MouseEvent, id: string) => {
|
|
23
|
+
e.stopPropagation()
|
|
24
|
+
await api('DELETE', `/secrets/${id}`)
|
|
25
|
+
loadSecrets()
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
const secretList = Object.values(secrets)
|
|
22
29
|
|
|
23
30
|
if (!secretList.length) {
|
|
@@ -44,7 +51,7 @@ export function SecretsList({ inSidebar }: Props) {
|
|
|
44
51
|
|
|
45
52
|
return (
|
|
46
53
|
<div className={`flex-1 overflow-y-auto ${inSidebar ? 'px-3 pb-4' : 'px-5 pb-6'}`}>
|
|
47
|
-
<div className=
|
|
54
|
+
<div className={inSidebar ? 'space-y-2' : 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3'}>
|
|
48
55
|
{secretList.map((secret) => {
|
|
49
56
|
const scopeLabel = secret.scope === 'global'
|
|
50
57
|
? 'All orchestrators'
|
|
@@ -68,7 +75,18 @@ export function SecretsList({ inSidebar }: Props) {
|
|
|
68
75
|
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
|
69
76
|
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
70
77
|
</svg>
|
|
71
|
-
<span className="text-[14px] font-600 text-text truncate">{secret.name}</span>
|
|
78
|
+
<span className="text-[14px] font-600 text-text truncate flex-1">{secret.name}</span>
|
|
79
|
+
{!inSidebar && (
|
|
80
|
+
<button
|
|
81
|
+
onClick={(e) => handleDelete(e, secret.id)}
|
|
82
|
+
className="text-text-3/40 hover:text-red-400 transition-colors p-0.5 shrink-0"
|
|
83
|
+
title="Delete"
|
|
84
|
+
>
|
|
85
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
86
|
+
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
87
|
+
</svg>
|
|
88
|
+
</button>
|
|
89
|
+
)}
|
|
72
90
|
</div>
|
|
73
91
|
<div className="flex items-center gap-2 pl-[22px]">
|
|
74
92
|
<span className="text-[11px] font-mono text-text-3">{secret.service}</span>
|
|
@@ -7,6 +7,7 @@ import { createSession, createCredential } from '@/lib/sessions'
|
|
|
7
7
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
8
8
|
import { DirBrowser } from '@/components/shared/dir-browser'
|
|
9
9
|
import { TOOL_LABELS, TOOL_DESCRIPTIONS } from '@/components/chat/tool-call-bubble'
|
|
10
|
+
import { ModelCombobox } from '@/components/shared/model-combobox'
|
|
10
11
|
import type { ProviderType, SessionTool } from '@/types'
|
|
11
12
|
|
|
12
13
|
export function NewSessionSheet() {
|
|
@@ -128,7 +129,7 @@ export function NewSessionSheet() {
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
const handleCreate = async () => {
|
|
131
|
-
const sessionName = name.trim() || 'New
|
|
132
|
+
const sessionName = name.trim() || 'New Chat'
|
|
132
133
|
const cwd = selectedDir || ''
|
|
133
134
|
const resolvedCredentialId = currentProvider?.requiresApiKey
|
|
134
135
|
? credentialId
|
|
@@ -173,14 +174,14 @@ export function NewSessionSheet() {
|
|
|
173
174
|
<BottomSheet open={open} onClose={onClose} wide>
|
|
174
175
|
{/* Header */}
|
|
175
176
|
<div className="mb-10">
|
|
176
|
-
<h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">New
|
|
177
|
-
<p className="text-[14px] text-text-3">Configure your AI
|
|
177
|
+
<h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">New Chat</h2>
|
|
178
|
+
<p className="text-[14px] text-text-3">Configure your AI chat</p>
|
|
178
179
|
</div>
|
|
179
180
|
|
|
180
181
|
{/* Name */}
|
|
181
182
|
<div className="mb-8">
|
|
182
183
|
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
|
|
183
|
-
|
|
184
|
+
Chat Name
|
|
184
185
|
</label>
|
|
185
186
|
<input
|
|
186
187
|
type="text"
|
|
@@ -269,16 +270,14 @@ export function NewSessionSheet() {
|
|
|
269
270
|
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
|
|
270
271
|
Model
|
|
271
272
|
</label>
|
|
272
|
-
<
|
|
273
|
+
<ModelCombobox
|
|
274
|
+
providerId={currentProvider.id}
|
|
273
275
|
value={model}
|
|
274
|
-
onChange={
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
<option key={m} value={m}>{m}</option>
|
|
280
|
-
))}
|
|
281
|
-
</select>
|
|
276
|
+
onChange={setModel}
|
|
277
|
+
models={currentProvider.models}
|
|
278
|
+
defaultModels={currentProvider.defaultModels}
|
|
279
|
+
className={`${inputClass} cursor-pointer`}
|
|
280
|
+
/>
|
|
282
281
|
</div>
|
|
283
282
|
)}
|
|
284
283
|
|
|
@@ -374,7 +373,7 @@ export function NewSessionSheet() {
|
|
|
374
373
|
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
375
374
|
Tools <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
|
|
376
375
|
</label>
|
|
377
|
-
<p className="text-[12px] text-text-3/60 mb-3">Allow this model to execute commands and access files in the
|
|
376
|
+
<p className="text-[12px] text-text-3/60 mb-3">Allow this model to execute commands and access files in the working directory.</p>
|
|
378
377
|
<div className="flex flex-wrap gap-2.5">
|
|
379
378
|
{([
|
|
380
379
|
{ id: 'shell' as SessionTool, label: 'Shell' },
|
|
@@ -470,7 +469,7 @@ export function NewSessionSheet() {
|
|
|
470
469
|
shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110"
|
|
471
470
|
style={{ fontFamily: 'inherit' }}
|
|
472
471
|
>
|
|
473
|
-
Create
|
|
472
|
+
Create Chat
|
|
474
473
|
</button>
|
|
475
474
|
</div>
|
|
476
475
|
</BottomSheet>
|
|
@@ -117,7 +117,7 @@ export function SessionCard({ session, active, onClick }: Props) {
|
|
|
117
117
|
onClick={handleDelete}
|
|
118
118
|
className="shrink-0 opacity-0 group-hover/card:opacity-100 transition-opacity duration-150
|
|
119
119
|
text-text-3 hover:text-red-400 p-0.5 -mr-1 cursor-pointer bg-transparent border-none"
|
|
120
|
-
title="Delete
|
|
120
|
+
title="Delete chat"
|
|
121
121
|
>
|
|
122
122
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
123
123
|
<line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
|
|
@@ -89,7 +89,7 @@ export function SessionList({ inSidebar, onSelect }: Props) {
|
|
|
89
89
|
<path d="M12 2L14.5 9.5L22 12L14.5 14.5L12 22L9.5 14.5L2 12L9.5 9.5L12 2Z" fill="currentColor" />
|
|
90
90
|
</svg>
|
|
91
91
|
</div>
|
|
92
|
-
<p className="font-display text-[15px] font-600 text-text-2">No
|
|
92
|
+
<p className="font-display text-[15px] font-600 text-text-2">No chats yet</p>
|
|
93
93
|
<p className="text-[13px] text-text-3/50">Create one to start chatting</p>
|
|
94
94
|
{!inSidebar && (
|
|
95
95
|
<button
|
|
@@ -99,7 +99,7 @@ export function SessionList({ inSidebar, onSelect }: Props) {
|
|
|
99
99
|
shadow-[0_4px_16px_rgba(99,102,241,0.2)]"
|
|
100
100
|
style={{ fontFamily: 'inherit' }}
|
|
101
101
|
>
|
|
102
|
-
+ New
|
|
102
|
+
+ New Chat
|
|
103
103
|
</button>
|
|
104
104
|
)}
|
|
105
105
|
</div>
|
|
@@ -124,12 +124,12 @@ export function SessionList({ inSidebar, onSelect }: Props) {
|
|
|
124
124
|
{filtered.length > 0 && (
|
|
125
125
|
<button
|
|
126
126
|
onClick={async () => {
|
|
127
|
-
if (!window.confirm(`Delete ${filtered.length}
|
|
127
|
+
if (!window.confirm(`Delete ${filtered.length} chat${filtered.length === 1 ? '' : 's'}?`)) return
|
|
128
128
|
await clearSessions(filtered.map((s) => s.id))
|
|
129
129
|
}}
|
|
130
130
|
className="ml-auto p-1.5 rounded-[8px] text-text-3/70 hover:text-red-400 hover:bg-red-400/[0.06]
|
|
131
131
|
cursor-pointer transition-all bg-transparent border-none"
|
|
132
|
-
title="Clear all
|
|
132
|
+
title="Clear all chats"
|
|
133
133
|
>
|
|
134
134
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
135
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" />
|
|
@@ -154,7 +154,7 @@ export function SessionList({ inSidebar, onSelect }: Props) {
|
|
|
154
154
|
<select
|
|
155
155
|
value={sortMode}
|
|
156
156
|
onChange={(e) => setSortMode(e.target.value as SortMode)}
|
|
157
|
-
aria-label="Sort
|
|
157
|
+
aria-label="Sort chats"
|
|
158
158
|
className="px-2 py-2 rounded-[12px] border border-white/[0.04] bg-surface text-text
|
|
159
159
|
text-[11px] outline-none cursor-pointer"
|
|
160
160
|
style={{ fontFamily: 'inherit' }}
|
|
@@ -176,7 +176,7 @@ export function SessionList({ inSidebar, onSelect }: Props) {
|
|
|
176
176
|
/>
|
|
177
177
|
<button
|
|
178
178
|
onClick={(e) => { e.stopPropagation(); togglePinSession(s.id) }}
|
|
179
|
-
aria-label={s.pinned ? 'Unpin
|
|
179
|
+
aria-label={s.pinned ? 'Unpin chat' : 'Pin chat'}
|
|
180
180
|
className={`absolute top-2 right-2 p-1 rounded-[6px] border-none cursor-pointer transition-all
|
|
181
181
|
${s.pinned
|
|
182
182
|
? 'text-amber-400 bg-amber-400/10 opacity-100'
|
|
@@ -193,7 +193,7 @@ export function SessionList({ inSidebar, onSelect }: Props) {
|
|
|
193
193
|
) : (
|
|
194
194
|
<div className="flex-1 flex flex-col items-center justify-center gap-3 text-text-3 p-8 text-center">
|
|
195
195
|
<p className="text-[13px] text-text-3/50">
|
|
196
|
-
No {typeFilter === 'orchestrated' ? 'AI' : typeFilter === 'active' ? 'active' : typeFilter}
|
|
196
|
+
No {typeFilter === 'orchestrated' ? 'AI' : typeFilter === 'active' ? 'active' : typeFilter} chats{search ? ` matching "${search}"` : ''}
|
|
197
197
|
</p>
|
|
198
198
|
</div>
|
|
199
199
|
)}
|