@swarmclawai/swarmclaw 0.7.8 → 0.8.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 +12 -15
- package/next.config.ts +13 -2
- package/package.json +4 -2
- package/src/app/api/agents/[id]/thread/route.ts +9 -0
- package/src/app/api/agents/route.ts +4 -0
- package/src/app/api/agents/thread-route.test.ts +133 -0
- package/src/app/api/approvals/route.test.ts +148 -0
- package/src/app/api/canvas/[sessionId]/route.ts +3 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -2
- package/src/app/api/chats/[id]/devserver/route.ts +48 -7
- package/src/app/api/chats/[id]/messages/route.ts +42 -18
- package/src/app/api/chats/[id]/route.ts +1 -1
- package/src/app/api/chats/[id]/stop/route.ts +5 -4
- package/src/app/api/chats/route.ts +22 -2
- package/src/app/api/clawhub/install/route.ts +28 -8
- package/src/app/api/connectors/[id]/route.ts +26 -1
- package/src/app/api/external-agents/route.test.ts +165 -0
- package/src/app/api/gateways/[id]/health/route.ts +27 -12
- package/src/app/api/gateways/[id]/route.ts +2 -0
- package/src/app/api/gateways/health-route.test.ts +135 -0
- package/src/app/api/gateways/route.ts +2 -0
- package/src/app/api/mcp-servers/route.test.ts +130 -0
- package/src/app/api/openclaw/deploy/route.ts +38 -5
- package/src/app/api/plugins/install/route.ts +46 -6
- package/src/app/api/plugins/marketplace/route.ts +48 -15
- package/src/app/api/preview-server/route.ts +26 -11
- package/src/app/api/schedules/[id]/run/route.ts +4 -0
- package/src/app/api/schedules/route.test.ts +86 -0
- package/src/app/api/schedules/route.ts +6 -1
- package/src/app/api/setup/check-provider/route.test.ts +19 -0
- package/src/app/api/setup/check-provider/route.ts +40 -10
- package/src/app/api/skills/[id]/route.ts +12 -0
- package/src/app/api/skills/import/route.ts +14 -12
- package/src/app/api/skills/route.ts +13 -1
- package/src/app/api/tasks/[id]/route.ts +10 -1
- package/src/app/api/tasks/import/github/route.test.ts +65 -0
- package/src/app/api/tasks/import/github/route.ts +337 -0
- package/src/app/api/wallets/[id]/approve/route.ts +17 -3
- package/src/app/api/wallets/[id]/route.ts +79 -33
- package/src/app/api/wallets/[id]/send/route.ts +19 -33
- package/src/app/api/wallets/route.ts +78 -61
- package/src/app/api/webhooks/[id]/route.ts +33 -6
- package/src/app/api/webhooks/route.test.ts +272 -0
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-card.tsx +9 -2
- package/src/components/agents/agent-chat-list.tsx +18 -2
- package/src/components/agents/agent-list.tsx +1 -0
- package/src/components/agents/agent-sheet.tsx +73 -24
- package/src/components/agents/inspector-panel.tsx +41 -0
- package/src/components/canvas/canvas-panel.tsx +236 -65
- package/src/components/chat/chat-card.tsx +36 -13
- package/src/components/chat/chat-header.tsx +44 -16
- package/src/components/chat/chat-list.tsx +28 -4
- package/src/components/chat/checkpoint-timeline.tsx +50 -34
- package/src/components/chat/message-bubble.tsx +208 -145
- package/src/components/chat/message-list.tsx +48 -19
- package/src/components/chatrooms/chatroom-message.tsx +2 -2
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
- package/src/components/connectors/connector-health.tsx +1 -1
- package/src/components/connectors/connector-list.tsx +7 -2
- package/src/components/connectors/connector-sheet.tsx +337 -148
- package/src/components/gateways/gateway-sheet.tsx +2 -2
- package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
- package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
- package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
- package/src/components/plugins/plugin-list.tsx +45 -9
- package/src/components/plugins/plugin-sheet.tsx +55 -7
- package/src/components/providers/provider-list.tsx +2 -1
- package/src/components/providers/provider-sheet.tsx +21 -2
- package/src/components/schedules/schedule-card.tsx +25 -1
- package/src/components/schedules/schedule-sheet.tsx +44 -2
- package/src/components/secrets/secret-sheet.tsx +21 -2
- package/src/components/shared/agent-switch-dialog.tsx +12 -1
- package/src/components/shared/bottom-sheet.tsx +13 -3
- package/src/components/shared/command-palette.tsx +8 -1
- package/src/components/shared/confirm-dialog.tsx +19 -4
- package/src/components/shared/connector-platform-icon.test.ts +28 -0
- package/src/components/shared/connector-platform-icon.tsx +39 -6
- package/src/components/shared/settings/plugin-manager.tsx +29 -6
- package/src/components/shared/settings/section-capability-policy.tsx +7 -3
- package/src/components/skills/skill-list.tsx +25 -0
- package/src/components/skills/skill-sheet.tsx +84 -12
- package/src/components/tasks/approvals-panel.tsx +191 -95
- package/src/components/tasks/task-board.tsx +273 -2
- package/src/components/tasks/task-card.tsx +38 -9
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
- package/src/components/wallets/wallet-panel.tsx +435 -90
- package/src/components/wallets/wallet-section.tsx +198 -48
- package/src/components/webhooks/webhook-sheet.tsx +22 -2
- package/src/lib/approval-display.ts +20 -0
- package/src/lib/canvas-content.ts +198 -0
- package/src/lib/chat-artifact-summary.ts +165 -0
- package/src/lib/chat-display.test.ts +91 -0
- package/src/lib/chat-display.ts +58 -0
- package/src/lib/chat-streaming-state.test.ts +47 -1
- package/src/lib/chat-streaming-state.ts +42 -0
- package/src/lib/ollama-model.ts +10 -0
- package/src/lib/openclaw-endpoint.test.ts +8 -0
- package/src/lib/openclaw-endpoint.ts +6 -1
- package/src/lib/plugin-install-cors.ts +46 -0
- package/src/lib/plugin-sources.test.ts +43 -0
- package/src/lib/plugin-sources.ts +77 -0
- package/src/lib/providers/ollama.ts +16 -6
- package/src/lib/providers/openclaw.test.ts +54 -0
- package/src/lib/providers/openclaw.ts +127 -11
- package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
- package/src/lib/schedule-dedupe.test.ts +66 -1
- package/src/lib/schedule-dedupe.ts +169 -12
- package/src/lib/schedule-origin.test.ts +20 -0
- package/src/lib/schedule-origin.ts +15 -0
- package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
- package/src/lib/server/agent-availability.ts +16 -0
- package/src/lib/server/agent-runtime-config.ts +12 -4
- package/src/lib/server/agent-thread-session.test.ts +51 -0
- package/src/lib/server/agent-thread-session.ts +7 -0
- package/src/lib/server/approval-match.ts +205 -0
- package/src/lib/server/approvals-auto-approve.test.ts +538 -1
- package/src/lib/server/approvals.ts +214 -1
- package/src/lib/server/assistant-control.test.ts +29 -0
- package/src/lib/server/assistant-control.ts +23 -0
- package/src/lib/server/build-llm.test.ts +79 -0
- package/src/lib/server/build-llm.ts +14 -4
- package/src/lib/server/canvas-content.test.ts +32 -0
- package/src/lib/server/canvas-content.ts +6 -0
- package/src/lib/server/capability-router.test.ts +11 -0
- package/src/lib/server/capability-router.ts +26 -1
- package/src/lib/server/chat-execution-advanced.test.ts +651 -0
- package/src/lib/server/chat-execution-disabled.test.ts +94 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
- package/src/lib/server/chat-execution.ts +353 -72
- package/src/lib/server/clawhub-client.test.ts +14 -8
- package/src/lib/server/connectors/manager.test.ts +1147 -0
- package/src/lib/server/connectors/manager.ts +362 -63
- package/src/lib/server/connectors/pairing.ts +26 -5
- package/src/lib/server/connectors/types.ts +2 -0
- package/src/lib/server/connectors/whatsapp.test.ts +134 -0
- package/src/lib/server/connectors/whatsapp.ts +271 -47
- package/src/lib/server/context-manager.ts +6 -1
- package/src/lib/server/daemon-state.ts +1 -1
- package/src/lib/server/data-dir.test.ts +37 -0
- package/src/lib/server/data-dir.ts +20 -1
- package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
- package/src/lib/server/devserver-launch.test.ts +60 -0
- package/src/lib/server/devserver-launch.ts +85 -0
- package/src/lib/server/elevenlabs.test.ts +189 -1
- package/src/lib/server/elevenlabs.ts +147 -43
- package/src/lib/server/ethereum.ts +590 -0
- package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
- package/src/lib/server/eval/agent-regression.test.ts +18 -1
- package/src/lib/server/eval/agent-regression.ts +383 -11
- package/src/lib/server/evm-swap.ts +475 -0
- package/src/lib/server/execution-log.ts +1 -0
- package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
- package/src/lib/server/heartbeat-service.ts +15 -10
- package/src/lib/server/heartbeat-wake.test.ts +112 -0
- package/src/lib/server/heartbeat-wake.ts +338 -57
- package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
- package/src/lib/server/mcp-client.test.ts +16 -0
- package/src/lib/server/mcp-client.ts +25 -0
- package/src/lib/server/memory-integration.test.ts +719 -0
- package/src/lib/server/memory-policy.test.ts +43 -0
- package/src/lib/server/memory-policy.ts +132 -0
- package/src/lib/server/memory-tiers.test.ts +60 -0
- package/src/lib/server/memory-tiers.ts +16 -0
- package/src/lib/server/ollama-runtime.ts +58 -0
- package/src/lib/server/openclaw-deploy.test.ts +109 -1
- package/src/lib/server/openclaw-deploy.ts +557 -81
- package/src/lib/server/openclaw-gateway.test.ts +131 -0
- package/src/lib/server/openclaw-gateway.ts +10 -4
- package/src/lib/server/openclaw-health.test.ts +35 -0
- package/src/lib/server/openclaw-health.ts +215 -47
- package/src/lib/server/orchestrator-lg.ts +2 -2
- package/src/lib/server/plugins-advanced.test.ts +351 -0
- package/src/lib/server/plugins.ts +205 -5
- package/src/lib/server/queue-advanced.test.ts +528 -0
- package/src/lib/server/queue-followups.test.ts +262 -0
- package/src/lib/server/queue-reconcile.test.ts +128 -0
- package/src/lib/server/queue.ts +293 -61
- package/src/lib/server/scheduler.ts +29 -1
- package/src/lib/server/session-note.test.ts +36 -0
- package/src/lib/server/session-note.ts +42 -0
- package/src/lib/server/session-run-manager.ts +52 -4
- package/src/lib/server/session-tools/canvas.ts +14 -12
- package/src/lib/server/session-tools/connector.test.ts +138 -0
- package/src/lib/server/session-tools/connector.ts +348 -61
- package/src/lib/server/session-tools/context.ts +12 -3
- package/src/lib/server/session-tools/crud.ts +221 -10
- package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
- package/src/lib/server/session-tools/delegate.ts +64 -8
- package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
- package/src/lib/server/session-tools/discovery.ts +80 -12
- package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
- package/src/lib/server/session-tools/file.ts +43 -4
- package/src/lib/server/session-tools/human-loop.ts +35 -5
- package/src/lib/server/session-tools/index.ts +44 -9
- package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
- package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
- package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
- package/src/lib/server/session-tools/memory.test.ts +93 -0
- package/src/lib/server/session-tools/memory.ts +546 -79
- package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
- package/src/lib/server/session-tools/plugin-creator.ts +57 -1
- package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
- package/src/lib/server/session-tools/schedule.ts +6 -1
- package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
- package/src/lib/server/session-tools/shell.ts +22 -3
- package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
- package/src/lib/server/session-tools/wallet.ts +1374 -139
- package/src/lib/server/session-tools/web-inputs.test.ts +162 -1
- package/src/lib/server/session-tools/web.ts +468 -64
- package/src/lib/server/skill-discovery.ts +128 -0
- package/src/lib/server/skill-eligibility.test.ts +84 -0
- package/src/lib/server/skill-eligibility.ts +95 -0
- package/src/lib/server/skill-prompt-budget.test.ts +102 -0
- package/src/lib/server/skill-prompt-budget.ts +125 -0
- package/src/lib/server/skills-normalize.test.ts +54 -0
- package/src/lib/server/skills-normalize.ts +372 -26
- package/src/lib/server/solana.ts +214 -29
- package/src/lib/server/storage.ts +65 -36
- package/src/lib/server/stream-agent-chat.test.ts +419 -9
- package/src/lib/server/stream-agent-chat.ts +887 -83
- package/src/lib/server/system-events.ts +1 -1
- package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
- package/src/lib/server/tool-loop-detection.test.ts +105 -0
- package/src/lib/server/tool-loop-detection.ts +260 -0
- package/src/lib/server/tool-planning.ts +4 -2
- package/src/lib/server/wallet-execution.test.ts +198 -0
- package/src/lib/server/wallet-portfolio.test.ts +98 -0
- package/src/lib/server/wallet-portfolio.ts +724 -0
- package/src/lib/server/wallet-service.test.ts +57 -0
- package/src/lib/server/wallet-service.ts +213 -0
- package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
- package/src/lib/server/watch-jobs.ts +17 -2
- package/src/lib/server/workspace-context.ts +111 -0
- package/src/lib/skill-save-payload.test.ts +39 -0
- package/src/lib/skill-save-payload.ts +37 -0
- package/src/lib/tasks.ts +28 -0
- package/src/lib/tool-event-summary.test.ts +30 -0
- package/src/lib/tool-event-summary.ts +37 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/wallet-transactions.test.ts +75 -0
- package/src/lib/wallet-transactions.ts +43 -0
- package/src/lib/wallet.test.ts +17 -0
- package/src/lib/wallet.ts +183 -0
- package/src/proxy.test.ts +31 -0
- package/src/proxy.ts +34 -2
- package/src/stores/use-chat-store.ts +15 -1
- package/src/types/index.ts +210 -14
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
import { useEffect, useState, useCallback, useMemo } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { api } from '@/lib/api-client'
|
|
6
|
+
import { getPluginSourceLabel } from '@/lib/plugin-sources'
|
|
6
7
|
import { toast } from 'sonner'
|
|
7
8
|
import type { Agent, MarketplacePlugin, PluginMeta } from '@/types'
|
|
8
9
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
9
10
|
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
10
11
|
|
|
11
12
|
type InstalledTab = 'core' | 'extensions'
|
|
12
|
-
type TopTab = InstalledTab | '
|
|
13
|
+
type TopTab = InstalledTab | 'marketplace'
|
|
13
14
|
|
|
14
15
|
export function PluginList({ inSidebar }: { inSidebar?: boolean }) {
|
|
15
16
|
const plugins = useAppStore((s) => s.plugins)
|
|
@@ -56,7 +57,7 @@ export function PluginList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
56
57
|
}, [])
|
|
57
58
|
|
|
58
59
|
useEffect(() => {
|
|
59
|
-
if (inSidebar || tab !== '
|
|
60
|
+
if (inSidebar || tab !== 'marketplace') return
|
|
60
61
|
const timer = setTimeout(() => { void loadMarketplace() }, 0)
|
|
61
62
|
return () => clearTimeout(timer)
|
|
62
63
|
}, [tab, inSidebar, loadMarketplace])
|
|
@@ -120,7 +121,13 @@ export function PluginList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
120
121
|
const toastId = toast.loading(`Installing ${p.name}...`)
|
|
121
122
|
try {
|
|
122
123
|
const safeFilename = `${p.id.replace(/[^a-zA-Z0-9.-]/g, '_')}.js`
|
|
123
|
-
await api('POST', '/plugins/install', {
|
|
124
|
+
await api('POST', '/plugins/install', {
|
|
125
|
+
url: p.url,
|
|
126
|
+
filename: safeFilename,
|
|
127
|
+
installMethod: 'marketplace',
|
|
128
|
+
sourceLabel: p.source,
|
|
129
|
+
installSource: p.catalogSource || p.source,
|
|
130
|
+
})
|
|
124
131
|
await loadPlugins()
|
|
125
132
|
toast.success(`Installed ${p.name}`, { id: toastId })
|
|
126
133
|
} catch (err: unknown) {
|
|
@@ -181,8 +188,8 @@ export function PluginList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
181
188
|
<TabButton active={tab === 'extensions'} onClick={() => setTab('extensions')} count={extensionPlugins.length}>
|
|
182
189
|
Extensions
|
|
183
190
|
</TabButton>
|
|
184
|
-
<TabButton active={tab === '
|
|
185
|
-
|
|
191
|
+
<TabButton active={tab === 'marketplace'} onClick={() => setTab('marketplace')}>
|
|
192
|
+
Marketplace
|
|
186
193
|
</TabButton>
|
|
187
194
|
</div>
|
|
188
195
|
|
|
@@ -214,17 +221,17 @@ export function PluginList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
214
221
|
emptyMessage={search ? 'No extensions match your search' : 'No extensions installed'}
|
|
215
222
|
emptyAction={!search ? (
|
|
216
223
|
<button
|
|
217
|
-
onClick={() => setTab('
|
|
224
|
+
onClick={() => setTab('marketplace')}
|
|
218
225
|
className="mt-3 px-4 py-2 rounded-[10px] bg-transparent text-accent-bright text-[12px] font-600 cursor-pointer border border-accent-bright/20 hover:bg-accent-soft transition-all"
|
|
219
226
|
style={{ fontFamily: 'inherit' }}
|
|
220
227
|
>
|
|
221
|
-
Browse
|
|
228
|
+
Browse Marketplace
|
|
222
229
|
</button>
|
|
223
230
|
) : undefined}
|
|
224
231
|
/>
|
|
225
232
|
)}
|
|
226
233
|
|
|
227
|
-
{tab === '
|
|
234
|
+
{tab === 'marketplace' && (
|
|
228
235
|
<MarketplaceTab
|
|
229
236
|
marketplace={marketplace}
|
|
230
237
|
loading={mpLoading}
|
|
@@ -447,6 +454,12 @@ function PluginCard({ plugin, allowDelete, agents, onEdit, onToggle, onDelete, o
|
|
|
447
454
|
{badge}
|
|
448
455
|
</span>
|
|
449
456
|
))}
|
|
457
|
+
{plugin.sourceLabel && (
|
|
458
|
+
<SourceChip label={getPluginSourceLabel(plugin.sourceLabel)} tone="publisher" />
|
|
459
|
+
)}
|
|
460
|
+
{plugin.installSource && plugin.installSource !== plugin.sourceLabel && (
|
|
461
|
+
<SourceChip label={`via ${getPluginSourceLabel(plugin.installSource)}`} tone="catalog" />
|
|
462
|
+
)}
|
|
450
463
|
{plugin.hasDependencyManifest && (
|
|
451
464
|
<span className={`text-[10px] font-700 px-1.5 py-0.5 rounded-full ${
|
|
452
465
|
plugin.dependencyInstallStatus === 'installed'
|
|
@@ -550,7 +563,14 @@ function MarketplaceTab({ marketplace, loading, installing, installedFilenames,
|
|
|
550
563
|
const q = search.toLowerCase()
|
|
551
564
|
const filtered = marketplace
|
|
552
565
|
.filter((p) => {
|
|
553
|
-
|
|
566
|
+
const sourceTerms = [getPluginSourceLabel(p.source).toLowerCase(), getPluginSourceLabel(p.catalogSource).toLowerCase()]
|
|
567
|
+
if (
|
|
568
|
+
q
|
|
569
|
+
&& !p.name.toLowerCase().includes(q)
|
|
570
|
+
&& !p.description.toLowerCase().includes(q)
|
|
571
|
+
&& !(p.tags ?? []).some((t) => t.toLowerCase().includes(q))
|
|
572
|
+
&& !sourceTerms.some((term) => term.includes(q))
|
|
573
|
+
) return false
|
|
554
574
|
if (activeTag && !(p.tags ?? []).includes(activeTag)) return false
|
|
555
575
|
return true
|
|
556
576
|
})
|
|
@@ -606,6 +626,12 @@ function MarketplaceTab({ marketplace, loading, installing, installedFilenames,
|
|
|
606
626
|
<span className="text-[10px] font-mono text-text-3/70">v{p.version}</span>
|
|
607
627
|
{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>}
|
|
608
628
|
</div>
|
|
629
|
+
<div className="flex items-center gap-1.5 mt-2 flex-wrap">
|
|
630
|
+
{p.source && <SourceChip label={getPluginSourceLabel(p.source)} tone="publisher" />}
|
|
631
|
+
{p.catalogSource && p.catalogSource !== p.source && (
|
|
632
|
+
<SourceChip label={`via ${getPluginSourceLabel(p.catalogSource)}`} tone="catalog" />
|
|
633
|
+
)}
|
|
634
|
+
</div>
|
|
609
635
|
<div className="text-[11px] text-text-3/60 mt-1 line-clamp-2">{p.description}</div>
|
|
610
636
|
<div className="flex items-center gap-2 mt-2">
|
|
611
637
|
<span className="text-[10px] text-text-3/70">by {p.author}</span>
|
|
@@ -645,3 +671,13 @@ function MarketplaceTab({ marketplace, loading, installing, installedFilenames,
|
|
|
645
671
|
</div>
|
|
646
672
|
)
|
|
647
673
|
}
|
|
674
|
+
|
|
675
|
+
function SourceChip({ label, tone }: { label: string; tone: 'publisher' | 'catalog' }) {
|
|
676
|
+
return (
|
|
677
|
+
<span className={tone === 'publisher'
|
|
678
|
+
? 'text-[10px] font-700 px-1.5 py-0.5 rounded-full bg-sky-500/10 text-sky-300'
|
|
679
|
+
: 'text-[10px] font-700 px-1.5 py-0.5 rounded-full bg-white/[0.05] text-text-3/75'}>
|
|
680
|
+
{label}
|
|
681
|
+
</span>
|
|
682
|
+
)
|
|
683
|
+
}
|
|
@@ -5,6 +5,7 @@ import { useAppStore } from '@/stores/use-app-store'
|
|
|
5
5
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
6
6
|
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
7
7
|
import { api } from '@/lib/api-client'
|
|
8
|
+
import { getPluginSourceLabel } from '@/lib/plugin-sources'
|
|
8
9
|
import { toast } from 'sonner'
|
|
9
10
|
import type { PluginMeta, PluginSettingsField, MarketplacePlugin } from '@/types'
|
|
10
11
|
|
|
@@ -176,7 +177,13 @@ export function PluginSheet() {
|
|
|
176
177
|
const toastId = toast.loading(`Installing ${p.name}...`)
|
|
177
178
|
try {
|
|
178
179
|
const safeFilename = `${p.id.replace(/[^a-zA-Z0-9.-]/g, '_')}.js`
|
|
179
|
-
await api('POST', '/plugins/install', {
|
|
180
|
+
await api('POST', '/plugins/install', {
|
|
181
|
+
url: p.url,
|
|
182
|
+
filename: safeFilename,
|
|
183
|
+
installMethod: 'marketplace',
|
|
184
|
+
sourceLabel: p.source,
|
|
185
|
+
installSource: p.catalogSource || p.source,
|
|
186
|
+
})
|
|
180
187
|
await loadPlugins()
|
|
181
188
|
toast.success(`Installed ${p.name}`, { id: toastId })
|
|
182
189
|
} catch (err: unknown) {
|
|
@@ -231,19 +238,31 @@ export function PluginSheet() {
|
|
|
231
238
|
</span>
|
|
232
239
|
</div>
|
|
233
240
|
|
|
234
|
-
<div className="grid grid-cols-
|
|
241
|
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2 mt-3">
|
|
235
242
|
<div className="rounded-[10px] bg-bg/50 border border-white/[0.05] px-2.5 py-2">
|
|
236
|
-
<div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60 mb-0.5">
|
|
243
|
+
<div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60 mb-0.5">Type</div>
|
|
237
244
|
<div className="text-[11px] text-text-2">
|
|
238
|
-
{editing.isBuiltin
|
|
245
|
+
{editing.isBuiltin
|
|
246
|
+
? 'Core Platform'
|
|
247
|
+
: editing.source === 'marketplace'
|
|
248
|
+
? 'Marketplace Extension'
|
|
249
|
+
: editing.source === 'manual'
|
|
250
|
+
? 'Manual URL Extension'
|
|
251
|
+
: 'Local Extension'}
|
|
239
252
|
</div>
|
|
240
253
|
</div>
|
|
241
254
|
<div className="rounded-[10px] bg-bg/50 border border-white/[0.05] px-2.5 py-2">
|
|
242
|
-
<div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60 mb-0.5">
|
|
243
|
-
<div className="text-[11px] text-text-2">{editing.
|
|
255
|
+
<div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60 mb-0.5">Publisher</div>
|
|
256
|
+
<div className="text-[11px] text-text-2">{getPluginSourceLabel(editing.sourceLabel)}</div>
|
|
257
|
+
</div>
|
|
258
|
+
<div className="rounded-[10px] bg-bg/50 border border-white/[0.05] px-2.5 py-2">
|
|
259
|
+
<div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60 mb-0.5">Installed Via</div>
|
|
260
|
+
<div className="text-[11px] text-text-2">{getPluginSourceLabel(editing.installSource || editing.sourceLabel)}</div>
|
|
244
261
|
</div>
|
|
245
262
|
</div>
|
|
246
263
|
|
|
264
|
+
<div className="text-[11px] text-text-3/60 mt-2">{editing.author || 'Unknown author'}</div>
|
|
265
|
+
|
|
247
266
|
<div className="text-[11px] font-mono text-text-3/60 mt-3 break-all">{editing.filename}</div>
|
|
248
267
|
<div className="flex items-center gap-1.5 mt-2 flex-wrap">
|
|
249
268
|
{pluginCapabilityBadges(editing).length > 0 ? (
|
|
@@ -255,6 +274,16 @@ export function PluginSheet() {
|
|
|
255
274
|
) : (
|
|
256
275
|
<span className="text-[10px] text-text-3/50">No declared tools/hooks metadata</span>
|
|
257
276
|
)}
|
|
277
|
+
{editing.sourceLabel && (
|
|
278
|
+
<span className="text-[10px] font-700 px-1.5 py-0.5 rounded-full bg-sky-500/10 text-sky-300">
|
|
279
|
+
{getPluginSourceLabel(editing.sourceLabel)}
|
|
280
|
+
</span>
|
|
281
|
+
)}
|
|
282
|
+
{editing.installSource && editing.installSource !== editing.sourceLabel && (
|
|
283
|
+
<span className="text-[10px] font-700 px-1.5 py-0.5 rounded-full bg-white/[0.05] text-text-3/75">
|
|
284
|
+
via {getPluginSourceLabel(editing.installSource)}
|
|
285
|
+
</span>
|
|
286
|
+
)}
|
|
258
287
|
</div>
|
|
259
288
|
|
|
260
289
|
{editing.autoDisabled && (
|
|
@@ -392,7 +421,14 @@ export function PluginSheet() {
|
|
|
392
421
|
const q = search.toLowerCase()
|
|
393
422
|
const filtered = marketplace
|
|
394
423
|
.filter((p) => {
|
|
395
|
-
|
|
424
|
+
const sourceTerms = [getPluginSourceLabel(p.source).toLowerCase(), getPluginSourceLabel(p.catalogSource).toLowerCase()]
|
|
425
|
+
if (
|
|
426
|
+
q
|
|
427
|
+
&& !p.name.toLowerCase().includes(q)
|
|
428
|
+
&& !p.description.toLowerCase().includes(q)
|
|
429
|
+
&& !(p.tags ?? []).some((t) => t.toLowerCase().includes(q))
|
|
430
|
+
&& !sourceTerms.some((term) => term.includes(q))
|
|
431
|
+
) return false
|
|
396
432
|
if (activeTag && !(p.tags ?? []).includes(activeTag)) return false
|
|
397
433
|
return true
|
|
398
434
|
})
|
|
@@ -458,6 +494,18 @@ export function PluginSheet() {
|
|
|
458
494
|
<span className="text-[10px] font-mono text-text-3/70">v{p.version}</span>
|
|
459
495
|
{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>}
|
|
460
496
|
</div>
|
|
497
|
+
<div className="flex items-center gap-1.5 mt-2 flex-wrap">
|
|
498
|
+
{p.source && (
|
|
499
|
+
<span className="text-[10px] font-700 px-1.5 py-0.5 rounded-full bg-sky-500/10 text-sky-300">
|
|
500
|
+
{getPluginSourceLabel(p.source)}
|
|
501
|
+
</span>
|
|
502
|
+
)}
|
|
503
|
+
{p.catalogSource && p.catalogSource !== p.source && (
|
|
504
|
+
<span className="text-[10px] font-700 px-1.5 py-0.5 rounded-full bg-white/[0.05] text-text-3/75">
|
|
505
|
+
via {getPluginSourceLabel(p.catalogSource)}
|
|
506
|
+
</span>
|
|
507
|
+
)}
|
|
508
|
+
</div>
|
|
461
509
|
<div className="text-[11px] text-text-3/60 mt-1">{p.description}</div>
|
|
462
510
|
<div className="flex items-center gap-2 mt-2">
|
|
463
511
|
<span className="text-[10px] text-text-3/70">by {p.author}</span>
|
|
@@ -125,6 +125,7 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
125
125
|
ok: boolean
|
|
126
126
|
verify?: {
|
|
127
127
|
ok: boolean
|
|
128
|
+
message?: string
|
|
128
129
|
error?: string
|
|
129
130
|
hint?: string
|
|
130
131
|
models?: string[]
|
|
@@ -150,7 +151,7 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
150
151
|
lastVerifiedOk: verify.verify ? verifiedOk : (existing?.deployment?.lastVerifiedOk ?? null),
|
|
151
152
|
lastVerifiedMessage: verify.verify
|
|
152
153
|
? (verifiedOk
|
|
153
|
-
? `Verified during save with ${verify.verify.models?.length || 0} model${(verify.verify.models?.length || 0) === 1 ? '' : 's'}.`
|
|
154
|
+
? (verify.verify.message || `Verified during save with ${verify.verify.models?.length || 0} model${(verify.verify.models?.length || 0) === 1 ? '' : 's'}.`)
|
|
154
155
|
: (verify.verify.error || verify.verify.hint || 'Verification failed.'))
|
|
155
156
|
: (existing?.deployment?.lastVerifiedMessage || null),
|
|
156
157
|
},
|
|
@@ -6,6 +6,7 @@ import { createProviderConfig, updateProviderConfig, deleteProviderConfig } from
|
|
|
6
6
|
import { api } from '@/lib/api-client'
|
|
7
7
|
import { fetchProviderModelDiscovery } from '@/lib/provider-model-discovery-client'
|
|
8
8
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
9
|
+
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
9
10
|
import { toast } from 'sonner'
|
|
10
11
|
|
|
11
12
|
export function ProviderSheet() {
|
|
@@ -40,6 +41,8 @@ export function ProviderSheet() {
|
|
|
40
41
|
const [liveLoading, setLiveLoading] = useState(false)
|
|
41
42
|
const [liveMessage, setLiveMessage] = useState('')
|
|
42
43
|
const [liveCached, setLiveCached] = useState(false)
|
|
44
|
+
const [confirmDelete, setConfirmDelete] = useState(false)
|
|
45
|
+
const [deleting, setDeleting] = useState(false)
|
|
43
46
|
|
|
44
47
|
// Find editing provider in custom configs OR built-in list
|
|
45
48
|
const editingCustom = editingId ? providerConfigs.find((c) => c.id === editingId) : null
|
|
@@ -122,6 +125,8 @@ export function ProviderSheet() {
|
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
const onClose = () => {
|
|
128
|
+
setConfirmDelete(false)
|
|
129
|
+
setDeleting(false)
|
|
125
130
|
setOpen(false)
|
|
126
131
|
setEditingId(null)
|
|
127
132
|
}
|
|
@@ -162,14 +167,17 @@ export function ProviderSheet() {
|
|
|
162
167
|
|
|
163
168
|
const handleDelete = async () => {
|
|
164
169
|
if (editingCustom) {
|
|
165
|
-
|
|
170
|
+
setDeleting(true)
|
|
166
171
|
try {
|
|
167
172
|
await deleteProviderConfig(editingCustom.id)
|
|
168
173
|
toast.success('Provider deleted')
|
|
169
174
|
await loadProviderConfigs()
|
|
175
|
+
setConfirmDelete(false)
|
|
170
176
|
onClose()
|
|
171
177
|
} catch (err: unknown) {
|
|
172
178
|
toast.error(err instanceof Error ? err.message : 'Failed to delete provider')
|
|
179
|
+
} finally {
|
|
180
|
+
setDeleting(false)
|
|
173
181
|
}
|
|
174
182
|
}
|
|
175
183
|
}
|
|
@@ -493,7 +501,7 @@ export function ProviderSheet() {
|
|
|
493
501
|
|
|
494
502
|
<div className="flex gap-3 pt-2 border-t border-white/[0.04]">
|
|
495
503
|
{editingCustom && (
|
|
496
|
-
<button onClick={
|
|
504
|
+
<button onClick={() => setConfirmDelete(true)} className="py-3.5 px-6 rounded-[14px] border border-red-500/20 bg-transparent text-red-400 text-[15px] font-600 cursor-pointer hover:bg-red-500/10 transition-all" style={{ fontFamily: 'inherit' }}>
|
|
497
505
|
Delete
|
|
498
506
|
</button>
|
|
499
507
|
)}
|
|
@@ -520,6 +528,17 @@ export function ProviderSheet() {
|
|
|
520
528
|
</button>
|
|
521
529
|
)}
|
|
522
530
|
</div>
|
|
531
|
+
<ConfirmDialog
|
|
532
|
+
open={confirmDelete}
|
|
533
|
+
title="Delete Provider?"
|
|
534
|
+
message={editingCustom ? `Delete custom provider "${editingCustom.name}"?` : 'Delete this provider?'}
|
|
535
|
+
confirmLabel={deleting ? 'Deleting...' : 'Delete'}
|
|
536
|
+
confirmDisabled={deleting}
|
|
537
|
+
cancelDisabled={deleting}
|
|
538
|
+
danger
|
|
539
|
+
onConfirm={() => { void handleDelete() }}
|
|
540
|
+
onCancel={() => { if (!deleting) setConfirmDelete(false) }}
|
|
541
|
+
/>
|
|
523
542
|
</BottomSheet>
|
|
524
543
|
)
|
|
525
544
|
}
|
|
@@ -4,6 +4,8 @@ import type { Schedule } from '@/types'
|
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { api } from '@/lib/api-client'
|
|
6
6
|
import { cronToHuman } from '@/lib/cron-human'
|
|
7
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
8
|
+
import { isUserCreatedSchedule } from '@/lib/schedule-origin'
|
|
7
9
|
|
|
8
10
|
const STATUS_COLORS: Record<string, string> = {
|
|
9
11
|
active: 'text-emerald-400 bg-emerald-400/[0.08]',
|
|
@@ -55,6 +57,7 @@ export function ScheduleCard({ schedule, inSidebar, index = 0 }: Props) {
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
const agent = agents[schedule.agentId]
|
|
60
|
+
const creatorAgent = schedule.createdByAgentId ? agents[schedule.createdByAgentId] : null
|
|
58
61
|
const statusClass = STATUS_COLORS[schedule.status] || STATUS_COLORS.paused
|
|
59
62
|
const canToggle = schedule.status === 'active' || schedule.status === 'paused'
|
|
60
63
|
|
|
@@ -101,7 +104,7 @@ export function ScheduleCard({ schedule, inSidebar, index = 0 }: Props) {
|
|
|
101
104
|
</div>
|
|
102
105
|
</div>
|
|
103
106
|
<div className="text-[12px] text-text-3/70 mt-1.5 truncate">
|
|
104
|
-
{agent?.name || 'Unknown agent'} · {schedule.scheduleType}
|
|
107
|
+
Runs on {agent?.name || 'Unknown agent'} · {schedule.scheduleType}
|
|
105
108
|
{!inSidebar && schedule.scheduleType === 'cron' && schedule.cron && (
|
|
106
109
|
<span className="text-text-3/50 ml-1" title={schedule.cron}>({cronToHuman(schedule.cron)})</span>
|
|
107
110
|
)}
|
|
@@ -113,6 +116,27 @@ export function ScheduleCard({ schedule, inSidebar, index = 0 }: Props) {
|
|
|
113
116
|
</span>
|
|
114
117
|
)}
|
|
115
118
|
</div>
|
|
119
|
+
<div className="flex flex-wrap items-center gap-1.5 mt-2">
|
|
120
|
+
{creatorAgent ? (
|
|
121
|
+
<span className="inline-flex max-w-full items-center gap-1.5 rounded-[7px] bg-white/[0.05] px-2 py-1 text-[10px] font-600 text-text-2">
|
|
122
|
+
<AgentAvatar
|
|
123
|
+
seed={creatorAgent.avatarSeed}
|
|
124
|
+
avatarUrl={creatorAgent.avatarUrl}
|
|
125
|
+
name={creatorAgent.name}
|
|
126
|
+
size={14}
|
|
127
|
+
/>
|
|
128
|
+
<span className="truncate">Created by {creatorAgent.name}</span>
|
|
129
|
+
</span>
|
|
130
|
+
) : (
|
|
131
|
+
<span className="inline-flex max-w-full items-center gap-1.5 rounded-[7px] bg-white/[0.04] px-2 py-1 text-[10px] font-600 text-text-3">
|
|
132
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
133
|
+
<circle cx="12" cy="8" r="4" />
|
|
134
|
+
<path d="M4 20c1.5-3.5 4.6-5 8-5s6.5 1.5 8 5" />
|
|
135
|
+
</svg>
|
|
136
|
+
<span className="truncate">{isUserCreatedSchedule(schedule) ? 'Created manually' : 'Creator unknown'}</span>
|
|
137
|
+
</span>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
116
140
|
<div className="text-[11px] text-text-3/60 mt-1">
|
|
117
141
|
Next: {formatNext(schedule.nextRunAt)}
|
|
118
142
|
</div>
|
|
@@ -5,12 +5,15 @@ import { useAppStore } from '@/stores/use-app-store'
|
|
|
5
5
|
import { createSchedule, updateSchedule, deleteSchedule } from '@/lib/schedules'
|
|
6
6
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
7
7
|
import { AgentPickerList } from '@/components/shared/agent-picker-list'
|
|
8
|
+
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
8
9
|
import { inputClass } from '@/components/shared/form-styles'
|
|
10
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
9
11
|
import type { ScheduleType, ScheduleStatus } from '@/types'
|
|
10
12
|
import cronstrue from 'cronstrue'
|
|
11
13
|
import { SectionLabel } from '@/components/shared/section-label'
|
|
12
14
|
import { SCHEDULE_TEMPLATES, type ScheduleTemplate } from '@/lib/schedule-templates'
|
|
13
15
|
import { HintTip } from '@/components/shared/hint-tip'
|
|
16
|
+
import { isUserCreatedSchedule } from '@/lib/schedule-origin'
|
|
14
17
|
import { toast } from 'sonner'
|
|
15
18
|
import {
|
|
16
19
|
Newspaper, BarChart3, HeartPulse, PenLine, Trash2,
|
|
@@ -102,6 +105,8 @@ export function ScheduleSheet() {
|
|
|
102
105
|
const [intervalMs, setIntervalMs] = useState(3600000)
|
|
103
106
|
const [status, setStatus] = useState<ScheduleStatus>('active')
|
|
104
107
|
const [customCron, setCustomCron] = useState(false)
|
|
108
|
+
const [confirmDelete, setConfirmDelete] = useState(false)
|
|
109
|
+
const [deleting, setDeleting] = useState(false)
|
|
105
110
|
|
|
106
111
|
const editing = editingId ? schedules[editingId] : null
|
|
107
112
|
const isCreating = !editing
|
|
@@ -163,6 +168,8 @@ export function ScheduleSheet() {
|
|
|
163
168
|
}, [cron])
|
|
164
169
|
|
|
165
170
|
const onClose = () => {
|
|
171
|
+
setConfirmDelete(false)
|
|
172
|
+
setDeleting(false)
|
|
166
173
|
setOpen(false)
|
|
167
174
|
setEditingId(null)
|
|
168
175
|
}
|
|
@@ -195,14 +202,17 @@ export function ScheduleSheet() {
|
|
|
195
202
|
|
|
196
203
|
const handleDelete = async () => {
|
|
197
204
|
if (!editing) return
|
|
198
|
-
|
|
205
|
+
setDeleting(true)
|
|
199
206
|
try {
|
|
200
207
|
await deleteSchedule(editing.id)
|
|
201
208
|
toast.success('Schedule deleted')
|
|
202
209
|
await loadSchedules()
|
|
210
|
+
setConfirmDelete(false)
|
|
203
211
|
onClose()
|
|
204
212
|
} catch (err: unknown) {
|
|
205
213
|
toast.error(err instanceof Error ? err.message : 'Failed to delete schedule')
|
|
214
|
+
} finally {
|
|
215
|
+
setDeleting(false)
|
|
206
216
|
}
|
|
207
217
|
}
|
|
208
218
|
|
|
@@ -211,6 +221,7 @@ export function ScheduleSheet() {
|
|
|
211
221
|
const step1Valid = scheduleType === 'cron' ? cron.trim().length > 0 : intervalMs > 0
|
|
212
222
|
|
|
213
223
|
const selectedAgent = agentId ? agents[agentId] : null
|
|
224
|
+
const creatorAgent = editing?.createdByAgentId ? agents[editing.createdByAgentId] : null
|
|
214
225
|
|
|
215
226
|
return (
|
|
216
227
|
<BottomSheet open={open} onClose={onClose} wide>
|
|
@@ -466,6 +477,26 @@ export function ScheduleSheet() {
|
|
|
466
477
|
<span className="text-[11px] text-text-3/50 uppercase tracking-wider font-600">Agent</span>
|
|
467
478
|
<div className="text-[14px] text-text font-600 mt-0.5">{selectedAgent?.name || agentId}</div>
|
|
468
479
|
</div>
|
|
480
|
+
{editing && (
|
|
481
|
+
<div>
|
|
482
|
+
<span className="text-[11px] text-text-3/50 uppercase tracking-wider font-600">Created By</span>
|
|
483
|
+
{creatorAgent ? (
|
|
484
|
+
<div className="mt-1 inline-flex items-center gap-2 rounded-[10px] bg-white/[0.04] px-3 py-2 text-[13px] text-text-2">
|
|
485
|
+
<AgentAvatar
|
|
486
|
+
seed={creatorAgent.avatarSeed}
|
|
487
|
+
avatarUrl={creatorAgent.avatarUrl}
|
|
488
|
+
name={creatorAgent.name}
|
|
489
|
+
size={18}
|
|
490
|
+
/>
|
|
491
|
+
<span>{creatorAgent.name}</span>
|
|
492
|
+
</div>
|
|
493
|
+
) : (
|
|
494
|
+
<div className="text-[13px] text-text-2 mt-0.5">
|
|
495
|
+
{isUserCreatedSchedule(editing) ? 'Manual / user-created' : 'Unknown'}
|
|
496
|
+
</div>
|
|
497
|
+
)}
|
|
498
|
+
</div>
|
|
499
|
+
)}
|
|
469
500
|
<div>
|
|
470
501
|
<span className="text-[11px] text-text-3/50 uppercase tracking-wider font-600">Task</span>
|
|
471
502
|
<div className="text-[13px] text-text-2 mt-0.5 whitespace-pre-wrap">{taskPrompt}</div>
|
|
@@ -497,7 +528,7 @@ export function ScheduleSheet() {
|
|
|
497
528
|
{/* Footer */}
|
|
498
529
|
<div className="flex gap-3 pt-2 border-t border-white/[0.04]">
|
|
499
530
|
{editing && step === 0 && (
|
|
500
|
-
<button onClick={
|
|
531
|
+
<button onClick={() => setConfirmDelete(true)} className="py-3.5 px-6 rounded-[14px] border border-red-500/20 bg-transparent text-red-400 text-[15px] font-600 cursor-pointer hover:bg-red-500/10 transition-all" style={{ fontFamily: 'inherit' }}>
|
|
501
532
|
Delete
|
|
502
533
|
</button>
|
|
503
534
|
)}
|
|
@@ -547,6 +578,17 @@ export function ScheduleSheet() {
|
|
|
547
578
|
</button>
|
|
548
579
|
)}
|
|
549
580
|
</div>
|
|
581
|
+
<ConfirmDialog
|
|
582
|
+
open={confirmDelete}
|
|
583
|
+
title="Delete Schedule?"
|
|
584
|
+
message={editing ? `Delete "${editing.name}"? This will remove the schedule from the app.` : 'Delete this schedule?'}
|
|
585
|
+
confirmLabel={deleting ? 'Deleting...' : 'Delete'}
|
|
586
|
+
confirmDisabled={deleting}
|
|
587
|
+
cancelDisabled={deleting}
|
|
588
|
+
danger
|
|
589
|
+
onConfirm={() => { void handleDelete() }}
|
|
590
|
+
onCancel={() => { if (!deleting) setConfirmDelete(false) }}
|
|
591
|
+
/>
|
|
550
592
|
</BottomSheet>
|
|
551
593
|
)
|
|
552
594
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useEffect, useState } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
6
|
+
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
6
7
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
7
8
|
import { api } from '@/lib/api-client'
|
|
8
9
|
import { toast } from 'sonner'
|
|
@@ -25,6 +26,8 @@ export function SecretSheet() {
|
|
|
25
26
|
const [scope, setScope] = useState<'global' | 'agent'>('global')
|
|
26
27
|
const [agentIds, setAgentIds] = useState<string[]>([])
|
|
27
28
|
const [saving, setSaving] = useState(false)
|
|
29
|
+
const [confirmDelete, setConfirmDelete] = useState(false)
|
|
30
|
+
const [deleting, setDeleting] = useState(false)
|
|
28
31
|
|
|
29
32
|
const editing = editingId ? secrets[editingId] : null
|
|
30
33
|
const agentList = Object.values(agents)
|
|
@@ -50,6 +53,8 @@ export function SecretSheet() {
|
|
|
50
53
|
}, [editing, open])
|
|
51
54
|
|
|
52
55
|
const handleClose = () => {
|
|
56
|
+
setConfirmDelete(false)
|
|
57
|
+
setDeleting(false)
|
|
53
58
|
setOpen(false)
|
|
54
59
|
setEditingId(null)
|
|
55
60
|
}
|
|
@@ -87,14 +92,17 @@ export function SecretSheet() {
|
|
|
87
92
|
|
|
88
93
|
const handleDelete = async () => {
|
|
89
94
|
if (!editing) return
|
|
90
|
-
|
|
95
|
+
setDeleting(true)
|
|
91
96
|
try {
|
|
92
97
|
await api('DELETE', `/secrets/${editing.id}`)
|
|
93
98
|
toast.success('Secret deleted')
|
|
94
99
|
await loadSecrets()
|
|
100
|
+
setConfirmDelete(false)
|
|
95
101
|
handleClose()
|
|
96
102
|
} catch (err: unknown) {
|
|
97
103
|
toast.error(err instanceof Error ? err.message : 'Failed to delete secret')
|
|
104
|
+
} finally {
|
|
105
|
+
setDeleting(false)
|
|
98
106
|
}
|
|
99
107
|
}
|
|
100
108
|
|
|
@@ -184,7 +192,7 @@ export function SecretSheet() {
|
|
|
184
192
|
<div className="flex gap-3 pt-3">
|
|
185
193
|
{editing && (
|
|
186
194
|
<button
|
|
187
|
-
onClick={
|
|
195
|
+
onClick={() => setConfirmDelete(true)}
|
|
188
196
|
className="px-5 py-3 rounded-[14px] border border-danger/30 bg-transparent text-danger text-[14px] font-600 cursor-pointer hover:bg-danger/10 transition-colors"
|
|
189
197
|
style={{ fontFamily: 'inherit' }}
|
|
190
198
|
>
|
|
@@ -202,6 +210,17 @@ export function SecretSheet() {
|
|
|
202
210
|
{saving ? 'Saving...' : editing ? 'Update' : 'Save'}
|
|
203
211
|
</button>
|
|
204
212
|
</div>
|
|
213
|
+
<ConfirmDialog
|
|
214
|
+
open={confirmDelete}
|
|
215
|
+
title="Delete Secret?"
|
|
216
|
+
message={editing ? `Delete "${editing.name}"? This will remove the stored secret from the app.` : 'Delete this secret?'}
|
|
217
|
+
confirmLabel={deleting ? 'Deleting...' : 'Delete'}
|
|
218
|
+
confirmDisabled={deleting}
|
|
219
|
+
cancelDisabled={deleting}
|
|
220
|
+
danger
|
|
221
|
+
onConfirm={() => { void handleDelete() }}
|
|
222
|
+
onCancel={() => { if (!deleting) setConfirmDelete(false) }}
|
|
223
|
+
/>
|
|
205
224
|
</div>
|
|
206
225
|
</BottomSheet>
|
|
207
226
|
)
|
|
@@ -4,6 +4,7 @@ import { useState, useEffect, useRef, useCallback, useMemo } from 'react'
|
|
|
4
4
|
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
6
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
7
|
+
import { toast } from 'sonner'
|
|
7
8
|
|
|
8
9
|
export function AgentSwitchDialog() {
|
|
9
10
|
const [open, setOpen] = useState(false)
|
|
@@ -47,10 +48,15 @@ export function AgentSwitchDialog() {
|
|
|
47
48
|
}, [agents, query])
|
|
48
49
|
|
|
49
50
|
const handleSelect = useCallback((agentId: string) => {
|
|
51
|
+
const agent = agents[agentId]
|
|
52
|
+
if (agent?.disabled === true && !agent.threadSessionId) {
|
|
53
|
+
toast.error(`${agent.name} is disabled. Re-enable it to start a new chat.`)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
50
56
|
setOpen(false)
|
|
51
57
|
void setCurrentAgent(agentId)
|
|
52
58
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
53
|
-
}, [])
|
|
59
|
+
}, [agents, setCurrentAgent])
|
|
54
60
|
|
|
55
61
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
56
62
|
if (e.key === 'ArrowDown') {
|
|
@@ -119,6 +125,11 @@ export function AgentSwitchDialog() {
|
|
|
119
125
|
<div className="flex-1 min-w-0">
|
|
120
126
|
<div className="flex items-center gap-2">
|
|
121
127
|
<span className="text-[13px] font-500 text-text truncate">{agent.name}</span>
|
|
128
|
+
{agent.disabled === true && (
|
|
129
|
+
<span className="px-1.5 py-0.5 rounded-[4px] bg-amber-400/[0.08] text-[10px] font-500 text-amber-300 shrink-0">
|
|
130
|
+
disabled
|
|
131
|
+
</span>
|
|
132
|
+
)}
|
|
122
133
|
{agent.id === currentAgentId && (
|
|
123
134
|
<span className="px-1.5 py-0.5 rounded-[4px] bg-accent-bright/15 text-[10px] font-500 text-accent-bright shrink-0">
|
|
124
135
|
current
|
|
@@ -9,9 +9,11 @@ interface Props {
|
|
|
9
9
|
onClose: () => void
|
|
10
10
|
children: ReactNode
|
|
11
11
|
wide?: boolean
|
|
12
|
+
title?: string
|
|
13
|
+
description?: string
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
export function BottomSheet({ open, onClose, children, wide }: Props) {
|
|
16
|
+
export function BottomSheet({ open, onClose, children, wide, title, description }: Props) {
|
|
15
17
|
return (
|
|
16
18
|
<DialogPrimitive.Root open={open} onOpenChange={(nextOpen) => { if (!nextOpen) onClose() }}>
|
|
17
19
|
<DialogPrimitive.Portal>
|
|
@@ -27,10 +29,18 @@ export function BottomSheet({ open, onClose, children, wide }: Props) {
|
|
|
27
29
|
${wide ? 'sm:max-w-[760px]' : 'sm:max-w-[560px]'}`}
|
|
28
30
|
style={{ animationDuration: '220ms' }}
|
|
29
31
|
>
|
|
30
|
-
<div className="relative shrink-0 px-4 pt-
|
|
32
|
+
<div className="relative shrink-0 px-4 pt-4 pr-14 sm:px-5 sm:pt-6 sm:pr-16">
|
|
31
33
|
<div className="mx-auto h-1 w-10 rounded-full bg-white/[0.08] sm:hidden" />
|
|
34
|
+
<DialogPrimitive.Title className="sr-only">
|
|
35
|
+
{title || 'Dialog'}
|
|
36
|
+
</DialogPrimitive.Title>
|
|
37
|
+
{description ? (
|
|
38
|
+
<DialogPrimitive.Description className="sr-only">
|
|
39
|
+
{description}
|
|
40
|
+
</DialogPrimitive.Description>
|
|
41
|
+
) : null}
|
|
32
42
|
<DialogPrimitive.Close
|
|
33
|
-
className="absolute right-
|
|
43
|
+
className="absolute right-4 top-3.5 inline-flex h-9 w-9 items-center justify-center rounded-[12px] border border-white/[0.06] bg-white/[0.03] text-text-3 transition-all hover:bg-white/[0.06] hover:text-text-2 focus:outline-none focus:ring-2 focus:ring-accent-bright/30 sm:right-5 sm:top-5"
|
|
34
44
|
>
|
|
35
45
|
<XIcon className="size-4" />
|
|
36
46
|
<span className="sr-only">Close</span>
|