clawport-ui 0.1.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/.env.example +35 -0
- package/BRANDING.md +131 -0
- package/CLAUDE.md +252 -0
- package/README.md +262 -0
- package/SETUP.md +337 -0
- package/app/agents/[id]/page.tsx +727 -0
- package/app/api/agents/route.ts +12 -0
- package/app/api/chat/[id]/route.ts +139 -0
- package/app/api/cron-runs/route.ts +13 -0
- package/app/api/crons/route.ts +12 -0
- package/app/api/kanban/chat/[id]/route.ts +119 -0
- package/app/api/kanban/chat-history/[ticketId]/route.ts +36 -0
- package/app/api/memory/route.ts +12 -0
- package/app/api/transcribe/route.ts +37 -0
- package/app/api/tts/route.ts +42 -0
- package/app/chat/[id]/page.tsx +10 -0
- package/app/chat/page.tsx +200 -0
- package/app/crons/page.tsx +870 -0
- package/app/docs/page.tsx +399 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +692 -0
- package/app/kanban/page.tsx +327 -0
- package/app/layout.tsx +45 -0
- package/app/memory/page.tsx +685 -0
- package/app/page.tsx +817 -0
- package/app/providers.tsx +37 -0
- package/app/settings/page.tsx +901 -0
- package/app/settings-provider.tsx +209 -0
- package/components/AgentAvatar.tsx +54 -0
- package/components/AgentNode.tsx +122 -0
- package/components/Breadcrumbs.tsx +126 -0
- package/components/DynamicFavicon.tsx +62 -0
- package/components/ErrorState.tsx +97 -0
- package/components/FeedView.tsx +494 -0
- package/components/GlobalSearch.tsx +571 -0
- package/components/GridView.tsx +532 -0
- package/components/ManorMap.tsx +157 -0
- package/components/MobileSidebar.tsx +251 -0
- package/components/NavLinks.tsx +271 -0
- package/components/OnboardingWizard.tsx +1067 -0
- package/components/Sidebar.tsx +115 -0
- package/components/ThemeToggle.tsx +108 -0
- package/components/chat/AgentList.tsx +537 -0
- package/components/chat/ConversationView.tsx +1047 -0
- package/components/chat/FileAttachment.tsx +140 -0
- package/components/chat/MediaPreview.tsx +111 -0
- package/components/chat/VoiceMessage.tsx +139 -0
- package/components/crons/PipelineGraph.tsx +327 -0
- package/components/crons/WeeklySchedule.tsx +630 -0
- package/components/docs/AgentsSection.tsx +209 -0
- package/components/docs/ApiReferenceSection.tsx +256 -0
- package/components/docs/ArchitectureSection.tsx +221 -0
- package/components/docs/ComponentsSection.tsx +253 -0
- package/components/docs/CronSystemSection.tsx +235 -0
- package/components/docs/DocSection.tsx +346 -0
- package/components/docs/GettingStartedSection.tsx +169 -0
- package/components/docs/ThemingSection.tsx +257 -0
- package/components/docs/TroubleshootingSection.tsx +200 -0
- package/components/kanban/AgentPicker.tsx +321 -0
- package/components/kanban/CreateTicketModal.tsx +333 -0
- package/components/kanban/KanbanBoard.tsx +70 -0
- package/components/kanban/KanbanColumn.tsx +166 -0
- package/components/kanban/TicketCard.tsx +245 -0
- package/components/kanban/TicketDetailPanel.tsx +850 -0
- package/components/ui/badge.tsx +48 -0
- package/components/ui/button.tsx +64 -0
- package/components/ui/card.tsx +92 -0
- package/components/ui/dialog.tsx +158 -0
- package/components/ui/scroll-area.tsx +58 -0
- package/components/ui/separator.tsx +28 -0
- package/components/ui/skeleton.tsx +27 -0
- package/components/ui/tabs.tsx +91 -0
- package/components/ui/tooltip.tsx +57 -0
- package/components.json +23 -0
- package/docs/API.md +648 -0
- package/docs/COMPONENTS.md +1059 -0
- package/docs/THEMING.md +795 -0
- package/lib/agents-registry.ts +35 -0
- package/lib/agents.json +282 -0
- package/lib/agents.test.ts +367 -0
- package/lib/agents.ts +32 -0
- package/lib/anthropic.test.ts +422 -0
- package/lib/anthropic.ts +220 -0
- package/lib/api-error.ts +16 -0
- package/lib/audio-recorder.test.ts +72 -0
- package/lib/audio-recorder.ts +169 -0
- package/lib/conversations.test.ts +331 -0
- package/lib/conversations.ts +117 -0
- package/lib/cron-pipelines.test.ts +69 -0
- package/lib/cron-pipelines.ts +58 -0
- package/lib/cron-runs.test.ts +118 -0
- package/lib/cron-runs.ts +67 -0
- package/lib/cron-utils.test.ts +222 -0
- package/lib/cron-utils.ts +160 -0
- package/lib/crons.test.ts +502 -0
- package/lib/crons.ts +114 -0
- package/lib/env.test.ts +44 -0
- package/lib/env.ts +14 -0
- package/lib/kanban/automation.test.ts +245 -0
- package/lib/kanban/automation.ts +143 -0
- package/lib/kanban/chat-store.test.ts +149 -0
- package/lib/kanban/chat-store.ts +81 -0
- package/lib/kanban/store.test.ts +238 -0
- package/lib/kanban/store.ts +98 -0
- package/lib/kanban/types.ts +50 -0
- package/lib/kanban/useAgentWork.ts +78 -0
- package/lib/memory.ts +45 -0
- package/lib/multimodal.test.ts +219 -0
- package/lib/multimodal.ts +68 -0
- package/lib/pipeline.integration.test.ts +343 -0
- package/lib/sanitize.ts +194 -0
- package/lib/settings.test.ts +137 -0
- package/lib/settings.ts +94 -0
- package/lib/styles.ts +24 -0
- package/lib/themes.ts +9 -0
- package/lib/transcribe.test.ts +141 -0
- package/lib/transcribe.ts +111 -0
- package/lib/types.ts +66 -0
- package/lib/utils.ts +6 -0
- package/lib/validation.test.ts +132 -0
- package/lib/validation.ts +80 -0
- package/next.config.ts +7 -0
- package/package.json +56 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/scripts/setup.mjs +215 -0
- package/tsconfig.json +34 -0
- package/vitest.config.ts +17 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ReactFlow,
|
|
5
|
+
Controls,
|
|
6
|
+
useNodesState,
|
|
7
|
+
useEdgesState,
|
|
8
|
+
Handle,
|
|
9
|
+
Position,
|
|
10
|
+
type Node,
|
|
11
|
+
type Edge,
|
|
12
|
+
type NodeProps,
|
|
13
|
+
ConnectionLineType,
|
|
14
|
+
} from "@xyflow/react"
|
|
15
|
+
import { useEffect, useMemo } from "react"
|
|
16
|
+
import type { CronJob } from "@/lib/types"
|
|
17
|
+
import { PIPELINES, getAllPipelineJobNames } from "@/lib/cron-pipelines"
|
|
18
|
+
import { formatDuration } from "@/lib/cron-utils"
|
|
19
|
+
|
|
20
|
+
interface PipelineGraphProps {
|
|
21
|
+
crons: CronJob[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* ─── Custom node ─────────────────────────────────────────────── */
|
|
25
|
+
|
|
26
|
+
function CronPipelineNode({ data }: NodeProps) {
|
|
27
|
+
const d = data as { name: string; schedule: string; status: string; deliveryTo: string | null; color: string } & Record<string, unknown>
|
|
28
|
+
const statusColor = d.status === "ok" ? "#22c55e" : d.status === "error" ? "#ef4444" : "#a1a1aa"
|
|
29
|
+
const hasDelivery = d.deliveryTo !== null
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
style={{
|
|
34
|
+
background: "var(--material-regular)",
|
|
35
|
+
backdropFilter: "blur(20px) saturate(180%)",
|
|
36
|
+
WebkitBackdropFilter: "blur(20px) saturate(180%)",
|
|
37
|
+
borderRadius: "var(--radius-md, 10px)",
|
|
38
|
+
border: "1px solid var(--separator)",
|
|
39
|
+
borderLeft: `3px solid ${d.color}`,
|
|
40
|
+
padding: "10px 14px",
|
|
41
|
+
minWidth: 180,
|
|
42
|
+
maxWidth: 220,
|
|
43
|
+
boxShadow: "var(--shadow-card, 0 2px 8px rgba(0,0,0,0.15))",
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
{/* Name + status dot */}
|
|
47
|
+
<div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 4 }}>
|
|
48
|
+
<div style={{ width: 7, height: 7, borderRadius: "50%", background: statusColor, flexShrink: 0 }} />
|
|
49
|
+
<div
|
|
50
|
+
style={{
|
|
51
|
+
fontSize: 12,
|
|
52
|
+
fontWeight: 600,
|
|
53
|
+
color: "var(--text-primary)",
|
|
54
|
+
whiteSpace: "nowrap",
|
|
55
|
+
overflow: "hidden",
|
|
56
|
+
textOverflow: "ellipsis",
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
{d.name}
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
{/* Schedule */}
|
|
64
|
+
<div style={{ fontSize: 10, color: "var(--text-secondary)", marginBottom: 2 }}>{d.schedule}</div>
|
|
65
|
+
|
|
66
|
+
{/* Delivery badge */}
|
|
67
|
+
{hasDelivery && (
|
|
68
|
+
<div
|
|
69
|
+
style={{
|
|
70
|
+
display: "inline-block",
|
|
71
|
+
fontSize: 9,
|
|
72
|
+
padding: "1px 6px",
|
|
73
|
+
borderRadius: 4,
|
|
74
|
+
background: "var(--accent, #6366f1)",
|
|
75
|
+
color: "#fff",
|
|
76
|
+
opacity: 0.8,
|
|
77
|
+
marginTop: 2,
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
delivered
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
<Handle type="target" position={Position.Left} style={{ opacity: 0 }} />
|
|
85
|
+
<Handle type="source" position={Position.Right} style={{ opacity: 0 }} />
|
|
86
|
+
</div>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const pipelineNodeTypes = { cronPipelineNode: CronPipelineNode }
|
|
91
|
+
|
|
92
|
+
/* ─── Agent colors ─────────────────────────────────────────────── */
|
|
93
|
+
|
|
94
|
+
const AGENT_COLORS: Record<string, string> = {
|
|
95
|
+
pulse: "#6366f1",
|
|
96
|
+
herald: "#f59e0b",
|
|
97
|
+
robin: "#10b981",
|
|
98
|
+
lumen: "#3b82f6",
|
|
99
|
+
echo: "#8b5cf6",
|
|
100
|
+
spark: "#f97316",
|
|
101
|
+
scribe: "#14b8a6",
|
|
102
|
+
kaze: "#ec4899",
|
|
103
|
+
jarvis: "#ef4444",
|
|
104
|
+
maven: "#84cc16",
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* ─── Layout builder ──────────────────────────────────────────── */
|
|
108
|
+
|
|
109
|
+
function buildPipelineLayout(crons: CronJob[]): { nodes: Node[]; edges: Edge[] } {
|
|
110
|
+
const cronMap = new Map(crons.map(c => [c.name, c]))
|
|
111
|
+
const nodes: Node[] = []
|
|
112
|
+
const edges: Edge[] = []
|
|
113
|
+
const placed = new Set<string>()
|
|
114
|
+
|
|
115
|
+
let groupY = 0
|
|
116
|
+
|
|
117
|
+
for (const pipeline of PIPELINES) {
|
|
118
|
+
// Group label node
|
|
119
|
+
nodes.push({
|
|
120
|
+
id: `label-${pipeline.name}`,
|
|
121
|
+
type: "default",
|
|
122
|
+
data: { label: pipeline.name },
|
|
123
|
+
position: { x: 0, y: groupY },
|
|
124
|
+
selectable: false,
|
|
125
|
+
draggable: false,
|
|
126
|
+
style: {
|
|
127
|
+
background: "transparent",
|
|
128
|
+
border: "none",
|
|
129
|
+
fontSize: 13,
|
|
130
|
+
fontWeight: 700,
|
|
131
|
+
color: "var(--text-secondary)",
|
|
132
|
+
padding: 0,
|
|
133
|
+
width: 200,
|
|
134
|
+
},
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
groupY += 36
|
|
138
|
+
|
|
139
|
+
// Determine node positions using topological ordering
|
|
140
|
+
// Collect unique job names in this pipeline preserving dependency order
|
|
141
|
+
const jobNames: string[] = []
|
|
142
|
+
for (const edge of pipeline.edges) {
|
|
143
|
+
if (!jobNames.includes(edge.from)) jobNames.push(edge.from)
|
|
144
|
+
if (!jobNames.includes(edge.to)) jobNames.push(edge.to)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Assign columns by dependency depth
|
|
148
|
+
const depth = new Map<string, number>()
|
|
149
|
+
for (const name of jobNames) depth.set(name, 0)
|
|
150
|
+
// Iterate to propagate depths
|
|
151
|
+
for (let pass = 0; pass < jobNames.length; pass++) {
|
|
152
|
+
for (const edge of pipeline.edges) {
|
|
153
|
+
const fromD = depth.get(edge.from) || 0
|
|
154
|
+
const toD = depth.get(edge.to) || 0
|
|
155
|
+
if (fromD + 1 > toD) depth.set(edge.to, fromD + 1)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Group by depth for vertical stacking
|
|
160
|
+
const byDepth = new Map<number, string[]>()
|
|
161
|
+
for (const [name, d] of depth) {
|
|
162
|
+
const arr = byDepth.get(d) || []
|
|
163
|
+
arr.push(name)
|
|
164
|
+
byDepth.set(d, arr)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const maxDepth = Math.max(...Array.from(byDepth.keys()), 0)
|
|
168
|
+
const colSpacing = 280
|
|
169
|
+
const rowSpacing = 80
|
|
170
|
+
|
|
171
|
+
for (let d = 0; d <= maxDepth; d++) {
|
|
172
|
+
const namesAtDepth = byDepth.get(d) || []
|
|
173
|
+
namesAtDepth.forEach((name, i) => {
|
|
174
|
+
const cron = cronMap.get(name)
|
|
175
|
+
const nodeId = `${pipeline.name}::${name}`
|
|
176
|
+
placed.add(name)
|
|
177
|
+
|
|
178
|
+
nodes.push({
|
|
179
|
+
id: nodeId,
|
|
180
|
+
type: "cronPipelineNode",
|
|
181
|
+
data: {
|
|
182
|
+
name,
|
|
183
|
+
schedule: cron?.scheduleDescription || "—",
|
|
184
|
+
status: cron?.status || "idle",
|
|
185
|
+
deliveryTo: cron?.delivery?.to || null,
|
|
186
|
+
color: AGENT_COLORS[cron?.agentId || ""] || "var(--text-secondary)",
|
|
187
|
+
} as Record<string, unknown>,
|
|
188
|
+
position: { x: d * colSpacing + 20, y: groupY + i * rowSpacing },
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Edges
|
|
194
|
+
for (const pEdge of pipeline.edges) {
|
|
195
|
+
const sourceId = `${pipeline.name}::${pEdge.from}`
|
|
196
|
+
const targetId = `${pipeline.name}::${pEdge.to}`
|
|
197
|
+
const sourceCron = cronMap.get(pEdge.from)
|
|
198
|
+
const isErrored = sourceCron?.status === "error"
|
|
199
|
+
|
|
200
|
+
edges.push({
|
|
201
|
+
id: `${sourceId}→${targetId}`,
|
|
202
|
+
source: sourceId,
|
|
203
|
+
target: targetId,
|
|
204
|
+
type: "smoothstep",
|
|
205
|
+
label: pEdge.artifact,
|
|
206
|
+
labelStyle: { fontSize: 9, fill: "var(--text-muted)" },
|
|
207
|
+
style: {
|
|
208
|
+
stroke: isErrored ? "#ef4444" : "var(--accent, #6366f1)",
|
|
209
|
+
strokeWidth: 1.5,
|
|
210
|
+
strokeDasharray: isErrored ? "6 4" : undefined,
|
|
211
|
+
opacity: isErrored ? 0.7 : 1,
|
|
212
|
+
},
|
|
213
|
+
animated: !isErrored,
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Advance Y for next group
|
|
218
|
+
const maxNodesPerCol = Math.max(
|
|
219
|
+
...Array.from(byDepth.values()).map(arr => arr.length),
|
|
220
|
+
1
|
|
221
|
+
)
|
|
222
|
+
groupY += maxNodesPerCol * rowSpacing + 40
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return { nodes, edges }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* ─── Standalone crons card grid ─────────────────────────────── */
|
|
229
|
+
|
|
230
|
+
function StandaloneCrons({ crons }: { crons: CronJob[] }) {
|
|
231
|
+
const pipelineNames = getAllPipelineJobNames()
|
|
232
|
+
const standalone = crons.filter(c => !pipelineNames.has(c.name))
|
|
233
|
+
|
|
234
|
+
if (standalone.length === 0) return null
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<div style={{ marginTop: 24 }}>
|
|
238
|
+
<div
|
|
239
|
+
style={{
|
|
240
|
+
fontSize: 13,
|
|
241
|
+
fontWeight: 700,
|
|
242
|
+
color: "var(--text-secondary)",
|
|
243
|
+
marginBottom: 12,
|
|
244
|
+
}}
|
|
245
|
+
>
|
|
246
|
+
Standalone Crons ({standalone.length})
|
|
247
|
+
</div>
|
|
248
|
+
<div
|
|
249
|
+
style={{
|
|
250
|
+
display: "grid",
|
|
251
|
+
gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))",
|
|
252
|
+
gap: 10,
|
|
253
|
+
}}
|
|
254
|
+
>
|
|
255
|
+
{standalone.map(cron => {
|
|
256
|
+
const statusColor = cron.status === "ok" ? "#22c55e" : cron.status === "error" ? "#ef4444" : "#a1a1aa"
|
|
257
|
+
const color = AGENT_COLORS[cron.agentId || ""] || "var(--text-secondary)"
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<div
|
|
261
|
+
key={cron.id}
|
|
262
|
+
style={{
|
|
263
|
+
background: "var(--material-regular)",
|
|
264
|
+
borderRadius: "var(--radius-md, 10px)",
|
|
265
|
+
border: "1px solid var(--separator)",
|
|
266
|
+
borderLeft: `3px solid ${color}`,
|
|
267
|
+
padding: "10px 14px",
|
|
268
|
+
}}
|
|
269
|
+
>
|
|
270
|
+
<div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 2 }}>
|
|
271
|
+
<div style={{ width: 6, height: 6, borderRadius: "50%", background: statusColor, flexShrink: 0 }} />
|
|
272
|
+
<div style={{ fontSize: 12, fontWeight: 600, color: "var(--text-primary)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
|
273
|
+
{cron.name}
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
<div style={{ fontSize: 10, color: "var(--text-secondary)" }}>
|
|
277
|
+
{cron.scheduleDescription || "—"}
|
|
278
|
+
</div>
|
|
279
|
+
{cron.lastDurationMs != null && (
|
|
280
|
+
<div style={{ fontSize: 10, color: "var(--text-muted)", marginTop: 2 }}>
|
|
281
|
+
{formatDuration(cron.lastDurationMs)}
|
|
282
|
+
</div>
|
|
283
|
+
)}
|
|
284
|
+
</div>
|
|
285
|
+
)
|
|
286
|
+
})}
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* ─── Main component ──────────────────────────────────────────── */
|
|
293
|
+
|
|
294
|
+
export function PipelineGraph({ crons }: PipelineGraphProps) {
|
|
295
|
+
const layout = useMemo(() => buildPipelineLayout(crons), [crons])
|
|
296
|
+
const [nodes, setNodes, onNodesChange] = useNodesState(layout.nodes)
|
|
297
|
+
const [edges, setEdges, onEdgesChange] = useEdgesState(layout.edges)
|
|
298
|
+
|
|
299
|
+
useEffect(() => {
|
|
300
|
+
const { nodes: n, edges: e } = buildPipelineLayout(crons)
|
|
301
|
+
setNodes(n)
|
|
302
|
+
setEdges(e)
|
|
303
|
+
}, [crons, setNodes, setEdges])
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<div>
|
|
307
|
+
<div style={{ height: 500, border: "1px solid var(--separator)", borderRadius: "var(--radius-md, 10px)", overflow: "hidden" }}>
|
|
308
|
+
<ReactFlow
|
|
309
|
+
nodes={nodes}
|
|
310
|
+
edges={edges}
|
|
311
|
+
onNodesChange={onNodesChange}
|
|
312
|
+
onEdgesChange={onEdgesChange}
|
|
313
|
+
nodeTypes={pipelineNodeTypes}
|
|
314
|
+
connectionLineType={ConnectionLineType.SmoothStep}
|
|
315
|
+
fitView
|
|
316
|
+
fitViewOptions={{ padding: 0.3 }}
|
|
317
|
+
minZoom={0.3}
|
|
318
|
+
maxZoom={2}
|
|
319
|
+
proOptions={{ hideAttribution: true }}
|
|
320
|
+
>
|
|
321
|
+
<Controls position="bottom-left" style={{ left: 8, bottom: 8 }} />
|
|
322
|
+
</ReactFlow>
|
|
323
|
+
</div>
|
|
324
|
+
<StandaloneCrons crons={crons} />
|
|
325
|
+
</div>
|
|
326
|
+
)
|
|
327
|
+
}
|