@dxos/react-ui-assistant 0.11.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.
- package/LICENSE +105 -0
- package/dist/lib/index.mjs +839 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/testing.mjs +214 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/types/src/components/ChatThread/ChatThread.d.ts +52 -0
- package/dist/types/src/components/ChatThread/ChatThread.d.ts.map +1 -0
- package/dist/types/src/components/ChatThread/ChatThread.stories.d.ts +38 -0
- package/dist/types/src/components/ChatThread/ChatThread.stories.d.ts.map +1 -0
- package/dist/types/src/components/ChatThread/index.d.ts +2 -0
- package/dist/types/src/components/ChatThread/index.d.ts.map +1 -0
- package/dist/types/src/components/MessageChrome/MessageChrome.d.ts +28 -0
- package/dist/types/src/components/MessageChrome/MessageChrome.d.ts.map +1 -0
- package/dist/types/src/components/MessageChrome/index.d.ts +2 -0
- package/dist/types/src/components/MessageChrome/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +4 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/components/widgets/FallbackWidget.d.ts +7 -0
- package/dist/types/src/components/widgets/FallbackWidget.d.ts.map +1 -0
- package/dist/types/src/components/widgets/ReasoningWidget.d.ts +25 -0
- package/dist/types/src/components/widgets/ReasoningWidget.d.ts.map +1 -0
- package/dist/types/src/components/widgets/ReasoningWidget.stories.d.ts +18 -0
- package/dist/types/src/components/widgets/ReasoningWidget.stories.d.ts.map +1 -0
- package/dist/types/src/components/widgets/ReferenceWidget.d.ts +9 -0
- package/dist/types/src/components/widgets/ReferenceWidget.d.ts.map +1 -0
- package/dist/types/src/components/widgets/SelectWidget.d.ts +14 -0
- package/dist/types/src/components/widgets/SelectWidget.d.ts.map +1 -0
- package/dist/types/src/components/widgets/StatsWidget.d.ts +12 -0
- package/dist/types/src/components/widgets/StatsWidget.d.ts.map +1 -0
- package/dist/types/src/components/widgets/StatusWidget.d.ts +12 -0
- package/dist/types/src/components/widgets/StatusWidget.d.ts.map +1 -0
- package/dist/types/src/components/widgets/SuggestionWidget.d.ts +11 -0
- package/dist/types/src/components/widgets/SuggestionWidget.d.ts.map +1 -0
- package/dist/types/src/components/widgets/SummaryWidget.d.ts +4 -0
- package/dist/types/src/components/widgets/SummaryWidget.d.ts.map +1 -0
- package/dist/types/src/components/widgets/ToolWidget.d.ts +10 -0
- package/dist/types/src/components/widgets/ToolWidget.d.ts.map +1 -0
- package/dist/types/src/components/widgets/ToolWidget.stories.d.ts +31 -0
- package/dist/types/src/components/widgets/ToolWidget.stories.d.ts.map +1 -0
- package/dist/types/src/components/widgets/defaults.d.ts +11 -0
- package/dist/types/src/components/widgets/defaults.d.ts.map +1 -0
- package/dist/types/src/components/widgets/format-time.d.ts +2 -0
- package/dist/types/src/components/widgets/format-time.d.ts.map +1 -0
- package/dist/types/src/components/widgets/index.d.ts +11 -0
- package/dist/types/src/components/widgets/index.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +6 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/registry.d.ts +11 -0
- package/dist/types/src/registry.d.ts.map +1 -0
- package/dist/types/src/renderer.d.ts +25 -0
- package/dist/types/src/renderer.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/test-generator.d.ts +7 -0
- package/dist/types/src/testing/test-generator.d.ts.map +1 -0
- package/dist/types/src/testing/test-generator.test.d.ts +2 -0
- package/dist/types/src/testing/test-generator.test.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +19 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +28 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +67 -0
- package/src/components/ChatThread/ChatThread.stories.tsx +419 -0
- package/src/components/ChatThread/ChatThread.tsx +188 -0
- package/src/components/ChatThread/index.ts +5 -0
- package/src/components/MessageChrome/MessageChrome.tsx +147 -0
- package/src/components/MessageChrome/index.ts +5 -0
- package/src/components/index.ts +7 -0
- package/src/components/widgets/FallbackWidget.tsx +26 -0
- package/src/components/widgets/ReasoningWidget.stories.tsx +68 -0
- package/src/components/widgets/ReasoningWidget.ts +139 -0
- package/src/components/widgets/ReferenceWidget.ts +28 -0
- package/src/components/widgets/SelectWidget.ts +39 -0
- package/src/components/widgets/StatsWidget.ts +31 -0
- package/src/components/widgets/StatusWidget.ts +53 -0
- package/src/components/widgets/SuggestionWidget.ts +50 -0
- package/src/components/widgets/SummaryWidget.tsx +27 -0
- package/src/components/widgets/ToolWidget.stories.tsx +55 -0
- package/src/components/widgets/ToolWidget.tsx +185 -0
- package/src/components/widgets/defaults.ts +14 -0
- package/src/components/widgets/format-time.ts +45 -0
- package/src/components/widgets/index.ts +14 -0
- package/src/index.ts +9 -0
- package/src/registry.tsx +144 -0
- package/src/renderer.ts +216 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/test-generator.test.ts +75 -0
- package/src/testing/test-generator.ts +280 -0
- package/src/translations.ts +27 -0
- package/src/types.ts +32 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/components/widgets/format-time.ts","../../src/components/widgets/FallbackWidget.tsx","../../src/components/widgets/defaults.ts","../../src/components/widgets/ReasoningWidget.ts","../../src/components/widgets/ReferenceWidget.ts","../../src/components/widgets/SelectWidget.ts","../../src/components/widgets/StatsWidget.ts","../../src/components/widgets/StatusWidget.ts","../../src/components/widgets/SuggestionWidget.ts","../../src/translations.ts","../../src/components/widgets/SummaryWidget.tsx","../../src/components/widgets/ToolWidget.tsx","../../src/registry.tsx","../../src/renderer.ts","../../src/components/MessageChrome/MessageChrome.tsx","../../src/components/ChatThread/ChatThread.tsx","../../src/types.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// Kept out of `BranchWidget.tsx`: react-refresh only fast-refreshes a module whose\n// exports are all components, so values exported beside them force a full page reload on\n// every edit.\n\n/**\n * Relative (\"5 min ago\", \"yesterday\") within two days, absolute date beyond that. The exact\n * timestamp is always available via the `title` tooltip.\n *\n * Recent prompts are the ones a reader places by elapsed time; older ones by date.\n */\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\nexport const formatTime = (created: string, now = Date.now()): string => {\n const date = new Date(created);\n if (Number.isNaN(date.getTime())) {\n return '';\n }\n\n const elapsed = now - date.getTime();\n if (elapsed < 0 || elapsed >= 2 * DAY_MS) {\n return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n }\n\n const format = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' });\n const minutes = Math.round(elapsed / 60_000);\n if (minutes < 1) {\n // Seconds rather than `format(0, 'minute')`, which renders as \"this minute\".\n return format.format(-Math.max(1, Math.round(elapsed / 1000)), 'second');\n }\n if (minutes < 60) {\n return format.format(-minutes, 'minute');\n }\n\n const hours = Math.round(minutes / 60);\n if (hours < 24) {\n return format.format(-hours, 'hour');\n }\n\n // `numeric: 'auto'` renders -1 day as \"yesterday\".\n return format.format(-Math.round(hours / 24), 'day');\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React from 'react';\n\nimport { TogglePanel } from '@dxos/react-ui-components';\nimport { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';\nimport { type XmlWidgetProps } from '@dxos/ui-editor';\n\nexport const FallbackWidget = ({ _tag, ...props }: XmlWidgetProps) => {\n return (\n <TogglePanel.Root>\n <TogglePanel.Content>\n <TogglePanel.Header classNames='dx-group-surface'>{_tag}</TogglePanel.Header>\n <TogglePanel.Body classNames='dx-modal-surface'>\n <TogglePanel.Viewport>\n <JsonHighlighter classNames='p-2! text-sm' data={props} />\n </TogglePanel.Viewport>\n </TogglePanel.Body>\n </TogglePanel.Content>\n </TogglePanel.Root>\n );\n};\n\nFallbackWidget.displayName = 'Fallback';\n","//\n// Copyright 2026 DXOS.org\n//\n\nexport const styles = {\n /**\n * Vertical rhythm for block widgets. Padding, never margin: CodeMirror measures a widget from its own\n * box, so a margin sits outside what it can see and its height model drifts from what is rendered —\n * the error accumulates down the document until the turn-fold gutter markers are visibly misaligned.\n * Note padding does not collapse between adjacent siblings the way margins do\n */\n padding: 'pt-2 pb-4',\n border: 'border border-subdued-separator rounded-sm',\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { WidgetType } from '@codemirror/view';\n\nimport { Domino } from '@dxos/ui';\n\nimport { styles } from './defaults';\n\n/**\n * Props used to derive a stable key for a reasoning block across CodeMirror widget rebuilds.\n * CodeMirror does not assign persistent IDs to widgets; streaming re-runs the factory each tick.\n */\nexport type ReasoningWidgetBlockIdProps = {\n id?: string;\n range?: { from: number; to: number };\n};\n\n/**\n * AI reasoning widget.\n */\nexport class ReasoningWidget extends WidgetType {\n readonly #pos: string;\n /** The timer id last registered for this instance (may differ from the map if superseded). */\n #ownedTimerId: ReturnType<typeof setTimeout> | null = null;\n\n constructor(\n private readonly text: string,\n pos: string | number | undefined,\n ) {\n super();\n this.#pos = pos === undefined || pos === '' ? 'reasoning' : String(pos);\n }\n\n override eq(other: this) {\n return this.text === other.text && this.#pos === other.#pos;\n }\n\n override toDOM() {\n return Domino.of('div')\n .classNames(styles.padding)\n .append(\n Domino.of('div')\n .classNames('relative overflow-hidden p-px', styles.border)\n .attributes({ 'data-trail-container': '' })\n .append(\n Domino.of('div')\n .classNames(\n 'relative z-10 dx-base-surface rounded-sm text-sm text-subdued py-1',\n 'grid grid-cols-[24px_1fr] gap-x-0.5 items-start',\n )\n .append(\n Domino.of('div')\n .classNames('flex h-5 w-full shrink-0 items-center justify-center self-start')\n .append(Domino.svg('ph--brain--regular').classNames('shrink-0 size-4 opacity-70')),\n Domino.of('div')\n .classNames('px-2 max-h-[5lh] overflow-y-auto dx-scrollbar-thin min-w-0')\n .text(this.text)\n .attributes({ 'data-reasoning-text': '' }),\n ),\n Domino.of('div').attributes({ 'data-id': this.#pos }),\n ),\n ).root;\n }\n\n override updateDOM(dom: HTMLElement) {\n // Update only the text leaf; `dom` may be wrapped in an outer shell, and the trail host is a\n // sibling of the text node inside the bordered container — setting `textContent` on the wrong\n // ancestor would remove `[data-id]`.\n dom.querySelector<HTMLElement>('[data-reasoning-text]')?.replaceChildren(this.text);\n\n // Recreate trail host if it was removed by a prior timeout (streaming paused > TRAIL_REMOVAL_DELAY_MS).\n const container = dom.querySelector<HTMLElement>('[data-trail-container]');\n let trailHost = container?.querySelector<HTMLElement>('[data-id]');\n if (container && !trailHost) {\n trailHost = Domino.of('div').attributes({ 'data-id': this.#pos }).root;\n container.append(trailHost);\n }\n if (trailHost?.childElementCount === 0) {\n trailHost.append(...createTrailLayers());\n }\n\n this.#scheduleTrailRemoval(dom);\n return true;\n }\n\n override destroy(_dom: HTMLElement) {\n this.#clearOwnedTrailTimer();\n }\n\n #scheduleTrailRemoval(dom: HTMLElement) {\n const previous = trailRemovalTimers.get(this.#pos);\n if (previous !== undefined) {\n clearTimeout(previous);\n }\n\n const timerId = setTimeout(() => {\n if (trailRemovalTimers.get(this.#pos) !== timerId) {\n return;\n }\n trailRemovalTimers.delete(this.#pos);\n this.#ownedTimerId = null;\n dom.querySelector<HTMLElement>('[data-id]')?.remove();\n }, TRAIL_REMOVAL_DELAY_MS);\n\n trailRemovalTimers.set(this.#pos, timerId);\n this.#ownedTimerId = timerId;\n }\n\n #clearOwnedTrailTimer() {\n const active = trailRemovalTimers.get(this.#pos);\n if (active !== undefined && active === this.#ownedTimerId) {\n clearTimeout(active);\n trailRemovalTimers.delete(this.#pos);\n }\n this.#ownedTimerId = null;\n }\n}\n\nconst trail = [\n 'absolute z-0 aspect-[2/1] w-16',\n 'bg-[radial-gradient(at_100%_50%,_theme(colors.green.700),_transparent_80%)]',\n '[offset-anchor:100%_50%] [offset-path:border-box]',\n];\n\n/** Delay after the last content update before hiding the trail. */\nconst TRAIL_REMOVAL_DELAY_MS = 1_000;\n\n/**\n * One pending removal timer per logical block. Streaming creates a new widget instance each tick;\n * `destroy` may run after the next instance has already scheduled, so we must not clear a newer timer.\n */\nconst trailRemovalTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\nconst createTrailLayers = (): [HTMLElement, HTMLElement] => [\n Domino.of('div').classNames(...trail, 'animate-trail').root,\n Domino.of('div').classNames(...trail, 'animate-trail-offset').root,\n];\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { WidgetType } from '@codemirror/view';\n\nimport { Domino } from '@dxos/ui';\n\nimport { styles } from './defaults';\n\nexport class ReferenceWidget extends WidgetType {\n constructor(\n private text: string,\n private dxn: string,\n ) {\n super();\n }\n\n override eq(other: this) {\n return this.dxn === other.dxn;\n }\n\n override toDOM() {\n return Domino.of('div')\n .classNames(styles.padding)\n .append(Domino.of('dx-anchor').classNames('dx-tag--anchor').attributes({ dxn: this.dxn }).text(this.text)).root;\n }\n}\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { WidgetType } from '@codemirror/view';\n\nimport { Domino } from '@dxos/ui';\n\nimport { styles } from './defaults';\n\n/**\n * Simple prompt widget.\n */\nexport class SelectWidget extends WidgetType {\n constructor(private options: string[]) {\n super();\n }\n\n override eq(other: this) {\n return JSON.stringify(this.options) === JSON.stringify(other.options);\n }\n\n /**\n * NOTE: Container must set var based on user's identity.\n */\n override toDOM() {\n return Domino.of('div')\n .attributes({ role: 'group' })\n .classNames(styles.padding, 'flex flex-wrap gap-1')\n .append(\n ...this.options.map((option) =>\n Domino.of('button')\n .classNames('dx-button inline-block max-w-[100cqi]')\n .attributes({ 'data-action': 'submit', 'data-value': option, 'data-density': 'md' })\n .text(option),\n ),\n ).root;\n }\n}\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { WidgetType } from '@codemirror/view';\n\nimport { Domino } from '@dxos/ui';\n\nimport { styles } from './defaults';\n\n/**\n * Simple stats widget.\n */\nexport class StatsWidget extends WidgetType {\n constructor(private text: string) {\n super();\n }\n\n override eq(other: this) {\n return this.text === other.text;\n }\n\n override toDOM() {\n return Domino.of('div').classNames(styles.padding, 'text-sm text-placeholder').text(this.text).root;\n }\n\n override updateDOM(dom: HTMLElement) {\n dom.textContent = this.text;\n return true;\n }\n}\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { WidgetType } from '@codemirror/view';\n\nimport { Domino } from '@dxos/ui';\n\nimport { styles } from './defaults';\n\n/**\n * AI status widget.\n */\nexport class StatusWidget extends WidgetType {\n constructor(private readonly text: string) {\n super();\n }\n\n override eq(other: this) {\n return this.text === other.text;\n }\n\n override toDOM() {\n return Domino.of('div')\n .classNames(styles.padding)\n .append(\n Domino.of('div')\n .classNames('relative overflow-hidden rounded-sm')\n .append(\n Domino.of('div')\n .classNames('grid grid-cols-[24px_1fr] gap-x-0.5 gap-y-0 items-start px-0.5 py-0.5 text-placeholder')\n .append(\n Domino.of('div')\n .classNames('flex h-5 w-full shrink-0 items-center justify-center self-start')\n .append(Domino.svg('ph--info--regular').classNames('shrink-0 size-4 opacity-70')),\n Domino.of('div')\n .classNames('relative min-w-0')\n .append(\n Domino.of('div')\n .classNames('relative z-10 rounded-sm text-sm leading-5')\n .attributes({ 'data-status-text': '' })\n .text(this.text),\n ),\n ),\n ),\n ).root;\n }\n\n override updateDOM(dom: HTMLElement) {\n dom.querySelector<HTMLElement>('[data-status-text]')?.replaceChildren(this.text);\n return true;\n }\n}\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { WidgetType } from '@codemirror/view';\n\nimport { Domino, mx } from '@dxos/ui';\n\n/**\n * Simple prompt widget.\n */\nexport class SuggestionWidget extends WidgetType {\n constructor(private text: string) {\n super();\n }\n\n override eq(other: this) {\n return this.text === other.text;\n }\n\n override toDOM() {\n // NOTE: Container must have `dx-inline-size-container` (or `dx-size-container`) to support cqi.\n //\n // Inline-level root so consecutive suggestions flow onto one wrapped line rather than stacking —\n // the tag is registered `block: false` and the renderer omits the usual block separator between\n // them.\n //\n // Vertical space must be **padding, never margin**: CodeMirror measures a widget from its own box,\n // and a margin sits outside what it can see, so the height model drifts from what is rendered, the\n // error accumulates down the document, and the turn-fold gutter markers end up visibly misaligned.\n // Adjacent chips are likewise spaced with trailing padding rather than a margin.\n return Domino.of('span')\n .classNames(mx('inline-flex max-w-[calc(100cqi-8px)] py-1 pe-1 overflow-hidden'))\n .append(\n Domino.of('button')\n .attributes({\n 'data-density': 'md',\n 'data-action': 'submit',\n 'data-value': this.text,\n })\n .classNames(mx('dx-button gap-2 w-full overflow-hidden'))\n .append(\n Domino.of('dx-icon').attributes({\n icon: 'ph--lightning--regular',\n }),\n Domino.of('span').classNames('flex-1 truncate min-w-0').text(this.text),\n ),\n ).root;\n }\n}\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type Resource } from '@dxos/react-ui';\n\nexport const translationKey = '@dxos/react-ui-assistant';\n\nexport const translations = [\n {\n 'en-US': {\n [translationKey]: {\n 'copy.label': 'Copy',\n 'rewind.label': 'Rewind to this prompt',\n 'summary.label': 'Summary',\n 'stats.label': 'Stats',\n 'tool-call.label': 'Calling',\n 'tool-result.label': 'Result',\n 'tool-error.label': 'Tool error',\n 'nav-first.label': 'First message',\n 'nav-previous.label': 'Previous message',\n 'nav-next.label': 'Next message',\n 'nav-last.label': 'Last message',\n },\n },\n },\n] as const satisfies Resource[];\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React from 'react';\n\nimport { useTranslation } from '@dxos/react-ui';\nimport { TogglePanel } from '@dxos/react-ui-components';\nimport { type XmlWidgetProps, getXmlTextChild } from '@dxos/ui-editor';\n\nimport { translationKey } from '../../translations';\nimport { styles } from './defaults';\n\nexport const SummaryWidget = ({ children }: XmlWidgetProps) => {\n const { t } = useTranslation(translationKey);\n\n return (\n <TogglePanel.Root>\n <TogglePanel.Content classNames={styles.border}>\n <TogglePanel.Header classNames='text-sm dx-group-surface'>{t('summary.label')}</TogglePanel.Header>\n <TogglePanel.Body>\n <div className='p-1 text-sm text-subdued'>{getXmlTextChild(children ?? [])}</div>\n </TogglePanel.Body>\n </TogglePanel.Content>\n </TogglePanel.Root>\n );\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport type * as Tool from 'effect/unstable/ai/Tool';\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { Icon, IconBlock, useTranslation } from '@dxos/react-ui';\nimport { NumericTabs, TextCrawl, TogglePanel, type TogglePanelRootProps } from '@dxos/react-ui-components';\nimport { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';\nimport { type ContentBlock } from '@dxos/types';\nimport { type XmlWidgetProps, getXmlTextChild } from '@dxos/ui-editor';\nimport { isNonNullable, safeParseJson } from '@dxos/util';\n\nimport { translationKey } from '../../translations';\n\nexport type ToolWidgetProps = XmlWidgetProps;\n\n/**\n * A run of tool blocks as one collapsible panel with a tab per call. The `<toolkit>` tag carries\n * the run as JSON — the item is rebuilt from its message alone, so there is no widget-state side\n * channel to accumulate blocks through.\n */\nexport const ToolWidget = ({ view, children }: ToolWidgetProps) => {\n const { t } = useTranslation(translationKey);\n const blocks = useMemo<ContentBlock.Any[]>(() => {\n const parsed = safeParseJson(getXmlTextChild(children ?? []) ?? '');\n return Array.isArray(parsed) ? (parsed as ContentBlock.Any[]) : [];\n }, [children]);\n\n const items = useMemo<ToolPanelProps['items']>(() => {\n let lastToolCall: { tool: Tool.Any | undefined; block: ContentBlock.ToolCall } | undefined;\n const tools: Tool.Any[] = [];\n return blocks\n .filter((block) => block._tag === 'toolCall' || block._tag === 'toolResult' || block._tag === 'stats')\n .map((block) => {\n switch (block._tag) {\n case 'toolCall': {\n if (block.pending && lastToolCall?.block.toolCallId === block.toolCallId) {\n return null;\n }\n\n const tool = tools.find((tool) => tool.name === block.name);\n lastToolCall = { tool, block };\n return {\n title: tool?.description ?? [t('tool-call.label'), block.name].filter(Boolean).join(' '),\n icon: block.operationIcon,\n content: {\n ...block,\n input: safeParseJson(block.input),\n },\n };\n }\n\n case 'toolResult': {\n // TODO(burdon): Parse error type.\n if (block.error) {\n return {\n title: t('tool-error.label'),\n icon: lastToolCall?.block.operationIcon,\n content: block,\n };\n }\n\n const title =\n lastToolCall?.tool?.description ??\n [t('tool-result.label'), lastToolCall?.block.name].filter(Boolean).join(' ');\n const icon = lastToolCall?.block.operationIcon;\n lastToolCall = undefined;\n return {\n title,\n icon,\n content: {\n ...block,\n result: typeof block.result === 'string' ? safeParseJson(block.result) : block.result,\n },\n };\n }\n\n case 'stats': {\n if (!lastToolCall) {\n return null;\n }\n\n return {\n title: t('stats.label'),\n icon: lastToolCall.block.operationIcon,\n content: block,\n };\n }\n }\n })\n .filter(isNonNullable);\n }, [blocks, t]);\n\n const handleChangeOpen = useCallback(() => {\n setTimeout(() => {\n // Measure after animation.\n view?.requestMeasure();\n }, 1_000);\n }, [view]);\n\n // Ignore if empty.\n if (!items.length) {\n return null;\n }\n\n return <ToolPanel items={items} onChangeOpen={handleChangeOpen} />;\n};\n\ntype ToolPanelItem = { title: string; icon?: string; content: any };\n\ntype ToolPanelProps = {\n items: ToolPanelItem[];\n} & Pick<TogglePanelRootProps, 'onChangeOpen'>;\n\nconst DEFAULT_TOOL_ICON = 'ph--wrench--regular';\n\nconst ToolPanel = ({ items, onChangeOpen }: ToolPanelProps) => {\n const tabsRef = useRef<HTMLDivElement>(null);\n const [selected, setSelected] = useState(0);\n const [open, setOpen] = useState(false);\n\n // Clamp selected to avoid out-of-bounds after items shrink.\n useEffect(() => {\n setSelected((prev) => Math.min(prev, Math.max(0, items.length - 1)));\n }, [items.length]);\n\n useEffect(() => {\n onChangeOpen?.(open);\n if (open) {\n tabsRef.current?.focus();\n }\n }, [open, onChangeOpen]);\n\n const handleSelect = useCallback((index: number) => {\n setSelected(index);\n }, []);\n\n // Prefer the icon from the latest tool call so the header reflects what's currently active.\n // TextCrawl shows multiple titles, but only one icon slot — using the most recent keeps it in sync\n // with the visible operation while it streams.\n const headerIcon = items[items.length - 1]?.icon ?? items[0]?.icon ?? DEFAULT_TOOL_ICON;\n\n return (\n <TogglePanel.Root open={open} onChangeOpen={setOpen}>\n <TogglePanel.Content>\n <TogglePanel.Header classNames='flex items-center gap-2 text-sm'>\n <div className='w-full grid grid-cols-[1fr_auto] items-center gap-2'>\n <TextCrawl\n key='status-roll'\n classNames='text-description'\n lines={items.map((item) => item.title)}\n autoAdvance\n greedy\n />\n <IconBlock>\n <Icon icon={headerIcon} size={4} />\n </IconBlock>\n </div>\n </TogglePanel.Header>\n <TogglePanel.Body>\n <TogglePanel.Viewport classNames='grid grid-cols-[32px_1fr]'>\n <NumericTabs\n ref={tabsRef}\n classNames='p-1'\n length={items.length}\n selected={selected}\n onSelect={handleSelect}\n />\n <JsonHighlighter\n data={items[selected]?.content}\n classNames='p-1 text-xs bg-transparent'\n replacer={{\n maxDepth: 3,\n maxArrayLen: 10,\n maxStringLen: 128,\n }}\n />\n </TogglePanel.Viewport>\n </TogglePanel.Body>\n </TogglePanel.Content>\n </TogglePanel.Root>\n );\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { AnchorWidget, type XmlWidgetRegistry, getXmlTextChild } from '@dxos/ui-editor';\n\nimport {\n FallbackWidget,\n ReasoningWidget,\n ReferenceWidget,\n SelectWidget,\n StatsWidget,\n StatusWidget,\n SuggestionWidget,\n SummaryWidget,\n ToolWidget,\n} from './components/widgets';\n\n/**\n * Floor for a portaled panel's collapsed height, reserved before its content paints. `heightMode:\n * 'min'` makes it a floor rather than a pin — without it the row is measured at the height of an\n * empty box and grows a frame later, a row moving under a reader scrolling up.\n */\nconst COLLAPSED_HEIGHT = 42;\n\n/**\n * Widgets for the tags {@link createRenderer} emits.\n *\n * Host-extensible by spreading: the plugin adds its `surface` widget (which renders an\n * app-framework Surface this package must not depend on) over the fallback registered here.\n * DOM widgets render synchronously from the tag alone; React widgets are portaled and declare a\n * collapsed-height floor so the row's first measurement is honest.\n */\nexport const assistantRegistry: XmlWidgetRegistry = {\n // No widget: the reader's own words stay in the document, where they can be selected and\n // searched. Registered so the markdown parser keeps the tag as one block, and framed by the\n // chrome, not here.\n 'prompt': {\n block: true,\n },\n\n //\n // DOM widgets\n //\n\n 'link-preview': {\n block: false,\n urlSchemes: ['dxn:', 'echo:'],\n factory: ({ label, dxn }) =>\n typeof label === 'string' && typeof dxn === 'string' ? new AnchorWidget(label, dxn) : null,\n },\n // A \"user\"-turn message generated by the system (a trigger, an encoded event): meant to start the\n // agent without reading as the reader's own prompt.\n 'synthetic': {\n block: true,\n factory: ({ children, range }) => {\n const text = getXmlTextChild(children ?? []);\n return text ? new ReasoningWidget(text, range?.from) : null;\n },\n },\n 'reasoning': {\n block: true,\n streaming: true,\n factory: ({ children, range }) => {\n const text = getXmlTextChild(children ?? []);\n return text ? new ReasoningWidget(text, range?.from) : null;\n },\n },\n 'status': {\n block: true,\n streaming: true,\n factory: ({ children }) => {\n const text = getXmlTextChild(children ?? []);\n return text ? new StatusWidget(text) : null;\n },\n },\n 'reference': {\n block: false,\n factory: ({ children, ref }) => {\n const text = getXmlTextChild(children ?? []);\n return text && typeof ref === 'string' ? new ReferenceWidget(text, ref) : null;\n },\n },\n // Inline so a run of suggestions flows onto one wrapped line instead of one block per line.\n 'suggestion': {\n block: false,\n factory: ({ children }) => {\n const text = getXmlTextChild(children ?? []);\n return text ? new SuggestionWidget(text) : null;\n },\n },\n 'select': {\n block: true,\n factory: ({ children }) => {\n const options = (children ?? [])\n .map((option: any) => option?._tag === 'option' && getXmlTextChild(option.children))\n .filter(Boolean) as string[];\n return options.length ? new SelectWidget(options) : null;\n },\n },\n 'stats': {\n block: true,\n factory: ({ children }) => {\n const text = getXmlTextChild(children ?? []);\n return text ? new StatsWidget(text) : null;\n },\n },\n\n //\n // React widgets (portaled outside the editor)\n //\n\n 'toolkit': {\n block: true,\n streaming: true,\n estimatedHeight: () => COLLAPSED_HEIGHT,\n heightMode: 'min',\n Component: ToolWidget,\n },\n 'summary': {\n block: true,\n estimatedHeight: () => COLLAPSED_HEIGHT,\n heightMode: 'min',\n Component: SummaryWidget,\n },\n // The host overrides this with a widget that can actually dispatch the surface.\n 'surface': {\n block: true,\n estimatedHeight: () => COLLAPSED_HEIGHT,\n heightMode: 'min',\n Component: FallbackWidget,\n },\n\n //\n // Fallback\n //\n\n 'json': {\n block: true,\n estimatedHeight: () => COLLAPSED_HEIGHT,\n heightMode: 'min',\n Component: FallbackWidget,\n },\n} as const;\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type URI } from '@dxos/keys';\nimport { type MessageRenderer } from '@dxos/react-ui-feed';\nimport { type ContentBlock, type Message } from '@dxos/types';\n\nimport { type ChatView } from './types';\n\nexport type CreateRendererOptions = {\n /** Resolves a reference's display label; the tag carries the DXN either way. */\n getObjectLabel?: (uri: URI.URI) => string;\n};\n\n/**\n * The assistant's projection of a message: each block flattened to markdown, everything that is not\n * prose emitted as an XML tag for the registry's widgets. The view type is a filter over blocks —\n * the model always carries everything, so switching views is a re-render, not a re-fetch.\n *\n * Consecutive tool blocks (`toolCall`/`toolResult`/`stats`) render as ONE `<toolkit>` tag carrying\n * the run as JSON: the panel widget shows a run of calls as tabs, and the item is rebuilt from its\n * message alone — there is no side channel accumulating widget state (the old `MessageSyncer`\n * machinery this package retires).\n */\nexport const createRenderer = (\n viewType: ChatView | undefined,\n { getObjectLabel = () => 'Object' }: CreateRendererOptions = {},\n): MessageRenderer => {\n return (message) => {\n const blocks = message.blocks.filter((block) => isBlockVisible(viewType, block));\n const segments: string[] = [];\n let tools: ContentBlock.Any[] = [];\n\n const flushTools = () => {\n if (tools.length) {\n segments.push(toolkitTag(tools));\n tools = [];\n }\n };\n\n for (const block of blocks) {\n if (block._tag === 'toolCall' || block._tag === 'toolResult' || (block._tag === 'stats' && tools.length)) {\n tools.push(block);\n continue;\n }\n\n flushTools();\n const rendered = blockToMarkdown(message, block, getObjectLabel);\n if (rendered) {\n segments.push(rendered);\n }\n }\n flushTools();\n\n // Suggestions are inline widgets meant to flow: a run of them joins on one line and wraps as\n // chips; everything else is separated by a blank line so each block parses as its own\n // markdown block.\n const text = segments\n .reduce<string[]>((parts, segment) => {\n const previous = parts[parts.length - 1];\n if (previous?.startsWith('<suggestion') && segment.startsWith('<suggestion')) {\n parts[parts.length - 1] = `${previous} ${segment}`;\n } else {\n parts.push(segment);\n }\n return parts;\n }, [])\n .join('\\n\\n');\n\n return { kind: 'markdown', text };\n };\n};\n\nconst isBlockVisible = (viewType: ChatView | undefined, block: ContentBlock.Any): boolean => {\n switch (viewType) {\n case 'debug':\n return true;\n case 'summary':\n // Only conversational text; hide reasoning, tool calls, status, stats and synthetic turns.\n return block._tag === 'text' && (block as ContentBlock.Text).disposition !== 'synthetic';\n case 'normal':\n return block._tag !== 'reasoning';\n case 'thinking':\n default:\n return true;\n }\n};\n\nconst blockToMarkdown = (\n message: Message.Message,\n block: ContentBlock.Any,\n getObjectLabel: (uri: URI.URI) => string,\n): string | undefined => {\n switch (block._tag) {\n case 'text': {\n if (message.sender.role === 'user') {\n // A synthetic turn is system-generated input (a trigger, an encoded event): shown like\n // reasoning, not framed as the reader's own words.\n return block.disposition === 'synthetic'\n ? tag('synthetic', block.text, block)\n : tag('prompt', block.text, block);\n }\n return block.text.trim() || undefined;\n }\n\n case 'reasoning':\n return tag('reasoning', block.reasoningText ?? block.redactedText ?? '', block);\n\n case 'status':\n return tag('status', block.statusText, block);\n\n case 'summary':\n return tag('summary', block.content, block);\n\n case 'suggestion':\n return block.pending ? undefined : tag('suggestion', block.text, block);\n\n case 'select':\n return block.pending || !block.options.length\n ? undefined\n : `<select>${block.options.map((option) => `<option>${escapeXml(option)}</option>`).join('')}</select>`;\n\n case 'reference': {\n if (block.pending) {\n return undefined;\n }\n const uri = block.reference.uri;\n return `<reference ref=\"${escapeAttribute(uri.toString())}\">${escapeXml(getObjectLabel(uri))}</reference>`;\n }\n\n case 'stats':\n // Only meaningful inside a tool run (grouped by the caller); bare stats render nothing.\n return undefined;\n\n case 'surface':\n return block.pending\n ? undefined\n : `<surface role=\"${escapeAttribute(block.role)}\">${escapeXml(JSON.stringify(block.data ?? {}))}</surface>`;\n\n default:\n // Nothing renders blank: an unknown block is shown as what it is.\n return tag('json', JSON.stringify(block), block);\n }\n};\n\n/** A run of tool blocks as one tag; the widget parses the payload back out. */\nconst toolkitTag = (blocks: ContentBlock.Any[]): string => {\n const pending = blocks.some((block) => block.pending);\n return `<toolkit${pending ? ' pending=\"true\"' : ''}>${escapeXml(JSON.stringify(blocks))}</toolkit>`;\n};\n\n/**\n * An XML tag holding block content.\n *\n * A pending block is emitted **unclosed**, which is the streaming contract: the item reconciles by\n * appending, so a closing tag written before the content is complete would be rewritten per chunk.\n * Paragraph breaks inside tag content are collapsed, since a blank line would end the enclosing\n * markdown block and leave the rest of the tag parsing as prose.\n */\nconst tag = (name: string, content: string, block: ContentBlock.Any): string => {\n const text = escapeXml(content.replace(/\\n\\n+/g, ' ').trim());\n if (!text.length && !block.pending) {\n return '';\n }\n\n const open = `<${name}>`;\n return block.pending ? `${open}${text}` : `${open}${text}</${name}>`;\n};\n\nconst escapeXml = (raw: string): string => raw.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\nconst escapeAttribute = (raw: string): string => escapeXml(raw).replace(/\"/g, '"');\n\n//\n// Estimates\n//\n\n/** Chrome around every row: the padding and the hover toolbar's reserved line. */\nconst ROW_CHROME = 46;\n/** Height of a wrapped line of body text, and how many characters fit at typical thread width. */\nconst LINE_HEIGHT = 24;\nconst LINE_CHARS = 90;\n/** A collapsed panel: reasoning, a tool run, a summary. */\nconst PANEL_HEIGHT = 50;\n\n/**\n * What a row will measure, from the message alone. Rough on purpose — its only job is to be close\n * enough that measuring the row does not move the rows below it.\n */\nexport const estimateRow = (message: Message.Message): number => {\n let height = ROW_CHROME;\n for (const block of message.blocks) {\n switch (block._tag) {\n case 'text': {\n for (const paragraph of block.text.split('\\n\\n')) {\n height += Math.max(1, Math.ceil(paragraph.length / LINE_CHARS)) * LINE_HEIGHT;\n }\n break;\n }\n case 'suggestion':\n height += LINE_HEIGHT + 8;\n break;\n case 'select':\n height += block.options.length * (LINE_HEIGHT + 10);\n break;\n case 'toolResult':\n case 'stats':\n // Folded into the preceding call's panel.\n break;\n default:\n height += PANEL_HEIGHT;\n }\n }\n\n return height;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { createContext } from '@radix-ui/react-context';\nimport React, { type PropsWithChildren } from 'react';\n\nimport { IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';\nimport { type MessageChromeProps } from '@dxos/react-ui-feed';\nimport { Message } from '@dxos/types';\nimport { mx } from '@dxos/ui-theme';\n\nimport { translationKey } from '../../translations';\nimport { formatTime } from '../widgets';\n\n//\n// Context\n//\n\nconst MESSAGE_CHROME_NAME = 'MessageChrome';\n\ntype MessageChromeContextValue = {\n /** Soft-fork the thread from the given prompt; the rewind button renders only when present. */\n onRewind?: (id: string) => void;\n};\n\nconst [MessageChromeProvider, useMessageChromeContext] = createContext<MessageChromeContextValue>(MESSAGE_CHROME_NAME);\n\nexport { MessageChromeProvider };\n\n//\n// Toolbars\n//\n\n/** Copies the message's extracted text — the model's truth, not the DOM's partial render. */\nconst CopyButton = ({ message }: { message: Message.Message }) => {\n const { t } = useTranslation(translationKey);\n return (\n <IconButton\n icon='ph--copy--regular'\n iconOnly\n label={t('copy.label')}\n variant='ghost'\n density='sm'\n onClick={() => void navigator.clipboard?.writeText(Message.extractText(message))}\n />\n );\n};\n\nconst timeOf = (message: Message.Message) => (\n <time dateTime={message.created} title={new Date(message.created).toLocaleString()}>\n {formatTime(message.created)}\n </time>\n);\n\nexport type MessageToolbarProps = ThemedClassName<{\n message: Message.Message;\n}>;\n\n/**\n * The controls under the reader's own prompt: copy, rewind (edit-and-resend), and when it was\n * sent. Revealed on hover by the chrome, but never removed from flow — chrome that appears and\n * disappears changes the row's height, and a pointer travelling down a scrolling list would then\n * move every row below it.\n */\nexport const PromptToolbar = ({ classNames, message }: MessageToolbarProps) => {\n const { t } = useTranslation(translationKey);\n const { onRewind } = useMessageChromeContext('PromptToolbar');\n\n return (\n <div role='toolbar' className={mx('flex items-center gap-1 text-xs text-description', classNames)}>\n <CopyButton message={message} />\n {onRewind && (\n <IconButton\n icon='ph--clock-counter-clockwise--regular'\n iconOnly\n label={t('rewind.label')}\n variant='ghost'\n density='sm'\n data-testid='chat.rewind'\n onClick={() => onRewind(message.id)}\n />\n )}\n {timeOf(message)}\n </div>\n );\n};\n\nPromptToolbar.displayName = 'PromptToolbar';\n\n/** The controls under an answer: copy, and when the answer finished. */\nexport const AssistantToolbar = ({ classNames, message }: MessageToolbarProps) => {\n return (\n <div role='toolbar' className={mx('flex items-center gap-1 text-xs text-description', classNames)}>\n <CopyButton message={message} />\n {timeOf(message)}\n </div>\n );\n};\n\nAssistantToolbar.displayName = 'AssistantToolbar';\n\n//\n// Chrome\n//\n\n/** Shared hover reveal: present in flow at all times, visible when the row is under the pointer. */\nconst reveal = 'pt-1 opacity-0 transition-opacity group-hover:opacity-100';\n\nconst Row = ({ children, classNames }: PropsWithChildren<{ classNames?: string }>) => (\n <div className={mx('group relative px-2 py-2', classNames)} data-testid='feed.message'>\n {children}\n </div>\n);\n\n/**\n * The assistant feed's per-message frame: the reader's prompts and the model's answers are framed\n * differently because they are different kinds of thing — a prompt is an instruction the thread\n * can be rewound to, an answer is a result.\n *\n * A prompt sits apart from the model's prose: right-aligned, at most two thirds of the viewport —\n * the chat convention that makes whose-turn legible at a glance — with its toolbar following the\n * bubble's edge.\n */\nexport const MessageChrome = ({ message, selected, children }: MessageChromeProps) => {\n const prompt = message.sender.role === 'user';\n\n return (\n <Row classNames={mx(selected && 'bg-hover-surface')}>\n {prompt ? (\n <div className='min-w-0 flex flex-col items-end'>\n <div className='max-w-[66%] min-w-0'>\n <div className='px-4 py-3 border-s-2 border-accent-bg rounded-sm bg-input-surface'>{children}</div>\n <PromptToolbar classNames={mx('justify-end', reveal)} message={message} />\n </div>\n </div>\n ) : (\n <div className='min-w-0'>\n {children}\n <AssistantToolbar classNames={reveal} message={message} />\n </div>\n )}\n </Row>\n );\n};\n\nMessageChrome.displayName = MESSAGE_CHROME_NAME;\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { createContext } from '@radix-ui/react-context';\nimport React, {\n type ComponentPropsWithoutRef,\n type PropsWithChildren,\n type Ref,\n useCallback,\n useEffect,\n useMemo,\n} from 'react';\n\nimport { setRef } from '@dxos/react-ui';\nimport {\n type FeedModel,\n type FeedNavigation,\n MessageList,\n type MessageRange,\n type ScrollToOptions,\n useMessageList,\n} from '@dxos/react-ui-feed';\nimport { type XmlWidgetRegistry } from '@dxos/ui-editor';\n\nimport { assistantRegistry } from '../../registry';\nimport { type CreateRendererOptions, createRenderer, estimateRow } from '../../renderer';\nimport { type ChatThreadEvent, type ChatView } from '../../types';\nimport { MessageChrome, MessageChromeProvider } from '../MessageChrome';\n\n//\n// Context\n//\n\nconst CHAT_THREAD_NAME = 'ChatThread';\n\ntype ChatThreadContextValue = {\n userHue?: string;\n onEvent?: (event: ChatThreadEvent) => void;\n};\n\nconst [ChatThreadProvider, useChatThreadContext] = createContext<ChatThreadContextValue>(CHAT_THREAD_NAME);\n\n//\n// Controller\n//\n\n/**\n * The imperative handle a host drives the thread through — the successor of the old\n * `MarkdownStreamController`, shrunk to what a message-indexed feed still needs: everything else\n * (visible range, spans, widget state) is either context or the model's own business.\n */\nexport type ChatThreadController = {\n model: FeedModel;\n scrollToBottom: (options?: ScrollToOptions) => void;\n scrollToIndex: (index: number, options?: ScrollToOptions) => void;\n /** The one seam every navigation driver shares: toolbar, arrows, rails. */\n navigation: FeedNavigation;\n};\n\n/** Bridges the feed's context out to the host's ref; renders nothing. */\nconst ControllerBridge = ({ controllerRef }: { controllerRef: Ref<ChatThreadController> }) => {\n const { model, scrollToBottom, scrollToIndex, navigation } = useMessageList('ChatThread.Controller');\n useEffect(() => {\n setRef(controllerRef, { model, scrollToBottom, scrollToIndex, navigation });\n return () => {\n setRef(controllerRef, null);\n };\n }, [controllerRef, model, scrollToBottom, scrollToIndex, navigation]);\n\n return null;\n};\n\n//\n// Root\n//\n\ntype ChatThreadRootProps = PropsWithChildren<\n CreateRendererOptions & {\n /** The thread's model. `useFeedModel(messages, { stops: 'prompt' })` adapts an array host. */\n model: FeedModel;\n viewType?: ChatView;\n /** Extends {@link assistantRegistry}; the host's entries win (e.g. a real `surface` widget). */\n registry?: XmlWidgetRegistry;\n /** The reader's identity hue, published to the DOM for the prompt frame's tokens. */\n userHue?: string;\n debug?: boolean;\n onEvent?: (event: ChatThreadEvent) => void;\n /** The visible index range, as the reader scrolls — what an outline rail tracks. */\n onRangeChange?: (range: MessageRange) => void;\n controllerRef?: Ref<ChatThreadController>;\n }\n>;\n\n/**\n * Headless root: the feed's `MessageList.Root` configured as an assistant thread — the view-typed\n * renderer, the widget registry, the prompt/answer chrome, and the chat behaviours (follow the\n * streaming tail; reserve room to bring the last prompt to the top). Composes with the feed's own\n * parts: `MessageList.Nav`, `useMessageList`, and the rails all work inside it.\n */\nconst ChatThreadRoot = ({\n children,\n model,\n viewType,\n registry,\n getObjectLabel,\n userHue,\n debug,\n onEvent,\n onRangeChange,\n controllerRef,\n}: ChatThreadRootProps) => {\n const renderer = useMemo(() => createRenderer(viewType, { getObjectLabel }), [viewType, getObjectLabel]);\n const merged = useMemo(() => (registry ? { ...assistantRegistry, ...registry } : assistantRegistry), [registry]);\n const handleRewind = useCallback((id: string) => onEvent?.({ type: 'rewind', id }), [onEvent]);\n\n return (\n <ChatThreadProvider userHue={userHue} onEvent={onEvent}>\n <MessageChromeProvider onRewind={onEvent ? handleRewind : undefined}>\n <MessageList.Root\n model={model}\n renderer={renderer}\n registry={merged}\n Chrome={MessageChrome}\n estimateSize={estimateRow}\n debug={debug}\n stickyBottom\n scrollPastEnd\n onRangeChange={onRangeChange}\n >\n {controllerRef && <ControllerBridge controllerRef={controllerRef} />}\n {children}\n </MessageList.Root>\n </MessageChromeProvider>\n </ChatThreadProvider>\n );\n};\n\nChatThreadRoot.displayName = 'ChatThread.Root';\n\n//\n// Viewport\n//\n\nconst CHAT_THREAD_VIEWPORT_NAME = 'ChatThread.Viewport';\n\ntype ChatThreadViewportProps = ComponentPropsWithoutRef<typeof MessageList.Viewport>;\n\n/**\n * The scrolling thread. Suggestion and select widgets are DOM widgets carrying\n * `data-action=\"submit\"` buttons; one delegated listener here turns those clicks into `submit`\n * events, which is what keeps the widgets renderable from the tag alone.\n */\nconst ChatThreadViewport = ({ children, ...props }: ChatThreadViewportProps) => {\n const { userHue, onEvent } = useChatThreadContext(CHAT_THREAD_VIEWPORT_NAME);\n\n const handleClick = useCallback(\n (event: React.MouseEvent) => {\n const action = (event.target as HTMLElement).closest<HTMLElement>('[data-action=\"submit\"]');\n const text = action?.getAttribute('data-value');\n if (text) {\n event.preventDefault();\n event.stopPropagation();\n onEvent?.({ type: 'submit', text });\n }\n },\n [onEvent],\n );\n\n return (\n <div className='contents' data-testid='assistant.thread' data-hue={userHue} onClickCapture={handleClick}>\n <MessageList.Viewport {...props}>{children}</MessageList.Viewport>\n </div>\n );\n};\n\nChatThreadViewport.displayName = CHAT_THREAD_VIEWPORT_NAME;\n\n//\n// ChatThread\n//\n\nexport const ChatThread = {\n Root: ChatThreadRoot,\n Viewport: ChatThreadViewport,\n};\n\nexport type { ChatThreadRootProps, ChatThreadViewportProps };\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Schema from 'effect/Schema';\n\n/**\n * Which projection of the thread the reader sees. A view is a filter over blocks, applied by the\n * renderer — the model always carries everything.\n *\n * - `summary`: only conversational text (prompts and replies).\n * - `normal`: everything except reasoning.\n * - `thinking`: everything, reasoning included.\n * - `debug`: every block, raw fallbacks visible.\n */\nexport const ChatView = Schema.Union([\n Schema.Literal('normal').annotate({ title: 'Normal' }),\n Schema.Literal('summary').annotate({ title: 'Summary' }),\n Schema.Literal('thinking').annotate({ title: 'Thinking' }),\n Schema.Literal('debug').annotate({ title: 'Debug' }),\n]);\nexport type ChatView = Schema.Schema.Type<typeof ChatView>;\n\n/**\n * What the thread reports outward. Deliberately the same shapes the assistant plugin's event bus\n * speaks, so a host can pipe these straight through.\n */\nexport type ChatThreadEvent =\n /** A suggestion or select option was chosen — the text is ready to submit. */\n | { type: 'submit'; text: string }\n /** Soft-fork the thread from the given message (the prompt toolbar's rewind). */\n | { type: 'rewind'; id: string };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { trim } from "@dxos/util";
|
|
2
|
+
import { Message, Organization } from "@dxos/types";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import { Database, Feed, Obj } from "@dxos/echo";
|
|
5
|
+
import { random } from "@dxos/random";
|
|
6
|
+
import { renderObjectLink, textStream } from "@dxos/react-ui-markdown";
|
|
7
|
+
//#region src/testing/test-generator.ts
|
|
8
|
+
var createMessage = (role, blocks) => {
|
|
9
|
+
return Obj.make(Message.Message, {
|
|
10
|
+
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11
|
+
sender: { role },
|
|
12
|
+
blocks
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
var createMessageGenerator = () => [
|
|
16
|
+
Effect.gen(function* () {
|
|
17
|
+
const { feed } = yield* Feed.ContextFeedService;
|
|
18
|
+
yield* Feed.append(feed, [createMessage("user", [{
|
|
19
|
+
_tag: "text",
|
|
20
|
+
text: random.lorem.sentence(5)
|
|
21
|
+
}])]);
|
|
22
|
+
}),
|
|
23
|
+
Effect.gen(function* () {
|
|
24
|
+
const { feed } = yield* Feed.ContextFeedService;
|
|
25
|
+
const { db } = yield* Database.Service;
|
|
26
|
+
const obj1 = db.add(Obj.make(Organization.Organization, {
|
|
27
|
+
name: "DXOS",
|
|
28
|
+
website: "https://dxos.org",
|
|
29
|
+
description: "DXOS is a decentralized network for collaborative applications."
|
|
30
|
+
}));
|
|
31
|
+
yield* Feed.append(feed, [createMessage("assistant", [{
|
|
32
|
+
_tag: "text",
|
|
33
|
+
text: [
|
|
34
|
+
random.lorem.paragraph(),
|
|
35
|
+
renderObjectLink(obj1),
|
|
36
|
+
random.lorem.paragraph(),
|
|
37
|
+
"\n"
|
|
38
|
+
].join(" ")
|
|
39
|
+
}])]);
|
|
40
|
+
}),
|
|
41
|
+
Effect.gen(function* () {
|
|
42
|
+
const { feed } = yield* Feed.ContextFeedService;
|
|
43
|
+
const { db } = yield* Database.Service;
|
|
44
|
+
const message = createMessage("assistant", [{
|
|
45
|
+
_tag: "text",
|
|
46
|
+
text: "",
|
|
47
|
+
pending: true
|
|
48
|
+
}]);
|
|
49
|
+
yield* Feed.append(feed, [message]);
|
|
50
|
+
const fullText = [
|
|
51
|
+
"Streaming a response **word by word** through the feed:",
|
|
52
|
+
random.lorem.paragraph(),
|
|
53
|
+
random.lorem.paragraph()
|
|
54
|
+
].join("\n\n");
|
|
55
|
+
yield* Effect.promise(async () => {
|
|
56
|
+
for await (const chunk of textStream(fullText, {
|
|
57
|
+
wordsPerChunk: 2,
|
|
58
|
+
chunkDelay: 60
|
|
59
|
+
})) {
|
|
60
|
+
Obj.update(message, (message) => {
|
|
61
|
+
const block = message.blocks[0];
|
|
62
|
+
block.text += chunk;
|
|
63
|
+
});
|
|
64
|
+
await db.appendToFeed(feed, []);
|
|
65
|
+
}
|
|
66
|
+
Obj.update(message, (message) => {
|
|
67
|
+
const block = message.blocks[0];
|
|
68
|
+
block.pending = false;
|
|
69
|
+
});
|
|
70
|
+
await db.appendToFeed(feed, []);
|
|
71
|
+
});
|
|
72
|
+
}),
|
|
73
|
+
Effect.gen(function* () {
|
|
74
|
+
const { feed } = yield* Feed.ContextFeedService;
|
|
75
|
+
yield* Feed.append(feed, [createMessage("assistant", [{
|
|
76
|
+
_tag: "text",
|
|
77
|
+
text: trim`
|
|
78
|
+
## Markdown
|
|
79
|
+
Here is a [link](https://dxos.org)
|
|
80
|
+
|
|
81
|
+
And some **bold** text.
|
|
82
|
+
|
|
83
|
+
And some JSON
|
|
84
|
+
\`\`\`json
|
|
85
|
+
{
|
|
86
|
+
"key": "value"
|
|
87
|
+
}
|
|
88
|
+
\`\`\`
|
|
89
|
+
|
|
90
|
+
And some code
|
|
91
|
+
\`\`\`js
|
|
92
|
+
const x = 1;
|
|
93
|
+
\`\`\`
|
|
94
|
+
` + "\n"
|
|
95
|
+
}, {
|
|
96
|
+
_tag: "text",
|
|
97
|
+
disposition: "cot",
|
|
98
|
+
text: [
|
|
99
|
+
random.lorem.paragraph(),
|
|
100
|
+
"",
|
|
101
|
+
...Array.from({ length: random.number.int({
|
|
102
|
+
min: 3,
|
|
103
|
+
max: 5
|
|
104
|
+
}) }).map((_, idx) => `${idx + 1}. ${random.lorem.paragraph()}`)
|
|
105
|
+
].join("\n") + "\n"
|
|
106
|
+
}])]);
|
|
107
|
+
}),
|
|
108
|
+
Effect.gen(function* () {
|
|
109
|
+
const { feed } = yield* Feed.ContextFeedService;
|
|
110
|
+
yield* Feed.append(feed, [createMessage("assistant", [
|
|
111
|
+
{
|
|
112
|
+
_tag: "text",
|
|
113
|
+
text: "How can I help?"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
_tag: "suggestion",
|
|
117
|
+
text: "List tools"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
_tag: "suggestion",
|
|
121
|
+
text: "Show info"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
_tag: "suggestion",
|
|
125
|
+
text: random.lorem.paragraph()
|
|
126
|
+
}
|
|
127
|
+
]), createMessage("assistant", [{
|
|
128
|
+
_tag: "text",
|
|
129
|
+
text: "Select an option:"
|
|
130
|
+
}, {
|
|
131
|
+
_tag: "select",
|
|
132
|
+
options: [
|
|
133
|
+
"Option 1",
|
|
134
|
+
"Option 2",
|
|
135
|
+
"Option 3"
|
|
136
|
+
]
|
|
137
|
+
}])]);
|
|
138
|
+
}),
|
|
139
|
+
Effect.gen(function* () {
|
|
140
|
+
const { feed } = yield* Feed.ContextFeedService;
|
|
141
|
+
yield* Feed.append(feed, [createMessage("assistant", [{ _tag: "toolkit" }])]);
|
|
142
|
+
}),
|
|
143
|
+
Effect.gen(function* () {
|
|
144
|
+
const { feed } = yield* Feed.ContextFeedService;
|
|
145
|
+
yield* Feed.append(feed, [createMessage("user", [{
|
|
146
|
+
_tag: "text",
|
|
147
|
+
text: random.lorem.sentence(5)
|
|
148
|
+
}])]);
|
|
149
|
+
}),
|
|
150
|
+
Effect.gen(function* () {
|
|
151
|
+
const { feed } = yield* Feed.ContextFeedService;
|
|
152
|
+
yield* Feed.append(feed, [
|
|
153
|
+
createMessage("assistant", [{
|
|
154
|
+
_tag: "text",
|
|
155
|
+
text: random.lorem.paragraph() + "\n"
|
|
156
|
+
}]),
|
|
157
|
+
createMessage("assistant", [{
|
|
158
|
+
_tag: "toolCall",
|
|
159
|
+
toolCallId: "1234",
|
|
160
|
+
name: "search",
|
|
161
|
+
input: JSON.stringify({}),
|
|
162
|
+
providerExecuted: false
|
|
163
|
+
}]),
|
|
164
|
+
createMessage("user", [{
|
|
165
|
+
_tag: "toolResult",
|
|
166
|
+
toolCallId: "1234",
|
|
167
|
+
name: "search",
|
|
168
|
+
result: "This is a tool result.",
|
|
169
|
+
providerExecuted: false
|
|
170
|
+
}])
|
|
171
|
+
]);
|
|
172
|
+
}),
|
|
173
|
+
Effect.gen(function* () {
|
|
174
|
+
const { feed } = yield* Feed.ContextFeedService;
|
|
175
|
+
yield* Feed.append(feed, [
|
|
176
|
+
createMessage("assistant", [{
|
|
177
|
+
_tag: "text",
|
|
178
|
+
text: random.lorem.paragraph() + "\n"
|
|
179
|
+
}]),
|
|
180
|
+
createMessage("assistant", [{
|
|
181
|
+
_tag: "toolCall",
|
|
182
|
+
toolCallId: "4567",
|
|
183
|
+
name: "create",
|
|
184
|
+
input: JSON.stringify({}),
|
|
185
|
+
providerExecuted: false
|
|
186
|
+
}]),
|
|
187
|
+
createMessage("user", [{
|
|
188
|
+
_tag: "toolResult",
|
|
189
|
+
toolCallId: "4567",
|
|
190
|
+
name: "create",
|
|
191
|
+
result: "This is a tool result.",
|
|
192
|
+
providerExecuted: false
|
|
193
|
+
}]),
|
|
194
|
+
createMessage("assistant", [{
|
|
195
|
+
_tag: "text",
|
|
196
|
+
text: Array.from({ length: random.number.int({
|
|
197
|
+
min: 2,
|
|
198
|
+
max: 3
|
|
199
|
+
}) }).map(() => random.lorem.paragraph()).join("\n\n") + "\n"
|
|
200
|
+
}])
|
|
201
|
+
]);
|
|
202
|
+
}),
|
|
203
|
+
Effect.gen(function* () {
|
|
204
|
+
const { feed } = yield* Feed.ContextFeedService;
|
|
205
|
+
yield* Feed.append(feed, [createMessage("assistant", [{
|
|
206
|
+
_tag: "text",
|
|
207
|
+
text: random.lorem.paragraph()
|
|
208
|
+
}])]);
|
|
209
|
+
})
|
|
210
|
+
];
|
|
211
|
+
//#endregion
|
|
212
|
+
export { createMessage, createMessageGenerator };
|
|
213
|
+
|
|
214
|
+
//# sourceMappingURL=testing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.mjs","names":[],"sources":["../../src/testing/test-generator.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Database, Feed, Obj } from '@dxos/echo';\nimport { type Mutable } from '@dxos/echo/Obj';\nimport { random } from '@dxos/random';\nimport { renderObjectLink, textStream } from '@dxos/react-ui-markdown';\nimport { type Actor, type ContentBlock, Message, Organization } from '@dxos/types';\nimport { trim } from '@dxos/util';\n\nexport const createMessage = (role: Actor.Role, blocks: ContentBlock.Any[]): Message.Message => {\n return Obj.make(Message.Message, {\n created: new Date().toISOString(),\n sender: { role },\n blocks,\n });\n};\n\nexport type MessageGenerator = Effect.Effect<void, never, Database.Service | Feed.ContextFeedService>;\n\nexport const createMessageGenerator = (): MessageGenerator[] => [\n Effect.gen(function* () {\n const { feed } = yield* Feed.ContextFeedService;\n yield* Feed.append(feed, [\n createMessage('user', [\n {\n _tag: 'text',\n text: random.lorem.sentence(5),\n },\n ]),\n ]);\n }),\n\n Effect.gen(function* () {\n const { feed } = yield* Feed.ContextFeedService;\n const { db } = yield* Database.Service;\n const obj1 = db.add(\n Obj.make(Organization.Organization, {\n name: 'DXOS',\n website: 'https://dxos.org',\n description: 'DXOS is a decentralized network for collaborative applications.',\n }),\n );\n // const obj2 = db.add(Obj.make(Person.Person, { fullName: 'Alice' }));\n // const obj3 = db.add(Obj.make(Person.Person, { fullName: 'Bob' }));\n // const obj4 = db.add(Obj.make(Person.Person, { fullName: 'Charlie' }));\n yield* Feed.append(feed, [\n createMessage('assistant', [\n // Inline tag.\n {\n _tag: 'text',\n text: [random.lorem.paragraph(), renderObjectLink(obj1), random.lorem.paragraph(), '\\n'].join(' '),\n },\n\n // Inline cards.\n // ...[obj1, obj2, obj3, obj4].map(\n // (obj) =>\n // ({\n // _tag: 'text',\n // text: renderObjectLink(obj, true) + '\\n',\n // }) satisfies ContentBlock.Text,\n // ),\n ]),\n ]);\n }),\n\n // Streaming text block: appends a pending text block, then mutates `text` in chunks\n // so the syncer renders progressive deltas through the feed (not via the controller).\n Effect.gen(function* () {\n const { feed } = yield* Feed.ContextFeedService;\n const { db } = yield* Database.Service;\n const message = createMessage('assistant', [{ _tag: 'text', text: '', pending: true }]);\n yield* Feed.append(feed, [message]);\n\n const fullText = [\n 'Streaming a response **word by word** through the feed:',\n random.lorem.paragraph(),\n random.lorem.paragraph(),\n ].join('\\n\\n');\n\n yield* Effect.promise(async () => {\n for await (const chunk of textStream(fullText, { wordsPerChunk: 2, chunkDelay: 60 })) {\n Obj.update(message, (message) => {\n const block = message.blocks[0] as Mutable<ContentBlock.Text>;\n block.text += chunk;\n });\n // Feed queries only react to feed-level updates, not in-place object mutations.\n await db.appendToFeed(feed, []);\n }\n Obj.update(message, (message) => {\n const block = message.blocks[0] as Mutable<ContentBlock.Text>;\n block.pending = false;\n });\n await db.appendToFeed(feed, []);\n });\n }),\n\n Effect.gen(function* () {\n const { feed } = yield* Feed.ContextFeedService;\n yield* Feed.append(feed, [\n createMessage('assistant', [\n {\n _tag: 'text',\n text:\n trim`\n ## Markdown\n Here is a [link](https://dxos.org)\n\n And some **bold** text.\n\n And some JSON\n \\`\\`\\`json\n {\n \"key\": \"value\"\n }\n \\`\\`\\`\n\n And some code\n \\`\\`\\`js\n const x = 1;\n \\`\\`\\`\n ` + '\\n',\n },\n {\n _tag: 'text',\n disposition: 'cot',\n text:\n [\n random.lorem.paragraph(),\n '',\n ...Array.from({\n length: random.number.int({ min: 3, max: 5 }),\n }).map((_, idx) => `${idx + 1}. ${random.lorem.paragraph()}`),\n ].join('\\n') + '\\n',\n },\n ]),\n ]);\n }),\n\n Effect.gen(function* () {\n const { feed } = yield* Feed.ContextFeedService;\n yield* Feed.append(feed, [\n createMessage('assistant', [\n {\n _tag: 'text',\n text: 'How can I help?',\n },\n {\n _tag: 'suggestion',\n text: 'List tools',\n },\n {\n _tag: 'suggestion',\n text: 'Show info',\n },\n {\n _tag: 'suggestion',\n text: random.lorem.paragraph(),\n },\n ]),\n createMessage('assistant', [\n {\n _tag: 'text',\n text: 'Select an option:',\n },\n {\n _tag: 'select',\n options: ['Option 1', 'Option 2', 'Option 3'],\n },\n ]),\n ]);\n }),\n\n Effect.gen(function* () {\n const { feed } = yield* Feed.ContextFeedService;\n yield* Feed.append(feed, [\n createMessage('assistant', [\n {\n _tag: 'toolkit',\n },\n ]),\n ]);\n }),\n\n Effect.gen(function* () {\n const { feed } = yield* Feed.ContextFeedService;\n yield* Feed.append(feed, [\n createMessage('user', [\n {\n _tag: 'text',\n text: random.lorem.sentence(5),\n },\n ]),\n ]);\n }),\n\n Effect.gen(function* () {\n const { feed } = yield* Feed.ContextFeedService;\n yield* Feed.append(feed, [\n createMessage('assistant', [\n {\n _tag: 'text',\n text: random.lorem.paragraph() + '\\n',\n },\n ]),\n createMessage('assistant', [\n {\n _tag: 'toolCall',\n toolCallId: '1234',\n name: 'search',\n input: JSON.stringify({}),\n providerExecuted: false,\n },\n ]),\n createMessage('user', [\n {\n _tag: 'toolResult',\n toolCallId: '1234',\n name: 'search',\n result: 'This is a tool result.',\n providerExecuted: false,\n },\n ]),\n ]);\n }),\n\n Effect.gen(function* () {\n const { feed } = yield* Feed.ContextFeedService;\n yield* Feed.append(feed, [\n createMessage('assistant', [\n {\n _tag: 'text',\n text: random.lorem.paragraph() + '\\n',\n },\n ]),\n createMessage('assistant', [\n {\n _tag: 'toolCall',\n toolCallId: '4567',\n name: 'create',\n input: JSON.stringify({}),\n providerExecuted: false,\n },\n ]),\n createMessage('user', [\n {\n _tag: 'toolResult',\n toolCallId: '4567',\n name: 'create',\n result: 'This is a tool result.',\n providerExecuted: false,\n },\n ]),\n createMessage('assistant', [\n {\n _tag: 'text',\n text:\n Array.from({ length: random.number.int({ min: 2, max: 3 }) })\n .map(() => random.lorem.paragraph())\n .join('\\n\\n') + '\\n',\n },\n ]),\n ]);\n }),\n\n Effect.gen(function* () {\n const { feed } = yield* Feed.ContextFeedService;\n yield* Feed.append(feed, [\n createMessage('assistant', [\n {\n _tag: 'text',\n text: random.lorem.paragraph(),\n },\n ]),\n ]);\n }),\n];\n"],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React, { type ComponentPropsWithoutRef, type PropsWithChildren, type Ref } from 'react';
|
|
2
|
+
import { type FeedModel, type FeedNavigation, MessageList, type MessageRange, type ScrollToOptions } from '@dxos/react-ui-feed';
|
|
3
|
+
import { type XmlWidgetRegistry } from '@dxos/ui-editor';
|
|
4
|
+
import { type CreateRendererOptions } from '../../renderer';
|
|
5
|
+
import { type ChatThreadEvent, type ChatView } from '../../types';
|
|
6
|
+
/**
|
|
7
|
+
* The imperative handle a host drives the thread through — the successor of the old
|
|
8
|
+
* `MarkdownStreamController`, shrunk to what a message-indexed feed still needs: everything else
|
|
9
|
+
* (visible range, spans, widget state) is either context or the model's own business.
|
|
10
|
+
*/
|
|
11
|
+
export type ChatThreadController = {
|
|
12
|
+
model: FeedModel;
|
|
13
|
+
scrollToBottom: (options?: ScrollToOptions) => void;
|
|
14
|
+
scrollToIndex: (index: number, options?: ScrollToOptions) => void;
|
|
15
|
+
/** The one seam every navigation driver shares: toolbar, arrows, rails. */
|
|
16
|
+
navigation: FeedNavigation;
|
|
17
|
+
};
|
|
18
|
+
type ChatThreadRootProps = PropsWithChildren<CreateRendererOptions & {
|
|
19
|
+
/** The thread's model. `useFeedModel(messages, { stops: 'prompt' })` adapts an array host. */
|
|
20
|
+
model: FeedModel;
|
|
21
|
+
viewType?: ChatView;
|
|
22
|
+
/** Extends {@link assistantRegistry}; the host's entries win (e.g. a real `surface` widget). */
|
|
23
|
+
registry?: XmlWidgetRegistry;
|
|
24
|
+
/** The reader's identity hue, published to the DOM for the prompt frame's tokens. */
|
|
25
|
+
userHue?: string;
|
|
26
|
+
debug?: boolean;
|
|
27
|
+
onEvent?: (event: ChatThreadEvent) => void;
|
|
28
|
+
/** The visible index range, as the reader scrolls — what an outline rail tracks. */
|
|
29
|
+
onRangeChange?: (range: MessageRange) => void;
|
|
30
|
+
controllerRef?: Ref<ChatThreadController>;
|
|
31
|
+
}>;
|
|
32
|
+
declare function ChatThreadRoot({ children, model, viewType, registry, getObjectLabel, userHue, debug, onEvent, onRangeChange, controllerRef }: ChatThreadRootProps): React.JSX.Element;
|
|
33
|
+
declare namespace ChatThreadRoot {
|
|
34
|
+
var displayName: string;
|
|
35
|
+
}
|
|
36
|
+
type ChatThreadViewportProps = ComponentPropsWithoutRef<typeof MessageList.Viewport>;
|
|
37
|
+
declare function ChatThreadViewport({ children, ...props }: ChatThreadViewportProps): React.JSX.Element;
|
|
38
|
+
declare namespace ChatThreadViewport {
|
|
39
|
+
var displayName: string;
|
|
40
|
+
}
|
|
41
|
+
export declare const ChatThread: {
|
|
42
|
+
Root: {
|
|
43
|
+
({ children, model, viewType, registry, getObjectLabel, userHue, debug, onEvent, onRangeChange, controllerRef, }: ChatThreadRootProps): React.JSX.Element;
|
|
44
|
+
displayName: string;
|
|
45
|
+
};
|
|
46
|
+
Viewport: {
|
|
47
|
+
({ children, ...props }: ChatThreadViewportProps): React.JSX.Element;
|
|
48
|
+
displayName: string;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export type { ChatThreadRootProps, ChatThreadViewportProps };
|
|
52
|
+
//# sourceMappingURL=ChatThread.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatThread.d.ts","sourceRoot":"","sources":["../../../../../src/components/ChatThread/ChatThread.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,EACZ,KAAK,wBAAwB,EAC7B,KAAK,iBAAiB,EACtB,KAAK,GAAG,EAIT,MAAM,OAAO,CAAC;AAGf,OAAO,EACL,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,WAAW,EACX,KAAK,YAAY,EACjB,KAAK,eAAe,EAErB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAGzD,OAAO,EAAE,KAAK,qBAAqB,EAA+B,MAAM,gBAAgB,CAAC;AACzF,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAoBlE;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,SAAS,CAAC;IACjB,cAAc,EAAE,CAAC,OAAO,CAAC,EAAE,eAAe,KAAK,IAAI,CAAC;IACpD,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,KAAK,IAAI,CAAC;IAClE,2EAA2E;IAC3E,UAAU,EAAE,cAAc,CAAC;CAC5B,CAAC;AAmBF,KAAK,mBAAmB,GAAG,iBAAiB,CAC1C,qBAAqB,GAAG;IACtB,8FAA8F;IAC9F,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,gGAAgG;IAChG,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAC3C,oFAAoF;IACpF,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC9C,aAAa,CAAC,EAAE,GAAG,CAAC,oBAAoB,CAAC,CAAC;CAC3C,CACF,CAAC;gCAQsB,EACtB,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,OAAO,EACP,KAAK,EACL,OAAO,EACP,aAAa,EACb,aAAa,EACd,EAAE,mBAAmB;;;;AAmCtB,KAAK,uBAAuB,GAAG,wBAAwB,CAAC,OAAO,WAAW,CAAC,QAAQ,CAAC,CAAC;oCAOzD,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,uBAAuB;;;;AA6B3E,eAAO,MAAM,UAAU;IACrB,IAAI;0HAxEH,mBAAmB;;;IAyEpB,QAAQ;iCA/B0C,uBAAuB;;;CAgC1E,CAAC;AAEF,YAAY,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { type ChatView } from '../../types';
|
|
3
|
+
/**
|
|
4
|
+
* The canonical assistant thread: `ChatThread` end to end — the view-typed renderer, the real
|
|
5
|
+
* widget registry, the prompt/answer chrome — with a reader typing prompts while an agent streams
|
|
6
|
+
* multi-block answers into the tail (status, reasoning, a tool run, then the answer: exactly the
|
|
7
|
+
* block sequence a model emits, via `streamTurn`).
|
|
8
|
+
*
|
|
9
|
+
* Everything goes through the shipping path and the model's *told* APIs: a typed prompt is
|
|
10
|
+
* `model.append`, the agent's turn mutates one identity via `model.patch`, and the follow keeps
|
|
11
|
+
* the tail at rest because the reader is there — scrolling up withdraws it, returning restores it.
|
|
12
|
+
*/
|
|
13
|
+
type StoryArgs = {
|
|
14
|
+
count?: number;
|
|
15
|
+
viewType?: ChatView;
|
|
16
|
+
debug?: boolean;
|
|
17
|
+
wordsPerChunk?: number;
|
|
18
|
+
chunkDelay?: number;
|
|
19
|
+
};
|
|
20
|
+
declare const meta: Meta<StoryArgs>;
|
|
21
|
+
export default meta;
|
|
22
|
+
type Story = StoryObj<StoryArgs>;
|
|
23
|
+
/** The loop, hands on: type a prompt, or press ▶ and watch. No play — this one is for people. */
|
|
24
|
+
export declare const Default: Story;
|
|
25
|
+
/**
|
|
26
|
+
* The whole conversation loop, driven as a reader would drive it.
|
|
27
|
+
*
|
|
28
|
+
* A prompt is typed and entered; the agent answers in blocks; the tail stays at rest through the
|
|
29
|
+
* stream (the follow at work); scrolling up mid-answer withdraws the follow and the feed holds
|
|
30
|
+
* still under a growing document.
|
|
31
|
+
*/
|
|
32
|
+
export declare const Assistant: Story;
|
|
33
|
+
/**
|
|
34
|
+
* Scrolling away mid-answer stops the follow, and it stays stopped: a reader looking at history is
|
|
35
|
+
* not dragged back by the stream. Returning to the tail opts back in.
|
|
36
|
+
*/
|
|
37
|
+
export declare const Interrupted: Story;
|
|
38
|
+
//# sourceMappingURL=ChatThread.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatThread.stories.d.ts","sourceRoot":"","sources":["../../../../../src/components/ChatThread/ChatThread.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAYjE,OAAO,EAAwB,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGlE;;;;;;;;;GASG;AACH,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAuOF,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,SAAS,CAczB,CAAC;eAEa,IAAI;AAEnB,KAAK,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;AAiBjC,iGAAiG;AACjG,eAAO,MAAM,OAAO,EAAE,KAAU,CAAC;AAEjC;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,EAAE,KA+DvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,KAsCzB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/ChatThread/index.ts"],"names":[],"mappings":"AAIA,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type ThemedClassName } from '@dxos/react-ui';
|
|
3
|
+
import { type MessageChromeProps } from '@dxos/react-ui-feed';
|
|
4
|
+
import { Message } from '@dxos/types';
|
|
5
|
+
type MessageChromeContextValue = {
|
|
6
|
+
/** Soft-fork the thread from the given prompt; the rewind button renders only when present. */
|
|
7
|
+
onRewind?: (id: string) => void;
|
|
8
|
+
};
|
|
9
|
+
declare const MessageChromeProvider: React.FC<MessageChromeContextValue & {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
}>;
|
|
12
|
+
export { MessageChromeProvider };
|
|
13
|
+
export type MessageToolbarProps = ThemedClassName<{
|
|
14
|
+
message: Message.Message;
|
|
15
|
+
}>;
|
|
16
|
+
export declare function PromptToolbar({ classNames, message }: MessageToolbarProps): React.JSX.Element;
|
|
17
|
+
export declare namespace PromptToolbar {
|
|
18
|
+
var displayName: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function AssistantToolbar({ classNames, message }: MessageToolbarProps): React.JSX.Element;
|
|
21
|
+
export declare namespace AssistantToolbar {
|
|
22
|
+
var displayName: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function MessageChrome({ message, selected, children }: MessageChromeProps): React.JSX.Element;
|
|
25
|
+
export declare namespace MessageChrome {
|
|
26
|
+
var displayName: string;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=MessageChrome.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageChrome.d.ts","sourceRoot":"","sources":["../../../../../src/components/MessageChrome/MessageChrome.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAiC,MAAM,OAAO,CAAC;AAEtD,OAAO,EAAc,KAAK,eAAe,EAAkB,MAAM,gBAAgB,CAAC;AAClF,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAYtC,KAAK,yBAAyB,GAAG;IAC/B,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,CAAC;AAEF,QAAA,MAAO,qBAAqB;;EAA0F,CAAC;AAEvH,OAAO,EAAE,qBAAqB,EAAE,CAAC;AA2BjC,MAAM,MAAM,mBAAmB,GAAG,eAAe,CAAC;IAChD,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC;CAC1B,CAAC,CAAC;sCAQ2B,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,mBAAmB;;;;yCA0BzC,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,mBAAmB;;;;sCAiC/C,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,kBAAkB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/MessageChrome/index.ts"],"names":[],"mappings":"AAIA,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/index.ts"],"names":[],"mappings":"AAIA,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type XmlWidgetProps } from '@dxos/ui-editor';
|
|
3
|
+
export declare function FallbackWidget({ _tag, ...props }: XmlWidgetProps): React.JSX.Element;
|
|
4
|
+
export declare namespace FallbackWidget {
|
|
5
|
+
var displayName: string;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=FallbackWidget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FallbackWidget.d.ts","sourceRoot":"","sources":["../../../../../src/components/widgets/FallbackWidget.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;uCAEvB,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,EAAE,cAAc"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { WidgetType } from '@codemirror/view';
|
|
2
|
+
/**
|
|
3
|
+
* Props used to derive a stable key for a reasoning block across CodeMirror widget rebuilds.
|
|
4
|
+
* CodeMirror does not assign persistent IDs to widgets; streaming re-runs the factory each tick.
|
|
5
|
+
*/
|
|
6
|
+
export type ReasoningWidgetBlockIdProps = {
|
|
7
|
+
id?: string;
|
|
8
|
+
range?: {
|
|
9
|
+
from: number;
|
|
10
|
+
to: number;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* AI reasoning widget.
|
|
15
|
+
*/
|
|
16
|
+
export declare class ReasoningWidget extends WidgetType {
|
|
17
|
+
#private;
|
|
18
|
+
private readonly text;
|
|
19
|
+
constructor(text: string, pos: string | number | undefined);
|
|
20
|
+
eq(other: this): boolean;
|
|
21
|
+
toDOM(): HTMLDivElement;
|
|
22
|
+
updateDOM(dom: HTMLElement): boolean;
|
|
23
|
+
destroy(_dom: HTMLElement): void;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=ReasoningWidget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReasoningWidget.d.ts","sourceRoot":"","sources":["../../../../../src/components/widgets/ReasoningWidget.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAM9C;;;GAGG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;CACtC,CAAC;AAEF;;GAEG;AACH,qBAAa,eAAgB,SAAQ,UAAU;;IAM3C,OAAO,CAAC,QAAQ,CAAC,IAAI;IADvB,YACmB,IAAI,EAAE,MAAM,EAC7B,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAIjC;IAEQ,EAAE,CAAC,KAAK,EAAE,IAAI,WAEtB;IAEQ,KAAK,mBAyBb;IAEQ,SAAS,CAAC,GAAG,EAAE,WAAW,WAmBlC;IAEQ,OAAO,CAAC,IAAI,EAAE,WAAW,QAEjC;CA6BF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
export type StoryArgs = {
|
|
4
|
+
text: string;
|
|
5
|
+
};
|
|
6
|
+
declare const meta: {
|
|
7
|
+
title: string;
|
|
8
|
+
component: ({ text }: StoryArgs) => React.JSX.Element;
|
|
9
|
+
decorators: import("@storybook/react").Decorator[];
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export default meta;
|
|
15
|
+
type Story = StoryObj<typeof meta>;
|
|
16
|
+
export declare const Default: Story;
|
|
17
|
+
export declare const LongText: Story;
|
|
18
|
+
//# sourceMappingURL=ReasoningWidget.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReasoningWidget.stories.d.ts","sourceRoot":"","sources":["../../../../../src/components/widgets/ReasoningWidget.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAA4B,MAAM,OAAO,CAAC;AAMjD,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAyBF,QAAA,MAAM,IAAI;;0BAtBsB,SAAS;;;QA2BrC,MAAM;;CAE2B,CAAC;eAEvB,IAAI;AAEnB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,OAAO,EAAE,KAIrB,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,KAUtB,CAAC"}
|