@swarmclawai/swarmclaw 0.5.2 → 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 +42 -7
- package/bin/swarmclaw.js +76 -16
- package/next.config.ts +11 -1
- package/package.json +4 -2
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- 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/[id]/route.ts +27 -0
- package/src/app/api/notifications/route.ts +68 -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 +155 -0
- 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 +20 -0
- package/src/app/api/tasks/bulk/route.ts +100 -0
- package/src/app/api/tasks/route.ts +1 -0
- package/src/app/api/usage/route.ts +45 -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 +42 -0
- package/src/cli/index.test.js +30 -0
- package/src/cli/spec.js +32 -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 +209 -83
- package/src/components/layout/mobile-header.tsx +2 -0
- 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 +3 -2
- 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 +223 -0
- 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 +296 -0
- 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/metrics-dashboard.tsx +78 -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/cron-human.ts +114 -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 +42 -0
- package/src/lib/server/daemon-state.ts +165 -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 +80 -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/proxy.ts +79 -2
- package/src/stores/use-app-store.ts +94 -3
- package/src/stores/use-chat-store.ts +48 -3
- package/src/stores/use-chatroom-store.ts +276 -0
- package/src/types/index.ts +69 -2
|
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
|
4
4
|
import { api } from '@/lib/api-client'
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
6
|
import { Badge } from '@/components/ui/badge'
|
|
7
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
7
8
|
import type { MemoryEntry } from '@/types'
|
|
8
9
|
|
|
9
10
|
export function KnowledgeList() {
|
|
@@ -13,6 +14,8 @@ export function KnowledgeList() {
|
|
|
13
14
|
const [error, setError] = useState<string | null>(null)
|
|
14
15
|
const [activeTag, setActiveTag] = useState<string | null>(null)
|
|
15
16
|
const searchRef = useRef(search)
|
|
17
|
+
const agents = useAppStore((s) => s.agents)
|
|
18
|
+
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
16
19
|
const setKnowledgeSheetOpen = useAppStore((s) => s.setKnowledgeSheetOpen)
|
|
17
20
|
const setEditingKnowledgeId = useAppStore((s) => s.setEditingKnowledgeId)
|
|
18
21
|
|
|
@@ -40,8 +43,10 @@ export function KnowledgeList() {
|
|
|
40
43
|
|
|
41
44
|
// Initial load
|
|
42
45
|
useEffect(() => {
|
|
46
|
+
loadAgents()
|
|
43
47
|
const timer = setTimeout(() => { void load(searchRef.current, activeTag) }, 0)
|
|
44
48
|
return () => clearTimeout(timer)
|
|
49
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
45
50
|
}, [load, activeTag])
|
|
46
51
|
|
|
47
52
|
// Debounced search
|
|
@@ -120,8 +125,14 @@ export function KnowledgeList() {
|
|
|
120
125
|
{entries.length > 0 ? (
|
|
121
126
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 px-5 pb-6">
|
|
122
127
|
{entries.map((entry) => {
|
|
123
|
-
const meta = entry.metadata as { tags?: string[] } | undefined
|
|
128
|
+
const meta = entry.metadata as { tags?: string[]; scope?: 'global' | 'agent'; agentIds?: string[] } | undefined
|
|
124
129
|
const tags = meta?.tags || []
|
|
130
|
+
const entryScope = meta?.scope || 'global'
|
|
131
|
+
const entryAgentIds = meta?.agentIds || []
|
|
132
|
+
const scopeLabel = entryScope === 'global' ? 'Global' : `${entryAgentIds.length} agent(s)`
|
|
133
|
+
const scopedAgents = entryScope === 'agent'
|
|
134
|
+
? entryAgentIds.map((id) => agents[id]).filter(Boolean)
|
|
135
|
+
: []
|
|
125
136
|
return (
|
|
126
137
|
<div
|
|
127
138
|
key={entry.id}
|
|
@@ -162,6 +173,25 @@ export function KnowledgeList() {
|
|
|
162
173
|
))}
|
|
163
174
|
</div>
|
|
164
175
|
)}
|
|
176
|
+
<div className="flex items-center gap-2 mt-1.5">
|
|
177
|
+
<span className={`text-[10px] font-600 ${
|
|
178
|
+
entryScope === 'global' ? 'text-emerald-400' : 'text-amber-400'
|
|
179
|
+
}`}>
|
|
180
|
+
{scopeLabel}
|
|
181
|
+
</span>
|
|
182
|
+
{scopedAgents.length > 0 && (
|
|
183
|
+
<div className="flex items-center gap-1.5">
|
|
184
|
+
<div className="flex items-center -space-x-1.5">
|
|
185
|
+
{scopedAgents.slice(0, 5).map((agent) => (
|
|
186
|
+
<AgentAvatar key={agent.id} seed={agent.avatarSeed} name={agent.name} size={16} className="ring-1 ring-surface" />
|
|
187
|
+
))}
|
|
188
|
+
</div>
|
|
189
|
+
{scopedAgents.length > 5 && (
|
|
190
|
+
<span className="text-[10px] font-600 text-text-3/60 ml-0.5">+{scopedAgents.length - 5}</span>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
)}
|
|
194
|
+
</div>
|
|
165
195
|
</div>
|
|
166
196
|
)
|
|
167
197
|
})}
|
|
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
|
|
4
4
|
import { api } from '@/lib/api-client'
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
6
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
7
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
7
8
|
import type { MemoryEntry } from '@/types'
|
|
8
9
|
|
|
9
10
|
const ACCEPTED_TYPES = '.txt,.md,.csv,.json,.jsonl,.html,.xml,.yaml,.yml,.toml,.py,.js,.ts,.tsx,.jsx,.go,.rs,.java,.c,.cpp,.h,.rb,.php,.sh,.sql,.log,.pdf'
|
|
@@ -21,16 +22,26 @@ export function KnowledgeSheet() {
|
|
|
21
22
|
const open = useAppStore((s) => s.knowledgeSheetOpen)
|
|
22
23
|
const setOpen = useAppStore((s) => s.setKnowledgeSheetOpen)
|
|
23
24
|
const editingId = useAppStore((s) => s.editingKnowledgeId)
|
|
25
|
+
const agents = useAppStore((s) => s.agents)
|
|
26
|
+
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
24
27
|
|
|
25
28
|
const [title, setTitle] = useState('')
|
|
26
29
|
const [content, setContent] = useState('')
|
|
27
30
|
const [tags, setTags] = useState('')
|
|
31
|
+
const [scope, setScope] = useState<'global' | 'agent'>('global')
|
|
32
|
+
const [agentIds, setAgentIds] = useState<string[]>([])
|
|
28
33
|
const [saving, setSaving] = useState(false)
|
|
29
34
|
const [uploadedFile, setUploadedFile] = useState<{ name: string; url: string; size: number } | null>(null)
|
|
30
35
|
const [uploading, setUploading] = useState(false)
|
|
31
36
|
const [isDragging, setIsDragging] = useState(false)
|
|
32
37
|
const dragCounter = useRef(0)
|
|
33
38
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
39
|
+
const agentList = Object.values(agents)
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (open) loadAgents()
|
|
43
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
44
|
+
}, [open])
|
|
34
45
|
|
|
35
46
|
useEffect(() => {
|
|
36
47
|
if (!open) return
|
|
@@ -38,8 +49,10 @@ export function KnowledgeSheet() {
|
|
|
38
49
|
void api<MemoryEntry>('GET', `/knowledge/${editingId}`).then((entry) => {
|
|
39
50
|
setTitle(entry.title)
|
|
40
51
|
setContent(entry.content)
|
|
41
|
-
const meta = entry.metadata as { tags?: string[] } | undefined
|
|
52
|
+
const meta = entry.metadata as { tags?: string[]; scope?: 'global' | 'agent'; agentIds?: string[] } | undefined
|
|
42
53
|
setTags(meta?.tags?.join(', ') || '')
|
|
54
|
+
setScope(meta?.scope || 'global')
|
|
55
|
+
setAgentIds(meta?.agentIds || [])
|
|
43
56
|
}).catch(() => {
|
|
44
57
|
setOpen(false)
|
|
45
58
|
})
|
|
@@ -47,6 +60,8 @@ export function KnowledgeSheet() {
|
|
|
47
60
|
setTitle('')
|
|
48
61
|
setContent('')
|
|
49
62
|
setTags('')
|
|
63
|
+
setScope('global')
|
|
64
|
+
setAgentIds([])
|
|
50
65
|
setUploadedFile(null)
|
|
51
66
|
}
|
|
52
67
|
}, [open, editingId, setOpen])
|
|
@@ -56,6 +71,8 @@ export function KnowledgeSheet() {
|
|
|
56
71
|
setTitle('')
|
|
57
72
|
setContent('')
|
|
58
73
|
setTags('')
|
|
74
|
+
setScope('global')
|
|
75
|
+
setAgentIds([])
|
|
59
76
|
setUploadedFile(null)
|
|
60
77
|
setIsDragging(false)
|
|
61
78
|
dragCounter.current = 0
|
|
@@ -128,6 +145,16 @@ export function KnowledgeSheet() {
|
|
|
128
145
|
if (file) void handleUpload(file)
|
|
129
146
|
}, [handleUpload])
|
|
130
147
|
|
|
148
|
+
const toggleAgent = (id: string) => {
|
|
149
|
+
setAgentIds((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id])
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const scopeHelperText = scope === 'global'
|
|
153
|
+
? 'This knowledge will be accessible to all agents'
|
|
154
|
+
: agentIds.length === 0
|
|
155
|
+
? 'Select which agents can access this knowledge'
|
|
156
|
+
: `${agentIds.length} agent(s) selected`
|
|
157
|
+
|
|
131
158
|
const handleSave = async () => {
|
|
132
159
|
setSaving(true)
|
|
133
160
|
try {
|
|
@@ -135,6 +162,8 @@ export function KnowledgeSheet() {
|
|
|
135
162
|
title: title.trim() || 'Untitled',
|
|
136
163
|
content,
|
|
137
164
|
tags: parseTags(tags),
|
|
165
|
+
scope,
|
|
166
|
+
agentIds: scope === 'agent' ? agentIds : [],
|
|
138
167
|
}
|
|
139
168
|
|
|
140
169
|
if (editingId) {
|
|
@@ -294,6 +323,58 @@ export function KnowledgeSheet() {
|
|
|
294
323
|
/>
|
|
295
324
|
</div>
|
|
296
325
|
|
|
326
|
+
<div className="mb-8">
|
|
327
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Scope</label>
|
|
328
|
+
<div className="flex p-1 rounded-[12px] bg-bg border border-white/[0.06]">
|
|
329
|
+
{(['global', 'agent'] as const).map((s) => (
|
|
330
|
+
<button
|
|
331
|
+
key={s}
|
|
332
|
+
onClick={() => setScope(s)}
|
|
333
|
+
className={`flex-1 py-2.5 rounded-[10px] text-center cursor-pointer transition-all text-[13px] font-600 border-none ${
|
|
334
|
+
scope === s ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'
|
|
335
|
+
}`}
|
|
336
|
+
style={{ fontFamily: 'inherit' }}
|
|
337
|
+
>
|
|
338
|
+
{s === 'global' ? 'Global' : 'Specific'}
|
|
339
|
+
</button>
|
|
340
|
+
))}
|
|
341
|
+
</div>
|
|
342
|
+
<p className="text-[11px] text-text-3/60 mt-1.5 pl-1">{scopeHelperText}</p>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
{scope === 'agent' && (
|
|
346
|
+
<div className="mb-8">
|
|
347
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Agents</label>
|
|
348
|
+
<div className="max-h-[240px] overflow-y-auto rounded-[12px] border border-white/[0.06] bg-white/[0.03]">
|
|
349
|
+
{agentList.length === 0 ? (
|
|
350
|
+
<p className="p-3 text-[12px] text-text-3">No agents available</p>
|
|
351
|
+
) : (
|
|
352
|
+
agentList.map((agent) => {
|
|
353
|
+
const selected = agentIds.includes(agent.id)
|
|
354
|
+
return (
|
|
355
|
+
<button
|
|
356
|
+
key={agent.id}
|
|
357
|
+
onClick={() => toggleAgent(agent.id)}
|
|
358
|
+
className={`w-full flex items-center gap-2.5 px-3 py-2 text-left transition-all cursor-pointer ${
|
|
359
|
+
selected ? 'bg-accent-soft/40' : 'hover:bg-white/[0.04]'
|
|
360
|
+
}`}
|
|
361
|
+
style={{ fontFamily: 'inherit' }}
|
|
362
|
+
>
|
|
363
|
+
<AgentAvatar seed={agent.avatarSeed} name={agent.name} size={24} />
|
|
364
|
+
<span className="text-[13px] text-text flex-1 truncate">{agent.name}</span>
|
|
365
|
+
{selected && (
|
|
366
|
+
<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">
|
|
367
|
+
<polyline points="20 6 9 17 4 12" />
|
|
368
|
+
</svg>
|
|
369
|
+
)}
|
|
370
|
+
</button>
|
|
371
|
+
)
|
|
372
|
+
})
|
|
373
|
+
)}
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
)}
|
|
377
|
+
|
|
297
378
|
<div className="flex gap-3 pt-2 border-t border-white/[0.04]">
|
|
298
379
|
<button
|
|
299
380
|
onClick={onClose}
|
|
@@ -305,7 +386,7 @@ export function KnowledgeSheet() {
|
|
|
305
386
|
<button
|
|
306
387
|
onClick={() => { void handleSave() }}
|
|
307
388
|
disabled={!title.trim() || saving}
|
|
308
|
-
className="flex-1 py-3.5 rounded-[14px] border-none bg-
|
|
389
|
+
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"
|
|
309
390
|
style={{ fontFamily: 'inherit' }}
|
|
310
391
|
>
|
|
311
392
|
{saving ? 'Saving...' : 'Save'}
|