@swarmclawai/swarmclaw 0.2.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 +577 -0
- package/bin/server-cmd.js +359 -0
- package/bin/swarmclaw.js +29 -0
- package/bin/swarmclaw.mjs +1504 -0
- package/next.config.ts +33 -0
- package/package.json +112 -0
- package/postcss.config.mjs +7 -0
- package/public/branding/swarmclaw-org-avatar.png +0 -0
- package/public/branding/swarmclaw-org-avatar.svg +58 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/connectors.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/new-session-openclaw.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/schedules.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/agents/[id]/route.ts +30 -0
- package/src/app/api/agents/[id]/thread/route.ts +66 -0
- package/src/app/api/agents/generate/route.ts +42 -0
- package/src/app/api/agents/route.ts +33 -0
- package/src/app/api/auth/route.ts +25 -0
- package/src/app/api/claude-skills/route.ts +42 -0
- package/src/app/api/clawhub/install/route.ts +39 -0
- package/src/app/api/clawhub/search/route.ts +11 -0
- package/src/app/api/connectors/[id]/route.ts +79 -0
- package/src/app/api/connectors/route.ts +60 -0
- package/src/app/api/credentials/[id]/route.ts +14 -0
- package/src/app/api/credentials/route.ts +31 -0
- package/src/app/api/daemon/health-check/route.ts +11 -0
- package/src/app/api/daemon/route.ts +22 -0
- package/src/app/api/dirs/pick/route.ts +60 -0
- package/src/app/api/dirs/route.ts +29 -0
- package/src/app/api/documents/[id]/route.ts +47 -0
- package/src/app/api/documents/route.ts +93 -0
- package/src/app/api/files/serve/route.ts +69 -0
- package/src/app/api/generate/info/route.ts +12 -0
- package/src/app/api/generate/route.ts +106 -0
- package/src/app/api/ip/route.ts +6 -0
- package/src/app/api/knowledge/[id]/route.ts +61 -0
- package/src/app/api/knowledge/route.ts +48 -0
- package/src/app/api/knowledge/upload/route.ts +86 -0
- package/src/app/api/logs/route.ts +65 -0
- package/src/app/api/mcp-servers/[id]/route.ts +32 -0
- package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
- package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
- package/src/app/api/mcp-servers/route.ts +27 -0
- package/src/app/api/memory/[id]/route.ts +126 -0
- package/src/app/api/memory/maintenance/route.ts +63 -0
- package/src/app/api/memory/route.ts +111 -0
- package/src/app/api/memory-images/[filename]/route.ts +36 -0
- package/src/app/api/orchestrator/run/route.ts +43 -0
- package/src/app/api/plugins/install/route.ts +58 -0
- package/src/app/api/plugins/marketplace/route.ts +33 -0
- package/src/app/api/plugins/route.ts +21 -0
- package/src/app/api/preview-server/route.ts +339 -0
- package/src/app/api/providers/[id]/models/route.ts +29 -0
- package/src/app/api/providers/[id]/route.ts +34 -0
- package/src/app/api/providers/configs/route.ts +7 -0
- package/src/app/api/providers/ollama/route.ts +30 -0
- package/src/app/api/providers/openclaw/health/route.ts +23 -0
- package/src/app/api/providers/route.ts +28 -0
- package/src/app/api/runs/[id]/route.ts +9 -0
- package/src/app/api/runs/route.ts +13 -0
- package/src/app/api/schedules/[id]/route.ts +28 -0
- package/src/app/api/schedules/[id]/run/route.ts +104 -0
- package/src/app/api/schedules/route.ts +78 -0
- package/src/app/api/secrets/[id]/route.ts +29 -0
- package/src/app/api/secrets/route.ts +42 -0
- package/src/app/api/sessions/[id]/browser/route.ts +13 -0
- package/src/app/api/sessions/[id]/chat/route.ts +96 -0
- package/src/app/api/sessions/[id]/clear/route.ts +19 -0
- package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
- package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
- package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
- package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
- package/src/app/api/sessions/[id]/messages/route.ts +9 -0
- package/src/app/api/sessions/[id]/retry/route.ts +28 -0
- package/src/app/api/sessions/[id]/route.ts +103 -0
- package/src/app/api/sessions/[id]/stop/route.ts +13 -0
- package/src/app/api/sessions/heartbeat/route.ts +26 -0
- package/src/app/api/sessions/route.ts +85 -0
- package/src/app/api/settings/route.ts +58 -0
- package/src/app/api/setup/check-provider/route.ts +326 -0
- package/src/app/api/setup/doctor/route.ts +250 -0
- package/src/app/api/skills/[id]/route.ts +40 -0
- package/src/app/api/skills/import/route.ts +69 -0
- package/src/app/api/skills/route.ts +28 -0
- package/src/app/api/tasks/[id]/route.ts +102 -0
- package/src/app/api/tasks/route.ts +115 -0
- package/src/app/api/tts/route.ts +40 -0
- package/src/app/api/upload/route.ts +18 -0
- package/src/app/api/uploads/[filename]/route.ts +59 -0
- package/src/app/api/usage/route.ts +35 -0
- package/src/app/api/version/route.ts +81 -0
- package/src/app/api/version/update/route.ts +95 -0
- package/src/app/api/webhooks/[id]/history/route.ts +13 -0
- package/src/app/api/webhooks/[id]/route.ts +204 -0
- package/src/app/api/webhooks/route.ts +37 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +370 -0
- package/src/app/layout.tsx +52 -0
- package/src/app/page.tsx +172 -0
- package/src/cli/index.js +1232 -0
- package/src/cli/index.test.js +281 -0
- package/src/cli/index.ts +1158 -0
- package/src/cli/spec.js +284 -0
- package/src/components/agents/agent-card.tsx +219 -0
- package/src/components/agents/agent-chat-list.tsx +165 -0
- package/src/components/agents/agent-list.tsx +110 -0
- package/src/components/agents/agent-sheet.tsx +1220 -0
- package/src/components/auth/access-key-gate.tsx +248 -0
- package/src/components/auth/setup-wizard.tsx +940 -0
- package/src/components/auth/user-picker.tsx +88 -0
- package/src/components/chat/chat-area.tsx +406 -0
- package/src/components/chat/chat-header.tsx +491 -0
- package/src/components/chat/chat-tool-toggles.tsx +161 -0
- package/src/components/chat/code-block.tsx +146 -0
- package/src/components/chat/dev-server-bar.tsx +39 -0
- package/src/components/chat/message-bubble.tsx +486 -0
- package/src/components/chat/message-list.tsx +299 -0
- package/src/components/chat/session-debug-panel.tsx +196 -0
- package/src/components/chat/streaming-bubble.tsx +85 -0
- package/src/components/chat/thinking-indicator.tsx +26 -0
- package/src/components/chat/tool-call-bubble.tsx +438 -0
- package/src/components/chat/tool-request-banner.tsx +103 -0
- package/src/components/connectors/connector-list.tsx +196 -0
- package/src/components/connectors/connector-sheet.tsx +804 -0
- package/src/components/input/chat-input.tsx +235 -0
- package/src/components/knowledge/knowledge-list.tsx +206 -0
- package/src/components/knowledge/knowledge-sheet.tsx +316 -0
- package/src/components/layout/app-layout.tsx +1016 -0
- package/src/components/layout/daemon-indicator.tsx +56 -0
- package/src/components/layout/mobile-header.tsx +31 -0
- package/src/components/layout/network-banner.tsx +17 -0
- package/src/components/layout/update-banner.tsx +130 -0
- package/src/components/logs/log-list.tsx +358 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
- package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
- package/src/components/memory/memory-card.tsx +63 -0
- package/src/components/memory/memory-detail.tsx +339 -0
- package/src/components/memory/memory-list.tsx +198 -0
- package/src/components/memory/memory-sheet.tsx +70 -0
- package/src/components/plugins/plugin-list.tsx +60 -0
- package/src/components/plugins/plugin-sheet.tsx +311 -0
- package/src/components/providers/provider-list.tsx +96 -0
- package/src/components/providers/provider-sheet.tsx +542 -0
- package/src/components/runs/run-list.tsx +231 -0
- package/src/components/schedules/schedule-card.tsx +63 -0
- package/src/components/schedules/schedule-list.tsx +76 -0
- package/src/components/schedules/schedule-sheet.tsx +336 -0
- package/src/components/secrets/secret-sheet.tsx +180 -0
- package/src/components/secrets/secrets-list.tsx +91 -0
- package/src/components/sessions/new-session-sheet.tsx +478 -0
- package/src/components/sessions/session-card.tsx +144 -0
- package/src/components/sessions/session-list.tsx +202 -0
- package/src/components/shared/ai-gen-block.tsx +77 -0
- package/src/components/shared/avatar.tsx +48 -0
- package/src/components/shared/bottom-sheet.tsx +30 -0
- package/src/components/shared/confirm-dialog.tsx +47 -0
- package/src/components/shared/connector-platform-icon.tsx +113 -0
- package/src/components/shared/dir-browser.tsx +285 -0
- package/src/components/shared/dropdown.tsx +55 -0
- package/src/components/shared/icon-button.tsx +25 -0
- package/src/components/shared/settings/plugin-manager.tsx +207 -0
- package/src/components/shared/settings/section-capability-policy.tsx +93 -0
- package/src/components/shared/settings/section-embedding.tsx +99 -0
- package/src/components/shared/settings/section-heartbeat.tsx +168 -0
- package/src/components/shared/settings/section-memory.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +108 -0
- package/src/components/shared/settings/section-providers.tsx +181 -0
- package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
- package/src/components/shared/settings/section-secrets.tsx +132 -0
- package/src/components/shared/settings/section-user-preferences.tsx +24 -0
- package/src/components/shared/settings/section-voice.tsx +53 -0
- package/src/components/shared/settings/settings-sheet.tsx +88 -0
- package/src/components/shared/settings/types.ts +7 -0
- package/src/components/shared/settings/utils.ts +13 -0
- package/src/components/shared/settings-sheet.tsx +1 -0
- package/src/components/shared/skeleton.tsx +19 -0
- package/src/components/shared/usage-badge.tsx +28 -0
- package/src/components/skills/clawhub-browser.tsx +225 -0
- package/src/components/skills/skill-list.tsx +70 -0
- package/src/components/skills/skill-sheet.tsx +254 -0
- package/src/components/tasks/task-board.tsx +96 -0
- package/src/components/tasks/task-card.tsx +179 -0
- package/src/components/tasks/task-column.tsx +73 -0
- package/src/components/tasks/task-list.tsx +118 -0
- package/src/components/tasks/task-sheet.tsx +415 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sonner.tsx +22 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/components/usage/usage-list.tsx +105 -0
- package/src/components/webhooks/webhook-list.tsx +166 -0
- package/src/components/webhooks/webhook-sheet.tsx +402 -0
- package/src/hooks/use-auto-resize.ts +20 -0
- package/src/hooks/use-media-query.ts +21 -0
- package/src/hooks/use-speech-recognition.ts +83 -0
- package/src/instrumentation.ts +8 -0
- package/src/lib/agents.ts +13 -0
- package/src/lib/api-client.ts +100 -0
- package/src/lib/chat.ts +60 -0
- package/src/lib/memory.ts +42 -0
- package/src/lib/openclaw-endpoint.test.ts +48 -0
- package/src/lib/openclaw-endpoint.ts +67 -0
- package/src/lib/provider-config.ts +13 -0
- package/src/lib/providers/anthropic.ts +135 -0
- package/src/lib/providers/claude-cli.ts +202 -0
- package/src/lib/providers/codex-cli.ts +260 -0
- package/src/lib/providers/index.ts +351 -0
- package/src/lib/providers/ollama.ts +131 -0
- package/src/lib/providers/openai.ts +164 -0
- package/src/lib/providers/openclaw.ts +330 -0
- package/src/lib/providers/opencode-cli.ts +164 -0
- package/src/lib/runtime-loop.ts +15 -0
- package/src/lib/schedule-dedupe.test.ts +84 -0
- package/src/lib/schedule-dedupe.ts +174 -0
- package/src/lib/schedule-name.ts +62 -0
- package/src/lib/schedules.ts +16 -0
- package/src/lib/server/agent-registry.ts +70 -0
- package/src/lib/server/api-routes.test.ts +362 -0
- package/src/lib/server/autonomy-contract.ts +200 -0
- package/src/lib/server/build-llm.ts +155 -0
- package/src/lib/server/capability-router.test.ts +21 -0
- package/src/lib/server/capability-router.ts +172 -0
- package/src/lib/server/chat-execution.ts +894 -0
- package/src/lib/server/clawhub-client.test.ts +161 -0
- package/src/lib/server/clawhub-client.ts +26 -0
- package/src/lib/server/connectors/connector-routing.test.ts +243 -0
- package/src/lib/server/connectors/discord.ts +116 -0
- package/src/lib/server/connectors/googlechat.ts +66 -0
- package/src/lib/server/connectors/manager.ts +559 -0
- package/src/lib/server/connectors/matrix.ts +78 -0
- package/src/lib/server/connectors/media.ts +149 -0
- package/src/lib/server/connectors/openclaw.test.ts +375 -0
- package/src/lib/server/connectors/openclaw.ts +1132 -0
- package/src/lib/server/connectors/signal.ts +183 -0
- package/src/lib/server/connectors/slack.ts +258 -0
- package/src/lib/server/connectors/teams.ts +94 -0
- package/src/lib/server/connectors/telegram.ts +221 -0
- package/src/lib/server/connectors/types.ts +62 -0
- package/src/lib/server/connectors/whatsapp.ts +349 -0
- package/src/lib/server/context-manager.ts +232 -0
- package/src/lib/server/cost.ts +31 -0
- package/src/lib/server/daemon-state.ts +354 -0
- package/src/lib/server/data-dir.ts +3 -0
- package/src/lib/server/embeddings.ts +111 -0
- package/src/lib/server/execution-log.ts +257 -0
- package/src/lib/server/gateway/protocol.test.ts +54 -0
- package/src/lib/server/gateway/protocol.ts +114 -0
- package/src/lib/server/heartbeat-service.ts +366 -0
- package/src/lib/server/knowledge-db.test.ts +441 -0
- package/src/lib/server/logger.ts +47 -0
- package/src/lib/server/main-agent-loop.ts +1017 -0
- package/src/lib/server/mcp-client.test.ts +342 -0
- package/src/lib/server/mcp-client.ts +130 -0
- package/src/lib/server/memory-db.ts +1078 -0
- package/src/lib/server/memory-graph.test.ts +153 -0
- package/src/lib/server/memory-graph.ts +138 -0
- package/src/lib/server/openclaw-health.ts +245 -0
- package/src/lib/server/orchestrator-lg.ts +431 -0
- package/src/lib/server/orchestrator.ts +364 -0
- package/src/lib/server/playwright-proxy.mjs +70 -0
- package/src/lib/server/plugins.ts +229 -0
- package/src/lib/server/process-manager.ts +327 -0
- package/src/lib/server/provider-health.ts +113 -0
- package/src/lib/server/queue.ts +859 -0
- package/src/lib/server/runtime-settings.ts +119 -0
- package/src/lib/server/scheduler.ts +196 -0
- package/src/lib/server/session-mailbox.ts +129 -0
- package/src/lib/server/session-run-manager.ts +512 -0
- package/src/lib/server/session-tools/connector.ts +124 -0
- package/src/lib/server/session-tools/context-mgmt.ts +103 -0
- package/src/lib/server/session-tools/context.ts +114 -0
- package/src/lib/server/session-tools/crud.ts +673 -0
- package/src/lib/server/session-tools/delegate.ts +708 -0
- package/src/lib/server/session-tools/file.ts +264 -0
- package/src/lib/server/session-tools/index.ts +164 -0
- package/src/lib/server/session-tools/memory.ts +230 -0
- package/src/lib/server/session-tools/session-info.ts +422 -0
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
- package/src/lib/server/session-tools/shell.ts +171 -0
- package/src/lib/server/session-tools/web.ts +408 -0
- package/src/lib/server/session-tools.ts +9 -0
- package/src/lib/server/skills-normalize.ts +130 -0
- package/src/lib/server/storage-mcp.test.ts +161 -0
- package/src/lib/server/storage.ts +670 -0
- package/src/lib/server/stream-agent-chat.ts +571 -0
- package/src/lib/server/task-reports.ts +122 -0
- package/src/lib/server/task-result.ts +161 -0
- package/src/lib/server/task-validation.test.ts +27 -0
- package/src/lib/server/task-validation.ts +90 -0
- package/src/lib/server/tool-capability-policy.test.ts +58 -0
- package/src/lib/server/tool-capability-policy.ts +262 -0
- package/src/lib/sessions.ts +68 -0
- package/src/lib/tasks.ts +20 -0
- package/src/lib/tts.ts +42 -0
- package/src/lib/upload.ts +10 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +43 -0
- package/src/stores/use-app-store.ts +468 -0
- package/src/stores/use-chat-store.ts +323 -0
- package/src/types/index.ts +621 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { createTask, updateTask, archiveTask, unarchiveTask } from '@/lib/tasks'
|
|
6
|
+
import { api } from '@/lib/api-client'
|
|
7
|
+
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
8
|
+
import { AiGenBlock } from '@/components/shared/ai-gen-block'
|
|
9
|
+
import { DirBrowser } from '@/components/shared/dir-browser'
|
|
10
|
+
import type { BoardTask, TaskComment } from '@/types'
|
|
11
|
+
|
|
12
|
+
function fmtTime(ts: number) {
|
|
13
|
+
const d = new Date(ts)
|
|
14
|
+
const now = new Date()
|
|
15
|
+
const isToday = d.toDateString() === now.toDateString()
|
|
16
|
+
if (isToday) return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
17
|
+
return d.toLocaleDateString([], { month: 'short', day: 'numeric' }) + ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function TaskSheet() {
|
|
21
|
+
const open = useAppStore((s) => s.taskSheetOpen)
|
|
22
|
+
const setOpen = useAppStore((s) => s.setTaskSheetOpen)
|
|
23
|
+
const editingId = useAppStore((s) => s.editingTaskId)
|
|
24
|
+
const setEditingId = useAppStore((s) => s.setEditingTaskId)
|
|
25
|
+
const tasks = useAppStore((s) => s.tasks)
|
|
26
|
+
const loadTasks = useAppStore((s) => s.loadTasks)
|
|
27
|
+
const agents = useAppStore((s) => s.agents)
|
|
28
|
+
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
29
|
+
|
|
30
|
+
const [title, setTitle] = useState('')
|
|
31
|
+
const [description, setDescription] = useState('')
|
|
32
|
+
const [agentId, setAgentId] = useState('')
|
|
33
|
+
const [commentText, setCommentText] = useState('')
|
|
34
|
+
const [images, setImages] = useState<string[]>([])
|
|
35
|
+
const [uploading, setUploading] = useState(false)
|
|
36
|
+
const [cwd, setCwd] = useState('')
|
|
37
|
+
const [file, setFile] = useState<string | null>(null)
|
|
38
|
+
|
|
39
|
+
// AI generation state
|
|
40
|
+
const [aiPrompt, setAiPrompt] = useState('')
|
|
41
|
+
const [generating, setGenerating] = useState(false)
|
|
42
|
+
const [generated, setGenerated] = useState(false)
|
|
43
|
+
const [genError, setGenError] = useState('')
|
|
44
|
+
const appSettings = useAppStore((s) => s.appSettings)
|
|
45
|
+
const loadSettings = useAppStore((s) => s.loadSettings)
|
|
46
|
+
|
|
47
|
+
const editing = editingId ? tasks[editingId] : null
|
|
48
|
+
const orchestrators = Object.values(agents).filter((p) => p.isOrchestrator)
|
|
49
|
+
|
|
50
|
+
const handleGenerate = async () => {
|
|
51
|
+
if (!aiPrompt.trim()) return
|
|
52
|
+
setGenerating(true)
|
|
53
|
+
setGenError('')
|
|
54
|
+
try {
|
|
55
|
+
const result = await api<{ title?: string; description?: string; error?: string }>('POST', '/generate', { type: 'task', prompt: aiPrompt })
|
|
56
|
+
if (result.error) {
|
|
57
|
+
setGenError(result.error)
|
|
58
|
+
} else if (result.title || result.description) {
|
|
59
|
+
if (result.title) setTitle(result.title)
|
|
60
|
+
if (result.description) setDescription(result.description)
|
|
61
|
+
setGenerated(true)
|
|
62
|
+
} else {
|
|
63
|
+
setGenError('AI returned empty response — try again')
|
|
64
|
+
}
|
|
65
|
+
} catch (err: unknown) {
|
|
66
|
+
setGenError(err instanceof Error ? err.message : 'Generation failed')
|
|
67
|
+
}
|
|
68
|
+
setGenerating(false)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (open) {
|
|
73
|
+
loadAgents()
|
|
74
|
+
loadSettings()
|
|
75
|
+
setAiPrompt('')
|
|
76
|
+
setGenerating(false)
|
|
77
|
+
setGenerated(false)
|
|
78
|
+
setGenError('')
|
|
79
|
+
if (editing) {
|
|
80
|
+
setTitle(editing.title)
|
|
81
|
+
setDescription(editing.description)
|
|
82
|
+
setAgentId(editing.agentId)
|
|
83
|
+
setImages(editing.images || [])
|
|
84
|
+
setCwd(editing.cwd || '')
|
|
85
|
+
setFile(editing.file || null)
|
|
86
|
+
} else {
|
|
87
|
+
setTitle('')
|
|
88
|
+
setDescription('')
|
|
89
|
+
setAgentId(orchestrators[0]?.id || '')
|
|
90
|
+
setImages([])
|
|
91
|
+
setCwd('')
|
|
92
|
+
setFile(null)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}, [open, editingId])
|
|
96
|
+
|
|
97
|
+
// Update default agent when orchestrators load (only if no agent selected yet)
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (open && !editing && !agentId && orchestrators.length) {
|
|
100
|
+
setAgentId(orchestrators[0].id)
|
|
101
|
+
}
|
|
102
|
+
}, [open, editing, agentId, orchestrators.length, agents])
|
|
103
|
+
|
|
104
|
+
const onClose = () => {
|
|
105
|
+
setOpen(false)
|
|
106
|
+
setEditingId(null)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const handleSave = async () => {
|
|
110
|
+
const payload: Partial<BoardTask> & { title: string; description: string; agentId: string } = { title: title.trim() || 'Untitled Task', description, agentId, images, cwd: cwd || undefined, file: file || undefined }
|
|
111
|
+
if (editing) {
|
|
112
|
+
await updateTask(editing.id, payload)
|
|
113
|
+
} else {
|
|
114
|
+
await createTask(payload)
|
|
115
|
+
}
|
|
116
|
+
await loadTasks()
|
|
117
|
+
onClose()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const handleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
121
|
+
const file = e.target.files?.[0]
|
|
122
|
+
if (!file) return
|
|
123
|
+
setUploading(true)
|
|
124
|
+
try {
|
|
125
|
+
const res = await fetch('/api/upload', {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: { 'x-filename': file.name },
|
|
128
|
+
body: await file.arrayBuffer(),
|
|
129
|
+
})
|
|
130
|
+
const data = await res.json()
|
|
131
|
+
if (data.url) setImages((prev) => [...prev, data.url])
|
|
132
|
+
} catch { /* ignore */ }
|
|
133
|
+
setUploading(false)
|
|
134
|
+
e.target.value = ''
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const handleArchive = async () => {
|
|
138
|
+
if (editing) {
|
|
139
|
+
await archiveTask(editing.id)
|
|
140
|
+
await loadTasks()
|
|
141
|
+
onClose()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const handleUnarchive = async () => {
|
|
146
|
+
if (editing) {
|
|
147
|
+
await unarchiveTask(editing.id)
|
|
148
|
+
await loadTasks()
|
|
149
|
+
onClose()
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const handleQueue = async () => {
|
|
154
|
+
if (editing && editing.status === 'backlog') {
|
|
155
|
+
await updateTask(editing.id, { status: 'queued' })
|
|
156
|
+
await loadTasks()
|
|
157
|
+
onClose()
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const handleAddComment = async () => {
|
|
162
|
+
if (!editing || !commentText.trim()) return
|
|
163
|
+
const c: TaskComment = {
|
|
164
|
+
id: crypto.randomUUID().slice(0, 8),
|
|
165
|
+
author: 'You',
|
|
166
|
+
text: commentText.trim(),
|
|
167
|
+
createdAt: Date.now(),
|
|
168
|
+
}
|
|
169
|
+
// Use atomic append to avoid race conditions with queue-added comments
|
|
170
|
+
await updateTask(editing.id, { appendComment: c } as Partial<BoardTask> & { appendComment: TaskComment })
|
|
171
|
+
await loadTasks()
|
|
172
|
+
setCommentText('')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const inputClass = "w-full px-4 py-3.5 rounded-[14px] border border-white/[0.08] bg-surface text-text text-[15px] outline-none transition-all duration-200 placeholder:text-text-3/50 focus-glow"
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<BottomSheet open={open} onClose={onClose}>
|
|
179
|
+
<div className="mb-10">
|
|
180
|
+
<h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">
|
|
181
|
+
{editing ? 'Edit Task' : 'New Task'}
|
|
182
|
+
</h2>
|
|
183
|
+
<p className="text-[14px] text-text-3">
|
|
184
|
+
{editing ? `Status: ${editing.status}` : 'Create a task and assign an orchestrator'}
|
|
185
|
+
</p>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{/* AI Generation */}
|
|
189
|
+
{!editing && <AiGenBlock
|
|
190
|
+
aiPrompt={aiPrompt} setAiPrompt={setAiPrompt}
|
|
191
|
+
generating={generating} generated={generated} genError={genError}
|
|
192
|
+
onGenerate={handleGenerate} appSettings={appSettings}
|
|
193
|
+
placeholder='Describe the task, e.g. "Audit all pages on example.com for SEO issues and broken links"'
|
|
194
|
+
/>}
|
|
195
|
+
|
|
196
|
+
<div className="mb-8">
|
|
197
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Title</label>
|
|
198
|
+
<input
|
|
199
|
+
type="text"
|
|
200
|
+
value={title}
|
|
201
|
+
onChange={(e) => setTitle(e.target.value)}
|
|
202
|
+
placeholder="e.g. Run full site audit"
|
|
203
|
+
className={inputClass}
|
|
204
|
+
style={{ fontFamily: 'inherit' }}
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<div className="mb-8">
|
|
209
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Description</label>
|
|
210
|
+
<textarea
|
|
211
|
+
value={description}
|
|
212
|
+
onChange={(e) => setDescription(e.target.value)}
|
|
213
|
+
placeholder="Detailed task instructions for the orchestrator..."
|
|
214
|
+
rows={4}
|
|
215
|
+
className={`${inputClass} resize-y min-h-[100px]`}
|
|
216
|
+
style={{ fontFamily: 'inherit' }}
|
|
217
|
+
/>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
{/* Images */}
|
|
221
|
+
<div className="mb-8">
|
|
222
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
|
|
223
|
+
Images <span className="normal-case tracking-normal font-normal text-text-3">(optional — reference designs, mockups, etc.)</span>
|
|
224
|
+
</label>
|
|
225
|
+
{images.length > 0 && (
|
|
226
|
+
<div className="flex gap-2 flex-wrap mb-3">
|
|
227
|
+
{images.map((url, i) => (
|
|
228
|
+
<div key={i} className="relative group">
|
|
229
|
+
<img src={url} alt="" className="w-20 h-20 rounded-[10px] object-cover border border-white/[0.08]" />
|
|
230
|
+
<button
|
|
231
|
+
onClick={() => setImages((prev) => prev.filter((_, idx) => idx !== i))}
|
|
232
|
+
className="absolute -top-1.5 -right-1.5 w-5 h-5 rounded-full bg-red-500 text-white flex items-center justify-center text-[11px] font-700 cursor-pointer
|
|
233
|
+
opacity-0 group-hover:opacity-100 transition-opacity border-none"
|
|
234
|
+
>
|
|
235
|
+
x
|
|
236
|
+
</button>
|
|
237
|
+
</div>
|
|
238
|
+
))}
|
|
239
|
+
</div>
|
|
240
|
+
)}
|
|
241
|
+
<label className={`inline-flex items-center gap-2 px-4 py-2.5 rounded-[12px] border border-white/[0.06] bg-surface text-text-3 text-[13px] font-600 cursor-pointer hover:bg-surface-2 transition-colors ${uploading ? 'opacity-50 pointer-events-none' : ''}`}>
|
|
242
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
243
|
+
<rect x="3" y="3" width="18" height="18" rx="2" />
|
|
244
|
+
<circle cx="8.5" cy="8.5" r="1.5" />
|
|
245
|
+
<polyline points="21 15 16 10 5 21" />
|
|
246
|
+
</svg>
|
|
247
|
+
{uploading ? 'Uploading...' : 'Add Image'}
|
|
248
|
+
<input type="file" accept="image/*" onChange={handleImageUpload} className="hidden" />
|
|
249
|
+
</label>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<div className="mb-8">
|
|
253
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Orchestrator</label>
|
|
254
|
+
{orchestrators.length > 0 ? (
|
|
255
|
+
<div className="flex flex-wrap gap-2">
|
|
256
|
+
{orchestrators.map((p) => (
|
|
257
|
+
<button
|
|
258
|
+
key={p.id}
|
|
259
|
+
onClick={() => setAgentId(p.id)}
|
|
260
|
+
className={`px-4 py-3 rounded-[12px] text-[14px] font-600 cursor-pointer transition-all border
|
|
261
|
+
${agentId === p.id
|
|
262
|
+
? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
|
|
263
|
+
: 'bg-surface border-white/[0.06] text-text-2 hover:bg-surface-2'}`}
|
|
264
|
+
style={{ fontFamily: 'inherit' }}
|
|
265
|
+
>
|
|
266
|
+
{p.name}
|
|
267
|
+
</button>
|
|
268
|
+
))}
|
|
269
|
+
</div>
|
|
270
|
+
) : (
|
|
271
|
+
<p className="text-[13px] text-text-3">No orchestrator agents configured. Create one in Agents first.</p>
|
|
272
|
+
)}
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
{/* Directory (optional) */}
|
|
276
|
+
<div className="mb-8">
|
|
277
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
|
|
278
|
+
Directory <span className="normal-case tracking-normal font-normal text-text-3">(optional — project to work in)</span>
|
|
279
|
+
</label>
|
|
280
|
+
<DirBrowser
|
|
281
|
+
value={cwd || null}
|
|
282
|
+
file={file}
|
|
283
|
+
onChange={(dir, f) => {
|
|
284
|
+
setCwd(dir)
|
|
285
|
+
setFile(f ?? null)
|
|
286
|
+
if (!title) {
|
|
287
|
+
const dirName = dir.split('/').pop() || ''
|
|
288
|
+
setTitle(dirName)
|
|
289
|
+
}
|
|
290
|
+
}}
|
|
291
|
+
onClear={() => { setCwd(''); setFile(null) }}
|
|
292
|
+
/>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
{editing?.result && (
|
|
296
|
+
<div className="mb-8">
|
|
297
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Result</label>
|
|
298
|
+
<div className="p-4 rounded-[14px] border border-white/[0.06] bg-surface text-[13px] text-text-2 whitespace-pre-wrap max-h-[200px] overflow-y-auto">
|
|
299
|
+
{editing.result}
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
)}
|
|
303
|
+
|
|
304
|
+
{editing && (editing.claudeResumeId || editing.codexResumeId || editing.opencodeResumeId || editing.cliResumeId) && (
|
|
305
|
+
<div className="mb-8">
|
|
306
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">CLI Sessions</label>
|
|
307
|
+
<div className="flex flex-wrap gap-2">
|
|
308
|
+
{editing.claudeResumeId && (
|
|
309
|
+
<div className="flex items-center gap-2 px-3 py-2 rounded-[10px] border border-white/[0.06] bg-surface">
|
|
310
|
+
<span className="text-[11px] font-600 text-amber-400">Claude</span>
|
|
311
|
+
<code className="text-[11px] text-text-3 font-mono">{editing.claudeResumeId}</code>
|
|
312
|
+
</div>
|
|
313
|
+
)}
|
|
314
|
+
{editing.codexResumeId && (
|
|
315
|
+
<div className="flex items-center gap-2 px-3 py-2 rounded-[10px] border border-white/[0.06] bg-surface">
|
|
316
|
+
<span className="text-[11px] font-600 text-emerald-400">Codex</span>
|
|
317
|
+
<code className="text-[11px] text-text-3 font-mono">{editing.codexResumeId}</code>
|
|
318
|
+
</div>
|
|
319
|
+
)}
|
|
320
|
+
{editing.opencodeResumeId && (
|
|
321
|
+
<div className="flex items-center gap-2 px-3 py-2 rounded-[10px] border border-white/[0.06] bg-surface">
|
|
322
|
+
<span className="text-[11px] font-600 text-sky-400">OpenCode</span>
|
|
323
|
+
<code className="text-[11px] text-text-3 font-mono">{editing.opencodeResumeId}</code>
|
|
324
|
+
</div>
|
|
325
|
+
)}
|
|
326
|
+
{!(editing.claudeResumeId || editing.codexResumeId || editing.opencodeResumeId) && editing.cliResumeId && (
|
|
327
|
+
<div className="flex items-center gap-2 px-3 py-2 rounded-[10px] border border-white/[0.06] bg-surface">
|
|
328
|
+
<span className="text-[11px] font-600 text-text-2">{editing.cliProvider || 'CLI'}</span>
|
|
329
|
+
<code className="text-[11px] text-text-3 font-mono">{editing.cliResumeId}</code>
|
|
330
|
+
</div>
|
|
331
|
+
)}
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
)}
|
|
335
|
+
|
|
336
|
+
{editing?.error && (
|
|
337
|
+
<div className="mb-8">
|
|
338
|
+
<label className="block font-display text-[12px] font-600 text-red-400 uppercase tracking-[0.08em] mb-3">Error</label>
|
|
339
|
+
<div className="p-4 rounded-[14px] border border-red-500/10 bg-red-500/[0.03] text-[13px] text-red-400/80 whitespace-pre-wrap">
|
|
340
|
+
{editing.error}
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
)}
|
|
344
|
+
|
|
345
|
+
{/* Comments */}
|
|
346
|
+
{editing && (
|
|
347
|
+
<div className="mb-8">
|
|
348
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
|
|
349
|
+
Comments {editing.comments?.length ? `(${editing.comments.length})` : ''}
|
|
350
|
+
</label>
|
|
351
|
+
|
|
352
|
+
{editing.comments && editing.comments.length > 0 && (
|
|
353
|
+
<div className="space-y-3 mb-4 max-h-[300px] overflow-y-auto">
|
|
354
|
+
{editing.comments.map((c) => (
|
|
355
|
+
<div key={c.id} className="p-3.5 rounded-[12px] border border-white/[0.06] bg-surface">
|
|
356
|
+
<div className="flex items-center gap-2 mb-1.5">
|
|
357
|
+
<span className={`text-[12px] font-600 ${c.agentId ? 'text-accent-bright' : 'text-text-2'}`}>
|
|
358
|
+
{c.author}
|
|
359
|
+
</span>
|
|
360
|
+
<span className="text-[10px] text-text-3/50 font-mono">{fmtTime(c.createdAt)}</span>
|
|
361
|
+
</div>
|
|
362
|
+
<p className="text-[13px] text-text-2 leading-[1.5] whitespace-pre-wrap">{c.text}</p>
|
|
363
|
+
</div>
|
|
364
|
+
))}
|
|
365
|
+
</div>
|
|
366
|
+
)}
|
|
367
|
+
|
|
368
|
+
<div className="flex gap-2">
|
|
369
|
+
<input
|
|
370
|
+
type="text"
|
|
371
|
+
value={commentText}
|
|
372
|
+
onChange={(e) => setCommentText(e.target.value)}
|
|
373
|
+
placeholder="Add a comment..."
|
|
374
|
+
className={`${inputClass} flex-1`}
|
|
375
|
+
style={{ fontFamily: 'inherit' }}
|
|
376
|
+
onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleAddComment() } }}
|
|
377
|
+
/>
|
|
378
|
+
<button
|
|
379
|
+
onClick={handleAddComment}
|
|
380
|
+
disabled={!commentText.trim()}
|
|
381
|
+
className="px-4 py-3 rounded-[14px] border-none bg-accent-soft text-accent-bright text-[13px] font-600 cursor-pointer disabled:opacity-30 hover:brightness-110 transition-all shrink-0"
|
|
382
|
+
style={{ fontFamily: 'inherit' }}
|
|
383
|
+
>
|
|
384
|
+
Post
|
|
385
|
+
</button>
|
|
386
|
+
</div>
|
|
387
|
+
</div>
|
|
388
|
+
)}
|
|
389
|
+
|
|
390
|
+
<div className="flex gap-3 pt-2 border-t border-white/[0.04]">
|
|
391
|
+
{editing && editing.status !== 'archived' && (
|
|
392
|
+
<button onClick={handleArchive} className="py-3.5 px-6 rounded-[14px] border border-white/[0.08] bg-transparent text-text-3 text-[15px] font-600 cursor-pointer hover:bg-white/[0.04] transition-all" style={{ fontFamily: 'inherit' }}>
|
|
393
|
+
Archive
|
|
394
|
+
</button>
|
|
395
|
+
)}
|
|
396
|
+
{editing && editing.status === 'archived' && (
|
|
397
|
+
<button onClick={handleUnarchive} className="py-3.5 px-6 rounded-[14px] border border-accent-bright/20 bg-transparent text-accent-bright text-[15px] font-600 cursor-pointer hover:bg-accent-bright/10 transition-all" style={{ fontFamily: 'inherit' }}>
|
|
398
|
+
Unarchive
|
|
399
|
+
</button>
|
|
400
|
+
)}
|
|
401
|
+
{editing && editing.status === 'backlog' && (
|
|
402
|
+
<button onClick={handleQueue} className="py-3.5 px-6 rounded-[14px] border border-amber-500/20 bg-transparent text-amber-400 text-[15px] font-600 cursor-pointer hover:bg-amber-500/10 transition-all" style={{ fontFamily: 'inherit' }}>
|
|
403
|
+
Queue
|
|
404
|
+
</button>
|
|
405
|
+
)}
|
|
406
|
+
<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' }}>
|
|
407
|
+
Cancel
|
|
408
|
+
</button>
|
|
409
|
+
<button onClick={handleSave} disabled={!title.trim() || !agentId} className="flex-1 py-3.5 rounded-[14px] border-none bg-[#6366F1] 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' }}>
|
|
410
|
+
{editing ? 'Save' : 'Create'}
|
|
411
|
+
</button>
|
|
412
|
+
</div>
|
|
413
|
+
</BottomSheet>
|
|
414
|
+
)
|
|
415
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Avatar as AvatarPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
function Avatar({
|
|
9
|
+
className,
|
|
10
|
+
size = "default",
|
|
11
|
+
...props
|
|
12
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
|
|
13
|
+
size?: "default" | "sm" | "lg"
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<AvatarPrimitive.Root
|
|
17
|
+
data-slot="avatar"
|
|
18
|
+
data-size={size}
|
|
19
|
+
className={cn(
|
|
20
|
+
"group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6",
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function AvatarImage({
|
|
29
|
+
className,
|
|
30
|
+
...props
|
|
31
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
|
32
|
+
return (
|
|
33
|
+
<AvatarPrimitive.Image
|
|
34
|
+
data-slot="avatar-image"
|
|
35
|
+
className={cn("aspect-square size-full", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function AvatarFallback({
|
|
42
|
+
className,
|
|
43
|
+
...props
|
|
44
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
|
45
|
+
return (
|
|
46
|
+
<AvatarPrimitive.Fallback
|
|
47
|
+
data-slot="avatar-fallback"
|
|
48
|
+
className={cn(
|
|
49
|
+
"bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs",
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
|
|
58
|
+
return (
|
|
59
|
+
<span
|
|
60
|
+
data-slot="avatar-badge"
|
|
61
|
+
className={cn(
|
|
62
|
+
"bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full ring-2 select-none",
|
|
63
|
+
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
|
|
64
|
+
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
|
|
65
|
+
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
data-slot="avatar-group"
|
|
77
|
+
className={cn(
|
|
78
|
+
"*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2",
|
|
79
|
+
className
|
|
80
|
+
)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function AvatarGroupCount({
|
|
87
|
+
className,
|
|
88
|
+
...props
|
|
89
|
+
}: React.ComponentProps<"div">) {
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
data-slot="avatar-group-count"
|
|
93
|
+
className={cn(
|
|
94
|
+
"bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
|
|
95
|
+
className
|
|
96
|
+
)}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
Avatar,
|
|
104
|
+
AvatarImage,
|
|
105
|
+
AvatarFallback,
|
|
106
|
+
AvatarBadge,
|
|
107
|
+
AvatarGroup,
|
|
108
|
+
AvatarGroupCount,
|
|
109
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { Slot } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
const badgeVariants = cva(
|
|
8
|
+
"inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
13
|
+
secondary:
|
|
14
|
+
"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
15
|
+
destructive:
|
|
16
|
+
"bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
17
|
+
outline:
|
|
18
|
+
"border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
19
|
+
ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
20
|
+
link: "text-primary underline-offset-4 [a&]:hover:underline",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultVariants: {
|
|
24
|
+
variant: "default",
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
function Badge({
|
|
30
|
+
className,
|
|
31
|
+
variant = "default",
|
|
32
|
+
asChild = false,
|
|
33
|
+
...props
|
|
34
|
+
}: React.ComponentProps<"span"> &
|
|
35
|
+
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
|
36
|
+
const Comp = asChild ? Slot.Root : "span"
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Comp
|
|
40
|
+
data-slot="badge"
|
|
41
|
+
data-variant={variant}
|
|
42
|
+
className={cn(badgeVariants({ variant }), className)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { Badge, badgeVariants }
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { Slot } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
const buttonVariants = cva(
|
|
8
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
13
|
+
destructive:
|
|
14
|
+
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
15
|
+
outline:
|
|
16
|
+
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
17
|
+
secondary:
|
|
18
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
19
|
+
ghost:
|
|
20
|
+
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
21
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
22
|
+
},
|
|
23
|
+
size: {
|
|
24
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
25
|
+
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
26
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
27
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
28
|
+
icon: "size-9",
|
|
29
|
+
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
|
|
30
|
+
"icon-sm": "size-8",
|
|
31
|
+
"icon-lg": "size-10",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
defaultVariants: {
|
|
35
|
+
variant: "default",
|
|
36
|
+
size: "default",
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
function Button({
|
|
42
|
+
className,
|
|
43
|
+
variant = "default",
|
|
44
|
+
size = "default",
|
|
45
|
+
asChild = false,
|
|
46
|
+
...props
|
|
47
|
+
}: React.ComponentProps<"button"> &
|
|
48
|
+
VariantProps<typeof buttonVariants> & {
|
|
49
|
+
asChild?: boolean
|
|
50
|
+
}) {
|
|
51
|
+
const Comp = asChild ? Slot.Root : "button"
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Comp
|
|
55
|
+
data-slot="button"
|
|
56
|
+
data-variant={variant}
|
|
57
|
+
data-size={size}
|
|
58
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { Button, buttonVariants }
|