@dxos/react-ui-feed 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/chunk-src.mjs +2228 -0
- package/dist/lib/chunk-src.mjs.map +1 -0
- package/dist/lib/index.mjs +2 -0
- package/dist/lib/testing.mjs +1923 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/types/src/components/Block/HtmlBlock.d.ts +14 -0
- package/dist/types/src/components/Block/HtmlBlock.d.ts.map +1 -0
- package/dist/types/src/components/Block/MarkdownBlock.d.ts +24 -0
- package/dist/types/src/components/Block/MarkdownBlock.d.ts.map +1 -0
- package/dist/types/src/components/Block/block.stories.d.ts +21 -0
- package/dist/types/src/components/Block/block.stories.d.ts.map +1 -0
- package/dist/types/src/components/Block/construction.d.ts +36 -0
- package/dist/types/src/components/Block/construction.d.ts.map +1 -0
- package/dist/types/src/components/Block/extensions.d.ts +23 -0
- package/dist/types/src/components/Block/extensions.d.ts.map +1 -0
- package/dist/types/src/components/Block/highlight.d.ts +14 -0
- package/dist/types/src/components/Block/highlight.d.ts.map +1 -0
- package/dist/types/src/components/Block/index.d.ts +6 -0
- package/dist/types/src/components/Block/index.d.ts.map +1 -0
- package/dist/types/src/components/Block/selection-group.d.ts +21 -0
- package/dist/types/src/components/Block/selection-group.d.ts.map +1 -0
- package/dist/types/src/components/Block/widget-state.d.ts +33 -0
- package/dist/types/src/components/Block/widget-state.d.ts.map +1 -0
- package/dist/types/src/components/MessageList/MessageList.d.ts +129 -0
- package/dist/types/src/components/MessageList/MessageList.d.ts.map +1 -0
- package/dist/types/src/components/MessageList/MessageList.stories.d.ts +47 -0
- package/dist/types/src/components/MessageList/MessageList.stories.d.ts.map +1 -0
- package/dist/types/src/components/MessageList/index.d.ts +3 -0
- package/dist/types/src/components/MessageList/index.d.ts.map +1 -0
- package/dist/types/src/components/MessageList/position-log.d.ts +73 -0
- package/dist/types/src/components/MessageList/position-log.d.ts.map +1 -0
- package/dist/types/src/components/Minimap/Minimap.d.ts +28 -0
- package/dist/types/src/components/Minimap/Minimap.d.ts.map +1 -0
- package/dist/types/src/components/Minimap/Minimap.stories.d.ts +21 -0
- package/dist/types/src/components/Minimap/Minimap.stories.d.ts.map +1 -0
- package/dist/types/src/components/Minimap/index.d.ts +2 -0
- package/dist/types/src/components/Minimap/index.d.ts.map +1 -0
- package/dist/types/src/components/Outline/Outline.d.ts +46 -0
- package/dist/types/src/components/Outline/Outline.d.ts.map +1 -0
- package/dist/types/src/components/Outline/Outline.stories.d.ts +37 -0
- package/dist/types/src/components/Outline/Outline.stories.d.ts.map +1 -0
- package/dist/types/src/components/Outline/index.d.ts +2 -0
- package/dist/types/src/components/Outline/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +5 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/debug/Debug.d.ts +17 -0
- package/dist/types/src/debug/Debug.d.ts.map +1 -0
- package/dist/types/src/debug/Stats.d.ts +30 -0
- package/dist/types/src/debug/Stats.d.ts.map +1 -0
- package/dist/types/src/debug/debug-model.d.ts +32 -0
- package/dist/types/src/debug/debug-model.d.ts.map +1 -0
- package/dist/types/src/debug/debug-model.test.d.ts +2 -0
- package/dist/types/src/debug/debug-model.test.d.ts.map +1 -0
- package/dist/types/src/debug/frame-meter.d.ts +48 -0
- package/dist/types/src/debug/frame-meter.d.ts.map +1 -0
- package/dist/types/src/debug/frame-meter.test.d.ts +2 -0
- package/dist/types/src/debug/frame-meter.test.d.ts.map +1 -0
- package/dist/types/src/debug/index.d.ts +6 -0
- package/dist/types/src/debug/index.d.ts.map +1 -0
- package/dist/types/src/debug/use-debug.d.ts +19 -0
- package/dist/types/src/debug/use-debug.d.ts.map +1 -0
- package/dist/types/src/hooks/follow.d.ts +89 -0
- package/dist/types/src/hooks/follow.d.ts.map +1 -0
- package/dist/types/src/hooks/follow.stories.d.ts +25 -0
- package/dist/types/src/hooks/follow.stories.d.ts.map +1 -0
- package/dist/types/src/hooks/follow.test.d.ts +2 -0
- package/dist/types/src/hooks/follow.test.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +6 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useDecorations.d.ts +31 -0
- package/dist/types/src/hooks/useDecorations.d.ts.map +1 -0
- package/dist/types/src/hooks/useFeedNavigation.d.ts +44 -0
- package/dist/types/src/hooks/useFeedNavigation.d.ts.map +1 -0
- package/dist/types/src/hooks/useFollow.d.ts +46 -0
- package/dist/types/src/hooks/useFollow.d.ts.map +1 -0
- package/dist/types/src/hooks/useItemSelection.d.ts +34 -0
- package/dist/types/src/hooks/useItemSelection.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/model/feed-model.d.ts +67 -0
- package/dist/types/src/model/feed-model.d.ts.map +1 -0
- package/dist/types/src/model/feed-model.test.d.ts +2 -0
- package/dist/types/src/model/feed-model.test.d.ts.map +1 -0
- package/dist/types/src/model/feed.d.ts +66 -0
- package/dist/types/src/model/feed.d.ts.map +1 -0
- package/dist/types/src/model/feed.test.d.ts +2 -0
- package/dist/types/src/model/feed.test.d.ts.map +1 -0
- package/dist/types/src/model/index.d.ts +5 -0
- package/dist/types/src/model/index.d.ts.map +1 -0
- package/dist/types/src/model/list-model.d.ts +61 -0
- package/dist/types/src/model/list-model.d.ts.map +1 -0
- package/dist/types/src/model/list-model.test.d.ts +2 -0
- package/dist/types/src/model/list-model.test.d.ts.map +1 -0
- package/dist/types/src/model/useListModel.d.ts +22 -0
- package/dist/types/src/model/useListModel.d.ts.map +1 -0
- package/dist/types/src/stories/assistant.stories.d.ts +38 -0
- package/dist/types/src/stories/assistant.stories.d.ts.map +1 -0
- package/dist/types/src/stories/baseline.stories.d.ts +32 -0
- package/dist/types/src/stories/baseline.stories.d.ts.map +1 -0
- package/dist/types/src/stories/bridge.stories.d.ts +72 -0
- package/dist/types/src/stories/bridge.stories.d.ts.map +1 -0
- package/dist/types/src/stories/construction.stories.d.ts +17 -0
- package/dist/types/src/stories/construction.stories.d.ts.map +1 -0
- package/dist/types/src/stories/fill.stories.d.ts +51 -0
- package/dist/types/src/stories/fill.stories.d.ts.map +1 -0
- package/dist/types/src/stories/horizontal.stories.d.ts +17 -0
- package/dist/types/src/stories/horizontal.stories.d.ts.map +1 -0
- package/dist/types/src/stories/mount.stories.d.ts +19 -0
- package/dist/types/src/stories/mount.stories.d.ts.map +1 -0
- package/dist/types/src/stories/navigation.stories.d.ts +33 -0
- package/dist/types/src/stories/navigation.stories.d.ts.map +1 -0
- package/dist/types/src/stories/streaming.stories.d.ts +17 -0
- package/dist/types/src/stories/streaming.stories.d.ts.map +1 -0
- package/dist/types/src/stories/tail.stories.d.ts +47 -0
- package/dist/types/src/stories/tail.stories.d.ts.map +1 -0
- package/dist/types/src/stories/widget.stories.d.ts +16 -0
- package/dist/types/src/stories/widget.stories.d.ts.map +1 -0
- package/dist/types/src/testing/FeedStory.d.ts +30 -0
- package/dist/types/src/testing/FeedStory.d.ts.map +1 -0
- package/dist/types/src/testing/MessageWindow.d.ts +48 -0
- package/dist/types/src/testing/MessageWindow.d.ts.map +1 -0
- package/dist/types/src/testing/controls.d.ts +46 -0
- package/dist/types/src/testing/controls.d.ts.map +1 -0
- package/dist/types/src/testing/debug/FeedStats.d.ts +18 -0
- package/dist/types/src/testing/debug/FeedStats.d.ts.map +1 -0
- package/dist/types/src/testing/debug/index.d.ts +4 -0
- package/dist/types/src/testing/debug/index.d.ts.map +1 -0
- package/dist/types/src/testing/debug/sweep.d.ts +18 -0
- package/dist/types/src/testing/debug/sweep.d.ts.map +1 -0
- package/dist/types/src/testing/debug/use-feed-debug.d.ts +33 -0
- package/dist/types/src/testing/debug/use-feed-debug.d.ts.map +1 -0
- package/dist/types/src/testing/generator.d.ts +16 -0
- package/dist/types/src/testing/generator.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +10 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/scenarios.d.ts +58 -0
- package/dist/types/src/testing/scenarios.d.ts.map +1 -0
- package/dist/types/src/testing/stream.d.ts +22 -0
- package/dist/types/src/testing/stream.d.ts.map +1 -0
- package/dist/types/src/testing/turn.d.ts +21 -0
- package/dist/types/src/testing/turn.d.ts.map +1 -0
- package/dist/types/src/testing/widgets.d.ts +3 -0
- package/dist/types/src/testing/widgets.d.ts.map +1 -0
- package/dist/types/src/virtualizer/Window.d.ts +137 -0
- package/dist/types/src/virtualizer/Window.d.ts.map +1 -0
- package/dist/types/src/virtualizer/Window.stories.d.ts +112 -0
- package/dist/types/src/virtualizer/Window.stories.d.ts.map +1 -0
- package/dist/types/src/virtualizer/index.d.ts +3 -0
- package/dist/types/src/virtualizer/index.d.ts.map +1 -0
- package/dist/types/src/virtualizer/placement.d.ts +188 -0
- package/dist/types/src/virtualizer/placement.d.ts.map +1 -0
- package/dist/types/src/virtualizer/placement.test.d.ts +2 -0
- package/dist/types/src/virtualizer/placement.test.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +72 -0
- package/src/components/Block/HtmlBlock.tsx +26 -0
- package/src/components/Block/MarkdownBlock.tsx +157 -0
- package/src/components/Block/block.stories.tsx +140 -0
- package/src/components/Block/construction.ts +77 -0
- package/src/components/Block/extensions.ts +95 -0
- package/src/components/Block/highlight.ts +53 -0
- package/src/components/Block/index.ts +10 -0
- package/src/components/Block/selection-group.ts +53 -0
- package/src/components/Block/widget-state.tsx +75 -0
- package/src/components/MessageList/MessageList.stories.tsx +111 -0
- package/src/components/MessageList/MessageList.tsx +647 -0
- package/src/components/MessageList/index.ts +6 -0
- package/src/components/MessageList/position-log.ts +167 -0
- package/src/components/Minimap/Minimap.stories.tsx +129 -0
- package/src/components/Minimap/Minimap.tsx +84 -0
- package/src/components/Minimap/index.ts +5 -0
- package/src/components/Outline/Outline.stories.tsx +196 -0
- package/src/components/Outline/Outline.tsx +383 -0
- package/src/components/Outline/index.ts +5 -0
- package/src/components/index.ts +8 -0
- package/src/debug/Debug.tsx +90 -0
- package/src/debug/Stats.tsx +57 -0
- package/src/debug/debug-model.test.ts +36 -0
- package/src/debug/debug-model.ts +66 -0
- package/src/debug/frame-meter.test.ts +49 -0
- package/src/debug/frame-meter.ts +188 -0
- package/src/debug/index.ts +9 -0
- package/src/debug/use-debug.tsx +41 -0
- package/src/hooks/follow.stories.tsx +200 -0
- package/src/hooks/follow.test.ts +105 -0
- package/src/hooks/follow.ts +241 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/useDecorations.tsx +54 -0
- package/src/hooks/useFeedNavigation.ts +90 -0
- package/src/hooks/useFollow.ts +258 -0
- package/src/hooks/useItemSelection.tsx +53 -0
- package/src/index.ts +7 -0
- package/src/model/feed-model.test.ts +84 -0
- package/src/model/feed-model.ts +214 -0
- package/src/model/feed.test.ts +65 -0
- package/src/model/feed.ts +157 -0
- package/src/model/index.ts +8 -0
- package/src/model/list-model.test.ts +71 -0
- package/src/model/list-model.ts +152 -0
- package/src/model/useListModel.ts +53 -0
- package/src/stories/assistant.stories.tsx +425 -0
- package/src/stories/baseline.stories.tsx +78 -0
- package/src/stories/bridge.stories.tsx +376 -0
- package/src/stories/construction.stories.tsx +147 -0
- package/src/stories/fill.stories.tsx +271 -0
- package/src/stories/horizontal.stories.tsx +129 -0
- package/src/stories/mount.stories.tsx +233 -0
- package/src/stories/navigation.stories.tsx +145 -0
- package/src/stories/streaming.stories.tsx +132 -0
- package/src/stories/tail.stories.tsx +149 -0
- package/src/stories/widget.stories.tsx +107 -0
- package/src/testing/FeedStory.tsx +452 -0
- package/src/testing/MessageWindow.tsx +139 -0
- package/src/testing/controls.tsx +96 -0
- package/src/testing/debug/FeedStats.tsx +182 -0
- package/src/testing/debug/index.ts +16 -0
- package/src/testing/debug/sweep.ts +85 -0
- package/src/testing/debug/use-feed-debug.ts +91 -0
- package/src/testing/generator.ts +90 -0
- package/src/testing/index.ts +13 -0
- package/src/testing/scenarios.tsx +552 -0
- package/src/testing/stream.ts +70 -0
- package/src/testing/turn.ts +91 -0
- package/src/testing/widgets.tsx +183 -0
- package/src/virtualizer/Window.stories.tsx +683 -0
- package/src/virtualizer/Window.tsx +435 -0
- package/src/virtualizer/index.ts +6 -0
- package/src/virtualizer/placement.test.ts +312 -0
- package/src/virtualizer/placement.ts +403 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { random } from '@dxos/random';
|
|
6
|
+
import { type ContentBlock } from '@dxos/types';
|
|
7
|
+
|
|
8
|
+
import { createAnswer, textStream } from './stream';
|
|
9
|
+
|
|
10
|
+
export type TurnOptions = {
|
|
11
|
+
chunkDelay?: number;
|
|
12
|
+
wordsPerChunk?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* One assistant turn as the blocks a model actually emits, arriving in order.
|
|
17
|
+
*
|
|
18
|
+
* The text-only stream exercises the easy half of reconciliation: a tail that only ever grows, which
|
|
19
|
+
* the item appends. A real turn is harder in three ways, and each is a separate way for an item to
|
|
20
|
+
* be rebuilt instead of extended —
|
|
21
|
+
*
|
|
22
|
+
* 1. a **status** block appears first and is removed once work begins, so the document shrinks;
|
|
23
|
+
* 2. **reasoning** streams inside an unclosed tag and is closed when it completes, which rewrites
|
|
24
|
+
* the end of the document rather than appending to it;
|
|
25
|
+
* 3. a **tool call** lands whole, then its **result** arrives as a second block after it.
|
|
26
|
+
*
|
|
27
|
+
* Yields the complete block array at each step; the caller replaces the message's blocks with it.
|
|
28
|
+
*/
|
|
29
|
+
export async function* streamTurn({ chunkDelay = 120, wordsPerChunk = 4 }: TurnOptions = {}): AsyncGenerator<
|
|
30
|
+
ContentBlock.Any[],
|
|
31
|
+
void
|
|
32
|
+
> {
|
|
33
|
+
const pause = () => new Promise((resolve) => setTimeout(resolve, chunkDelay));
|
|
34
|
+
|
|
35
|
+
// A status the reader sees before anything else exists, and which is gone by the answer.
|
|
36
|
+
const status: ContentBlock.Any = { _tag: 'status', statusText: 'Thinking…', pending: true };
|
|
37
|
+
yield [status];
|
|
38
|
+
await pause();
|
|
39
|
+
|
|
40
|
+
let reasoning = '';
|
|
41
|
+
for await (const chunk of textStream(random.lorem.paragraph(), { chunkDelay, wordsPerChunk })) {
|
|
42
|
+
reasoning += chunk;
|
|
43
|
+
yield [status, { _tag: 'reasoning', reasoningText: reasoning, pending: true }];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Closing the tag and dropping the status in one step: the document does not grow here, and an
|
|
47
|
+
// item that only knows how to append would replace itself instead.
|
|
48
|
+
const reasoned: ContentBlock.Any = { _tag: 'reasoning', reasoningText: reasoning };
|
|
49
|
+
yield [reasoned];
|
|
50
|
+
await pause();
|
|
51
|
+
|
|
52
|
+
const toolCallId = `tool-${random.number.int({ min: 1000, max: 9999 })}`;
|
|
53
|
+
const call: ContentBlock.Any = {
|
|
54
|
+
_tag: 'toolCall',
|
|
55
|
+
toolCallId,
|
|
56
|
+
name: 'search',
|
|
57
|
+
input: '{}',
|
|
58
|
+
providerExecuted: false,
|
|
59
|
+
pending: true,
|
|
60
|
+
};
|
|
61
|
+
yield [reasoned, call];
|
|
62
|
+
await pause();
|
|
63
|
+
|
|
64
|
+
const called: ContentBlock.Any = { ...call, pending: false };
|
|
65
|
+
const result: ContentBlock.Any = {
|
|
66
|
+
_tag: 'toolResult',
|
|
67
|
+
toolCallId,
|
|
68
|
+
name: 'search',
|
|
69
|
+
result: random.lorem.sentence(12),
|
|
70
|
+
providerExecuted: false,
|
|
71
|
+
};
|
|
72
|
+
yield [reasoned, called, result];
|
|
73
|
+
await pause();
|
|
74
|
+
|
|
75
|
+
let text = '';
|
|
76
|
+
for await (const chunk of textStream(createAnswer(), { chunkDelay, wordsPerChunk })) {
|
|
77
|
+
text += chunk;
|
|
78
|
+
yield [reasoned, called, result, { _tag: 'text', text, pending: true }];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Suggestions arrive only once the answer is complete, so the last step both closes the text and
|
|
82
|
+
// extends the message — the two kinds of change at once.
|
|
83
|
+
yield [
|
|
84
|
+
reasoned,
|
|
85
|
+
called,
|
|
86
|
+
result,
|
|
87
|
+
{ _tag: 'text', text },
|
|
88
|
+
{ _tag: 'suggestion', text: 'Tell me more' },
|
|
89
|
+
{ _tag: 'suggestion', text: 'Show the sources' },
|
|
90
|
+
];
|
|
91
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { Icon, IconBlock } from '@dxos/react-ui';
|
|
8
|
+
import { TogglePanel } from '@dxos/react-ui-components';
|
|
9
|
+
import { type XmlWidgetProps, type XmlWidgetRegistry, getXmlTextChild } from '@dxos/ui-editor';
|
|
10
|
+
import { mx } from '@dxos/ui-theme';
|
|
11
|
+
|
|
12
|
+
import { useWidgetState } from '../components';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Widgets for the block kinds an assistant turn contains.
|
|
16
|
+
*
|
|
17
|
+
* Approximations of `plugin-assistant`'s, deliberately: what the engine has to prove is that a
|
|
18
|
+
* per-message document can carry block widgets at all — that they mount, measure and survive
|
|
19
|
+
* streaming — not that these are the widgets the plugin will ship. The real ones move down when the
|
|
20
|
+
* plugin does.
|
|
21
|
+
*
|
|
22
|
+
* Every widget renders from the tag alone. The plugin keeps tool state beside the document and
|
|
23
|
+
* re-applies it after a remount; an item here is rebuilt from its message, so state that is not in
|
|
24
|
+
* the document has nowhere to survive.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A collapsible block: the header is what the reader sees while scrolling past, the body is what
|
|
29
|
+
* they open when they care. Reasoning and tool traces are long, incidental and frequent — rendering
|
|
30
|
+
* them expanded turns a chat into a log.
|
|
31
|
+
*
|
|
32
|
+
* The disclosure animates, and every frame of it is measured twice — by CodeMirror, to re-place the
|
|
33
|
+
* lines under the widget, and by the virtualizer, to re-place the rows under the item. That is what
|
|
34
|
+
* it costs for the content below to travel with the panel instead of jumping to its new place; the
|
|
35
|
+
* geometry stays consistent because `MarkdownBlock` re-measures the document as the widget resizes.
|
|
36
|
+
*/
|
|
37
|
+
const Panel = ({
|
|
38
|
+
stateKey,
|
|
39
|
+
icon,
|
|
40
|
+
title,
|
|
41
|
+
children,
|
|
42
|
+
classNames,
|
|
43
|
+
}: {
|
|
44
|
+
/** Identifies this panel within its message, so its open flag can outlive the row. */
|
|
45
|
+
stateKey: string;
|
|
46
|
+
icon: string;
|
|
47
|
+
title: string;
|
|
48
|
+
children: React.ReactNode;
|
|
49
|
+
classNames?: string;
|
|
50
|
+
}) => {
|
|
51
|
+
const [open, setOpen] = useWidgetState(stateKey, false);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
// The open flag is published to the DOM so a test can ask whether it survived the row being
|
|
55
|
+
// unmounted — which is the whole question a virtualized feed raises about widget state.
|
|
56
|
+
<div data-testid='feed.widget' data-open={open}>
|
|
57
|
+
<TogglePanel.Root open={open} onChangeOpen={setOpen}>
|
|
58
|
+
{/* A minimum height, not a fixed one: the widget's content is portaled and paints after
|
|
59
|
+
CodeMirror has placed the box, so without a floor the row is measured empty and grows a
|
|
60
|
+
frame later — a row jumping under the reader. `estimatedHeight` in the registry looks like
|
|
61
|
+
the answer and is not: it sets `height` and `overflow: hidden` on the widget root, which
|
|
62
|
+
pins the panel shut. */}
|
|
63
|
+
<TogglePanel.Content classNames={mx('min-h-[2.125rem] rounded border border-subdued-separator', classNames)}>
|
|
64
|
+
<TogglePanel.Header classNames='flex items-center gap-2 px-2 py-1 text-sm'>
|
|
65
|
+
<span className='grow text-description truncate'>{title}</span>
|
|
66
|
+
<IconBlock>
|
|
67
|
+
<Icon icon={icon} size={4} />
|
|
68
|
+
</IconBlock>
|
|
69
|
+
</TogglePanel.Header>
|
|
70
|
+
<TogglePanel.Body>
|
|
71
|
+
<TogglePanel.Viewport classNames='px-2 pb-1 text-sm'>{children}</TogglePanel.Viewport>
|
|
72
|
+
</TogglePanel.Body>
|
|
73
|
+
</TogglePanel.Content>
|
|
74
|
+
</TogglePanel.Root>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const Frame = ({ icon, title, children, classNames }: XmlWidgetProps<any> & { classNames?: string }) => (
|
|
80
|
+
<div className={mx('flex gap-2 px-2 py-1 rounded border border-subdued-separator text-sm', classNames)}>
|
|
81
|
+
{icon && <Icon icon={icon} size={4} classNames='mt-1 shrink-0 text-description' />}
|
|
82
|
+
<div className='min-w-0'>
|
|
83
|
+
{title && <p className='text-xs text-description'>{title}</p>}
|
|
84
|
+
{children}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const Reasoning = ({ children, range }: XmlWidgetProps) => {
|
|
90
|
+
const text = getXmlTextChild(children ?? []) ?? '';
|
|
91
|
+
return (
|
|
92
|
+
<Panel
|
|
93
|
+
stateKey={`reasoning:${range?.from}`}
|
|
94
|
+
icon='ph--brain--regular'
|
|
95
|
+
title={text.slice(0, 80)}
|
|
96
|
+
classNames='opacity-70'
|
|
97
|
+
>
|
|
98
|
+
<p>{text}</p>
|
|
99
|
+
</Panel>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const Status = ({ children }: XmlWidgetProps) => (
|
|
104
|
+
<p className='px-2 py-1 text-sm text-description animate-pulse'>{getXmlTextChild(children ?? [])}</p>
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const ToolCall = ({ name, pending, range }: XmlWidgetProps<{ name?: string; pending?: string }>) => (
|
|
108
|
+
<Panel
|
|
109
|
+
stateKey={`toolCall:${range?.from}`}
|
|
110
|
+
icon={pending ? 'ph--circle-notch--regular' : 'ph--wrench--regular'}
|
|
111
|
+
title={`Calling ${name}`}
|
|
112
|
+
>
|
|
113
|
+
<p className='font-mono text-xs'>{name}</p>
|
|
114
|
+
</Panel>
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const ToolResult = ({ children, for: tool, range }: XmlWidgetProps<{ for?: string }>) => (
|
|
118
|
+
<Panel stateKey={`toolResult:${range?.from}`} icon='ph--check--regular' title={`${tool} returned`}>
|
|
119
|
+
<p className='font-mono text-xs'>{getXmlTextChild(children ?? [])}</p>
|
|
120
|
+
</Panel>
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Inline, so a run of them wraps as chips rather than stacking one per line.
|
|
124
|
+
const Suggestion = ({ children }: XmlWidgetProps) => (
|
|
125
|
+
<button type='button' className='me-1 px-2 py-0.5 rounded-full border border-separator text-sm'>
|
|
126
|
+
{getXmlTextChild(children ?? [])}
|
|
127
|
+
</button>
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const Select = ({ children }: XmlWidgetProps) => {
|
|
131
|
+
const options = (children ?? [])
|
|
132
|
+
.map((option: any) => option?._tag === 'option' && getXmlTextChild(option.children))
|
|
133
|
+
.filter(Boolean) as string[];
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<div className='flex flex-col gap-1 py-1'>
|
|
137
|
+
{options.map((option) => (
|
|
138
|
+
<button key={option} type='button' className='px-2 py-1 text-left rounded border border-separator text-sm'>
|
|
139
|
+
{option}
|
|
140
|
+
</button>
|
|
141
|
+
))}
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const Json = ({ children }: XmlWidgetProps) => (
|
|
147
|
+
<pre className='px-2 py-1 overflow-x-auto rounded bg-input-surface text-xs'>{getXmlTextChild(children ?? [])}</pre>
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Floor for a panel's collapsed height, reserved before its portaled content paints.
|
|
152
|
+
*
|
|
153
|
+
* `heightMode: 'min'` is what makes this a floor rather than a pin — the default pins the box and
|
|
154
|
+
* clips it, so an opened panel stays shut. Without any reservation the row is measured at the height
|
|
155
|
+
* of an empty box and grows a frame later, which is a row moving under a reader scrolling up.
|
|
156
|
+
*/
|
|
157
|
+
const COLLAPSED_HEIGHT = 42;
|
|
158
|
+
|
|
159
|
+
export const chatRegistry: XmlWidgetRegistry = {
|
|
160
|
+
// No widget: the reader's own words stay in the document, where they can be selected and searched
|
|
161
|
+
// like any other text. Registered so the markdown parser keeps the tag as one block — an
|
|
162
|
+
// unregistered tag opens a paragraph that swallows the lines after it.
|
|
163
|
+
prompt: { block: true },
|
|
164
|
+
reasoning: {
|
|
165
|
+
block: true,
|
|
166
|
+
streaming: true,
|
|
167
|
+
estimatedHeight: () => COLLAPSED_HEIGHT,
|
|
168
|
+
heightMode: 'min',
|
|
169
|
+
Component: Reasoning,
|
|
170
|
+
},
|
|
171
|
+
status: {
|
|
172
|
+
block: true,
|
|
173
|
+
streaming: true,
|
|
174
|
+
estimatedHeight: () => COLLAPSED_HEIGHT,
|
|
175
|
+
heightMode: 'min',
|
|
176
|
+
Component: Status,
|
|
177
|
+
},
|
|
178
|
+
toolCall: { block: true, estimatedHeight: () => COLLAPSED_HEIGHT, heightMode: 'min', Component: ToolCall },
|
|
179
|
+
toolResult: { block: true, estimatedHeight: () => COLLAPSED_HEIGHT, heightMode: 'min', Component: ToolResult },
|
|
180
|
+
suggestion: { block: false, Component: Suggestion },
|
|
181
|
+
select: { block: true, Component: Select },
|
|
182
|
+
json: { block: true, Component: Json },
|
|
183
|
+
};
|