@neocode-ai/web 1.1.1

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 (86) hide show
  1. package/README.md +54 -0
  2. package/astro.config.mjs +145 -0
  3. package/config.mjs +14 -0
  4. package/package.json +41 -0
  5. package/public/robots.txt +6 -0
  6. package/public/theme.json +183 -0
  7. package/src/assets/lander/check.svg +2 -0
  8. package/src/assets/lander/copy.svg +2 -0
  9. package/src/assets/lander/screenshot-github.png +0 -0
  10. package/src/assets/lander/screenshot-splash.png +0 -0
  11. package/src/assets/lander/screenshot-vscode.png +0 -0
  12. package/src/assets/lander/screenshot.png +0 -0
  13. package/src/assets/logo-dark.svg +20 -0
  14. package/src/assets/logo-light.svg +20 -0
  15. package/src/assets/logo-ornate-dark.svg +18 -0
  16. package/src/assets/logo-ornate-light.svg +18 -0
  17. package/src/assets/web/web-homepage-active-session.png +0 -0
  18. package/src/assets/web/web-homepage-new-session.png +0 -0
  19. package/src/assets/web/web-homepage-see-servers.png +0 -0
  20. package/src/components/Head.astro +50 -0
  21. package/src/components/Header.astro +128 -0
  22. package/src/components/Hero.astro +11 -0
  23. package/src/components/Lander.astro +713 -0
  24. package/src/components/Share.tsx +634 -0
  25. package/src/components/SiteTitle.astro +59 -0
  26. package/src/components/icons/custom.tsx +87 -0
  27. package/src/components/icons/index.tsx +4454 -0
  28. package/src/components/share/common.tsx +77 -0
  29. package/src/components/share/content-bash.module.css +85 -0
  30. package/src/components/share/content-bash.tsx +67 -0
  31. package/src/components/share/content-code.module.css +26 -0
  32. package/src/components/share/content-code.tsx +32 -0
  33. package/src/components/share/content-diff.module.css +153 -0
  34. package/src/components/share/content-diff.tsx +231 -0
  35. package/src/components/share/content-error.module.css +64 -0
  36. package/src/components/share/content-error.tsx +24 -0
  37. package/src/components/share/content-markdown.module.css +154 -0
  38. package/src/components/share/content-markdown.tsx +75 -0
  39. package/src/components/share/content-text.module.css +63 -0
  40. package/src/components/share/content-text.tsx +37 -0
  41. package/src/components/share/copy-button.module.css +30 -0
  42. package/src/components/share/copy-button.tsx +28 -0
  43. package/src/components/share/part.module.css +428 -0
  44. package/src/components/share/part.tsx +780 -0
  45. package/src/components/share.module.css +832 -0
  46. package/src/content/docs/1-0.mdx +67 -0
  47. package/src/content/docs/acp.mdx +156 -0
  48. package/src/content/docs/agents.mdx +720 -0
  49. package/src/content/docs/cli.mdx +597 -0
  50. package/src/content/docs/commands.mdx +323 -0
  51. package/src/content/docs/config.mdx +683 -0
  52. package/src/content/docs/custom-tools.mdx +170 -0
  53. package/src/content/docs/ecosystem.mdx +76 -0
  54. package/src/content/docs/enterprise.mdx +170 -0
  55. package/src/content/docs/formatters.mdx +130 -0
  56. package/src/content/docs/github.mdx +321 -0
  57. package/src/content/docs/gitlab.mdx +195 -0
  58. package/src/content/docs/ide.mdx +48 -0
  59. package/src/content/docs/index.mdx +359 -0
  60. package/src/content/docs/keybinds.mdx +191 -0
  61. package/src/content/docs/lsp.mdx +188 -0
  62. package/src/content/docs/mcp-servers.mdx +511 -0
  63. package/src/content/docs/models.mdx +223 -0
  64. package/src/content/docs/modes.mdx +331 -0
  65. package/src/content/docs/network.mdx +57 -0
  66. package/src/content/docs/permissions.mdx +237 -0
  67. package/src/content/docs/plugins.mdx +362 -0
  68. package/src/content/docs/providers.mdx +1889 -0
  69. package/src/content/docs/rules.mdx +180 -0
  70. package/src/content/docs/sdk.mdx +391 -0
  71. package/src/content/docs/server.mdx +286 -0
  72. package/src/content/docs/share.mdx +128 -0
  73. package/src/content/docs/skills.mdx +220 -0
  74. package/src/content/docs/themes.mdx +369 -0
  75. package/src/content/docs/tools.mdx +345 -0
  76. package/src/content/docs/troubleshooting.mdx +300 -0
  77. package/src/content/docs/tui.mdx +390 -0
  78. package/src/content/docs/web.mdx +136 -0
  79. package/src/content/docs/windows-wsl.mdx +113 -0
  80. package/src/content/docs/zen.mdx +251 -0
  81. package/src/content.config.ts +7 -0
  82. package/src/pages/[...slug].md.ts +18 -0
  83. package/src/pages/s/[id].astro +113 -0
  84. package/src/styles/custom.css +405 -0
  85. package/src/types/lang-map.d.ts +27 -0
  86. package/tsconfig.json +9 -0
@@ -0,0 +1,634 @@
1
+ import { For, Show, onMount, Suspense, onCleanup, createMemo, createSignal, SuspenseList, createEffect } from "solid-js"
2
+ import { DateTime } from "luxon"
3
+ import { createStore, reconcile, unwrap } from "solid-js/store"
4
+ import { IconArrowDown } from "./icons"
5
+ import { IconNeocode } from "./icons/custom"
6
+ import styles from "./share.module.css"
7
+ import type { MessageV2 } from "neocode/session/message-v2"
8
+ import type { Message } from "neocode/session/message"
9
+ import type { Session } from "neocode/session/index"
10
+ import { Part, ProviderIcon } from "./share/part"
11
+
12
+ type MessageWithParts = MessageV2.Info & { parts: MessageV2.Part[] }
13
+
14
+ type Status = "disconnected" | "connecting" | "connected" | "error" | "reconnecting"
15
+
16
+ function scrollToAnchor(id: string) {
17
+ const el = document.getElementById(id)
18
+ if (!el) return
19
+
20
+ el.scrollIntoView({ behavior: "smooth" })
21
+ }
22
+
23
+ function getStatusText(status: [Status, string?]): string {
24
+ switch (status[0]) {
25
+ case "connected":
26
+ return "Connected, waiting for messages..."
27
+ case "connecting":
28
+ return "Connecting..."
29
+ case "disconnected":
30
+ return "Disconnected"
31
+ case "reconnecting":
32
+ return "Reconnecting..."
33
+ case "error":
34
+ return status[1] || "Error"
35
+ default:
36
+ return "Unknown"
37
+ }
38
+ }
39
+
40
+ export default function Share(props: { id: string; api: string; info: Session.Info }) {
41
+ let lastScrollY = 0
42
+ let hasScrolledToAnchor = false
43
+ let scrollTimeout: number | undefined
44
+ let scrollSentinel: HTMLElement | undefined
45
+ let scrollObserver: IntersectionObserver | undefined
46
+
47
+ const params = new URLSearchParams(window.location.search)
48
+ const debug = params.get("debug") === "true"
49
+
50
+ const [showScrollButton, setShowScrollButton] = createSignal(false)
51
+ const [isButtonHovered, setIsButtonHovered] = createSignal(false)
52
+ const [isNearBottom, setIsNearBottom] = createSignal(false)
53
+
54
+ const [store, setStore] = createStore<{
55
+ info?: Session.Info
56
+ messages: Record<string, MessageWithParts>
57
+ }>({
58
+ info: {
59
+ id: props.id,
60
+ title: props.info.title,
61
+ version: props.info.version,
62
+ time: {
63
+ created: props.info.time.created,
64
+ updated: props.info.time.updated,
65
+ },
66
+ },
67
+ messages: {},
68
+ })
69
+ const messages = createMemo(() => Object.values(store.messages).toSorted((a, b) => a.id?.localeCompare(b.id)))
70
+ const [connectionStatus, setConnectionStatus] = createSignal<[Status, string?]>(["disconnected", "Disconnected"])
71
+ createEffect(() => {
72
+ console.log(unwrap(store))
73
+ })
74
+
75
+ onMount(() => {
76
+ const apiUrl = props.api
77
+
78
+ if (!props.id) {
79
+ setConnectionStatus(["error", "id not found"])
80
+ return
81
+ }
82
+
83
+ if (!apiUrl) {
84
+ console.error("API URL not found in environment variables")
85
+ setConnectionStatus(["error", "API URL not found"])
86
+ return
87
+ }
88
+
89
+ let reconnectTimer: number | undefined
90
+ let socket: WebSocket | null = null
91
+
92
+ // Function to create and set up WebSocket with auto-reconnect
93
+ const setupWebSocket = () => {
94
+ // Close any existing connection
95
+ if (socket) {
96
+ socket.close()
97
+ }
98
+
99
+ setConnectionStatus(["connecting"])
100
+
101
+ // Always use secure WebSocket protocol (wss)
102
+ const wsBaseUrl = apiUrl.replace(/^https?:\/\//, "wss://")
103
+ const wsUrl = `${wsBaseUrl}/share_poll?id=${props.id}`
104
+ console.log("Connecting to WebSocket URL:", wsUrl)
105
+
106
+ // Create WebSocket connection
107
+ socket = new WebSocket(wsUrl)
108
+
109
+ // Handle connection opening
110
+ socket.onopen = () => {
111
+ setConnectionStatus(["connected"])
112
+ console.log("WebSocket connection established")
113
+ }
114
+
115
+ // Handle incoming messages
116
+ socket.onmessage = (event) => {
117
+ console.log("WebSocket message received")
118
+ try {
119
+ const d = JSON.parse(event.data)
120
+ const [root, type, ...splits] = d.key.split("/")
121
+ if (root !== "session") return
122
+ if (type === "info") {
123
+ setStore("info", reconcile(d.content))
124
+ return
125
+ }
126
+ if (type === "message") {
127
+ const [, messageID] = splits
128
+ if ("metadata" in d.content) {
129
+ d.content = fromV1(d.content)
130
+ }
131
+ d.content.parts = d.content.parts ?? store.messages[messageID]?.parts ?? []
132
+ setStore("messages", messageID, reconcile(d.content))
133
+ }
134
+ if (type === "part") {
135
+ setStore("messages", d.content.messageID, "parts", (arr) => {
136
+ const index = arr.findIndex((x) => x.id === d.content.id)
137
+ if (index === -1) arr.push(d.content)
138
+ if (index > -1) arr[index] = d.content
139
+ return [...arr]
140
+ })
141
+ }
142
+ } catch (error) {
143
+ console.error("Error parsing WebSocket message:", error)
144
+ }
145
+ }
146
+
147
+ // Handle errors
148
+ socket.onerror = (error) => {
149
+ console.error("WebSocket error:", error)
150
+ setConnectionStatus(["error", "Connection failed"])
151
+ }
152
+
153
+ // Handle connection close and reconnection
154
+ socket.onclose = (event) => {
155
+ console.log(`WebSocket closed: ${event.code} ${event.reason}`)
156
+ setConnectionStatus(["reconnecting"])
157
+
158
+ // Try to reconnect after 2 seconds
159
+ clearTimeout(reconnectTimer)
160
+ reconnectTimer = window.setTimeout(setupWebSocket, 2000) as unknown as number
161
+ }
162
+ }
163
+
164
+ // Initial connection
165
+ setupWebSocket()
166
+
167
+ // Clean up on component unmount
168
+ onCleanup(() => {
169
+ console.log("Cleaning up WebSocket connection")
170
+ if (socket) {
171
+ socket.close()
172
+ }
173
+ clearTimeout(reconnectTimer)
174
+ })
175
+ })
176
+
177
+ function checkScrollNeed() {
178
+ const currentScrollY = window.scrollY
179
+ const isScrollingDown = currentScrollY > lastScrollY
180
+ const scrolled = currentScrollY > 200 // Show after scrolling 200px
181
+
182
+ // Only show when scrolling down, scrolled enough, and not near bottom
183
+ const shouldShow = isScrollingDown && scrolled && !isNearBottom()
184
+
185
+ // Update last scroll position
186
+ lastScrollY = currentScrollY
187
+
188
+ if (shouldShow) {
189
+ setShowScrollButton(true)
190
+ // Clear existing timeout
191
+ if (scrollTimeout) {
192
+ clearTimeout(scrollTimeout)
193
+ }
194
+ // Hide button after 3 seconds of no scrolling (unless hovered)
195
+ scrollTimeout = window.setTimeout(() => {
196
+ if (!isButtonHovered()) {
197
+ setShowScrollButton(false)
198
+ }
199
+ }, 1500)
200
+ } else if (!isButtonHovered()) {
201
+ // Only hide if not hovered (to prevent disappearing while user is about to click)
202
+ setShowScrollButton(false)
203
+ if (scrollTimeout) {
204
+ clearTimeout(scrollTimeout)
205
+ }
206
+ }
207
+ }
208
+
209
+ onMount(() => {
210
+ lastScrollY = window.scrollY // Initialize scroll position
211
+
212
+ // Create sentinel element
213
+ const sentinel = document.createElement("div")
214
+ sentinel.style.height = "1px"
215
+ sentinel.style.position = "absolute"
216
+ sentinel.style.bottom = "100px"
217
+ sentinel.style.width = "100%"
218
+ sentinel.style.pointerEvents = "none"
219
+ document.body.appendChild(sentinel)
220
+
221
+ // Create intersection observer
222
+ const observer = new IntersectionObserver((entries) => {
223
+ setIsNearBottom(entries[0].isIntersecting)
224
+ })
225
+ observer.observe(sentinel)
226
+
227
+ // Store references for cleanup
228
+ scrollSentinel = sentinel
229
+ scrollObserver = observer
230
+
231
+ checkScrollNeed()
232
+ window.addEventListener("scroll", checkScrollNeed)
233
+ window.addEventListener("resize", checkScrollNeed)
234
+ })
235
+
236
+ onCleanup(() => {
237
+ window.removeEventListener("scroll", checkScrollNeed)
238
+ window.removeEventListener("resize", checkScrollNeed)
239
+
240
+ // Clean up observer and sentinel
241
+ if (scrollObserver) {
242
+ scrollObserver.disconnect()
243
+ }
244
+ if (scrollSentinel) {
245
+ document.body.removeChild(scrollSentinel)
246
+ }
247
+
248
+ if (scrollTimeout) {
249
+ clearTimeout(scrollTimeout)
250
+ }
251
+ })
252
+
253
+ const data = createMemo(() => {
254
+ const result = {
255
+ rootDir: undefined as string | undefined,
256
+ created: undefined as number | undefined,
257
+ completed: undefined as number | undefined,
258
+ messages: [] as MessageWithParts[],
259
+ models: {} as Record<string, string[]>,
260
+ cost: 0,
261
+ tokens: {
262
+ input: 0,
263
+ output: 0,
264
+ reasoning: 0,
265
+ },
266
+ }
267
+
268
+ if (!store.info) return result
269
+
270
+ result.created = store.info.time.created
271
+
272
+ const msgs = messages()
273
+ for (let i = 0; i < msgs.length; i++) {
274
+ const msg = msgs[i]
275
+
276
+ result.messages.push(msg)
277
+
278
+ if (msg.role === "assistant") {
279
+ result.cost += msg.cost
280
+ result.tokens.input += msg.tokens.input
281
+ result.tokens.output += msg.tokens.output
282
+ result.tokens.reasoning += msg.tokens.reasoning
283
+
284
+ result.models[`${msg.providerID} ${msg.modelID}`] = [msg.providerID, msg.modelID]
285
+
286
+ if (msg.path.root) {
287
+ result.rootDir = msg.path.root
288
+ }
289
+
290
+ if (msg.time.completed) {
291
+ result.completed = msg.time.completed
292
+ }
293
+ }
294
+ }
295
+ return result
296
+ })
297
+
298
+ return (
299
+ <Show when={store.info}>
300
+ <main classList={{ [styles.root]: true, "not-content": true }}>
301
+ <div data-component="header">
302
+ <h1 data-component="header-title">{store.info?.title}</h1>
303
+ <div data-component="header-details">
304
+ <ul data-component="header-stats">
305
+ <li title="neocode version" data-slot="item">
306
+ <div data-slot="icon" title="neocode">
307
+ <IconNeocode width={16} height={16} />
308
+ </div>
309
+ <Show when={store.info?.version} fallback="v0.0.1">
310
+ <span>v{store.info?.version}</span>
311
+ </Show>
312
+ </li>
313
+ {Object.values(data().models).length > 0 ? (
314
+ <For each={Object.values(data().models)}>
315
+ {([provider, model]) => (
316
+ <li data-slot="item">
317
+ <div data-slot="icon" title={provider}>
318
+ <ProviderIcon model={model} />
319
+ </div>
320
+ <span data-slot="model">{model}</span>
321
+ </li>
322
+ )}
323
+ </For>
324
+ ) : (
325
+ <li>
326
+ <span data-element-label>Models</span>
327
+ <span data-placeholder>&mdash;</span>
328
+ </li>
329
+ )}
330
+ </ul>
331
+ <div
332
+ data-component="header-time"
333
+ title={DateTime.fromMillis(data().created || 0).toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)}
334
+ >
335
+ {DateTime.fromMillis(data().created || 0).toLocaleString(DateTime.DATETIME_MED)}
336
+ </div>
337
+ </div>
338
+ </div>
339
+
340
+ <div>
341
+ <Show when={data().messages.length > 0} fallback={<p>Waiting for messages...</p>}>
342
+ <div class={styles.parts}>
343
+ <SuspenseList revealOrder="forwards">
344
+ <For each={data().messages}>
345
+ {(msg, msgIndex) => {
346
+ const filteredParts = createMemo(() =>
347
+ msg.parts.filter((x, index) => {
348
+ if (x.type === "step-start" && index > 0) return false
349
+ if (x.type === "snapshot") return false
350
+ if (x.type === "patch") return false
351
+ if (x.type === "step-finish") return false
352
+ if (x.type === "text" && x.synthetic === true) return false
353
+ if (x.type === "tool" && x.tool === "todoread") return false
354
+ if (x.type === "text" && !x.text) return false
355
+ if (x.type === "tool" && (x.state.status === "pending" || x.state.status === "running"))
356
+ return false
357
+ return true
358
+ }),
359
+ )
360
+
361
+ return (
362
+ <Suspense>
363
+ <For each={filteredParts()}>
364
+ {(part, partIndex) => {
365
+ const last = createMemo(
366
+ () =>
367
+ data().messages.length === msgIndex() + 1 && filteredParts().length === partIndex() + 1,
368
+ )
369
+
370
+ onMount(() => {
371
+ const hash = window.location.hash.slice(1)
372
+ // Wait till all parts are loaded
373
+ if (
374
+ hash !== "" &&
375
+ !hasScrolledToAnchor &&
376
+ filteredParts().length === partIndex() + 1 &&
377
+ data().messages.length === msgIndex() + 1
378
+ ) {
379
+ hasScrolledToAnchor = true
380
+ scrollToAnchor(hash)
381
+ }
382
+ })
383
+
384
+ return <Part last={last()} part={part} index={partIndex()} message={msg} />
385
+ }}
386
+ </For>
387
+ </Suspense>
388
+ )
389
+ }}
390
+ </For>
391
+ </SuspenseList>
392
+ <div data-section="part" data-part-type="summary">
393
+ <div data-section="decoration">
394
+ <span data-status={connectionStatus()[0]}></span>
395
+ </div>
396
+ <div data-section="content">
397
+ <p data-section="copy">{getStatusText(connectionStatus())}</p>
398
+ <ul data-section="stats">
399
+ <li>
400
+ <span data-element-label>Cost</span>
401
+ {data().cost !== undefined ? (
402
+ <span>${data().cost.toFixed(2)}</span>
403
+ ) : (
404
+ <span data-placeholder>&mdash;</span>
405
+ )}
406
+ </li>
407
+ <li>
408
+ <span data-element-label>Input Tokens</span>
409
+ {data().tokens.input ? <span>{data().tokens.input}</span> : <span data-placeholder>&mdash;</span>}
410
+ </li>
411
+ <li>
412
+ <span data-element-label>Output Tokens</span>
413
+ {data().tokens.output ? (
414
+ <span>{data().tokens.output}</span>
415
+ ) : (
416
+ <span data-placeholder>&mdash;</span>
417
+ )}
418
+ </li>
419
+ <li>
420
+ <span data-element-label>Reasoning Tokens</span>
421
+ {data().tokens.reasoning ? (
422
+ <span>{data().tokens.reasoning}</span>
423
+ ) : (
424
+ <span data-placeholder>&mdash;</span>
425
+ )}
426
+ </li>
427
+ </ul>
428
+ </div>
429
+ </div>
430
+ </div>
431
+ </Show>
432
+ </div>
433
+
434
+ <Show when={debug}>
435
+ <div style={{ margin: "2rem 0" }}>
436
+ <div
437
+ style={{
438
+ border: "1px solid #ccc",
439
+ padding: "1rem",
440
+ "overflow-y": "auto",
441
+ }}
442
+ >
443
+ <Show when={data().messages.length > 0} fallback={<p>Waiting for messages...</p>}>
444
+ <ul style={{ "list-style-type": "none", padding: 0 }}>
445
+ <For each={data().messages}>
446
+ {(msg) => (
447
+ <li
448
+ style={{
449
+ padding: "0.75rem",
450
+ margin: "0.75rem 0",
451
+ "box-shadow": "0 1px 3px rgba(0,0,0,0.1)",
452
+ }}
453
+ >
454
+ <div>
455
+ <strong>Key:</strong> {msg.id}
456
+ </div>
457
+ <pre>{JSON.stringify(msg, null, 2)}</pre>
458
+ </li>
459
+ )}
460
+ </For>
461
+ </ul>
462
+ </Show>
463
+ </div>
464
+ </div>
465
+ </Show>
466
+
467
+ <Show when={showScrollButton()}>
468
+ <button
469
+ type="button"
470
+ class={styles["scroll-button"]}
471
+ onClick={() => document.body.scrollIntoView({ behavior: "smooth", block: "end" })}
472
+ onMouseEnter={() => {
473
+ setIsButtonHovered(true)
474
+ if (scrollTimeout) {
475
+ clearTimeout(scrollTimeout)
476
+ }
477
+ }}
478
+ onMouseLeave={() => {
479
+ setIsButtonHovered(false)
480
+ if (showScrollButton()) {
481
+ scrollTimeout = window.setTimeout(() => {
482
+ if (!isButtonHovered()) {
483
+ setShowScrollButton(false)
484
+ }
485
+ }, 3000)
486
+ }
487
+ }}
488
+ title="Scroll to bottom"
489
+ aria-label="Scroll to bottom"
490
+ >
491
+ <IconArrowDown width={20} height={20} />
492
+ </button>
493
+ </Show>
494
+ </main>
495
+ </Show>
496
+ )
497
+ }
498
+
499
+ export function fromV1(v1: Message.Info): MessageWithParts {
500
+ if (v1.role === "assistant") {
501
+ return {
502
+ id: v1.id,
503
+ sessionID: v1.metadata.sessionID,
504
+ role: "assistant",
505
+ time: {
506
+ created: v1.metadata.time.created,
507
+ completed: v1.metadata.time.completed,
508
+ },
509
+ cost: v1.metadata.assistant!.cost,
510
+ path: v1.metadata.assistant!.path,
511
+ summary: v1.metadata.assistant!.summary,
512
+ tokens: v1.metadata.assistant!.tokens ?? {
513
+ input: 0,
514
+ output: 0,
515
+ cache: {
516
+ read: 0,
517
+ write: 0,
518
+ },
519
+ reasoning: 0,
520
+ },
521
+ modelID: v1.metadata.assistant!.modelID,
522
+ providerID: v1.metadata.assistant!.providerID,
523
+ mode: "build",
524
+ system: v1.metadata.assistant!.system,
525
+ error: v1.metadata.error,
526
+ parts: v1.parts.flatMap((part, index): MessageV2.Part[] => {
527
+ const base = {
528
+ id: index.toString(),
529
+ messageID: v1.id,
530
+ sessionID: v1.metadata.sessionID,
531
+ }
532
+ if (part.type === "text") {
533
+ return [
534
+ {
535
+ ...base,
536
+ type: "text",
537
+ text: part.text,
538
+ },
539
+ ]
540
+ }
541
+ if (part.type === "step-start") {
542
+ return [
543
+ {
544
+ ...base,
545
+ type: "step-start",
546
+ },
547
+ ]
548
+ }
549
+ if (part.type === "tool-invocation") {
550
+ return [
551
+ {
552
+ ...base,
553
+ type: "tool",
554
+ callID: part.toolInvocation.toolCallId,
555
+ tool: part.toolInvocation.toolName,
556
+ state: (() => {
557
+ if (part.toolInvocation.state === "partial-call") {
558
+ return {
559
+ status: "pending",
560
+ }
561
+ }
562
+
563
+ const { title, time, ...metadata } = v1.metadata.tool[part.toolInvocation.toolCallId]
564
+ if (part.toolInvocation.state === "call") {
565
+ return {
566
+ status: "running",
567
+ input: part.toolInvocation.args,
568
+ time: {
569
+ start: time.start,
570
+ },
571
+ }
572
+ }
573
+
574
+ if (part.toolInvocation.state === "result") {
575
+ return {
576
+ status: "completed",
577
+ input: part.toolInvocation.args,
578
+ output: part.toolInvocation.result,
579
+ title,
580
+ time,
581
+ metadata,
582
+ }
583
+ }
584
+ throw new Error("unknown tool invocation state")
585
+ })(),
586
+ },
587
+ ]
588
+ }
589
+ return []
590
+ }),
591
+ }
592
+ }
593
+
594
+ if (v1.role === "user") {
595
+ return {
596
+ id: v1.id,
597
+ sessionID: v1.metadata.sessionID,
598
+ role: "user",
599
+ time: {
600
+ created: v1.metadata.time.created,
601
+ },
602
+ parts: v1.parts.flatMap((part, index): MessageV2.Part[] => {
603
+ const base = {
604
+ id: index.toString(),
605
+ messageID: v1.id,
606
+ sessionID: v1.metadata.sessionID,
607
+ }
608
+ if (part.type === "text") {
609
+ return [
610
+ {
611
+ ...base,
612
+ type: "text",
613
+ text: part.text,
614
+ },
615
+ ]
616
+ }
617
+ if (part.type === "file") {
618
+ return [
619
+ {
620
+ ...base,
621
+ type: "file",
622
+ mime: part.mediaType,
623
+ filename: part.filename,
624
+ url: part.url,
625
+ },
626
+ ]
627
+ }
628
+ return []
629
+ }),
630
+ }
631
+ }
632
+
633
+ throw new Error("unknown message type")
634
+ }
@@ -0,0 +1,59 @@
1
+ ---
2
+ import { logos } from 'virtual:starlight/user-images';
3
+ import config from 'virtual:starlight/user-config';
4
+ const { siteTitle, siteTitleHref } = Astro.locals.starlightRoute;
5
+ ---
6
+
7
+ <a href="/" class="site-title sl-flex">
8
+ {
9
+ config.logo && logos.dark && (
10
+ <>
11
+ <img
12
+ class:list={{ 'light:sl-hidden print:hidden': !('src' in config.logo) }}
13
+ alt={config.logo.alt}
14
+ src={logos.dark.src}
15
+ width={logos.dark.width}
16
+ height={logos.dark.height}
17
+ />
18
+ {/* Show light alternate if a user configure both light and dark logos. */}
19
+ {!('src' in config.logo) && (
20
+ <img
21
+ class="dark:sl-hidden print:block"
22
+ alt={config.logo.alt}
23
+ src={logos.light?.src}
24
+ width={logos.light?.width}
25
+ height={logos.light?.height}
26
+ />
27
+ )}
28
+ </>
29
+ )
30
+ }
31
+ <span class:list={{ 'sr-only': config.logo?.replacesTitle }} translate="no">
32
+ {siteTitle}
33
+ </span>
34
+ </a>
35
+
36
+ <style>
37
+ @layer starlight.core {
38
+ .site-title {
39
+ align-items: center;
40
+ gap: var(--sl-nav-gap);
41
+ font-size: var(--sl-text-h4);
42
+ font-weight: 600;
43
+ color: var(--sl-color-text-accent);
44
+ text-decoration: none;
45
+ white-space: nowrap;
46
+ min-width: 0;
47
+ }
48
+ span {
49
+ overflow: hidden;
50
+ }
51
+ img {
52
+ height: calc(var(--sl-nav-height) - 2 * var(--sl-nav-pad-y));
53
+ width: auto;
54
+ max-width: 100%;
55
+ object-fit: contain;
56
+ object-position: 0 50%;
57
+ }
58
+ }
59
+ </style>