@swarmclawai/swarmclaw 0.5.3 → 0.6.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 +39 -8
- package/bin/swarmclaw.js +76 -16
- package/next.config.ts +11 -1
- package/package.json +4 -2
- package/scripts/postinstall.mjs +18 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +410 -0
- package/src/app/api/chatrooms/[id]/members/route.ts +82 -0
- package/src/app/api/chatrooms/[id]/pins/route.ts +39 -0
- package/src/app/api/chatrooms/[id]/reactions/route.ts +42 -0
- package/src/app/api/chatrooms/[id]/route.ts +84 -0
- package/src/app/api/chatrooms/route.ts +50 -0
- package/src/app/api/credentials/route.ts +2 -3
- package/src/app/api/knowledge/[id]/route.ts +13 -2
- package/src/app/api/knowledge/route.ts +8 -1
- package/src/app/api/memory/route.ts +8 -0
- package/src/app/api/notifications/route.ts +4 -0
- package/src/app/api/orchestrator/run/route.ts +1 -1
- package/src/app/api/plugins/install/route.ts +2 -2
- package/src/app/api/search/route.ts +51 -1
- package/src/app/api/sessions/[id]/chat/route.ts +2 -0
- package/src/app/api/sessions/[id]/edit-resend/route.ts +1 -1
- package/src/app/api/sessions/[id]/fork/route.ts +1 -1
- package/src/app/api/sessions/route.ts +3 -3
- package/src/app/api/settings/route.ts +9 -0
- package/src/app/api/setup/check-provider/route.ts +3 -16
- package/src/app/api/skills/[id]/route.ts +6 -0
- package/src/app/api/skills/route.ts +6 -0
- package/src/app/api/tasks/[id]/route.ts +12 -0
- package/src/app/api/tasks/bulk/route.ts +100 -0
- package/src/app/api/tasks/route.ts +1 -0
- package/src/app/api/webhooks/[id]/route.ts +15 -1
- package/src/app/globals.css +58 -15
- package/src/app/page.tsx +142 -13
- package/src/cli/index.js +24 -0
- package/src/cli/index.test.js +30 -0
- package/src/cli/spec.js +16 -0
- package/src/components/agents/agent-avatar.tsx +57 -10
- package/src/components/agents/agent-card.tsx +48 -15
- package/src/components/agents/agent-chat-list.tsx +123 -10
- package/src/components/agents/agent-list.tsx +50 -19
- package/src/components/agents/agent-sheet.tsx +56 -63
- package/src/components/auth/access-key-gate.tsx +10 -3
- package/src/components/auth/setup-wizard.tsx +2 -2
- package/src/components/auth/user-picker.tsx +31 -3
- package/src/components/chat/activity-moment.tsx +169 -0
- package/src/components/chat/chat-header.tsx +2 -0
- package/src/components/chat/chat-tool-toggles.tsx +1 -1
- package/src/components/chat/file-path-chip.tsx +125 -0
- package/src/components/chat/markdown-utils.ts +9 -0
- package/src/components/chat/message-bubble.tsx +46 -295
- package/src/components/chat/message-list.tsx +50 -1
- package/src/components/chat/streaming-bubble.tsx +36 -46
- package/src/components/chat/suggestions-bar.tsx +1 -1
- package/src/components/chat/thinking-indicator.tsx +72 -10
- package/src/components/chat/tool-call-bubble.tsx +66 -70
- package/src/components/chat/tool-request-banner.tsx +31 -7
- package/src/components/chat/transfer-agent-picker.tsx +63 -0
- package/src/components/chatrooms/agent-hover-card.tsx +124 -0
- package/src/components/chatrooms/chatroom-input.tsx +320 -0
- package/src/components/chatrooms/chatroom-list.tsx +123 -0
- package/src/components/chatrooms/chatroom-message.tsx +427 -0
- package/src/components/chatrooms/chatroom-sheet.tsx +215 -0
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +134 -0
- package/src/components/chatrooms/chatroom-typing-bar.tsx +88 -0
- package/src/components/chatrooms/chatroom-view.tsx +344 -0
- package/src/components/chatrooms/reaction-picker.tsx +273 -0
- package/src/components/connectors/connector-sheet.tsx +34 -47
- package/src/components/home/home-view.tsx +501 -0
- package/src/components/input/chat-input.tsx +79 -41
- package/src/components/knowledge/knowledge-list.tsx +31 -1
- package/src/components/knowledge/knowledge-sheet.tsx +83 -2
- package/src/components/layout/app-layout.tsx +175 -95
- package/src/components/layout/update-banner.tsx +2 -2
- package/src/components/logs/log-list.tsx +2 -2
- package/src/components/mcp-servers/mcp-server-sheet.tsx +1 -1
- package/src/components/memory/memory-agent-list.tsx +143 -0
- package/src/components/memory/memory-browser.tsx +205 -0
- package/src/components/memory/memory-card.tsx +34 -7
- package/src/components/memory/memory-detail.tsx +359 -120
- package/src/components/memory/memory-sheet.tsx +157 -23
- package/src/components/plugins/plugin-list.tsx +1 -1
- package/src/components/plugins/plugin-sheet.tsx +1 -1
- package/src/components/projects/project-detail.tsx +509 -0
- package/src/components/projects/project-list.tsx +195 -59
- package/src/components/providers/provider-list.tsx +2 -2
- package/src/components/providers/provider-sheet.tsx +3 -3
- package/src/components/schedules/schedule-card.tsx +1 -1
- package/src/components/schedules/schedule-list.tsx +1 -1
- package/src/components/schedules/schedule-sheet.tsx +25 -25
- package/src/components/secrets/secret-sheet.tsx +47 -24
- package/src/components/secrets/secrets-list.tsx +18 -8
- package/src/components/sessions/new-session-sheet.tsx +33 -65
- package/src/components/sessions/session-card.tsx +45 -14
- package/src/components/sessions/session-list.tsx +35 -18
- package/src/components/shared/agent-picker-list.tsx +90 -0
- package/src/components/shared/agent-switch-dialog.tsx +156 -0
- package/src/components/shared/attachment-chip.tsx +165 -0
- package/src/components/shared/avatar.tsx +10 -1
- package/src/components/shared/check-icon.tsx +12 -0
- package/src/components/shared/confirm-dialog.tsx +1 -1
- package/src/components/shared/empty-state.tsx +32 -0
- package/src/components/shared/file-preview.tsx +34 -0
- package/src/components/shared/form-styles.ts +2 -0
- package/src/components/shared/keyboard-shortcuts-dialog.tsx +116 -0
- package/src/components/shared/notification-center.tsx +44 -6
- package/src/components/shared/profile-sheet.tsx +115 -0
- package/src/components/shared/reply-quote.tsx +26 -0
- package/src/components/shared/search-dialog.tsx +14 -5
- package/src/components/shared/section-label.tsx +12 -0
- package/src/components/shared/settings/plugin-manager.tsx +1 -1
- package/src/components/shared/settings/section-providers.tsx +1 -1
- package/src/components/shared/settings/section-secrets.tsx +1 -1
- package/src/components/shared/settings/section-theme.tsx +95 -0
- package/src/components/shared/settings/section-user-preferences.tsx +39 -0
- package/src/components/shared/settings/settings-page.tsx +180 -27
- package/src/components/shared/settings/settings-sheet.tsx +9 -73
- package/src/components/shared/sheet-footer.tsx +33 -0
- package/src/components/skills/skill-list.tsx +61 -30
- package/src/components/skills/skill-sheet.tsx +81 -2
- package/src/components/tasks/task-board.tsx +448 -26
- package/src/components/tasks/task-card.tsx +46 -9
- package/src/components/tasks/task-column.tsx +62 -3
- package/src/components/tasks/task-list.tsx +12 -4
- package/src/components/tasks/task-sheet.tsx +89 -72
- package/src/components/ui/hover-card.tsx +52 -0
- package/src/components/usage/usage-list.tsx +1 -1
- package/src/components/webhooks/webhook-sheet.tsx +1 -1
- package/src/hooks/use-view-router.ts +69 -19
- package/src/instrumentation.ts +15 -1
- package/src/lib/chat.ts +2 -0
- package/src/lib/memory.ts +3 -0
- package/src/lib/server/chat-execution.ts +24 -4
- package/src/lib/server/connectors/manager.ts +11 -0
- package/src/lib/server/context-manager.ts +225 -13
- package/src/lib/server/create-notification.ts +14 -2
- package/src/lib/server/daemon-state.ts +157 -10
- package/src/lib/server/execution-log.ts +1 -0
- package/src/lib/server/heartbeat-service.ts +40 -5
- package/src/lib/server/heartbeat-wake.ts +110 -0
- package/src/lib/server/langgraph-checkpoint.ts +1 -0
- package/src/lib/server/memory-consolidation.ts +92 -0
- package/src/lib/server/memory-db.ts +51 -6
- package/src/lib/server/openclaw-gateway.ts +9 -1
- package/src/lib/server/provider-health.ts +125 -0
- package/src/lib/server/queue.ts +5 -4
- package/src/lib/server/scheduler.ts +8 -0
- package/src/lib/server/session-run-manager.ts +4 -0
- package/src/lib/server/session-tools/chatroom.ts +136 -0
- package/src/lib/server/session-tools/context-mgmt.ts +36 -18
- package/src/lib/server/session-tools/index.ts +2 -0
- package/src/lib/server/session-tools/memory.ts +6 -1
- package/src/lib/server/storage.ts +53 -29
- package/src/lib/server/stream-agent-chat.ts +153 -47
- package/src/lib/server/system-events.ts +49 -0
- package/src/lib/server/ws-hub.ts +11 -0
- package/src/lib/soul-suggestions.ts +109 -0
- package/src/lib/tasks.ts +4 -1
- package/src/lib/view-routes.ts +36 -1
- package/src/lib/ws-client.ts +14 -4
- package/src/stores/use-app-store.ts +36 -2
- package/src/stores/use-chat-store.ts +48 -3
- package/src/stores/use-chatroom-store.ts +276 -0
- package/src/types/index.ts +56 -2
|
@@ -2,87 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
-
import { BottomSheet } from '../bottom-sheet'
|
|
6
|
-
import { inputClass } from './utils'
|
|
7
|
-
import { UserPreferencesSection } from './section-user-preferences'
|
|
8
|
-
import { OrchestratorSection } from './section-orchestrator'
|
|
9
|
-
import { RuntimeLoopSection } from './section-runtime-loop'
|
|
10
|
-
import { CapabilityPolicySection } from './section-capability-policy'
|
|
11
|
-
import { VoiceSection } from './section-voice'
|
|
12
|
-
import { HeartbeatSection } from './section-heartbeat'
|
|
13
|
-
import { EmbeddingSection } from './section-embedding'
|
|
14
|
-
import { MemorySection } from './section-memory'
|
|
15
|
-
import { SecretsSection } from './section-secrets'
|
|
16
|
-
import { ProvidersSection } from './section-providers'
|
|
17
|
-
import { PluginManager } from './plugin-manager'
|
|
18
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Legacy settings sheet — redirects to the full settings page.
|
|
8
|
+
* Kept for backwards compat in case any code calls setSettingsOpen(true).
|
|
9
|
+
*/
|
|
19
10
|
export function SettingsSheet() {
|
|
20
11
|
const open = useAppStore((s) => s.settingsOpen)
|
|
21
12
|
const setOpen = useAppStore((s) => s.setSettingsOpen)
|
|
22
|
-
const
|
|
23
|
-
const loadCredentials = useAppStore((s) => s.loadCredentials)
|
|
24
|
-
const appSettings = useAppStore((s) => s.appSettings)
|
|
25
|
-
const loadSettings = useAppStore((s) => s.loadSettings)
|
|
26
|
-
const updateSettings = useAppStore((s) => s.updateSettings)
|
|
27
|
-
const loadSecrets = useAppStore((s) => s.loadSecrets)
|
|
28
|
-
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
29
|
-
const credentials = useAppStore((s) => s.credentials)
|
|
13
|
+
const setActiveView = useAppStore((s) => s.setActiveView)
|
|
30
14
|
|
|
31
15
|
useEffect(() => {
|
|
32
16
|
if (open) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
loadSettings()
|
|
36
|
-
loadSecrets()
|
|
37
|
-
loadAgents()
|
|
17
|
+
setActiveView('settings')
|
|
18
|
+
setOpen(false)
|
|
38
19
|
}
|
|
20
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
39
21
|
}, [open])
|
|
40
22
|
|
|
41
|
-
|
|
42
|
-
const patchSettings = updateSettings
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<BottomSheet open={open} onClose={() => setOpen(false)} wide>
|
|
46
|
-
{/* Header */}
|
|
47
|
-
<div className="mb-10">
|
|
48
|
-
<h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">Settings</h2>
|
|
49
|
-
<p className="text-[14px] text-text-3">Manage providers, API keys & orchestrator engine</p>
|
|
50
|
-
</div>
|
|
51
|
-
|
|
52
|
-
<UserPreferencesSection appSettings={appSettings} patchSettings={patchSettings} inputClass={inputClass} />
|
|
53
|
-
<OrchestratorSection appSettings={appSettings} patchSettings={patchSettings} inputClass={inputClass} />
|
|
54
|
-
<RuntimeLoopSection appSettings={appSettings} patchSettings={patchSettings} inputClass={inputClass} />
|
|
55
|
-
<CapabilityPolicySection appSettings={appSettings} patchSettings={patchSettings} inputClass={inputClass} />
|
|
56
|
-
<VoiceSection appSettings={appSettings} patchSettings={patchSettings} inputClass={inputClass} />
|
|
57
|
-
<HeartbeatSection appSettings={appSettings} patchSettings={patchSettings} inputClass={inputClass} />
|
|
58
|
-
<EmbeddingSection appSettings={appSettings} patchSettings={patchSettings} inputClass={inputClass} credList={credList} />
|
|
59
|
-
<MemorySection appSettings={appSettings} patchSettings={patchSettings} inputClass={inputClass} />
|
|
60
|
-
<SecretsSection appSettings={appSettings} patchSettings={patchSettings} inputClass={inputClass} />
|
|
61
|
-
<ProvidersSection appSettings={appSettings} patchSettings={patchSettings} inputClass={inputClass} />
|
|
62
|
-
|
|
63
|
-
{/* Plugins */}
|
|
64
|
-
<div className="mb-10">
|
|
65
|
-
<h3 className="font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
66
|
-
Plugins
|
|
67
|
-
</h3>
|
|
68
|
-
<p className="text-[12px] text-text-3 mb-5">
|
|
69
|
-
Extend agent behavior with hooks. Install from the marketplace, a URL, or drop .js files into <code className="text-[11px] font-mono text-text-2">data/plugins/</code>.
|
|
70
|
-
<span className="text-text-3/70 ml-1">OpenClaw plugins are also supported.</span>
|
|
71
|
-
</p>
|
|
72
|
-
<PluginManager />
|
|
73
|
-
</div>
|
|
74
|
-
|
|
75
|
-
{/* Done */}
|
|
76
|
-
<div className="pt-2 border-t border-white/[0.04]">
|
|
77
|
-
<button
|
|
78
|
-
onClick={() => setOpen(false)}
|
|
79
|
-
className="w-full py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[15px] font-600 cursor-pointer
|
|
80
|
-
hover:bg-surface-2 transition-all duration-200"
|
|
81
|
-
style={{ fontFamily: 'inherit' }}
|
|
82
|
-
>
|
|
83
|
-
Done
|
|
84
|
-
</button>
|
|
85
|
-
</div>
|
|
86
|
-
</BottomSheet>
|
|
87
|
-
)
|
|
23
|
+
return null
|
|
88
24
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type React from 'react'
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
onCancel: () => void
|
|
5
|
+
onSave: () => void
|
|
6
|
+
saveLabel?: string
|
|
7
|
+
saveDisabled?: boolean
|
|
8
|
+
/** Extra buttons rendered on the left (e.g. Archive, Delete) */
|
|
9
|
+
left?: React.ReactNode
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function SheetFooter({ onCancel, onSave, saveLabel = 'Save', saveDisabled, left }: Props) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="flex gap-3 pt-2 border-t border-white/[0.04]">
|
|
15
|
+
{left}
|
|
16
|
+
<button
|
|
17
|
+
onClick={onCancel}
|
|
18
|
+
className="flex-1 py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[15px] font-600 cursor-pointer hover:bg-surface-2 transition-all"
|
|
19
|
+
style={{ fontFamily: 'inherit' }}
|
|
20
|
+
>
|
|
21
|
+
Cancel
|
|
22
|
+
</button>
|
|
23
|
+
<button
|
|
24
|
+
onClick={onSave}
|
|
25
|
+
disabled={saveDisabled}
|
|
26
|
+
className="flex-1 py-3.5 rounded-[14px] border-none bg-accent-bright text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-30 transition-all shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110"
|
|
27
|
+
style={{ fontFamily: 'inherit' }}
|
|
28
|
+
>
|
|
29
|
+
{saveLabel}
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -4,6 +4,7 @@ import { useEffect, useState, useCallback } from 'react'
|
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { api } from '@/lib/api-client'
|
|
6
6
|
import { Badge } from '@/components/ui/badge'
|
|
7
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
7
8
|
import { ClawHubBrowser } from './clawhub-browser'
|
|
8
9
|
import { toast } from 'sonner'
|
|
9
10
|
|
|
@@ -27,6 +28,8 @@ interface SearchResponse {
|
|
|
27
28
|
export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
|
|
28
29
|
const skills = useAppStore((s) => s.skills)
|
|
29
30
|
const loadSkills = useAppStore((s) => s.loadSkills)
|
|
31
|
+
const agents = useAppStore((s) => s.agents)
|
|
32
|
+
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
30
33
|
const setSkillSheetOpen = useAppStore((s) => s.setSkillSheetOpen)
|
|
31
34
|
const setEditingSkillId = useAppStore((s) => s.setEditingSkillId)
|
|
32
35
|
const activeProjectFilter = useAppStore((s) => s.activeProjectFilter)
|
|
@@ -45,6 +48,8 @@ export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
45
48
|
|
|
46
49
|
useEffect(() => {
|
|
47
50
|
loadSkills()
|
|
51
|
+
loadAgents()
|
|
52
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
48
53
|
}, [])
|
|
49
54
|
|
|
50
55
|
const skillList = Object.values(skills).filter((s) => !activeProjectFilter || s.projectId === activeProjectFilter)
|
|
@@ -258,37 +263,63 @@ export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
258
263
|
</div>
|
|
259
264
|
) : (
|
|
260
265
|
<div className={inSidebar ? 'space-y-2' : 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3'}>
|
|
261
|
-
{skillList.map((skill) =>
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
266
|
+
{skillList.map((skill) => {
|
|
267
|
+
const skillScope = skill.scope || 'global'
|
|
268
|
+
const skillAgentIds = skill.agentIds || []
|
|
269
|
+
const scopeLabel = skillScope === 'global' ? 'Global' : `${skillAgentIds.length} agent(s)`
|
|
270
|
+
const scopedAgents = skillScope === 'agent'
|
|
271
|
+
? skillAgentIds.map((id) => agents[id]).filter(Boolean)
|
|
272
|
+
: []
|
|
273
|
+
return (
|
|
274
|
+
<button
|
|
275
|
+
key={skill.id}
|
|
276
|
+
onClick={() => handleEdit(skill.id)}
|
|
277
|
+
className="w-full text-left p-4 rounded-[14px] border border-white/[0.06] bg-surface hover:bg-surface-2 transition-all cursor-pointer"
|
|
278
|
+
>
|
|
279
|
+
<div className="flex items-center justify-between mb-1">
|
|
280
|
+
<span className="font-display text-[14px] font-600 text-text truncate">{skill.name}</span>
|
|
281
|
+
<div className="flex items-center gap-2 shrink-0 ml-2">
|
|
282
|
+
<span className="text-[10px] font-mono text-text-3/50">{skill.filename}</span>
|
|
283
|
+
{!inSidebar && (
|
|
284
|
+
<button
|
|
285
|
+
onClick={(e) => handleDelete(e, skill.id)}
|
|
286
|
+
className="text-text-3/40 hover:text-red-400 transition-colors p-0.5"
|
|
287
|
+
title="Delete"
|
|
288
|
+
>
|
|
289
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
290
|
+
<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" />
|
|
291
|
+
</svg>
|
|
292
|
+
</button>
|
|
293
|
+
)}
|
|
294
|
+
</div>
|
|
282
295
|
</div>
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
296
|
+
{skill.description && (
|
|
297
|
+
<p className="text-[12px] text-text-3/60 line-clamp-2">{skill.description}</p>
|
|
298
|
+
)}
|
|
299
|
+
<div className="flex items-center gap-2 mt-1.5">
|
|
300
|
+
<span className="text-[11px] text-text-3/70">{skill.content.length} chars</span>
|
|
301
|
+
<span className="text-[11px] text-text-3/60">·</span>
|
|
302
|
+
<span className={`text-[10px] font-600 ${
|
|
303
|
+
skillScope === 'global' ? 'text-emerald-400' : 'text-amber-400'
|
|
304
|
+
}`}>
|
|
305
|
+
{scopeLabel}
|
|
306
|
+
</span>
|
|
307
|
+
</div>
|
|
308
|
+
{scopedAgents.length > 0 && (
|
|
309
|
+
<div className="flex items-center gap-1.5 mt-1.5">
|
|
310
|
+
<div className="flex items-center -space-x-1.5">
|
|
311
|
+
{scopedAgents.slice(0, 5).map((agent) => (
|
|
312
|
+
<AgentAvatar key={agent.id} seed={agent.avatarSeed} name={agent.name} size={16} className="ring-1 ring-surface" />
|
|
313
|
+
))}
|
|
314
|
+
</div>
|
|
315
|
+
{scopedAgents.length > 5 && (
|
|
316
|
+
<span className="text-[10px] font-600 text-text-3/60 ml-0.5">+{scopedAgents.length - 5}</span>
|
|
317
|
+
)}
|
|
318
|
+
</div>
|
|
319
|
+
)}
|
|
320
|
+
</button>
|
|
321
|
+
)
|
|
322
|
+
})}
|
|
292
323
|
</div>
|
|
293
324
|
)
|
|
294
325
|
)}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useEffect, useState, useRef } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
6
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
6
7
|
import { api } from '@/lib/api-client'
|
|
7
8
|
|
|
8
9
|
export function SkillSheet() {
|
|
@@ -12,18 +13,23 @@ export function SkillSheet() {
|
|
|
12
13
|
const setEditingId = useAppStore((s) => s.setEditingSkillId)
|
|
13
14
|
const skills = useAppStore((s) => s.skills)
|
|
14
15
|
const loadSkills = useAppStore((s) => s.loadSkills)
|
|
16
|
+
const agents = useAppStore((s) => s.agents)
|
|
17
|
+
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
15
18
|
const fileRef = useRef<HTMLInputElement>(null)
|
|
16
19
|
|
|
17
20
|
const [name, setName] = useState('')
|
|
18
21
|
const [filename, setFilename] = useState('')
|
|
19
22
|
const [description, setDescription] = useState('')
|
|
20
23
|
const [content, setContent] = useState('')
|
|
24
|
+
const [scope, setScope] = useState<'global' | 'agent'>('global')
|
|
25
|
+
const [agentIds, setAgentIds] = useState<string[]>([])
|
|
21
26
|
const [importUrl, setImportUrl] = useState('')
|
|
22
27
|
const [importingUrl, setImportingUrl] = useState(false)
|
|
23
28
|
const [importError, setImportError] = useState('')
|
|
24
29
|
const [importNotice, setImportNotice] = useState('')
|
|
25
30
|
|
|
26
31
|
const editing = editingId ? skills[editingId] : null
|
|
32
|
+
const agentList = Object.values(agents)
|
|
27
33
|
|
|
28
34
|
const handleImportFromUrl = async () => {
|
|
29
35
|
if (!importUrl.trim()) return
|
|
@@ -48,6 +54,11 @@ export function SkillSheet() {
|
|
|
48
54
|
}
|
|
49
55
|
}
|
|
50
56
|
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (open) loadAgents()
|
|
59
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
60
|
+
}, [open])
|
|
61
|
+
|
|
51
62
|
useEffect(() => {
|
|
52
63
|
if (open) {
|
|
53
64
|
setImportUrl('')
|
|
@@ -59,11 +70,15 @@ export function SkillSheet() {
|
|
|
59
70
|
setFilename(editing.filename)
|
|
60
71
|
setDescription(editing.description || '')
|
|
61
72
|
setContent(editing.content)
|
|
73
|
+
setScope(editing.scope || 'global')
|
|
74
|
+
setAgentIds(editing.agentIds || [])
|
|
62
75
|
} else {
|
|
63
76
|
setName('')
|
|
64
77
|
setFilename('')
|
|
65
78
|
setDescription('')
|
|
66
79
|
setContent('')
|
|
80
|
+
setScope('global')
|
|
81
|
+
setAgentIds([])
|
|
67
82
|
}
|
|
68
83
|
}
|
|
69
84
|
}, [open, editingId])
|
|
@@ -87,12 +102,24 @@ export function SkillSheet() {
|
|
|
87
102
|
e.target.value = ''
|
|
88
103
|
}
|
|
89
104
|
|
|
105
|
+
const toggleAgent = (id: string) => {
|
|
106
|
+
setAgentIds((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id])
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const scopeHelperText = scope === 'global'
|
|
110
|
+
? 'This skill will be accessible to all agents'
|
|
111
|
+
: agentIds.length === 0
|
|
112
|
+
? 'Select which agents can access this skill'
|
|
113
|
+
: `${agentIds.length} agent(s) selected`
|
|
114
|
+
|
|
90
115
|
const handleSave = async () => {
|
|
91
116
|
const data = {
|
|
92
117
|
name: name.trim() || 'Unnamed Skill',
|
|
93
118
|
filename: filename.trim() || `${name.trim().toLowerCase().replace(/\s+/g, '-')}.md`,
|
|
94
119
|
description,
|
|
95
120
|
content,
|
|
121
|
+
scope,
|
|
122
|
+
agentIds: scope === 'agent' ? agentIds : [],
|
|
96
123
|
}
|
|
97
124
|
if (editing) {
|
|
98
125
|
await api('PUT', `/skills/${editing.id}`, data)
|
|
@@ -155,7 +182,7 @@ export function SkillSheet() {
|
|
|
155
182
|
<button
|
|
156
183
|
onClick={handleImportFromUrl}
|
|
157
184
|
disabled={importingUrl || !importUrl.trim()}
|
|
158
|
-
className="px-4 py-3 rounded-[12px] border-none bg-
|
|
185
|
+
className="px-4 py-3 rounded-[12px] border-none bg-accent-bright text-white text-[13px] font-600 cursor-pointer disabled:opacity-30 transition-all hover:brightness-110"
|
|
159
186
|
style={{ fontFamily: 'inherit' }}
|
|
160
187
|
>
|
|
161
188
|
{importingUrl ? 'Importing...' : 'Import'}
|
|
@@ -191,6 +218,58 @@ export function SkillSheet() {
|
|
|
191
218
|
<p className="text-[11px] text-text-3/70 mt-2">{content.length} characters</p>
|
|
192
219
|
</div>
|
|
193
220
|
|
|
221
|
+
<div className="mb-8">
|
|
222
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Scope</label>
|
|
223
|
+
<div className="flex p-1 rounded-[12px] bg-bg border border-white/[0.06]">
|
|
224
|
+
{(['global', 'agent'] as const).map((s) => (
|
|
225
|
+
<button
|
|
226
|
+
key={s}
|
|
227
|
+
onClick={() => setScope(s)}
|
|
228
|
+
className={`flex-1 py-2.5 rounded-[10px] text-center cursor-pointer transition-all text-[13px] font-600 border-none ${
|
|
229
|
+
scope === s ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'
|
|
230
|
+
}`}
|
|
231
|
+
style={{ fontFamily: 'inherit' }}
|
|
232
|
+
>
|
|
233
|
+
{s === 'global' ? 'Global' : 'Specific'}
|
|
234
|
+
</button>
|
|
235
|
+
))}
|
|
236
|
+
</div>
|
|
237
|
+
<p className="text-[11px] text-text-3/60 mt-1.5 pl-1">{scopeHelperText}</p>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
{scope === 'agent' && (
|
|
241
|
+
<div className="mb-8">
|
|
242
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Agents</label>
|
|
243
|
+
<div className="max-h-[240px] overflow-y-auto rounded-[12px] border border-white/[0.06] bg-white/[0.03]">
|
|
244
|
+
{agentList.length === 0 ? (
|
|
245
|
+
<p className="p-3 text-[12px] text-text-3">No agents available</p>
|
|
246
|
+
) : (
|
|
247
|
+
agentList.map((agent) => {
|
|
248
|
+
const selected = agentIds.includes(agent.id)
|
|
249
|
+
return (
|
|
250
|
+
<button
|
|
251
|
+
key={agent.id}
|
|
252
|
+
onClick={() => toggleAgent(agent.id)}
|
|
253
|
+
className={`w-full flex items-center gap-2.5 px-3 py-2 text-left transition-all cursor-pointer ${
|
|
254
|
+
selected ? 'bg-accent-soft/40' : 'hover:bg-white/[0.04]'
|
|
255
|
+
}`}
|
|
256
|
+
style={{ fontFamily: 'inherit' }}
|
|
257
|
+
>
|
|
258
|
+
<AgentAvatar seed={agent.avatarSeed} name={agent.name} size={24} />
|
|
259
|
+
<span className="text-[13px] text-text flex-1 truncate">{agent.name}</span>
|
|
260
|
+
{selected && (
|
|
261
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="text-accent-bright shrink-0">
|
|
262
|
+
<polyline points="20 6 9 17 4 12" />
|
|
263
|
+
</svg>
|
|
264
|
+
)}
|
|
265
|
+
</button>
|
|
266
|
+
)
|
|
267
|
+
})
|
|
268
|
+
)}
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
|
|
194
273
|
<div className="flex gap-3 pt-2 border-t border-white/[0.04]">
|
|
195
274
|
{editing && (
|
|
196
275
|
<button onClick={handleDelete} 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' }}>
|
|
@@ -200,7 +279,7 @@ export function SkillSheet() {
|
|
|
200
279
|
<button onClick={onClose} className="flex-1 py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[15px] font-600 cursor-pointer hover:bg-surface-2 transition-all" style={{ fontFamily: 'inherit' }}>
|
|
201
280
|
Cancel
|
|
202
281
|
</button>
|
|
203
|
-
<button onClick={handleSave} disabled={!name.trim() || !content.trim()} className="flex-1 py-3.5 rounded-[14px] border-none bg-
|
|
282
|
+
<button onClick={handleSave} disabled={!name.trim() || !content.trim()} className="flex-1 py-3.5 rounded-[14px] border-none bg-accent-bright text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-30 transition-all shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110" style={{ fontFamily: 'inherit' }}>
|
|
204
283
|
{editing ? 'Save' : 'Create'}
|
|
205
284
|
</button>
|
|
206
285
|
</div>
|