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.
Files changed (132) hide show
  1. package/.env.example +35 -0
  2. package/BRANDING.md +131 -0
  3. package/CLAUDE.md +252 -0
  4. package/README.md +262 -0
  5. package/SETUP.md +337 -0
  6. package/app/agents/[id]/page.tsx +727 -0
  7. package/app/api/agents/route.ts +12 -0
  8. package/app/api/chat/[id]/route.ts +139 -0
  9. package/app/api/cron-runs/route.ts +13 -0
  10. package/app/api/crons/route.ts +12 -0
  11. package/app/api/kanban/chat/[id]/route.ts +119 -0
  12. package/app/api/kanban/chat-history/[ticketId]/route.ts +36 -0
  13. package/app/api/memory/route.ts +12 -0
  14. package/app/api/transcribe/route.ts +37 -0
  15. package/app/api/tts/route.ts +42 -0
  16. package/app/chat/[id]/page.tsx +10 -0
  17. package/app/chat/page.tsx +200 -0
  18. package/app/crons/page.tsx +870 -0
  19. package/app/docs/page.tsx +399 -0
  20. package/app/favicon.ico +0 -0
  21. package/app/globals.css +692 -0
  22. package/app/kanban/page.tsx +327 -0
  23. package/app/layout.tsx +45 -0
  24. package/app/memory/page.tsx +685 -0
  25. package/app/page.tsx +817 -0
  26. package/app/providers.tsx +37 -0
  27. package/app/settings/page.tsx +901 -0
  28. package/app/settings-provider.tsx +209 -0
  29. package/components/AgentAvatar.tsx +54 -0
  30. package/components/AgentNode.tsx +122 -0
  31. package/components/Breadcrumbs.tsx +126 -0
  32. package/components/DynamicFavicon.tsx +62 -0
  33. package/components/ErrorState.tsx +97 -0
  34. package/components/FeedView.tsx +494 -0
  35. package/components/GlobalSearch.tsx +571 -0
  36. package/components/GridView.tsx +532 -0
  37. package/components/ManorMap.tsx +157 -0
  38. package/components/MobileSidebar.tsx +251 -0
  39. package/components/NavLinks.tsx +271 -0
  40. package/components/OnboardingWizard.tsx +1067 -0
  41. package/components/Sidebar.tsx +115 -0
  42. package/components/ThemeToggle.tsx +108 -0
  43. package/components/chat/AgentList.tsx +537 -0
  44. package/components/chat/ConversationView.tsx +1047 -0
  45. package/components/chat/FileAttachment.tsx +140 -0
  46. package/components/chat/MediaPreview.tsx +111 -0
  47. package/components/chat/VoiceMessage.tsx +139 -0
  48. package/components/crons/PipelineGraph.tsx +327 -0
  49. package/components/crons/WeeklySchedule.tsx +630 -0
  50. package/components/docs/AgentsSection.tsx +209 -0
  51. package/components/docs/ApiReferenceSection.tsx +256 -0
  52. package/components/docs/ArchitectureSection.tsx +221 -0
  53. package/components/docs/ComponentsSection.tsx +253 -0
  54. package/components/docs/CronSystemSection.tsx +235 -0
  55. package/components/docs/DocSection.tsx +346 -0
  56. package/components/docs/GettingStartedSection.tsx +169 -0
  57. package/components/docs/ThemingSection.tsx +257 -0
  58. package/components/docs/TroubleshootingSection.tsx +200 -0
  59. package/components/kanban/AgentPicker.tsx +321 -0
  60. package/components/kanban/CreateTicketModal.tsx +333 -0
  61. package/components/kanban/KanbanBoard.tsx +70 -0
  62. package/components/kanban/KanbanColumn.tsx +166 -0
  63. package/components/kanban/TicketCard.tsx +245 -0
  64. package/components/kanban/TicketDetailPanel.tsx +850 -0
  65. package/components/ui/badge.tsx +48 -0
  66. package/components/ui/button.tsx +64 -0
  67. package/components/ui/card.tsx +92 -0
  68. package/components/ui/dialog.tsx +158 -0
  69. package/components/ui/scroll-area.tsx +58 -0
  70. package/components/ui/separator.tsx +28 -0
  71. package/components/ui/skeleton.tsx +27 -0
  72. package/components/ui/tabs.tsx +91 -0
  73. package/components/ui/tooltip.tsx +57 -0
  74. package/components.json +23 -0
  75. package/docs/API.md +648 -0
  76. package/docs/COMPONENTS.md +1059 -0
  77. package/docs/THEMING.md +795 -0
  78. package/lib/agents-registry.ts +35 -0
  79. package/lib/agents.json +282 -0
  80. package/lib/agents.test.ts +367 -0
  81. package/lib/agents.ts +32 -0
  82. package/lib/anthropic.test.ts +422 -0
  83. package/lib/anthropic.ts +220 -0
  84. package/lib/api-error.ts +16 -0
  85. package/lib/audio-recorder.test.ts +72 -0
  86. package/lib/audio-recorder.ts +169 -0
  87. package/lib/conversations.test.ts +331 -0
  88. package/lib/conversations.ts +117 -0
  89. package/lib/cron-pipelines.test.ts +69 -0
  90. package/lib/cron-pipelines.ts +58 -0
  91. package/lib/cron-runs.test.ts +118 -0
  92. package/lib/cron-runs.ts +67 -0
  93. package/lib/cron-utils.test.ts +222 -0
  94. package/lib/cron-utils.ts +160 -0
  95. package/lib/crons.test.ts +502 -0
  96. package/lib/crons.ts +114 -0
  97. package/lib/env.test.ts +44 -0
  98. package/lib/env.ts +14 -0
  99. package/lib/kanban/automation.test.ts +245 -0
  100. package/lib/kanban/automation.ts +143 -0
  101. package/lib/kanban/chat-store.test.ts +149 -0
  102. package/lib/kanban/chat-store.ts +81 -0
  103. package/lib/kanban/store.test.ts +238 -0
  104. package/lib/kanban/store.ts +98 -0
  105. package/lib/kanban/types.ts +50 -0
  106. package/lib/kanban/useAgentWork.ts +78 -0
  107. package/lib/memory.ts +45 -0
  108. package/lib/multimodal.test.ts +219 -0
  109. package/lib/multimodal.ts +68 -0
  110. package/lib/pipeline.integration.test.ts +343 -0
  111. package/lib/sanitize.ts +194 -0
  112. package/lib/settings.test.ts +137 -0
  113. package/lib/settings.ts +94 -0
  114. package/lib/styles.ts +24 -0
  115. package/lib/themes.ts +9 -0
  116. package/lib/transcribe.test.ts +141 -0
  117. package/lib/transcribe.ts +111 -0
  118. package/lib/types.ts +66 -0
  119. package/lib/utils.ts +6 -0
  120. package/lib/validation.test.ts +132 -0
  121. package/lib/validation.ts +80 -0
  122. package/next.config.ts +7 -0
  123. package/package.json +56 -0
  124. package/postcss.config.mjs +7 -0
  125. package/public/file.svg +1 -0
  126. package/public/globe.svg +1 -0
  127. package/public/next.svg +1 -0
  128. package/public/vercel.svg +1 -0
  129. package/public/window.svg +1 -0
  130. package/scripts/setup.mjs +215 -0
  131. package/tsconfig.json +34 -0
  132. 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
+ }