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,209 @@
1
+ import {
2
+ Heading,
3
+ SubHeading,
4
+ Paragraph,
5
+ CodeBlock,
6
+ InlineCode,
7
+ Table,
8
+ BulletList,
9
+ NumberedList,
10
+ Callout,
11
+ } from "./DocSection";
12
+
13
+ export function AgentsSection() {
14
+ return (
15
+ <>
16
+ <Heading>Agents</Heading>
17
+ <Paragraph>
18
+ ClawPort ships with a default agent registry at{" "}
19
+ <InlineCode>lib/agents.json</InlineCode>. This is a working example
20
+ showing a full team hierarchy. It works out of the box if your OpenClaw
21
+ workspace has matching agent SOUL files.
22
+ </Paragraph>
23
+
24
+ <SubHeading>Using Your Own Agents</SubHeading>
25
+ <Paragraph>
26
+ To define your own agent team, create a file at:
27
+ </Paragraph>
28
+ <CodeBlock>{`$WORKSPACE_PATH/clawport/agents.json`}</CodeBlock>
29
+ <Paragraph>
30
+ ClawPort checks for this file on every request. If it exists, it
31
+ replaces the bundled registry entirely. If it's missing or contains
32
+ invalid JSON, the bundled default is used as a fallback.
33
+ </Paragraph>
34
+
35
+ <SubHeading>Agent Entry Format</SubHeading>
36
+ <CodeBlock title="agents.json">
37
+ {`[
38
+ {
39
+ "id": "my-agent",
40
+ "name": "My Agent",
41
+ "title": "What this agent does",
42
+ "reportsTo": null,
43
+ "directReports": [],
44
+ "soulPath": "agents/my-agent/SOUL.md",
45
+ "voiceId": null,
46
+ "color": "#06b6d4",
47
+ "emoji": "\u{1F916}",
48
+ "tools": ["read", "write"],
49
+ "memoryPath": null,
50
+ "description": "One-liner about this agent."
51
+ }
52
+ ]`}
53
+ </CodeBlock>
54
+
55
+ <SubHeading>Field Reference</SubHeading>
56
+ <Table
57
+ headers={["Field", "Type", "Description"]}
58
+ rows={[
59
+ [
60
+ <InlineCode key="id">id</InlineCode>,
61
+ "string",
62
+ 'Unique slug for the agent (e.g., "vera")',
63
+ ],
64
+ [
65
+ <InlineCode key="name">name</InlineCode>,
66
+ "string",
67
+ 'Display name (e.g., "VERA")',
68
+ ],
69
+ [
70
+ <InlineCode key="title">title</InlineCode>,
71
+ "string",
72
+ 'Role title (e.g., "Chief Strategy Officer")',
73
+ ],
74
+ [
75
+ <InlineCode key="rt">reportsTo</InlineCode>,
76
+ "string | null",
77
+ "Parent agent id for the org chart. null for the root.",
78
+ ],
79
+ [
80
+ <InlineCode key="dr">directReports</InlineCode>,
81
+ "string[]",
82
+ "Array of child agent ids",
83
+ ],
84
+ [
85
+ <InlineCode key="sp">soulPath</InlineCode>,
86
+ "string | null",
87
+ "Path to the agent's SOUL.md, relative to WORKSPACE_PATH",
88
+ ],
89
+ [
90
+ <InlineCode key="vi">voiceId</InlineCode>,
91
+ "string | null",
92
+ "ElevenLabs voice ID (requires ELEVENLABS_API_KEY)",
93
+ ],
94
+ [
95
+ <InlineCode key="co">color</InlineCode>,
96
+ "string",
97
+ "Hex color for the agent's node in the Org Map",
98
+ ],
99
+ [
100
+ <InlineCode key="em">emoji</InlineCode>,
101
+ "string",
102
+ "Emoji shown as the agent's avatar",
103
+ ],
104
+ [
105
+ <InlineCode key="to">tools</InlineCode>,
106
+ "string[]",
107
+ "List of tools this agent has access to",
108
+ ],
109
+ [
110
+ <InlineCode key="mp">memoryPath</InlineCode>,
111
+ "string | null",
112
+ "Path to agent-specific memory (relative to WORKSPACE_PATH)",
113
+ ],
114
+ [
115
+ <InlineCode key="de">description</InlineCode>,
116
+ "string",
117
+ "One-line description shown in the UI",
118
+ ],
119
+ ]}
120
+ />
121
+
122
+ <SubHeading>Hierarchy Rules</SubHeading>
123
+ <BulletList
124
+ items={[
125
+ <>
126
+ Exactly one agent should have{" "}
127
+ <InlineCode>{"\"reportsTo\": null"}</InlineCode> -- this is your
128
+ root/orchestrator node.
129
+ </>,
130
+ <>
131
+ <InlineCode>directReports</InlineCode> should be consistent with{" "}
132
+ <InlineCode>reportsTo</InlineCode>. If agent B reports to agent A,
133
+ then A's directReports should include B's id.
134
+ </>,
135
+ "The Org Map uses these relationships to build the org chart automatically.",
136
+ ]}
137
+ />
138
+
139
+ <SubHeading>Example: Minimal Two-Agent Setup</SubHeading>
140
+ <CodeBlock title="agents.json">
141
+ {`[
142
+ {
143
+ "id": "boss",
144
+ "name": "Boss",
145
+ "title": "Orchestrator",
146
+ "reportsTo": null,
147
+ "directReports": ["worker"],
148
+ "soulPath": "SOUL.md",
149
+ "voiceId": null,
150
+ "color": "#f5c518",
151
+ "emoji": "\u{1F451}",
152
+ "tools": ["read", "write", "exec", "message"],
153
+ "memoryPath": null,
154
+ "description": "Top-level orchestrator."
155
+ },
156
+ {
157
+ "id": "worker",
158
+ "name": "Worker",
159
+ "title": "Task Runner",
160
+ "reportsTo": "boss",
161
+ "directReports": [],
162
+ "soulPath": "agents/worker/SOUL.md",
163
+ "voiceId": null,
164
+ "color": "#22c55e",
165
+ "emoji": "\u{2699}\u{FE0F}",
166
+ "tools": ["read", "write"],
167
+ "memoryPath": null,
168
+ "description": "Handles assigned tasks."
169
+ }
170
+ ]`}
171
+ </CodeBlock>
172
+
173
+ <SubHeading>Registry Resolution</SubHeading>
174
+ <NumberedList
175
+ items={[
176
+ <>
177
+ <InlineCode>loadRegistry()</InlineCode> checks{" "}
178
+ <InlineCode>$WORKSPACE_PATH/clawport/agents.json</InlineCode> first
179
+ (user override).
180
+ </>,
181
+ <>
182
+ Falls back to bundled <InlineCode>lib/agents.json</InlineCode> if
183
+ the workspace file is missing or has invalid JSON.
184
+ </>,
185
+ <>
186
+ <InlineCode>lib/agents.ts</InlineCode> merges in SOUL.md content
187
+ from each agent's <InlineCode>soulPath</InlineCode>.
188
+ </>,
189
+ "The result is the full agent list used by all pages.",
190
+ ]}
191
+ />
192
+
193
+ <Callout type="tip">
194
+ You can add a new agent without editing any source code -- just update
195
+ your workspace <InlineCode>agents.json</InlineCode>. The agent will
196
+ automatically appear in the Org Map, Chat, and Detail pages.
197
+ </Callout>
198
+
199
+ <SubHeading>Agent Display Overrides</SubHeading>
200
+ <Paragraph>
201
+ Each agent can have per-agent emoji and/or profile image overrides via
202
+ the Settings page. These are stored in{" "}
203
+ <InlineCode>ClawPortSettings.agentOverrides</InlineCode> keyed by agent ID.
204
+ The <InlineCode>getAgentDisplay()</InlineCode> function resolves the
205
+ effective visual for each agent, considering overrides.
206
+ </Paragraph>
207
+ </>
208
+ );
209
+ }
@@ -0,0 +1,256 @@
1
+ import {
2
+ Heading,
3
+ SubHeading,
4
+ Paragraph,
5
+ CodeBlock,
6
+ InlineCode,
7
+ Table,
8
+ Callout,
9
+ InfoCard,
10
+ } from "./DocSection";
11
+
12
+ export function ApiReferenceSection() {
13
+ return (
14
+ <>
15
+ <Heading>API Reference</Heading>
16
+ <Paragraph>
17
+ All API routes are Next.js App Router route handlers under{" "}
18
+ <InlineCode>app/api/</InlineCode>. The base URL during development is{" "}
19
+ <InlineCode>http://localhost:3000</InlineCode>.
20
+ </Paragraph>
21
+
22
+ <InfoCard title="Error Format">
23
+ <Paragraph>
24
+ All error responses share a consistent JSON shape:
25
+ </Paragraph>
26
+ <CodeBlock>{`{ "error": "Human-readable error message" }`}</CodeBlock>
27
+ </InfoCard>
28
+
29
+ <SubHeading>Route Summary</SubHeading>
30
+ <Table
31
+ headers={["Method", "Endpoint", "Gateway", "Content-Type"]}
32
+ rows={[
33
+ [
34
+ "GET",
35
+ <InlineCode key="1">/api/agents</InlineCode>,
36
+ "No",
37
+ "application/json",
38
+ ],
39
+ [
40
+ "POST",
41
+ <InlineCode key="2">/api/chat/[id]</InlineCode>,
42
+ "Yes",
43
+ "text/event-stream",
44
+ ],
45
+ [
46
+ "GET",
47
+ <InlineCode key="3">/api/crons</InlineCode>,
48
+ "No",
49
+ "application/json",
50
+ ],
51
+ [
52
+ "GET",
53
+ <InlineCode key="4">/api/cron-runs</InlineCode>,
54
+ "No",
55
+ "application/json",
56
+ ],
57
+ [
58
+ "GET",
59
+ <InlineCode key="5">/api/memory</InlineCode>,
60
+ "No",
61
+ "application/json",
62
+ ],
63
+ [
64
+ "POST",
65
+ <InlineCode key="6">/api/tts</InlineCode>,
66
+ "Yes",
67
+ "audio/mpeg",
68
+ ],
69
+ [
70
+ "POST",
71
+ <InlineCode key="7">/api/transcribe</InlineCode>,
72
+ "Yes",
73
+ "application/json",
74
+ ],
75
+ [
76
+ "POST",
77
+ <InlineCode key="8">/api/kanban/chat/[id]</InlineCode>,
78
+ "Yes",
79
+ "text/event-stream",
80
+ ],
81
+ [
82
+ "GET",
83
+ <InlineCode key="9">/api/kanban/chat-history/[ticketId]</InlineCode>,
84
+ "No",
85
+ "application/json",
86
+ ],
87
+ [
88
+ "POST",
89
+ <InlineCode key="10">/api/kanban/chat-history/[ticketId]</InlineCode>,
90
+ "No",
91
+ "application/json",
92
+ ],
93
+ ]}
94
+ />
95
+
96
+ {/* ── GET /api/agents ────────────────────────────────────── */}
97
+ <SubHeading>GET /api/agents</SubHeading>
98
+ <Paragraph>
99
+ Returns the full list of registered agents, each with their SOUL.md
100
+ content loaded from the filesystem. No parameters required.
101
+ </Paragraph>
102
+ <Table
103
+ headers={["Field", "Type", "Description"]}
104
+ rows={[
105
+ [<InlineCode key="id">id</InlineCode>, "string", "Slug identifier"],
106
+ [<InlineCode key="n">name</InlineCode>, "string", "Display name"],
107
+ [<InlineCode key="t">title</InlineCode>, "string", "Role title"],
108
+ [
109
+ <InlineCode key="s">soul</InlineCode>,
110
+ "string | null",
111
+ "Full SOUL.md content, or null if file not found",
112
+ ],
113
+ [
114
+ <InlineCode key="c">crons</InlineCode>,
115
+ "CronJob[]",
116
+ "Always [] from this endpoint (populated client-side)",
117
+ ],
118
+ ]}
119
+ />
120
+
121
+ {/* ── POST /api/chat/[id] ────────────────────────────────── */}
122
+ <SubHeading>POST /api/chat/[id]</SubHeading>
123
+ <Paragraph>
124
+ Send a chat message to an agent and receive a streaming response. Has
125
+ two pipelines depending on whether the latest user message contains
126
+ images.
127
+ </Paragraph>
128
+ <Table
129
+ headers={["Field", "Type", "Required", "Description"]}
130
+ rows={[
131
+ [
132
+ <InlineCode key="m">messages</InlineCode>,
133
+ "ApiMessage[]",
134
+ "Yes",
135
+ "Conversation history",
136
+ ],
137
+ [
138
+ <InlineCode key="o">operatorName</InlineCode>,
139
+ "string",
140
+ "No",
141
+ 'Name shown to the agent. Defaults to "Operator"',
142
+ ],
143
+ ]}
144
+ />
145
+ <Paragraph>
146
+ <strong style={{ color: "var(--text-primary)" }}>Pipeline 1 (Text):</strong>{" "}
147
+ Streaming chat completion via the gateway. Response is SSE with{" "}
148
+ <InlineCode>{"data: {\"content\":\"token\"}"}</InlineCode> frames.
149
+ </Paragraph>
150
+ <Paragraph>
151
+ <strong style={{ color: "var(--text-primary)" }}>Pipeline 2 (Vision):</strong>{" "}
152
+ When the latest message contains image_url content. Uses CLI chat.send +
153
+ chat.history polling. Complete response arrives in a single SSE frame.
154
+ </Paragraph>
155
+
156
+ {/* ── GET /api/crons ─────────────────────────────────────── */}
157
+ <SubHeading>GET /api/crons</SubHeading>
158
+ <Paragraph>
159
+ Returns all cron jobs registered with OpenClaw, enriched with schedule
160
+ descriptions, agent ownership, and delivery config. Runs{" "}
161
+ <InlineCode>openclaw cron list --json</InlineCode> via the CLI.
162
+ </Paragraph>
163
+
164
+ {/* ── GET /api/cron-runs ─────────────────────────────────── */}
165
+ <SubHeading>GET /api/cron-runs</SubHeading>
166
+ <Paragraph>
167
+ Returns cron run history parsed from JSONL log files on the filesystem.
168
+ Results sorted newest-first. Optional{" "}
169
+ <InlineCode>jobId</InlineCode> query parameter filters to a specific
170
+ job.
171
+ </Paragraph>
172
+
173
+ {/* ── GET /api/memory ────────────────────────────────────── */}
174
+ <SubHeading>GET /api/memory</SubHeading>
175
+ <Paragraph>
176
+ Returns the contents of key memory files from the workspace: long-term
177
+ memory, team memory, team intel, and the daily logs for today and
178
+ yesterday. Only files that exist are included in the response.
179
+ </Paragraph>
180
+
181
+ {/* ── POST /api/tts ──────────────────────────────────────── */}
182
+ <SubHeading>POST /api/tts</SubHeading>
183
+ <Paragraph>
184
+ Converts text to speech audio using the OpenClaw gateway's TTS endpoint.
185
+ </Paragraph>
186
+ <Table
187
+ headers={["Field", "Type", "Required", "Description"]}
188
+ rows={[
189
+ [
190
+ <InlineCode key="t">text</InlineCode>,
191
+ "string",
192
+ "Yes",
193
+ "The text to synthesize",
194
+ ],
195
+ [
196
+ <InlineCode key="v">voice</InlineCode>,
197
+ "string",
198
+ "No",
199
+ 'Voice identifier. Defaults to "alloy"',
200
+ ],
201
+ ]}
202
+ />
203
+
204
+ {/* ── POST /api/transcribe ───────────────────────────────── */}
205
+ <SubHeading>POST /api/transcribe</SubHeading>
206
+ <Paragraph>
207
+ Transcribes audio to text using the Whisper endpoint. Request body is
208
+ multipart form data with an <InlineCode>audio</InlineCode> file field.
209
+ </Paragraph>
210
+
211
+ {/* ── SSE Protocol ───────────────────────────────────────── */}
212
+ <SubHeading>SSE Stream Protocol</SubHeading>
213
+ <Paragraph>
214
+ All streaming chat endpoints use the same Server-Sent Events protocol:
215
+ </Paragraph>
216
+ <CodeBlock>
217
+ {`data: {"content":"Hello"}
218
+
219
+ data: {"content":" there"}
220
+
221
+ data: [DONE]`}
222
+ </CodeBlock>
223
+ <Callout type="note">
224
+ Content-Type is <InlineCode>text/event-stream</InlineCode> with{" "}
225
+ <InlineCode>Cache-Control: no-cache</InlineCode>. If a stream error
226
+ occurs mid-response, the server sends [DONE] and closes the connection.
227
+ </Callout>
228
+
229
+ <SubHeading>Client-Side Consumption</SubHeading>
230
+ <CodeBlock title="example">
231
+ {`const reader = response.body.getReader()
232
+ const decoder = new TextDecoder()
233
+ let fullText = ''
234
+
235
+ while (true) {
236
+ const { done, value } = await reader.read()
237
+ if (done) break
238
+
239
+ const chunk = decoder.decode(value, { stream: true })
240
+ const lines = chunk.split('\\n')
241
+
242
+ for (const line of lines) {
243
+ if (line.startsWith('data: ')) {
244
+ const payload = line.slice(6)
245
+ if (payload === '[DONE]') return fullText
246
+ try {
247
+ const { content } = JSON.parse(payload)
248
+ fullText += content
249
+ } catch { /* skip malformed frames */ }
250
+ }
251
+ }
252
+ }`}
253
+ </CodeBlock>
254
+ </>
255
+ );
256
+ }
@@ -0,0 +1,221 @@
1
+ import {
2
+ Heading,
3
+ SubHeading,
4
+ Paragraph,
5
+ CodeBlock,
6
+ InlineCode,
7
+ Table,
8
+ BulletList,
9
+ Callout,
10
+ InfoCard,
11
+ } from "./DocSection";
12
+
13
+ export function ArchitectureSection() {
14
+ return (
15
+ <>
16
+ <Heading>Architecture</Heading>
17
+ <Paragraph>
18
+ ClawPort is a Next.js 16 dashboard for managing OpenClaw AI agents. It
19
+ provides an org chart (Org Map), direct agent chat with multimodal
20
+ support, cron monitoring, kanban task board, and memory browsing. All AI
21
+ calls route through the OpenClaw gateway -- no separate API keys needed.
22
+ </Paragraph>
23
+
24
+ <SubHeading>Tech Stack</SubHeading>
25
+ <BulletList
26
+ items={[
27
+ "Next.js 16.1.6 (App Router, Turbopack)",
28
+ "React 19.2.3, TypeScript 5",
29
+ "Tailwind CSS 4 with CSS custom properties for theming",
30
+ "Vitest 4 with jsdom environment (17 suites, 288 tests)",
31
+ "OpenAI SDK (routed to Claude via OpenClaw gateway at localhost:18789)",
32
+ "React Flow (@xyflow/react) for org chart",
33
+ ]}
34
+ />
35
+
36
+ <SubHeading>Agent Registry Resolution</SubHeading>
37
+ <CodeBlock>
38
+ {`loadRegistry() checks:
39
+ 1. $WORKSPACE_PATH/clawport/agents.json (user override)
40
+ 2. Bundled lib/agents.json (default)`}
41
+ </CodeBlock>
42
+ <Paragraph>
43
+ <InlineCode>lib/agents-registry.ts</InlineCode> exports{" "}
44
+ <InlineCode>loadRegistry()</InlineCode>.{" "}
45
+ <InlineCode>lib/agents.ts</InlineCode> calls it to build the full agent
46
+ list (merging in SOUL.md content from the workspace). Users customize
47
+ their agent team by dropping an{" "}
48
+ <InlineCode>agents.json</InlineCode> into their workspace -- no source
49
+ edits needed.
50
+ </Paragraph>
51
+
52
+ <SubHeading>Chat Pipeline (Text)</SubHeading>
53
+ <CodeBlock>
54
+ {`Client -> POST /api/chat/[id] -> OpenAI SDK -> localhost:18789/v1/chat/completions -> Claude
55
+ (streaming SSE response)`}
56
+ </CodeBlock>
57
+
58
+ <SubHeading>Chat Pipeline (Images/Vision)</SubHeading>
59
+ <Paragraph>
60
+ The gateway's HTTP endpoint strips image_url content. Vision uses the
61
+ CLI agent pipeline:
62
+ </Paragraph>
63
+ <CodeBlock>
64
+ {`Client resizes image to 1200px max (Canvas API)
65
+ -> base64 data URL in message
66
+ -> POST /api/chat/[id]
67
+ -> Detects image in LATEST user message only (not history)
68
+ -> execFile: openclaw gateway call chat.send --params <json> --token <token>
69
+ -> Polls: openclaw gateway call chat.history every 2s
70
+ -> Matches response by timestamp >= sendTs
71
+ -> Returns assistant text via SSE`}
72
+ </CodeBlock>
73
+
74
+ <InfoCard title="Design Decisions">
75
+ <BulletList
76
+ items={[
77
+ <>
78
+ <strong style={{ color: "var(--text-primary)" }}>
79
+ Why send-then-poll?
80
+ </strong>{" "}
81
+ chat.send is async -- it returns immediately. We poll chat.history
82
+ until the assistant's response appears.
83
+ </>,
84
+ <>
85
+ <strong style={{ color: "var(--text-primary)" }}>
86
+ Why CLI and not WebSocket?
87
+ </strong>{" "}
88
+ The gateway WebSocket requires device keypair signing for
89
+ operator.write scope. The CLI has the device keys; custom clients
90
+ don't.
91
+ </>,
92
+ <>
93
+ <strong style={{ color: "var(--text-primary)" }}>
94
+ Why resize to 1200px?
95
+ </strong>{" "}
96
+ macOS ARG_MAX is 1MB. Unresized photos can produce multi-MB base64
97
+ that exceeds CLI argument limits (E2BIG error).
98
+ </>,
99
+ ]}
100
+ />
101
+ </InfoCard>
102
+
103
+ <SubHeading>Voice Message Pipeline</SubHeading>
104
+ <CodeBlock>
105
+ {`Browser MediaRecorder (webm/opus or mp4)
106
+ -> AudioContext AnalyserNode captures waveform (40-60 samples)
107
+ -> Stop -> audioBlob + waveform data
108
+ -> POST /api/transcribe (Whisper via gateway)
109
+ -> Transcription text sent as message content
110
+ -> Audio data URL + waveform stored in message for playback`}
111
+ </CodeBlock>
112
+
113
+ <SubHeading>operatorName Flow</SubHeading>
114
+ <CodeBlock>
115
+ {`OnboardingWizard / Settings page
116
+ -> ClawPortSettings.operatorName (localStorage)
117
+ -> settings-provider.tsx (React context)
118
+ -> NavLinks.tsx (dynamic initials + display name)
119
+ -> ConversationView.tsx (sends operatorName in POST body)
120
+ -> /api/chat/[id] route (injects into system prompt)`}
121
+ </CodeBlock>
122
+ <Callout type="note">
123
+ No hardcoded operator names anywhere. Falls back to "Operator" / "??"
124
+ when unset.
125
+ </Callout>
126
+
127
+ <SubHeading>Directory Structure</SubHeading>
128
+ <CodeBlock>
129
+ {`app/
130
+ page.tsx -- Org Map (React Flow org chart)
131
+ chat/page.tsx -- Multi-agent messenger
132
+ agents/[id]/page.tsx -- Agent detail profile
133
+ kanban/page.tsx -- Task board
134
+ crons/page.tsx -- Cron job monitor
135
+ memory/page.tsx -- Memory file browser
136
+ settings/page.tsx -- ClawPort personalization
137
+ docs/page.tsx -- Documentation browser
138
+ api/
139
+ agents/route.ts -- GET agents from registry
140
+ chat/[id]/route.ts -- POST chat (text + vision)
141
+ crons/route.ts -- GET crons via CLI
142
+ memory/route.ts -- GET memory files
143
+ tts/route.ts -- POST text-to-speech
144
+ transcribe/route.ts -- POST audio transcription
145
+
146
+ components/
147
+ OrgMap.tsx -- React Flow graph with auto-layout
148
+ AgentNode.tsx -- Custom node for the org chart
149
+ Sidebar.tsx -- Desktop navigation sidebar
150
+ MobileSidebar.tsx -- Mobile hamburger menu
151
+ NavLinks.tsx -- Sidebar nav links
152
+ ThemeToggle.tsx -- Theme switcher (5 themes)
153
+ GlobalSearch.tsx -- Cmd+K agent search
154
+ chat/ -- Chat components
155
+ kanban/ -- Kanban components
156
+ crons/ -- Cron components
157
+ docs/ -- Documentation components
158
+
159
+ lib/
160
+ agents.ts -- Agent registry + SOUL.md reader
161
+ agents-registry.ts -- Registry loader
162
+ anthropic.ts -- Vision pipeline (send + poll)
163
+ conversations.ts -- Conversation store (localStorage)
164
+ settings.ts -- ClawPortSettings type + persistence
165
+ themes.ts -- Theme definitions
166
+ types.ts -- Shared TypeScript types`}
167
+ </CodeBlock>
168
+
169
+ <SubHeading>Key Libraries</SubHeading>
170
+ <Table
171
+ headers={["File", "Purpose"]}
172
+ rows={[
173
+ [
174
+ <InlineCode key="a">lib/agents.ts</InlineCode>,
175
+ "Agent list builder -- calls loadRegistry(), merges SOUL.md",
176
+ ],
177
+ [
178
+ <InlineCode key="ar">lib/agents-registry.ts</InlineCode>,
179
+ "loadRegistry() -- workspace override -> bundled fallback",
180
+ ],
181
+ [
182
+ <InlineCode key="an">lib/anthropic.ts</InlineCode>,
183
+ "Vision pipeline: hasImageContent, sendViaOpenClaw (send + poll), execCli",
184
+ ],
185
+ [
186
+ <InlineCode key="c">lib/conversations.ts</InlineCode>,
187
+ "Conversation store with localStorage persistence",
188
+ ],
189
+ [
190
+ <InlineCode key="e">lib/env.ts</InlineCode>,
191
+ "requireEnv(name) -- safe env var access with clear errors",
192
+ ],
193
+ [
194
+ <InlineCode key="m">lib/multimodal.ts</InlineCode>,
195
+ "buildApiContent() -- converts Message+Media to OpenAI API format",
196
+ ],
197
+ [
198
+ <InlineCode key="s">lib/settings.ts</InlineCode>,
199
+ "ClawPortSettings type, loadSettings(), saveSettings() (localStorage)",
200
+ ],
201
+ [
202
+ <InlineCode key="v">lib/validation.ts</InlineCode>,
203
+ "validateChatMessages() -- validates text + multimodal content arrays",
204
+ ],
205
+ ]}
206
+ />
207
+
208
+ <SubHeading>Conventions</SubHeading>
209
+ <BulletList
210
+ items={[
211
+ "No external charting/media libraries -- native Web APIs (Canvas, MediaRecorder, AudioContext)",
212
+ "Base64 data URLs for all persisted media (not blob URLs)",
213
+ "CSS custom properties for theming -- no Tailwind color classes directly",
214
+ "Inline styles referencing CSS vars (e.g., style={{ color: 'var(--text-primary)' }})",
215
+ "Tests colocated with source: lib/foo.ts + lib/foo.test.ts",
216
+ "Call requireEnv() inside functions, not at module top level",
217
+ ]}
218
+ />
219
+ </>
220
+ );
221
+ }