@swarmclawai/swarmclaw 0.7.2 → 0.7.4
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 +116 -50
- package/bin/package-manager.js +157 -0
- package/bin/package-manager.test.js +90 -0
- package/bin/server-cmd.js +38 -7
- package/bin/swarmclaw.js +54 -4
- package/bin/update-cmd.js +48 -10
- package/bin/update-cmd.test.js +55 -0
- package/package.json +8 -3
- package/scripts/postinstall.mjs +26 -0
- package/src/app/api/agents/[id]/route.ts +43 -0
- package/src/app/api/agents/[id]/thread/route.ts +39 -8
- package/src/app/api/agents/route.ts +35 -2
- package/src/app/api/auth/route.ts +77 -8
- package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
- package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/route.ts +6 -0
- package/src/app/api/chats/[id]/browser/route.ts +5 -1
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +30 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +23 -1
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
- package/src/app/api/external-agents/[id]/route.ts +31 -0
- package/src/app/api/external-agents/register/route.ts +3 -0
- package/src/app/api/external-agents/route.ts +66 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/gateways/[id]/health/route.ts +28 -0
- package/src/app/api/gateways/[id]/route.ts +79 -0
- package/src/app/api/gateways/route.ts +57 -0
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +12 -4
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +3 -26
- package/src/app/api/plugins/settings/route.ts +17 -12
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
- package/src/app/api/schedules/[id]/route.ts +38 -9
- package/src/app/api/schedules/route.ts +51 -28
- package/src/app/api/settings/route.ts +55 -17
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +16 -6
- package/src/app/api/tasks/bulk/route.ts +3 -3
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +135 -17
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +38 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +21 -12
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-card.tsx +15 -12
- package/src/components/agents/agent-chat-list.tsx +101 -1
- package/src/components/agents/agent-list.tsx +46 -9
- package/src/components/agents/agent-sheet.tsx +456 -23
- package/src/components/agents/inspector-panel.tsx +110 -49
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +70 -27
- package/src/components/chat/chat-card.tsx +6 -21
- package/src/components/chat/chat-header.tsx +263 -366
- package/src/components/chat/chat-list.tsx +62 -26
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +145 -19
- package/src/components/chatrooms/chatroom-input.tsx +96 -33
- package/src/components/chatrooms/chatroom-list.tsx +141 -72
- package/src/components/chatrooms/chatroom-message.tsx +7 -6
- package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
- package/src/components/chatrooms/chatroom-view.tsx +422 -209
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +385 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/memory/memory-browser.tsx +71 -6
- package/src/components/memory/memory-card.tsx +18 -0
- package/src/components/memory/memory-detail.tsx +58 -31
- package/src/components/memory/memory-sheet.tsx +32 -4
- package/src/components/plugins/plugin-list.tsx +15 -3
- package/src/components/plugins/plugin-sheet.tsx +118 -9
- package/src/components/projects/project-detail.tsx +189 -1
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/command-palette.tsx +111 -24
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +88 -6
- package/src/components/shared/settings/section-orchestrator.tsx +6 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +248 -47
- package/src/components/tasks/approvals-panel.tsx +211 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +7 -7
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/provider-model-discovery-client.ts +29 -0
- package/src/lib/providers/index.ts +12 -5
- package/src/lib/runtime-loop.ts +105 -3
- package/src/lib/safe-storage.ts +6 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/agent-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +264 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +44 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
- package/src/lib/server/chat-execution.ts +402 -125
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +74 -2
- package/src/lib/server/chatroom-helpers.ts +144 -11
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +994 -130
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +189 -10
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/daemon-state.ts +62 -3
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/eval/agent-regression.test.ts +47 -0
- package/src/lib/server/eval/agent-regression.ts +1742 -0
- package/src/lib/server/eval/runner.ts +11 -1
- package/src/lib/server/eval/store.ts +2 -1
- package/src/lib/server/heartbeat-service.ts +23 -43
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +31 -964
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +6 -5
- package/src/lib/server/openclaw-gateway.ts +123 -36
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +18 -8
- package/src/lib/server/orchestrator.ts +5 -4
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +215 -0
- package/src/lib/server/plugins.ts +832 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +4 -21
- package/src/lib/server/runtime-settings.test.ts +119 -0
- package/src/lib/server/runtime-settings.ts +12 -92
- package/src/lib/server/schedule-normalization.ts +187 -0
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -80
- package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
- package/src/lib/server/session-tools/calendar.ts +2 -12
- package/src/lib/server/session-tools/connector.ts +109 -8
- package/src/lib/server/session-tools/context.ts +14 -2
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +96 -34
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +406 -20
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +40 -12
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/email.ts +1 -3
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +243 -24
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +1 -3
- package/src/lib/server/session-tools/index.ts +87 -2
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +162 -12
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +142 -4
- package/src/lib/server/session-tools/plugin-creator.ts +95 -25
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +1 -3
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +58 -4
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +195 -27
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +13 -10
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +947 -108
- package/src/lib/server/storage.ts +255 -10
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +185 -25
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -11
- package/src/lib/server/tool-aliases.ts +80 -12
- package/src/lib/server/tool-capability-policy.ts +7 -1
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +62 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +43 -7
- package/src/stores/use-chat-store.ts +31 -2
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +470 -44
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
- package/src/components/chat/new-chat-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -17
- package/src/lib/server/session-run-manager.test.ts +0 -26
|
@@ -197,12 +197,120 @@ export function HomeView() {
|
|
|
197
197
|
SwarmClaw
|
|
198
198
|
</h1>
|
|
199
199
|
<p className="text-[14px] text-text-3 mt-1">
|
|
200
|
-
|
|
200
|
+
Workspace overview for your agent chats, tasks, and automations
|
|
201
201
|
</p>
|
|
202
202
|
</div>
|
|
203
203
|
|
|
204
|
+
{/* Quick actions / triage */}
|
|
205
|
+
<section className="mb-8" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.15s both' }}>
|
|
206
|
+
<SectionHeader
|
|
207
|
+
label="Needs Attention"
|
|
208
|
+
actionLabel="Open Tasks"
|
|
209
|
+
onViewAll={activeTaskCount > 0 ? () => setActiveView('tasks') : undefined}
|
|
210
|
+
/>
|
|
211
|
+
<div className="rounded-[18px] border border-white/[0.06] bg-white/[0.025] p-4">
|
|
212
|
+
<div className="flex flex-wrap items-center gap-2 mb-4">
|
|
213
|
+
<StatusPill label={`${allTasks.filter((task) => task.status === 'failed').length} failed task${allTasks.filter((task) => task.status === 'failed').length === 1 ? '' : 's'}`} tone={allTasks.some((task) => task.status === 'failed') ? 'danger' : 'neutral'} />
|
|
214
|
+
<StatusPill label={`${allTasks.filter((task) => task.pendingApproval).length} awaiting approval`} tone={allTasks.some((task) => task.pendingApproval) ? 'warning' : 'neutral'} />
|
|
215
|
+
<StatusPill label={`${allConnectors.filter((connector) => connector.status === 'error').length} connector issue${allConnectors.filter((connector) => connector.status === 'error').length === 1 ? '' : 's'}`} tone={allConnectors.some((connector) => connector.status === 'error') ? 'danger' : 'neutral'} />
|
|
216
|
+
<StatusPill label={`${runningAgentIds.size} active agent${runningAgentIds.size === 1 ? '' : 's'}`} tone={runningAgentIds.size > 0 ? 'success' : 'neutral'} />
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
{(() => {
|
|
220
|
+
const now = Date.now()
|
|
221
|
+
const items = [
|
|
222
|
+
...allTasks
|
|
223
|
+
.filter((task) => task.pendingApproval)
|
|
224
|
+
.slice(0, 2)
|
|
225
|
+
.map((task) => ({
|
|
226
|
+
id: `approval:${task.id}`,
|
|
227
|
+
tone: 'warning' as const,
|
|
228
|
+
label: task.title,
|
|
229
|
+
meta: `${task.agentId && agents[task.agentId] ? agents[task.agentId]!.name : 'Task'} is waiting for approval`,
|
|
230
|
+
onClick: () => handleTaskClick(task),
|
|
231
|
+
})),
|
|
232
|
+
...allTasks
|
|
233
|
+
.filter((task) => task.status === 'failed')
|
|
234
|
+
.slice(0, 2)
|
|
235
|
+
.map((task) => ({
|
|
236
|
+
id: `failed:${task.id}`,
|
|
237
|
+
tone: 'danger' as const,
|
|
238
|
+
label: task.title,
|
|
239
|
+
meta: `Failed ${timeAgo(task.updatedAt || task.createdAt)}`,
|
|
240
|
+
onClick: () => handleTaskClick(task),
|
|
241
|
+
})),
|
|
242
|
+
...allConnectors
|
|
243
|
+
.filter((connector) => connector.status === 'error')
|
|
244
|
+
.slice(0, 2)
|
|
245
|
+
.map((connector) => ({
|
|
246
|
+
id: `connector:${connector.id}`,
|
|
247
|
+
tone: 'danger' as const,
|
|
248
|
+
label: connector.name,
|
|
249
|
+
meta: `${PLATFORM_LABELS[connector.platform] || connector.platform} connector needs attention`,
|
|
250
|
+
onClick: () => setActiveView('connectors'),
|
|
251
|
+
})),
|
|
252
|
+
...Object.values(schedules)
|
|
253
|
+
.filter((schedule) => schedule.status === 'active' && schedule.nextRunAt && schedule.nextRunAt < now)
|
|
254
|
+
.slice(0, 2)
|
|
255
|
+
.map((schedule) => ({
|
|
256
|
+
id: `schedule:${schedule.id}`,
|
|
257
|
+
tone: 'warning' as const,
|
|
258
|
+
label: schedule.name,
|
|
259
|
+
meta: 'Schedule missed its expected run window',
|
|
260
|
+
onClick: () => setActiveView('schedules'),
|
|
261
|
+
})),
|
|
262
|
+
...unreadNotifications
|
|
263
|
+
.slice(0, 2)
|
|
264
|
+
.map((notification) => ({
|
|
265
|
+
id: `notification:${notification.id}`,
|
|
266
|
+
tone: 'info' as const,
|
|
267
|
+
label: notification.title,
|
|
268
|
+
meta: notification.message || 'Unread notification',
|
|
269
|
+
onClick: () => handleNotificationClick(notification),
|
|
270
|
+
})),
|
|
271
|
+
].slice(0, 6)
|
|
272
|
+
|
|
273
|
+
if (items.length === 0) {
|
|
274
|
+
return (
|
|
275
|
+
<div className="rounded-[14px] border border-dashed border-white/[0.06] bg-white/[0.02] px-4 py-5">
|
|
276
|
+
<p className="text-[13px] font-600 text-text">Everything looks stable.</p>
|
|
277
|
+
<p className="text-[12px] text-text-3/60 mt-1">
|
|
278
|
+
No failed tasks, no waiting approvals, and no connector issues right now.
|
|
279
|
+
</p>
|
|
280
|
+
</div>
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
286
|
+
{items.map((item) => (
|
|
287
|
+
<button
|
|
288
|
+
key={item.id}
|
|
289
|
+
onClick={item.onClick}
|
|
290
|
+
className="flex items-start gap-3 rounded-[14px] border border-white/[0.06] bg-transparent px-4 py-3 text-left hover:bg-white/[0.04] transition-colors cursor-pointer"
|
|
291
|
+
style={{ fontFamily: 'inherit' }}
|
|
292
|
+
>
|
|
293
|
+
<div className={`mt-0.5 h-2.5 w-2.5 rounded-full shrink-0 ${
|
|
294
|
+
item.tone === 'danger'
|
|
295
|
+
? 'bg-red-400'
|
|
296
|
+
: item.tone === 'warning'
|
|
297
|
+
? 'bg-amber-400'
|
|
298
|
+
: 'bg-sky-400'
|
|
299
|
+
}`} />
|
|
300
|
+
<div className="min-w-0">
|
|
301
|
+
<div className="text-[13px] font-600 text-text truncate">{item.label}</div>
|
|
302
|
+
<div className="text-[11px] text-text-3/65 mt-1">{item.meta}</div>
|
|
303
|
+
</div>
|
|
304
|
+
</button>
|
|
305
|
+
))}
|
|
306
|
+
</div>
|
|
307
|
+
)
|
|
308
|
+
})()}
|
|
309
|
+
</div>
|
|
310
|
+
</section>
|
|
311
|
+
|
|
204
312
|
{/* Quick Stats */}
|
|
205
|
-
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-
|
|
313
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-8">
|
|
206
314
|
<StatCard label="Agents" value={String(agentCount)} hint="Total active agents configured in your dashboard" index={0} />
|
|
207
315
|
<StatCard label="Active Tasks" value={String(activeTaskCount)} accent={activeTaskCount > 0} hint="Tasks currently running or queued for execution" index={1} />
|
|
208
316
|
<StatCard label="Today's Cost" value={`$${todayCost.toFixed(2)}`} hint="Estimated API cost for today across all providers" index={2} />
|
|
@@ -507,7 +615,7 @@ export function HomeView() {
|
|
|
507
615
|
)
|
|
508
616
|
}
|
|
509
617
|
|
|
510
|
-
function SectionHeader({ label, onViewAll }: { label: string; onViewAll?: () => void }) {
|
|
618
|
+
function SectionHeader({ label, onViewAll, actionLabel = 'View all →' }: { label: string; onViewAll?: () => void; actionLabel?: string }) {
|
|
511
619
|
return (
|
|
512
620
|
<div className="flex items-center justify-between mb-3">
|
|
513
621
|
<h2 className="font-display text-[13px] font-600 text-text-2 uppercase tracking-[0.08em]">
|
|
@@ -519,13 +627,29 @@ function SectionHeader({ label, onViewAll }: { label: string; onViewAll?: () =>
|
|
|
519
627
|
className="text-[11px] text-text-3/50 hover:text-text-3 transition-colors bg-transparent border-none cursor-pointer"
|
|
520
628
|
style={{ fontFamily: 'inherit' }}
|
|
521
629
|
>
|
|
522
|
-
|
|
630
|
+
{actionLabel}
|
|
523
631
|
</button>
|
|
524
632
|
)}
|
|
525
633
|
</div>
|
|
526
634
|
)
|
|
527
635
|
}
|
|
528
636
|
|
|
637
|
+
function StatusPill({ label, tone }: { label: string; tone: 'neutral' | 'warning' | 'danger' | 'success' }) {
|
|
638
|
+
const toneClasses = tone === 'danger'
|
|
639
|
+
? 'border-red-400/20 bg-red-400/[0.05] text-red-300/85'
|
|
640
|
+
: tone === 'warning'
|
|
641
|
+
? 'border-amber-400/20 bg-amber-400/[0.05] text-amber-300/85'
|
|
642
|
+
: tone === 'success'
|
|
643
|
+
? 'border-emerald-400/20 bg-emerald-400/[0.05] text-emerald-300/85'
|
|
644
|
+
: 'border-white/[0.06] bg-white/[0.03] text-text-3/75'
|
|
645
|
+
|
|
646
|
+
return (
|
|
647
|
+
<div className={`rounded-[999px] border px-3 py-1.5 text-[11px] font-600 ${toneClasses}`}>
|
|
648
|
+
{label}
|
|
649
|
+
</div>
|
|
650
|
+
)
|
|
651
|
+
}
|
|
652
|
+
|
|
529
653
|
function StatCard({ label, value, accent, hint, index = 0 }: { label: string; value: string; accent?: boolean; hint?: string; index?: number }) {
|
|
530
654
|
return (
|
|
531
655
|
<div
|
|
@@ -24,9 +24,11 @@ const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10 MB
|
|
|
24
24
|
|
|
25
25
|
export function ChatInput({ streaming, onSend, onStop, pluginChatActions = [] }: Props) {
|
|
26
26
|
const [value, setValue] = useState('')
|
|
27
|
+
const [extrasOpen, setExtrasOpen] = useState(false)
|
|
27
28
|
const { ref: textareaRef, resize } = useAutoResize()
|
|
28
29
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
29
30
|
const imageInputRef = useRef<HTMLInputElement>(null)
|
|
31
|
+
const extrasRef = useRef<HTMLDivElement>(null)
|
|
30
32
|
const pendingFiles = useChatStore((s) => s.pendingFiles)
|
|
31
33
|
const addPendingFile = useChatStore((s) => s.addPendingFile)
|
|
32
34
|
const removePendingFile = useChatStore((s) => s.removePendingFile)
|
|
@@ -37,6 +39,17 @@ export function ChatInput({ streaming, onSend, onStop, pluginChatActions = [] }:
|
|
|
37
39
|
const addQueuedMessage = useChatStore((s) => s.addQueuedMessage)
|
|
38
40
|
const removeQueuedMessage = useChatStore((s) => s.removeQueuedMessage)
|
|
39
41
|
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!extrasOpen) return
|
|
44
|
+
const handler = (e: MouseEvent) => {
|
|
45
|
+
if (extrasRef.current && !extrasRef.current.contains(e.target as Node)) {
|
|
46
|
+
setExtrasOpen(false)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
document.addEventListener('mousedown', handler)
|
|
50
|
+
return () => document.removeEventListener('mousedown', handler)
|
|
51
|
+
}, [extrasOpen])
|
|
52
|
+
|
|
40
53
|
// Draft persistence: restore on session change
|
|
41
54
|
const draftTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
42
55
|
useEffect(() => {
|
|
@@ -61,6 +74,10 @@ export function ChatInput({ streaming, onSend, onStop, pluginChatActions = [] }:
|
|
|
61
74
|
if (!text && !pendingFiles.length) return
|
|
62
75
|
// If streaming, queue the message instead of blocking
|
|
63
76
|
if (streaming) {
|
|
77
|
+
if (pendingFiles.length > 0) {
|
|
78
|
+
toast.error('Wait for the current reply to finish before sending files.')
|
|
79
|
+
return
|
|
80
|
+
}
|
|
64
81
|
if (text) {
|
|
65
82
|
addQueuedMessage(text)
|
|
66
83
|
setValue('')
|
|
@@ -133,24 +150,30 @@ export function ChatInput({ streaming, onSend, onStop, pluginChatActions = [] }:
|
|
|
133
150
|
return (
|
|
134
151
|
<div className="shrink-0 px-4 md:px-12 lg:px-16 pb-4 pt-2 fixed bottom-0 left-0 right-0 z-20 bg-bg/95 backdrop-blur-md md:relative md:z-auto md:bg-transparent md:backdrop-blur-none"
|
|
135
152
|
style={{ paddingBottom: 'max(16px, env(safe-area-inset-bottom))' }}>
|
|
136
|
-
<div>
|
|
153
|
+
<div className="relative" ref={extrasRef}>
|
|
137
154
|
{streaming && (
|
|
138
|
-
<div className="flex
|
|
155
|
+
<div className="mb-2 flex flex-wrap items-center justify-between gap-2 rounded-[14px] border border-amber-500/15 bg-amber-500/[0.06] px-3.5 py-2">
|
|
156
|
+
<div className="min-w-0">
|
|
157
|
+
<div className="text-[12px] font-600 text-amber-300">Reply in progress</div>
|
|
158
|
+
<div className="text-[11px] text-amber-200/70">
|
|
159
|
+
New text sends queue automatically. File uploads wait for the current reply to finish.
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
139
162
|
<button
|
|
140
163
|
onClick={onStop}
|
|
141
|
-
className="px-
|
|
142
|
-
text-danger text-[
|
|
143
|
-
active:scale-95 hover:bg-danger/[0.1] hover:border-danger/30"
|
|
164
|
+
className="px-4 py-2 rounded-pill border border-danger/20 bg-danger/[0.06]
|
|
165
|
+
text-danger text-[12px] font-600 cursor-pointer transition-all duration-200
|
|
166
|
+
active:scale-95 hover:bg-danger/[0.1] hover:border-danger/30 shrink-0"
|
|
144
167
|
style={{ fontFamily: 'inherit' }}
|
|
145
168
|
>
|
|
146
|
-
Stop
|
|
169
|
+
Stop
|
|
147
170
|
</button>
|
|
148
171
|
</div>
|
|
149
172
|
)}
|
|
150
173
|
|
|
151
174
|
{queuedMessages.length > 0 && (
|
|
152
175
|
<div className="flex flex-wrap items-center gap-1.5 mb-2">
|
|
153
|
-
<span className="label-mono text-amber-400/70">
|
|
176
|
+
<span className="label-mono text-amber-400/70">Sending next</span>
|
|
154
177
|
{queuedMessages.map((msg, i) => (
|
|
155
178
|
<span key={i} className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-amber-500/10 border border-amber-500/15 text-[12px] text-amber-300 font-mono max-w-[200px]">
|
|
156
179
|
<span className="truncate">{msg}</span>
|
|
@@ -195,92 +218,19 @@ export function ChatInput({ streaming, onSend, onStop, pluginChatActions = [] }:
|
|
|
195
218
|
|
|
196
219
|
<div className="flex items-center gap-1 px-4 pb-3.5">
|
|
197
220
|
<button
|
|
198
|
-
|
|
221
|
+
type="button"
|
|
222
|
+
onClick={() => setExtrasOpen((open) => !open)}
|
|
199
223
|
className="flex items-center gap-1.5 px-3 py-2 rounded-[10px] border-none bg-transparent
|
|
200
224
|
text-text-3 text-[13px] cursor-pointer hover:text-text-2 hover:bg-white/[0.05] transition-all duration-200"
|
|
201
225
|
style={{ fontFamily: 'inherit' }}
|
|
202
226
|
>
|
|
203
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="
|
|
204
|
-
<path d="
|
|
227
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
228
|
+
<path d="M12 5v14" />
|
|
229
|
+
<path d="M5 12h14" />
|
|
205
230
|
</svg>
|
|
206
|
-
<span className="hidden sm:inline">
|
|
231
|
+
<span className="hidden sm:inline">Add</span>
|
|
207
232
|
</button>
|
|
208
233
|
|
|
209
|
-
<button
|
|
210
|
-
onClick={() => imageInputRef.current?.click()}
|
|
211
|
-
className="flex items-center gap-1.5 px-3 py-2 rounded-[10px] border-none bg-transparent
|
|
212
|
-
text-text-3 text-[13px] cursor-pointer hover:text-text-2 hover:bg-white/[0.05] transition-all duration-200"
|
|
213
|
-
style={{ fontFamily: 'inherit' }}
|
|
214
|
-
>
|
|
215
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
|
|
216
|
-
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
|
217
|
-
<circle cx="8.5" cy="8.5" r="1.5" />
|
|
218
|
-
<polyline points="21 15 16 10 5 21" />
|
|
219
|
-
</svg>
|
|
220
|
-
<span className="hidden sm:inline">Image</span>
|
|
221
|
-
</button>
|
|
222
|
-
|
|
223
|
-
{/* Plugin Chat Actions */}
|
|
224
|
-
{pluginChatActions.map((action) => (
|
|
225
|
-
<Tooltip key={action.id}>
|
|
226
|
-
<TooltipTrigger asChild>
|
|
227
|
-
<button
|
|
228
|
-
onClick={() => {
|
|
229
|
-
if (action.action === 'message') onSend(action.value)
|
|
230
|
-
else if (action.action === 'link') window.open(action.value, '_blank')
|
|
231
|
-
}}
|
|
232
|
-
className="flex items-center gap-1.5 px-3 py-2 rounded-[10px] border-none bg-emerald-500/[0.05]
|
|
233
|
-
text-emerald-400 text-[13px] cursor-pointer hover:text-emerald-300 hover:bg-emerald-500/[0.1] transition-all duration-200"
|
|
234
|
-
style={{ fontFamily: 'inherit' }}
|
|
235
|
-
>
|
|
236
|
-
{action.label}
|
|
237
|
-
</button>
|
|
238
|
-
</TooltipTrigger>
|
|
239
|
-
{action.tooltip && <TooltipContent>{action.tooltip}</TooltipContent>}
|
|
240
|
-
</Tooltip>
|
|
241
|
-
))}
|
|
242
|
-
|
|
243
|
-
{micSupported && (
|
|
244
|
-
<button
|
|
245
|
-
onClick={toggleRecording}
|
|
246
|
-
className={`flex items-center gap-1.5 px-3 py-2 rounded-[10px] border-none bg-transparent
|
|
247
|
-
text-[13px] cursor-pointer transition-all duration-200
|
|
248
|
-
${recording ? 'text-danger' : 'text-text-3 hover:text-text-2 hover:bg-white/[0.05]'}`}
|
|
249
|
-
style={recording ? { animation: 'mic-pulse 1.5s ease-out infinite', fontFamily: 'inherit' } : { fontFamily: 'inherit' }}
|
|
250
|
-
>
|
|
251
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
|
|
252
|
-
<rect x="9" y="2" width="6" height="11" rx="3" />
|
|
253
|
-
<path d="M5 10a7 7 0 0 0 14 0" />
|
|
254
|
-
<line x1="12" y1="19" x2="12" y2="22" />
|
|
255
|
-
</svg>
|
|
256
|
-
</button>
|
|
257
|
-
)}
|
|
258
|
-
|
|
259
|
-
<Tooltip>
|
|
260
|
-
<TooltipTrigger asChild>
|
|
261
|
-
<button
|
|
262
|
-
type="button"
|
|
263
|
-
onClick={() => { useChatStore.getState().clearContext() }}
|
|
264
|
-
disabled={streaming}
|
|
265
|
-
className="flex items-center gap-1.5 px-3 py-2 rounded-[10px] border-none bg-transparent
|
|
266
|
-
text-text-3 text-[13px] cursor-pointer hover:text-amber-400 hover:bg-amber-400/10 transition-all duration-200 disabled:opacity-30 disabled:pointer-events-none"
|
|
267
|
-
style={{ fontFamily: 'inherit' }}
|
|
268
|
-
>
|
|
269
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
|
|
270
|
-
<line x1="2" y1="12" x2="22" y2="12" />
|
|
271
|
-
<polyline points="8 8 4 12 8 16" />
|
|
272
|
-
<polyline points="16 8 20 12 16 16" />
|
|
273
|
-
</svg>
|
|
274
|
-
<span className="hidden sm:inline">New context</span>
|
|
275
|
-
</button>
|
|
276
|
-
</TooltipTrigger>
|
|
277
|
-
<TooltipContent side="top" sideOffset={8}
|
|
278
|
-
className="bg-raised border border-white/[0.08] text-text shadow-[0_8px_32px_rgba(0,0,0,0.5)] rounded-[10px] px-3.5 py-2.5 max-w-[220px]">
|
|
279
|
-
<div className="font-display text-[12px] font-600 mb-0.5">New context window</div>
|
|
280
|
-
<div className="text-[11px] text-text-3 leading-[1.4]">Adds a marker — messages above it won't be sent to the AI. Nothing is deleted.</div>
|
|
281
|
-
</TooltipContent>
|
|
282
|
-
</Tooltip>
|
|
283
|
-
|
|
284
234
|
<div className="flex-1" />
|
|
285
235
|
|
|
286
236
|
<span className="text-[11px] text-text-3/60 tabular-nums mr-2 font-mono">
|
|
@@ -314,6 +264,105 @@ export function ChatInput({ streaming, onSend, onStop, pluginChatActions = [] }:
|
|
|
314
264
|
</div>
|
|
315
265
|
</div>
|
|
316
266
|
|
|
267
|
+
{extrasOpen && (
|
|
268
|
+
<div className="absolute left-0 bottom-[72px] w-[280px] max-w-[calc(100vw-2rem)] rounded-[16px] border border-white/[0.08] bg-raised/95 p-2 shadow-[0_18px_64px_rgba(0,0,0,0.55)] backdrop-blur-xl">
|
|
269
|
+
<button
|
|
270
|
+
type="button"
|
|
271
|
+
onClick={() => {
|
|
272
|
+
setExtrasOpen(false)
|
|
273
|
+
fileInputRef.current?.click()
|
|
274
|
+
}}
|
|
275
|
+
className="flex w-full items-center gap-2 rounded-[10px] px-3 py-2 text-left text-[13px] text-text-2 hover:bg-white/[0.05] cursor-pointer transition-colors"
|
|
276
|
+
style={{ fontFamily: 'inherit' }}
|
|
277
|
+
>
|
|
278
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
|
|
279
|
+
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" />
|
|
280
|
+
</svg>
|
|
281
|
+
Attach files
|
|
282
|
+
</button>
|
|
283
|
+
<button
|
|
284
|
+
type="button"
|
|
285
|
+
onClick={() => {
|
|
286
|
+
setExtrasOpen(false)
|
|
287
|
+
imageInputRef.current?.click()
|
|
288
|
+
}}
|
|
289
|
+
className="flex w-full items-center gap-2 rounded-[10px] px-3 py-2 text-left text-[13px] text-text-2 hover:bg-white/[0.05] cursor-pointer transition-colors"
|
|
290
|
+
style={{ fontFamily: 'inherit' }}
|
|
291
|
+
>
|
|
292
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
|
|
293
|
+
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
|
294
|
+
<circle cx="8.5" cy="8.5" r="1.5" />
|
|
295
|
+
<polyline points="21 15 16 10 5 21" />
|
|
296
|
+
</svg>
|
|
297
|
+
Add image
|
|
298
|
+
</button>
|
|
299
|
+
{micSupported && (
|
|
300
|
+
<button
|
|
301
|
+
type="button"
|
|
302
|
+
onClick={() => {
|
|
303
|
+
setExtrasOpen(false)
|
|
304
|
+
toggleRecording()
|
|
305
|
+
}}
|
|
306
|
+
className={`flex w-full items-center gap-2 rounded-[10px] px-3 py-2 text-left text-[13px] cursor-pointer transition-colors ${
|
|
307
|
+
recording ? 'text-danger bg-danger/[0.06]' : 'text-text-2 hover:bg-white/[0.05]'
|
|
308
|
+
}`}
|
|
309
|
+
style={recording ? { animation: 'mic-pulse 1.5s ease-out infinite', fontFamily: 'inherit' } : { fontFamily: 'inherit' }}
|
|
310
|
+
>
|
|
311
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
|
|
312
|
+
<rect x="9" y="2" width="6" height="11" rx="3" />
|
|
313
|
+
<path d="M5 10a7 7 0 0 0 14 0" />
|
|
314
|
+
<line x1="12" y1="19" x2="12" y2="22" />
|
|
315
|
+
</svg>
|
|
316
|
+
{recording ? 'Stop microphone' : 'Use microphone'}
|
|
317
|
+
</button>
|
|
318
|
+
)}
|
|
319
|
+
<button
|
|
320
|
+
type="button"
|
|
321
|
+
onClick={() => {
|
|
322
|
+
setExtrasOpen(false)
|
|
323
|
+
void useChatStore.getState().clearContext()
|
|
324
|
+
}}
|
|
325
|
+
disabled={streaming}
|
|
326
|
+
className="flex w-full items-center gap-2 rounded-[10px] px-3 py-2 text-left text-[13px] text-text-2 hover:bg-white/[0.05] cursor-pointer transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
|
327
|
+
style={{ fontFamily: 'inherit' }}
|
|
328
|
+
>
|
|
329
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
|
|
330
|
+
<line x1="2" y1="12" x2="22" y2="12" />
|
|
331
|
+
<polyline points="8 8 4 12 8 16" />
|
|
332
|
+
<polyline points="16 8 20 12 16 16" />
|
|
333
|
+
</svg>
|
|
334
|
+
New context window
|
|
335
|
+
</button>
|
|
336
|
+
{pluginChatActions.length > 0 && (
|
|
337
|
+
<>
|
|
338
|
+
<div className="mx-2 my-1 h-px bg-white/[0.06]" />
|
|
339
|
+
<div className="px-3 pb-1 pt-1 text-[10px] font-700 uppercase tracking-[0.08em] text-text-3/50">
|
|
340
|
+
Quick actions
|
|
341
|
+
</div>
|
|
342
|
+
{pluginChatActions.map((action) => (
|
|
343
|
+
<Tooltip key={action.id}>
|
|
344
|
+
<TooltipTrigger asChild>
|
|
345
|
+
<button
|
|
346
|
+
type="button"
|
|
347
|
+
onClick={() => {
|
|
348
|
+
setExtrasOpen(false)
|
|
349
|
+
if (action.action === 'message') onSend(action.value)
|
|
350
|
+
else if (action.action === 'link') window.open(action.value, '_blank')
|
|
351
|
+
}}
|
|
352
|
+
className="flex w-full items-center gap-2 rounded-[10px] px-3 py-2 text-left text-[13px] text-emerald-300 hover:bg-emerald-500/[0.08] cursor-pointer transition-colors"
|
|
353
|
+
style={{ fontFamily: 'inherit' }}
|
|
354
|
+
>
|
|
355
|
+
{action.label}
|
|
356
|
+
</button>
|
|
357
|
+
</TooltipTrigger>
|
|
358
|
+
{action.tooltip && <TooltipContent>{action.tooltip}</TooltipContent>}
|
|
359
|
+
</Tooltip>
|
|
360
|
+
))}
|
|
361
|
+
</>
|
|
362
|
+
)}
|
|
363
|
+
</div>
|
|
364
|
+
)}
|
|
365
|
+
|
|
317
366
|
<input
|
|
318
367
|
ref={fileInputRef}
|
|
319
368
|
type="file"
|