@dxos/react-ui-chat 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lib/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/components/ChatDialog/ChatDialog.tsx","../../src/components/ChatEditor/commands.ts","../../src/components/ChatEditor/references.ts","../../src/components/ChatEditor/useChatExtensions.ts","../../src/components/ChatEditor/ChatEditor.tsx","../../src/components/ChatStatus/ChatStatusContext.ts","../../src/components/ChatStatus/format.ts","../../src/components/ChatStatus/ChatStatus.tsx","../../src/components/ChatStatusIndicator/ChatStatusIndicator.tsx"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { type Dispatch, type PropsWithChildren, type SetStateAction, useEffect, useState } from 'react';\n\nimport { ResizeHandle, type Size, resizeAttributes, sizeStyle } from '@dxos/react-ui-dnd';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Dialog from '@dxos/react-ui/Dialog';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { mx } from '@dxos/ui-theme';\n\n// TODO(burdon): Factor out.\nconst Endcap = ({ children }: PropsWithChildren) => {\n return (\n <div className='grid w-[var(--dx-rail-action)] h-[var(--dx-rail-action)] items-center justify-center'>\n {children}\n </div>\n );\n};\n\n//\n// Context\n//\n\ntype ChatDialogContextValue = {\n open: boolean;\n setOpen: Dispatch<SetStateAction<boolean>>;\n expanded: boolean;\n setExpanded: Dispatch<SetStateAction<boolean>>;\n size: Size;\n setSize: Dispatch<SetStateAction<Size>>;\n};\n\nconst [ChatDialogContextProvider, useChatDialogContext] = Hooks.createContext<ChatDialogContextValue>('ChatDialog');\n\n//\n// Root\n//\n\ntype ChatDialogRootProps = PropsWithChildren<{\n open?: boolean;\n expanded?: boolean;\n onOpenChange?: (open: boolean) => void;\n onExpandedChange?: (expanded: boolean) => void;\n onEscape?: () => void;\n}>;\n\nconst ChatDialogRoot = ({\n children,\n open: openProp = false,\n expanded: expandedProp = false,\n onOpenChange,\n onExpandedChange,\n onEscape,\n}: ChatDialogRootProps) => {\n const [size, setSize] = useState<Size>('min-content');\n const [open, setOpen] = Hooks.useControlledState<boolean>(openProp, onOpenChange);\n const [expanded, setExpanded] = Hooks.useControlledState<boolean>(expandedProp, onExpandedChange);\n\n // NOTE: We set the min size to 5rem (80px), and the header and prompt bar to 40px (i.e., the rail-size) each.\n // The dialog has no vertical padding and has box-content so that when closed it collapses to the size of the header and prompt bar.\n return (\n <ChatDialogContextProvider\n open={open}\n setOpen={setOpen}\n expanded={expanded}\n setExpanded={setExpanded}\n size={size}\n setSize={setSize}\n >\n <Dialog.Root\n modal={false}\n open={open}\n onOpenChange={({ open }) => setOpen(open)}\n onEscapeKeyDown={onEscape}\n onInteractOutside={(event) => event.preventDefault()}\n >\n <Dialog.Content\n size='md'\n placement='end'\n scrim={false}\n classNames='grid grid-rows-[var(--dx-rail-action)_1fr_min-content] p-0 overflow-hidden box-content'\n >\n {children}\n </Dialog.Content>\n </Dialog.Root>\n </ChatDialogContextProvider>\n );\n};\n\nChatDialogRoot.displayName = 'ChatDialog.Root';\n\n//\n// Header\n//\n\nconst CHAT_DIALOG_HEADER_NAME = 'ChatDialog.Header';\n\ntype ChatDialogHeaderProps = Util.ThemedClassName<{\n title?: string;\n}>;\n\nconst ChatDialogHeader = ({ classNames, title }: ChatDialogHeaderProps) => {\n const { expanded, setExpanded } = useChatDialogContext(CHAT_DIALOG_HEADER_NAME);\n\n return (\n <div\n className={mx('grid grid-cols-[var(--dx-rail-action)_1fr_min-content] items-center overflow-hidden', classNames)}\n >\n <Endcap>\n <Dialog.CloseTrigger>\n <Icon.Icon icon='ph--x--regular' />\n </Dialog.CloseTrigger>\n </Endcap>\n <Dialog.Title\n classNames='flex w-full justify-center text-sm text-fg-subtle select-none cursor-pointer'\n onClick={() => setExpanded((expanded) => !expanded)}\n >\n {title}\n </Dialog.Title>\n <Endcap>\n <Button.Root\n variant='ghost'\n icon='ph--caret-up--regular'\n iconOnly\n classNames={mx('p-1! [&>svg]:transition [&>svg]:duration-200', expanded && '[&>svg]:rotate-180')}\n label={expanded ? 'Close' : 'Open'}\n onClick={() => setExpanded((expanded) => !expanded)}\n />\n </Endcap>\n </div>\n );\n};\n\nChatDialogHeader.displayName = CHAT_DIALOG_HEADER_NAME;\n\n//\n// Content\n//\n\nconst CHAT_DIALOG_CONTENT_NAME = 'ChatDialog.Content';\n\ntype ChatDialogContentProps = Util.ThemedClassName<PropsWithChildren>;\n\nconst ChatDialogContent = ({ children, classNames }: ChatDialogContentProps) => {\n const { expanded, size, setSize } = useChatDialogContext(CHAT_DIALOG_CONTENT_NAME);\n useEffect(() => {\n setSize(expanded ? 'min-content' : 0);\n }, [expanded]);\n\n return (\n <div\n className={mx(\n 'border-t border-b border-separator-subtle',\n 'transition ease-in-out duration-0 [&:not([data-dx-resizing=\"true\"])]:duration-200',\n classNames,\n )}\n style={{\n ...sizeStyle(size, 'vertical', true),\n maxBlockSize: 'calc(100dvh - env(safe-area-inset-bottom) - env(safe-area-inset-top) - 14rem)',\n }}\n {...resizeAttributes}\n >\n <ResizeHandle\n side='block-start'\n iconPosition='center'\n defaultSize='min-content'\n fallbackSize={0}\n minSize={0}\n onSizeChange={setSize}\n />\n {children}\n </div>\n );\n};\n\nChatDialogContent.displayName = CHAT_DIALOG_CONTENT_NAME;\n\n//\n// Footer\n//\n\nconst CHAT_DIALOG_FOOTER_NAME = 'ChatDialog.Footer';\n\ntype ChatDialogFooterProps = Util.ThemedClassName<PropsWithChildren>;\n\nconst ChatDialogFooter = ({ children, classNames }: ChatDialogFooterProps) => {\n return <div className={mx(classNames)}>{children}</div>;\n};\n\nChatDialogFooter.displayName = CHAT_DIALOG_FOOTER_NAME;\n\n//\n// ChatDialog\n//\n\nexport const ChatDialog = {\n Root: ChatDialogRoot,\n Header: ChatDialogHeader,\n Content: ChatDialogContent,\n Footer: ChatDialogFooter,\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport {\n type CompletionContext,\n type CompletionResult,\n acceptCompletion,\n autocompletion,\n completionStatus,\n currentCompletions,\n selectedCompletionIndex,\n setSelectedCompletion,\n} from '@codemirror/autocomplete';\nimport { type Extension, Prec } from '@codemirror/state';\nimport { Decoration, type DecorationSet, EditorView, ViewPlugin, type ViewUpdate, keymap } from '@codemirror/view';\n\nexport type CommandData = { sentinel: string; description?: string };\n\n/** Pure prefix matcher, exported for tests. */\nexport const matchCommands = (all: CommandData[], token: string): CommandData[] =>\n token.startsWith('$') || token.startsWith('/') ? all.filter(({ sentinel }) => sentinel.startsWith(token)) : [];\n\nexport type CommandsOptions = { getCommands: () => CommandData[] };\n\n/**\n * Command completion: typing `$` offers the bound context's instruction sentinels anywhere;\n * typing `/` at the start of the prompt offers registered slash commands.\n */\nconst commandMark = Decoration.mark({\n // Inline in text, so the pill keeps its own inset from the words either side.\n class: 'dx-tag dx-tag-inline mx-0.5',\n // Baseline alignment puts the pill's text on the line's baseline; the negative block margin\n // cancels the padded pill's line-box growth (same recipe as `.dx-tag-anchor` in CodeMirror).\n attributes: {\n 'data-hue': 'blue',\n 'style': 'margin-block: -5px;',\n 'font-family': 'var(--dx-font-mono, monospace)',\n },\n});\n\n/** Decorates a prompt-leading registered command token as a tag. */\nconst commandDecorations = (getCommands: () => CommandData[]) => {\n const build = (view: EditorView): DecorationSet => {\n const match = /^[$/][\\w:-]+/.exec(view.state.doc.line(1).text);\n return match && getCommands().some(({ sentinel }) => sentinel === match[0])\n ? Decoration.set([commandMark.range(0, match[0].length)])\n : Decoration.none;\n };\n return ViewPlugin.fromClass(\n class {\n decorations: DecorationSet;\n constructor(view: EditorView) {\n this.decorations = build(view);\n }\n update(update: ViewUpdate) {\n if (update.docChanged) {\n this.decorations = build(update.view);\n }\n }\n },\n {\n decorations: (instance) => instance.decorations,\n // Atomic: the cursor steps over the tag as one unit and backspace removes it whole.\n provide: (plugin) => EditorView.atomicRanges.of((view) => view.plugin(plugin)?.decorations ?? Decoration.none),\n },\n );\n};\n\n/** Two-column command/description layout; shared column widths via subgrid. */\nconst commandTooltipTheme = EditorView.theme({\n '.cm-tooltip-autocomplete > ul': {\n display: 'grid',\n gridTemplateColumns: 'max-content 1fr',\n columnGap: '0.75rem',\n },\n '.cm-tooltip-autocomplete > ul > li': {\n display: 'grid',\n gridTemplateColumns: 'subgrid',\n gridColumn: 'span 2',\n alignItems: 'center',\n },\n '.cm-tooltip-autocomplete .cm-completionLabel': {\n fontFamily: 'var(--dx-font-mono, monospace)',\n },\n '.cm-tooltip-autocomplete .cm-completionDetail': {\n fontStyle: 'normal',\n fontSize: '0.875rem',\n marginInlineStart: '0',\n },\n});\n\nexport const commands = ({ getCommands }: CommandsOptions): Extension => [\n commandTooltipTheme,\n commandDecorations(getCommands),\n autocompletion({\n // The prompt sits at the bottom of its panel, so the list opens upward.\n aboveCursor: true,\n override: [\n (context: CompletionContext): CompletionResult | null => {\n const word = context.matchBefore(/[$/][\\w:-]*/);\n if (!word || (word.from === word.to && !context.explicit)) {\n return null;\n }\n // Slash commands are prompt-leading only, so a mid-sentence path fragment never completes.\n if (context.state.sliceDoc(word.from, word.from + 1) === '/' && word.from !== 0) {\n return null;\n }\n const options = matchCommands(getCommands(), context.state.sliceDoc(word.from, word.to));\n if (options.length === 0) {\n return null;\n }\n return {\n from: word.from,\n // Accepting appends the separator space, so the arguments can be typed immediately.\n options: options.map(({ sentinel, description }) => ({\n label: sentinel,\n detail: description,\n apply: `${sentinel} `,\n })),\n };\n },\n ],\n }),\n // Arrow navigation stops at the ends of the list rather than cycling.\n Prec.highest(\n keymap.of([\n {\n key: 'ArrowDown',\n run: (view) => {\n if (completionStatus(view.state) !== 'active') {\n return false;\n }\n const index = selectedCompletionIndex(view.state);\n if (index === null) {\n return false;\n }\n if (index < currentCompletions(view.state).length - 1) {\n view.dispatch({ effects: setSelectedCompletion(index + 1) });\n }\n return true;\n },\n },\n {\n key: 'ArrowUp',\n run: (view) => {\n if (completionStatus(view.state) !== 'active') {\n return false;\n }\n const index = selectedCompletionIndex(view.state);\n if (index === null) {\n return false;\n }\n if (index > 0) {\n view.dispatch({ effects: setSelectedCompletion(index - 1) });\n }\n return true;\n },\n },\n ]),\n ),\n // The host editor's `submit()` extension also binds Enter at `Prec.highest`; ties between equal\n // precedences break by extension order, so the caller must place this extension ahead of `submit()`.\n // `completionStatus`/`acceptCompletion` are imported from the same module as `autocompletion()` above\n // (not re-derived in a different package) so this always reads the completion state `autocompletion()`\n // actually wrote — a cross-package import of the same-named API can resolve to a distinct bundled\n // copy of `@codemirror/autocomplete` under some bundler configurations, silently reading `null`.\n Prec.highest(\n keymap.of([\n {\n key: 'Enter',\n run: (view) => (completionStatus(view.state) === 'active' ? acceptCompletion(view) : false),\n },\n ]),\n ),\n];\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type CompletionResult, autocompletion, completionKeymap } from '@codemirror/autocomplete';\nimport { type Extension, RangeSet } from '@codemirror/state';\nimport {\n Decoration,\n type DecorationSet,\n EditorView,\n ViewPlugin,\n type ViewUpdate,\n WidgetType,\n keymap,\n} from '@codemirror/view';\n\nimport { Mutex } from '@dxos/async';\nimport { invariant } from '@dxos/invariant';\n\n// TODO(dmaretskyi): Consider adding details renderer for when you hover over the reference.\nexport type ReferenceData = {\n uri: string; // TODO(burdon): Rename dxn.\n label: string;\n};\n\n/**\n * @deprecated\n */\n// TODO(burdon): Remove.\nexport interface ReferencesProvider {\n getReferences({ query }: { query: string }): Promise<ReferenceData[]>;\n resolveReference({ uri }: { uri: string }): Promise<ReferenceData | null>;\n}\n\nexport type ReferencesOptions = {\n provider: ReferencesProvider;\n\n /**\n * @default '@'\n */\n triggerCharacter?: string;\n\n /**\n * Prevent the autocomplete from closing when the user blurs the editor.\n * @default false\n */\n debug?: boolean;\n};\n\n/**\n * Lookup object references.\n * @deprecated\n */\n// TODO(burdon): Remove.\nexport const references = ({ provider, triggerCharacter = '@', debug = false }: ReferencesOptions): Extension => {\n invariant(triggerCharacter.length === 1);\n\n const decorationField = ViewPlugin.fromClass(\n class ReferenceView {\n // TODO(burdon): Why?\n private _mutex = new Mutex();\n\n decorations: DecorationSet = Decoration.set([]);\n\n constructor(view: EditorView) {\n queueMicrotask(async () => {\n const guard = await this._mutex.acquire();\n try {\n this.decorations = await this._computeDecorations(view);\n } finally {\n guard.release();\n }\n });\n }\n\n update(update: ViewUpdate) {\n if (update.docChanged) {\n queueMicrotask(async () => {\n const guard = await this._mutex.acquire();\n try {\n this.decorations = await this._computeDecorations(update.view);\n } finally {\n guard.release();\n }\n });\n }\n }\n\n private async _computeDecorations(view: EditorView): Promise<DecorationSet> {\n const text = view.state.doc.toString();\n const references = text.matchAll(new RegExp(`${triggerCharacter}[a-zA-Z0-9@:]+\\\\s`, 'g'));\n\n const decorations = [];\n for (const match of references) {\n const reference = match[0];\n const uri = reference.slice(1, -1);\n const data = await provider.resolveReference({ uri });\n if (data) {\n decorations.push(\n Decoration.replace({\n widget: new ReferenceWidget(data),\n }).range(match.index!, match.index! + reference.length),\n );\n }\n }\n\n return Decoration.set(decorations);\n }\n },\n {\n decorations: (v) => v.decorations,\n provide: () => [\n EditorView.atomicRanges.of(\n (view): DecorationSet => view.plugin(decorationField)?.decorations ?? RangeSet.empty,\n ),\n ],\n },\n );\n\n return [\n decorationField,\n\n EditorView.theme({\n '.cm-reference-pill': {\n borderRadius: '0.25rem',\n borderWidth: '1px',\n marginRight: '0.25rem',\n marginLeft: '0.25rem',\n },\n }),\n\n autocompletion({\n tooltipClass: () => 'shadow rounded-sm',\n activateOnTyping: true,\n aboveCursor: true,\n closeOnBlur: !debug,\n override: [\n async (context): Promise<CompletionResult | null> => {\n const match = context.matchBefore(new RegExp(`${triggerCharacter}[a-zA-Z0-9]+`));\n if (!match || match?.to === match?.from) {\n return null;\n }\n\n const query = match.text.slice(1);\n const references = await provider.getReferences({ query });\n return {\n from: match.from,\n filter: false,\n options: references.map((reference) => ({\n label: reference.label,\n apply: `${triggerCharacter}${reference.uri} `,\n })),\n };\n },\n ],\n }),\n\n keymap.of(completionKeymap),\n ];\n};\n\nclass ReferenceWidget extends WidgetType {\n constructor(private data: ReferenceData) {\n super();\n }\n\n override ignoreEvent() {\n return true;\n }\n\n override eq(other: this) {\n return this.data.uri === other.data.uri;\n }\n\n override toDOM() {\n const span = document.createElement('span');\n span.textContent = `@${this.data.label}`;\n span.className = 'cm-reference-pill';\n return span;\n }\n}\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type Extension } from '@codemirror/state';\nimport { useMemo } from 'react';\n\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport {\n createBasicExtensions,\n createMarkdownExtensions,\n createThemeExtensions,\n decorateMarkdown,\n formattingKeymap,\n submit,\n xmlFormatting,\n} from '@dxos/ui-editor';\nimport { isTruthy } from '@dxos/util';\n\nimport { type ChatEditorProps } from './ChatEditor.tsx';\n\n// Kept out of `ChatEditor.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\nexport const useChatExtensions = ({\n extensions,\n markdown = false,\n lineWrapping = false,\n placeholder,\n onSubmit,\n}: ChatEditorProps) => {\n const themeMode = Hooks.useThemeMode();\n const onSubmitRef = Hooks.useDynamicRef(onSubmit);\n return useMemo<Extension[]>(\n () =>\n [\n createThemeExtensions({ themeMode, syntaxHighlighting: markdown }),\n createBasicExtensions({ bracketMatching: false, lineWrapping, placeholder }),\n xmlFormatting(),\n markdown && [createMarkdownExtensions(), decorateMarkdown(), formattingKeymap()],\n // Caller extensions (e.g. `commands()`'s completion-aware Enter binding) must precede\n // `submit()`: both bind Enter at `Prec.highest`, and CodeMirror breaks precedence ties by\n // extension order, so listing `submit()` first would always win and swallow the keystroke.\n extensions,\n submit({ onSubmit: (text) => onSubmitRef.current?.(text) }),\n ]\n .flat()\n .filter(isTruthy),\n [themeMode, markdown, lineWrapping, placeholder, extensions, onSubmitRef],\n );\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type Extension } from '@codemirror/state';\nimport React, { forwardRef } from 'react';\n\nimport { Editor, type EditorController, type UseTextEditorProps } from '@dxos/react-ui-editor';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { type BasicExtensionsOptions, type SubmitOptions } from '@dxos/ui-editor';\n\nimport { type ReferencesOptions } from './references.ts';\nimport { useChatExtensions } from './useChatExtensions.ts';\n\nexport interface ChatEditorController extends EditorController {}\n\nexport type ChatEditorProps = Util.ThemedClassName<\n {\n extensions?: Extension;\n references?: ReferencesOptions;\n /** Enable inline markdown formatting (decoration, syntax highlighting, and formatting shortcuts). */\n markdown?: boolean;\n } & (SubmitOptions &\n Pick<UseTextEditorProps, 'id' | 'autoFocus'> &\n Pick<BasicExtensionsOptions, 'lineWrapping' | 'placeholder'>)\n>;\n\nexport const ChatEditor = forwardRef<ChatEditorController, ChatEditorProps>(\n (\n {\n classNames,\n autoFocus,\n extensions: extensionsProp,\n markdown = false,\n lineWrapping = false,\n placeholder,\n onSubmit,\n },\n forwardRef,\n ) => {\n const extensions = useChatExtensions({ extensions: extensionsProp, markdown, lineWrapping, placeholder, onSubmit });\n\n // TODO(burdon): Popover.\n return (\n <Editor.Root ref={forwardRef}>\n <Editor.View classNames={classNames} autoFocus={autoFocus} extensions={extensions} />\n </Editor.Root>\n );\n },\n);\n","import * as Hooks from '@dxos/react-ui/Hooks';\n//\n// Copyright 2025 DXOS.org\n//\n\n// Kept out of `ChatStatus.tsx`: react-refresh only fast-refreshes a module whose exports are all\n// components, so a context and its hook exported beside them force a full page reload on every edit.\n\n//\n// Context\n//\n\nexport type ChatStatusContextValue = {\n /** Whole seconds elapsed since ChatStatus.Root mounted. Only advances while `running` is true. */\n elapsed: number;\n /** Whether the ChatStatus.Root tick is currently active. Toggled via the ChatStatusController. */\n running: boolean;\n};\n\nexport const [ChatStatusProvider, useChatStatusContext] = Hooks.createContext<ChatStatusContextValue>('ChatStatus');\n","//\n// Copyright 2025 DXOS.org\n//\n\nconst SECOND = 1_000;\nconst MINUTE = 60 * SECOND;\nconst HOUR = 60 * MINUTE;\n\n/**\n * Formats elapsed milliseconds as a human-readable duration.\n * Tiers: `Ns` (< 60s), `Nm Xs` (< 60m), `Nh Xm` (≥ 60m).\n * Negative inputs clamp to `0s`.\n */\nexport const formatElapsed = (ms: number): string => {\n const safe = Math.max(0, ms);\n if (safe < MINUTE) {\n return `${Math.floor(safe / SECOND)}s`;\n }\n if (safe < HOUR) {\n const m = Math.floor(safe / MINUTE);\n const s = Math.floor((safe % MINUTE) / SECOND);\n return `${m}m ${s}s`;\n }\n const h = Math.floor(safe / HOUR);\n const m = Math.floor((safe % HOUR) / MINUTE);\n return `${h}h ${m}m`;\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { type PropsWithChildren, type Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react';\n\nimport type * as Util from '@dxos/react-ui/Util';\nimport { mx } from '@dxos/ui-theme';\n\nimport { ChatStatusProvider, useChatStatusContext } from './ChatStatusContext.ts';\nimport { formatElapsed } from './format.ts';\n\nconst TICK_MS = 1_000;\n\n//\n// Controller\n//\n\nexport type ChatStatusController = {\n /** Resume the tick. No-op if already running. */\n start: () => void;\n /** Pause the tick. The reported `elapsed` freezes at its last value. */\n stop: () => void;\n};\n\n//\n// Root\n//\n\nexport type RootProps = Util.ThemedClassName<\n PropsWithChildren<{\n /** Whether the tick starts running on mount. Defaults to `true`. */\n defaultRunning?: boolean;\n }>\n>;\n\nconst Root = forwardRef<ChatStatusController, RootProps>(\n ({ classNames, children, defaultRunning = true }: RootProps, forwardedRef: Ref<ChatStatusController>) => {\n const [elapsed, setElapsed] = useState(0);\n const [running, setRunning] = useState(defaultRunning);\n\n useEffect(() => {\n if (!running) {\n return;\n }\n\n // Anchor to a wall-clock timestamp so `elapsed` self-corrects after\n // tab-throttling: browsers clamp setTimeout to >=1s in inactive tabs\n // and >=1min when the page is hidden, so a count-the-ticks approach\n // would under-report after a background period. Computing elapsed from\n // (Date.now() - startedAt) restores the true wall-clock value on the\n // next fired tick. The initial elapsed (preserved across stop/start)\n // is folded into the anchor so paused intervals are excluded.\n const startedAt = Date.now() - elapsed * TICK_MS;\n let timeout: ReturnType<typeof setTimeout>;\n const tick = () => {\n setElapsed(Math.floor((Date.now() - startedAt) / TICK_MS));\n timeout = setTimeout(tick, TICK_MS - (Date.now() % TICK_MS));\n };\n timeout = setTimeout(tick, TICK_MS - (Date.now() % TICK_MS));\n return () => clearTimeout(timeout);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- `elapsed` is\n // captured once at resume; depending on it would restart the timer on\n // every tick.\n }, [running]);\n\n useImperativeHandle(\n forwardedRef,\n () => ({\n start: () => setRunning(true),\n stop: () => setRunning(false),\n }),\n [],\n );\n\n return (\n <ChatStatusProvider elapsed={elapsed} running={running}>\n <span className={mx('inline-flex items-center gap-2 text-fg-muted font-mono tabular-nums', classNames)}>\n {children}\n </span>\n </ChatStatusProvider>\n );\n },\n);\n\nRoot.displayName = 'ChatChatStatus.Root';\n\n//\n// Icon\n//\n\nexport type IconProps = Util.ThemedClassName<PropsWithChildren>;\n\n/**\n * Animated leading indicator. Defaults to a halo-pulse dot. Pass children to override.\n */\nconst Icon = ({ classNames, children }: IconProps) => {\n if (children !== undefined) {\n return <>{children}</>;\n }\n\n return (\n <span\n aria-hidden='true'\n className={mx('inline-block size-2 rounded-full bg-current text-info-text animate-pulse', classNames)}\n />\n );\n};\n\n//\n// Stopwatch\n//\n\nexport type StopwatchProps = Util.ThemedClassName<{\n /** Seconds to add to the context elapsed value before formatting. Defaults to 0. */\n offset?: number;\n}>;\n\n/**\n * Elapsed-time display, driven by the ChatStatus.Root context tick.\n * Use `offset` to shift the displayed value; remount `ChatStatus.Root` to reset the underlying counter.\n * Marked `aria-live='off'` so screen readers don't announce every tick.\n */\nconst Stopwatch = ({ classNames, offset = 0 }: StopwatchProps) => {\n const { elapsed } = useChatStatusContext('ChatChatStatus.Stopwatch');\n return (\n <span aria-live='off' className={mx(classNames)}>\n {formatElapsed((elapsed + offset) * 1_000)}\n </span>\n );\n};\n\n//\n// Separator\n//\n\nexport type SeparatorProps = Util.ThemedClassName<unknown>;\n\n/**\n * Middle-dot separator. Decorative — `aria-hidden`.\n */\nconst Separator = ({ classNames }: SeparatorProps) => (\n <span aria-hidden='true' className={mx('opacity-50', classNames)}>\n ·\n </span>\n);\n\n//\n// Text\n//\n\nexport type TextProps = Util.ThemedClassName<PropsWithChildren>;\n\n/**\n * Generic text node — useful for token counts and other inline metadata.\n */\nconst Text = ({ classNames, children }: TextProps) => <span className={mx(classNames)}>{children}</span>;\n\n//\n// ChatStatus\n//\n\nexport const ChatStatus = {\n Root,\n Icon,\n Stopwatch,\n Separator,\n Text,\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { useEffect, useState } from 'react';\n\nimport { ShapeSpinner, type SpinnerProps } from '@dxos/react-ui-components';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Tooltip from '@dxos/react-ui/Tooltip';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { mx } from '@dxos/ui-theme';\n\nconst ANIMATION_PERIOD = 3_000;\n\nexport type ChatStatusIndicatorProps = Util.ThemedClassName<\n {\n // TODO(burdon): Preset (model) triggers reset.\n preset?: string;\n processing?: boolean;\n error?: Error;\n } & Pick<SpinnerProps, 'size' | 'onClick'>\n>;\n\nexport const ChatStatusIndicator = ({ classNames, preset, processing, error, ...props }: ChatStatusIndicatorProps) => {\n const [init, setInit] = useState(false);\n useEffect(() => setInit(false), [preset]);\n Hooks.useTimeout(\n async () => {\n setInit(true);\n },\n ANIMATION_PERIOD / 2,\n [preset],\n );\n\n return (\n <div className={mx('relative flex', classNames)}>\n <ShapeSpinner\n duration={ANIMATION_PERIOD}\n state={!init ? 'alert' : error ? 'error' : processing ? 'thinking' : 'ready'}\n {...props}\n />\n {error && (\n <Tooltip.Trigger asChild content={error.message}>\n <div className='dx-cover' />\n </Tooltip.Trigger>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["Icon","_Fragment"],"sources":["../../src/components/ChatDialog/ChatDialog.tsx","../../src/components/ChatEditor/commands.ts","../../src/components/ChatEditor/references.ts","../../src/components/ChatEditor/useChatExtensions.ts","../../src/components/ChatEditor/ChatEditor.tsx","../../src/components/ChatStatus/ChatStatusContext.ts","../../src/components/ChatStatus/format.ts","../../src/components/ChatStatus/ChatStatus.tsx","../../src/components/ChatStatusIndicator/ChatStatusIndicator.tsx"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { type Dispatch, type PropsWithChildren, type SetStateAction, useEffect, useState } from 'react';\n\nimport { ResizeHandle, type Size, resizeAttributes, sizeStyle } from '@dxos/react-ui-dnd';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Dialog from '@dxos/react-ui/Dialog';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { mx } from '@dxos/ui-theme';\n\n// TODO(burdon): Factor out.\nconst Endcap = ({ children }: PropsWithChildren) => {\n return (\n <div className='grid w-[var(--dx-rail-action)] h-[var(--dx-rail-action)] items-center justify-center'>\n {children}\n </div>\n );\n};\n\n//\n// Context\n//\n\ntype ChatDialogContextValue = {\n open: boolean;\n setOpen: Dispatch<SetStateAction<boolean>>;\n expanded: boolean;\n setExpanded: Dispatch<SetStateAction<boolean>>;\n size: Size;\n setSize: Dispatch<SetStateAction<Size>>;\n};\n\nconst [ChatDialogContextProvider, useChatDialogContext] = Hooks.createContext<ChatDialogContextValue>('ChatDialog');\n\n//\n// Root\n//\n\ntype ChatDialogRootProps = PropsWithChildren<{\n open?: boolean;\n expanded?: boolean;\n onOpenChange?: (open: boolean) => void;\n onExpandedChange?: (expanded: boolean) => void;\n onEscape?: () => void;\n}>;\n\nconst ChatDialogRoot = ({\n children,\n open: openProp = false,\n expanded: expandedProp = false,\n onOpenChange,\n onExpandedChange,\n onEscape,\n}: ChatDialogRootProps) => {\n const [size, setSize] = useState<Size>('min-content');\n const [open, setOpen] = Hooks.useControlledState<boolean>(openProp, onOpenChange);\n const [expanded, setExpanded] = Hooks.useControlledState<boolean>(expandedProp, onExpandedChange);\n\n // NOTE: We set the min size to 5rem (80px), and the header and prompt bar to 40px (i.e., the rail-size) each.\n // The dialog has no vertical padding and has box-content so that when closed it collapses to the size of the header and prompt bar.\n return (\n <ChatDialogContextProvider\n open={open}\n setOpen={setOpen}\n expanded={expanded}\n setExpanded={setExpanded}\n size={size}\n setSize={setSize}\n >\n <Dialog.Root\n modal={false}\n open={open}\n onOpenChange={({ open }) => setOpen(open)}\n onEscapeKeyDown={onEscape}\n onInteractOutside={(event) => event.preventDefault()}\n >\n <Dialog.Content\n size='md'\n placement='end'\n scrim={false}\n classNames='grid grid-rows-[var(--dx-rail-action)_1fr_min-content] p-0 overflow-hidden box-content'\n >\n {children}\n </Dialog.Content>\n </Dialog.Root>\n </ChatDialogContextProvider>\n );\n};\n\nChatDialogRoot.displayName = 'ChatDialog.Root';\n\n//\n// Header\n//\n\nconst CHAT_DIALOG_HEADER_NAME = 'ChatDialog.Header';\n\ntype ChatDialogHeaderProps = Util.ThemedClassName<{\n title?: string;\n}>;\n\nconst ChatDialogHeader = ({ classNames, title }: ChatDialogHeaderProps) => {\n const { expanded, setExpanded } = useChatDialogContext(CHAT_DIALOG_HEADER_NAME);\n\n return (\n <div\n className={mx('grid grid-cols-[var(--dx-rail-action)_1fr_min-content] items-center overflow-hidden', classNames)}\n >\n <Endcap>\n <Dialog.CloseTrigger>\n <Icon.Icon icon='ph--x--regular' />\n </Dialog.CloseTrigger>\n </Endcap>\n <Dialog.Title\n classNames='flex w-full justify-center text-sm text-fg-subtle select-none cursor-pointer'\n onClick={() => setExpanded((expanded) => !expanded)}\n >\n {title}\n </Dialog.Title>\n <Endcap>\n <Button.Root\n variant='ghost'\n icon='ph--caret-up--regular'\n iconOnly\n classNames={mx('p-1! [&>svg]:transition [&>svg]:duration-200', expanded && '[&>svg]:rotate-180')}\n label={expanded ? 'Close' : 'Open'}\n onClick={() => setExpanded((expanded) => !expanded)}\n />\n </Endcap>\n </div>\n );\n};\n\nChatDialogHeader.displayName = CHAT_DIALOG_HEADER_NAME;\n\n//\n// Content\n//\n\nconst CHAT_DIALOG_CONTENT_NAME = 'ChatDialog.Content';\n\ntype ChatDialogContentProps = Util.ThemedClassName<PropsWithChildren>;\n\nconst ChatDialogContent = ({ children, classNames }: ChatDialogContentProps) => {\n const { expanded, size, setSize } = useChatDialogContext(CHAT_DIALOG_CONTENT_NAME);\n useEffect(() => {\n setSize(expanded ? 'min-content' : 0);\n }, [expanded]);\n\n return (\n <div\n className={mx(\n 'border-t border-b border-separator-subtle',\n 'transition ease-in-out duration-0 [&:not([data-dx-resizing=\"true\"])]:duration-200',\n classNames,\n )}\n style={{\n ...sizeStyle(size, 'vertical', true),\n maxBlockSize: 'calc(100dvh - env(safe-area-inset-bottom) - env(safe-area-inset-top) - 14rem)',\n }}\n {...resizeAttributes}\n >\n <ResizeHandle\n side='block-start'\n iconPosition='center'\n defaultSize='min-content'\n fallbackSize={0}\n minSize={0}\n onSizeChange={setSize}\n />\n {children}\n </div>\n );\n};\n\nChatDialogContent.displayName = CHAT_DIALOG_CONTENT_NAME;\n\n//\n// Footer\n//\n\nconst CHAT_DIALOG_FOOTER_NAME = 'ChatDialog.Footer';\n\ntype ChatDialogFooterProps = Util.ThemedClassName<PropsWithChildren>;\n\nconst ChatDialogFooter = ({ children, classNames }: ChatDialogFooterProps) => {\n return <div className={mx(classNames)}>{children}</div>;\n};\n\nChatDialogFooter.displayName = CHAT_DIALOG_FOOTER_NAME;\n\n//\n// ChatDialog\n//\n\nexport const ChatDialog = {\n Root: ChatDialogRoot,\n Header: ChatDialogHeader,\n Content: ChatDialogContent,\n Footer: ChatDialogFooter,\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport {\n type CompletionContext,\n type CompletionResult,\n acceptCompletion,\n autocompletion,\n completionStatus,\n currentCompletions,\n selectedCompletionIndex,\n setSelectedCompletion,\n} from '@codemirror/autocomplete';\nimport { type Extension, Prec } from '@codemirror/state';\nimport { Decoration, type DecorationSet, EditorView, ViewPlugin, type ViewUpdate, keymap } from '@codemirror/view';\n\nexport type CommandData = { sentinel: string; description?: string };\n\n/** Pure prefix matcher, exported for tests. */\nexport const matchCommands = (all: CommandData[], token: string): CommandData[] =>\n token.startsWith('$') || token.startsWith('/') ? all.filter(({ sentinel }) => sentinel.startsWith(token)) : [];\n\nexport type CommandsOptions = { getCommands: () => CommandData[] };\n\n/**\n * Command completion: typing `$` offers the bound context's instruction sentinels anywhere;\n * typing `/` at the start of the prompt offers registered slash commands.\n */\nconst commandMark = Decoration.mark({\n // Inline in text, so the pill keeps its own inset from the words either side.\n class: 'dx-tag dx-tag-inline mx-0.5',\n // Baseline alignment puts the pill's text on the line's baseline; the negative block margin\n // cancels the padded pill's line-box growth (same recipe as `.dx-tag-anchor` in CodeMirror).\n attributes: {\n 'data-hue': 'blue',\n 'style': 'margin-block: -5px;',\n 'font-family': 'var(--dx-font-mono, monospace)',\n },\n});\n\n/** Decorates a prompt-leading registered command token as a tag. */\nconst commandDecorations = (getCommands: () => CommandData[]) => {\n const build = (view: EditorView): DecorationSet => {\n const match = /^[$/][\\w:-]+/.exec(view.state.doc.line(1).text);\n return match && getCommands().some(({ sentinel }) => sentinel === match[0])\n ? Decoration.set([commandMark.range(0, match[0].length)])\n : Decoration.none;\n };\n return ViewPlugin.fromClass(\n class {\n decorations: DecorationSet;\n constructor(view: EditorView) {\n this.decorations = build(view);\n }\n update(update: ViewUpdate) {\n if (update.docChanged) {\n this.decorations = build(update.view);\n }\n }\n },\n {\n decorations: (instance) => instance.decorations,\n // Atomic: the cursor steps over the tag as one unit and backspace removes it whole.\n provide: (plugin) => EditorView.atomicRanges.of((view) => view.plugin(plugin)?.decorations ?? Decoration.none),\n },\n );\n};\n\n/** Two-column command/description layout; shared column widths via subgrid. */\nconst commandTooltipTheme = EditorView.theme({\n '.cm-tooltip-autocomplete > ul': {\n display: 'grid',\n gridTemplateColumns: 'max-content 1fr',\n columnGap: '0.75rem',\n },\n '.cm-tooltip-autocomplete > ul > li': {\n display: 'grid',\n gridTemplateColumns: 'subgrid',\n gridColumn: 'span 2',\n alignItems: 'center',\n },\n '.cm-tooltip-autocomplete .cm-completionLabel': {\n fontFamily: 'var(--dx-font-mono, monospace)',\n },\n '.cm-tooltip-autocomplete .cm-completionDetail': {\n fontStyle: 'normal',\n fontSize: '0.875rem',\n marginInlineStart: '0',\n },\n});\n\nexport const commands = ({ getCommands }: CommandsOptions): Extension => [\n commandTooltipTheme,\n commandDecorations(getCommands),\n autocompletion({\n // The prompt sits at the bottom of its panel, so the list opens upward.\n aboveCursor: true,\n override: [\n (context: CompletionContext): CompletionResult | null => {\n const word = context.matchBefore(/[$/][\\w:-]*/);\n if (!word || (word.from === word.to && !context.explicit)) {\n return null;\n }\n // Slash commands are prompt-leading only, so a mid-sentence path fragment never completes.\n if (context.state.sliceDoc(word.from, word.from + 1) === '/' && word.from !== 0) {\n return null;\n }\n const options = matchCommands(getCommands(), context.state.sliceDoc(word.from, word.to));\n if (options.length === 0) {\n return null;\n }\n return {\n from: word.from,\n // Accepting appends the separator space, so the arguments can be typed immediately.\n options: options.map(({ sentinel, description }) => ({\n label: sentinel,\n detail: description,\n apply: `${sentinel} `,\n })),\n };\n },\n ],\n }),\n // Arrow navigation stops at the ends of the list rather than cycling.\n Prec.highest(\n keymap.of([\n {\n key: 'ArrowDown',\n run: (view) => {\n if (completionStatus(view.state) !== 'active') {\n return false;\n }\n const index = selectedCompletionIndex(view.state);\n if (index === null) {\n return false;\n }\n if (index < currentCompletions(view.state).length - 1) {\n view.dispatch({ effects: setSelectedCompletion(index + 1) });\n }\n return true;\n },\n },\n {\n key: 'ArrowUp',\n run: (view) => {\n if (completionStatus(view.state) !== 'active') {\n return false;\n }\n const index = selectedCompletionIndex(view.state);\n if (index === null) {\n return false;\n }\n if (index > 0) {\n view.dispatch({ effects: setSelectedCompletion(index - 1) });\n }\n return true;\n },\n },\n ]),\n ),\n // The host editor's `submit()` extension also binds Enter at `Prec.highest`; ties between equal\n // precedences break by extension order, so the caller must place this extension ahead of `submit()`.\n // `completionStatus`/`acceptCompletion` are imported from the same module as `autocompletion()` above\n // (not re-derived in a different package) so this always reads the completion state `autocompletion()`\n // actually wrote — a cross-package import of the same-named API can resolve to a distinct bundled\n // copy of `@codemirror/autocomplete` under some bundler configurations, silently reading `null`.\n Prec.highest(\n keymap.of([\n {\n key: 'Enter',\n run: (view) => (completionStatus(view.state) === 'active' ? acceptCompletion(view) : false),\n },\n ]),\n ),\n];\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type CompletionResult, autocompletion, completionKeymap } from '@codemirror/autocomplete';\nimport { type Extension, RangeSet } from '@codemirror/state';\nimport {\n Decoration,\n type DecorationSet,\n EditorView,\n ViewPlugin,\n type ViewUpdate,\n WidgetType,\n keymap,\n} from '@codemirror/view';\n\nimport { Mutex } from '@dxos/async';\nimport { invariant } from '@dxos/invariant';\n\n// TODO(dmaretskyi): Consider adding details renderer for when you hover over the reference.\nexport type ReferenceData = {\n uri: string; // TODO(burdon): Rename dxn.\n label: string;\n};\n\n/**\n * @deprecated\n */\n// TODO(burdon): Remove.\nexport interface ReferencesProvider {\n getReferences({ query }: { query: string }): Promise<ReferenceData[]>;\n resolveReference({ uri }: { uri: string }): Promise<ReferenceData | null>;\n}\n\nexport type ReferencesOptions = {\n provider: ReferencesProvider;\n\n /**\n * @default '@'\n */\n triggerCharacter?: string;\n\n /**\n * Prevent the autocomplete from closing when the user blurs the editor.\n * @default false\n */\n debug?: boolean;\n};\n\n/**\n * Lookup object references.\n * @deprecated\n */\n// TODO(burdon): Remove.\nexport const references = ({ provider, triggerCharacter = '@', debug = false }: ReferencesOptions): Extension => {\n invariant(triggerCharacter.length === 1);\n\n const decorationField = ViewPlugin.fromClass(\n class ReferenceView {\n // TODO(burdon): Why?\n private _mutex = new Mutex();\n\n decorations: DecorationSet = Decoration.set([]);\n\n constructor(view: EditorView) {\n queueMicrotask(async () => {\n const guard = await this._mutex.acquire();\n try {\n this.decorations = await this._computeDecorations(view);\n } finally {\n guard.release();\n }\n });\n }\n\n update(update: ViewUpdate) {\n if (update.docChanged) {\n queueMicrotask(async () => {\n const guard = await this._mutex.acquire();\n try {\n this.decorations = await this._computeDecorations(update.view);\n } finally {\n guard.release();\n }\n });\n }\n }\n\n private async _computeDecorations(view: EditorView): Promise<DecorationSet> {\n const text = view.state.doc.toString();\n const references = text.matchAll(new RegExp(`${triggerCharacter}[a-zA-Z0-9@:]+\\\\s`, 'g'));\n\n const decorations = [];\n for (const match of references) {\n const reference = match[0];\n const uri = reference.slice(1, -1);\n const data = await provider.resolveReference({ uri });\n if (data) {\n decorations.push(\n Decoration.replace({\n widget: new ReferenceWidget(data),\n }).range(match.index!, match.index! + reference.length),\n );\n }\n }\n\n return Decoration.set(decorations);\n }\n },\n {\n decorations: (v) => v.decorations,\n provide: () => [\n EditorView.atomicRanges.of(\n (view): DecorationSet => view.plugin(decorationField)?.decorations ?? RangeSet.empty,\n ),\n ],\n },\n );\n\n return [\n decorationField,\n\n EditorView.theme({\n '.cm-reference-pill': {\n borderRadius: '0.25rem',\n borderWidth: '1px',\n marginRight: '0.25rem',\n marginLeft: '0.25rem',\n },\n }),\n\n autocompletion({\n tooltipClass: () => 'shadow rounded-sm',\n activateOnTyping: true,\n aboveCursor: true,\n closeOnBlur: !debug,\n override: [\n async (context): Promise<CompletionResult | null> => {\n const match = context.matchBefore(new RegExp(`${triggerCharacter}[a-zA-Z0-9]+`));\n if (!match || match?.to === match?.from) {\n return null;\n }\n\n const query = match.text.slice(1);\n const references = await provider.getReferences({ query });\n return {\n from: match.from,\n filter: false,\n options: references.map((reference) => ({\n label: reference.label,\n apply: `${triggerCharacter}${reference.uri} `,\n })),\n };\n },\n ],\n }),\n\n keymap.of(completionKeymap),\n ];\n};\n\nclass ReferenceWidget extends WidgetType {\n constructor(private data: ReferenceData) {\n super();\n }\n\n override ignoreEvent() {\n return true;\n }\n\n override eq(other: this) {\n return this.data.uri === other.data.uri;\n }\n\n override toDOM() {\n const span = document.createElement('span');\n span.textContent = `@${this.data.label}`;\n span.className = 'cm-reference-pill';\n return span;\n }\n}\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type Extension } from '@codemirror/state';\nimport { useMemo } from 'react';\n\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport {\n createBasicExtensions,\n createMarkdownExtensions,\n createThemeExtensions,\n decorateMarkdown,\n formattingKeymap,\n submit,\n xmlFormatting,\n} from '@dxos/ui-editor';\nimport { isTruthy } from '@dxos/util';\n\nimport { type ChatEditorProps } from './ChatEditor.tsx';\n\n// Kept out of `ChatEditor.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\nexport const useChatExtensions = ({\n extensions,\n markdown = false,\n lineWrapping = false,\n placeholder,\n onSubmit,\n}: ChatEditorProps) => {\n const themeMode = Hooks.useThemeMode();\n const onSubmitRef = Hooks.useDynamicRef(onSubmit);\n return useMemo<Extension[]>(\n () =>\n [\n createThemeExtensions({ themeMode, syntaxHighlighting: markdown }),\n createBasicExtensions({ bracketMatching: false, lineWrapping, placeholder }),\n xmlFormatting(),\n markdown && [createMarkdownExtensions(), decorateMarkdown(), formattingKeymap()],\n // Caller extensions (e.g. `commands()`'s completion-aware Enter binding) must precede\n // `submit()`: both bind Enter at `Prec.highest`, and CodeMirror breaks precedence ties by\n // extension order, so listing `submit()` first would always win and swallow the keystroke.\n extensions,\n submit({ onSubmit: (text) => onSubmitRef.current?.(text) }),\n ]\n .flat()\n .filter(isTruthy),\n [themeMode, markdown, lineWrapping, placeholder, extensions, onSubmitRef],\n );\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type Extension } from '@codemirror/state';\nimport React, { forwardRef } from 'react';\n\nimport { Editor, type EditorController, type UseTextEditorProps } from '@dxos/react-ui-editor';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { type BasicExtensionsOptions, type SubmitOptions } from '@dxos/ui-editor';\n\nimport { type ReferencesOptions } from './references.ts';\nimport { useChatExtensions } from './useChatExtensions.ts';\n\nexport interface ChatEditorController extends EditorController {}\n\nexport type ChatEditorProps = Util.ThemedClassName<\n {\n extensions?: Extension;\n references?: ReferencesOptions;\n /** Enable inline markdown formatting (decoration, syntax highlighting, and formatting shortcuts). */\n markdown?: boolean;\n } & (SubmitOptions &\n Pick<UseTextEditorProps, 'id' | 'autoFocus'> &\n Pick<BasicExtensionsOptions, 'lineWrapping' | 'placeholder'>)\n>;\n\nexport const ChatEditor = forwardRef<ChatEditorController, ChatEditorProps>(\n (\n {\n classNames,\n autoFocus,\n extensions: extensionsProp,\n markdown = false,\n lineWrapping = false,\n placeholder,\n onSubmit,\n },\n forwardRef,\n ) => {\n const extensions = useChatExtensions({ extensions: extensionsProp, markdown, lineWrapping, placeholder, onSubmit });\n\n // TODO(burdon): Popover.\n return (\n <Editor.Root ref={forwardRef}>\n <Editor.View classNames={classNames} autoFocus={autoFocus} extensions={extensions} />\n </Editor.Root>\n );\n },\n);\n","import * as Hooks from '@dxos/react-ui/Hooks';\n//\n// Copyright 2025 DXOS.org\n//\n\n// Kept out of `ChatStatus.tsx`: react-refresh only fast-refreshes a module whose exports are all\n// components, so a context and its hook exported beside them force a full page reload on every edit.\n\n//\n// Context\n//\n\nexport type ChatStatusContextValue = {\n /** Whole seconds elapsed since ChatStatus.Root mounted. Only advances while `running` is true. */\n elapsed: number;\n /** Whether the ChatStatus.Root tick is currently active. Toggled via the ChatStatusController. */\n running: boolean;\n};\n\nexport const [ChatStatusProvider, useChatStatusContext] = Hooks.createContext<ChatStatusContextValue>('ChatStatus');\n","//\n// Copyright 2025 DXOS.org\n//\n\nconst SECOND = 1_000;\nconst MINUTE = 60 * SECOND;\nconst HOUR = 60 * MINUTE;\n\n/**\n * Formats elapsed milliseconds as a human-readable duration.\n * Tiers: `Ns` (< 60s), `Nm Xs` (< 60m), `Nh Xm` (≥ 60m).\n * Negative inputs clamp to `0s`.\n */\nexport const formatElapsed = (ms: number): string => {\n const safe = Math.max(0, ms);\n if (safe < MINUTE) {\n return `${Math.floor(safe / SECOND)}s`;\n }\n if (safe < HOUR) {\n const m = Math.floor(safe / MINUTE);\n const s = Math.floor((safe % MINUTE) / SECOND);\n return `${m}m ${s}s`;\n }\n const h = Math.floor(safe / HOUR);\n const m = Math.floor((safe % HOUR) / MINUTE);\n return `${h}h ${m}m`;\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { type PropsWithChildren, type Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react';\n\nimport type * as Util from '@dxos/react-ui/Util';\nimport { mx } from '@dxos/ui-theme';\n\nimport { ChatStatusProvider, useChatStatusContext } from './ChatStatusContext.ts';\nimport { formatElapsed } from './format.ts';\n\nconst TICK_MS = 1_000;\n\n//\n// Controller\n//\n\nexport type ChatStatusController = {\n /** Resume the tick. No-op if already running. */\n start: () => void;\n /** Pause the tick. The reported `elapsed` freezes at its last value. */\n stop: () => void;\n};\n\n//\n// Root\n//\n\nexport type RootProps = Util.ThemedClassName<\n PropsWithChildren<{\n /** Whether the tick starts running on mount. Defaults to `true`. */\n defaultRunning?: boolean;\n }>\n>;\n\nconst Root = forwardRef<ChatStatusController, RootProps>(\n ({ classNames, children, defaultRunning = true }: RootProps, forwardedRef: Ref<ChatStatusController>) => {\n const [elapsed, setElapsed] = useState(0);\n const [running, setRunning] = useState(defaultRunning);\n\n useEffect(() => {\n if (!running) {\n return;\n }\n\n // Anchor to a wall-clock timestamp so `elapsed` self-corrects after\n // tab-throttling: browsers clamp setTimeout to >=1s in inactive tabs\n // and >=1min when the page is hidden, so a count-the-ticks approach\n // would under-report after a background period. Computing elapsed from\n // (Date.now() - startedAt) restores the true wall-clock value on the\n // next fired tick. The initial elapsed (preserved across stop/start)\n // is folded into the anchor so paused intervals are excluded.\n const startedAt = Date.now() - elapsed * TICK_MS;\n let timeout: ReturnType<typeof setTimeout>;\n const tick = () => {\n setElapsed(Math.floor((Date.now() - startedAt) / TICK_MS));\n timeout = setTimeout(tick, TICK_MS - (Date.now() % TICK_MS));\n };\n timeout = setTimeout(tick, TICK_MS - (Date.now() % TICK_MS));\n return () => clearTimeout(timeout);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- `elapsed` is\n // captured once at resume; depending on it would restart the timer on\n // every tick.\n }, [running]);\n\n useImperativeHandle(\n forwardedRef,\n () => ({\n start: () => setRunning(true),\n stop: () => setRunning(false),\n }),\n [],\n );\n\n return (\n <ChatStatusProvider elapsed={elapsed} running={running}>\n <span className={mx('inline-flex items-center gap-2 text-fg-muted font-mono tabular-nums', classNames)}>\n {children}\n </span>\n </ChatStatusProvider>\n );\n },\n);\n\nRoot.displayName = 'ChatChatStatus.Root';\n\n//\n// Icon\n//\n\nexport type IconProps = Util.ThemedClassName<PropsWithChildren>;\n\n/**\n * Animated leading indicator. Defaults to a halo-pulse dot. Pass children to override.\n */\nconst Icon = ({ classNames, children }: IconProps) => {\n if (children !== undefined) {\n return <>{children}</>;\n }\n\n return (\n <span\n aria-hidden='true'\n className={mx('inline-block size-2 rounded-full bg-current text-info-text animate-pulse', classNames)}\n />\n );\n};\n\n//\n// Stopwatch\n//\n\nexport type StopwatchProps = Util.ThemedClassName<{\n /** Seconds to add to the context elapsed value before formatting. Defaults to 0. */\n offset?: number;\n}>;\n\n/**\n * Elapsed-time display, driven by the ChatStatus.Root context tick.\n * Use `offset` to shift the displayed value; remount `ChatStatus.Root` to reset the underlying counter.\n * Marked `aria-live='off'` so screen readers don't announce every tick.\n */\nconst Stopwatch = ({ classNames, offset = 0 }: StopwatchProps) => {\n const { elapsed } = useChatStatusContext('ChatChatStatus.Stopwatch');\n return (\n <span aria-live='off' className={mx(classNames)}>\n {formatElapsed((elapsed + offset) * 1_000)}\n </span>\n );\n};\n\n//\n// Separator\n//\n\nexport type SeparatorProps = Util.ThemedClassName<unknown>;\n\n/**\n * Middle-dot separator. Decorative — `aria-hidden`.\n */\nconst Separator = ({ classNames }: SeparatorProps) => (\n <span aria-hidden='true' className={mx('opacity-50', classNames)}>\n ·\n </span>\n);\n\n//\n// Text\n//\n\nexport type TextProps = Util.ThemedClassName<PropsWithChildren>;\n\n/**\n * Generic text node — useful for token counts and other inline metadata.\n */\nconst Text = ({ classNames, children }: TextProps) => <span className={mx(classNames)}>{children}</span>;\n\n//\n// ChatStatus\n//\n\nexport const ChatStatus = {\n Root,\n Icon,\n Stopwatch,\n Separator,\n Text,\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { useEffect, useState } from 'react';\n\nimport { ShapeSpinner, type SpinnerProps } from '@dxos/react-ui-components';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Tooltip from '@dxos/react-ui/Tooltip';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { mx } from '@dxos/ui-theme';\n\nconst ANIMATION_PERIOD = 3_000;\n\nexport type ChatStatusIndicatorProps = Util.ThemedClassName<\n {\n // TODO(burdon): Preset (model) triggers reset.\n preset?: string;\n processing?: boolean;\n error?: Error;\n } & Pick<SpinnerProps, 'size' | 'onClick'>\n>;\n\nexport const ChatStatusIndicator = ({ classNames, preset, processing, error, ...props }: ChatStatusIndicatorProps) => {\n const [init, setInit] = useState(false);\n useEffect(() => setInit(false), [preset]);\n Hooks.useTimeout(\n async () => {\n setInit(true);\n },\n ANIMATION_PERIOD / 2,\n [preset],\n );\n\n return (\n <div className={mx('relative flex', classNames)}>\n <ShapeSpinner\n duration={ANIMATION_PERIOD}\n state={!init ? 'alert' : error ? 'error' : processing ? 'thinking' : 'ready'}\n {...props}\n />\n {error && (\n <Tooltip.Trigger asChild content={error.message}>\n <div className='dx-cover' />\n </Tooltip.Trigger>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|