basalt-ui 1.11.0 → 1.13.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/AGENTS.md +21 -21
- package/CHANGELOG.md +32 -0
- package/README.md +63 -51
- package/agent/rules/basalt-agent.md +14 -0
- package/agent/rules/basalt-content.md +16 -3
- package/configs/oxlint-plugin.js +7 -3
- package/configs/oxlint-plugin.test.ts +34 -5
- package/configs/oxlint.json +4 -4
- package/dist/agent/adapter.d.ts +150 -0
- package/dist/agent/adapter.d.ts.map +1 -0
- package/dist/agent/adapter.js +482 -0
- package/dist/agent/adapter.test.d.ts +2 -0
- package/dist/agent/adapter.test.d.ts.map +1 -0
- package/dist/agent/ai-sdk-transport.d.ts.map +1 -1
- package/dist/agent/ai-sdk-transport.js +6 -2
- package/dist/agent/ai-sdk-transport.test.d.ts +2 -0
- package/dist/agent/ai-sdk-transport.test.d.ts.map +1 -0
- package/dist/agent/coalesce.d.ts +20 -4
- package/dist/agent/coalesce.d.ts.map +1 -1
- package/dist/agent/coalesce.js +76 -8
- package/dist/agent/coalesce.test.d.ts +2 -0
- package/dist/agent/coalesce.test.d.ts.map +1 -0
- package/dist/agent/foreign.test.d.ts +2 -0
- package/dist/agent/foreign.test.d.ts.map +1 -0
- package/dist/agent/id.d.ts +78 -0
- package/dist/agent/id.d.ts.map +1 -1
- package/dist/agent/id.js +30 -0
- package/dist/agent/id.test.d.ts +2 -0
- package/dist/agent/id.test.d.ts.map +1 -0
- package/dist/agent/index.d.ts +2 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +3 -0
- package/dist/agent/merge.d.ts.map +1 -1
- package/dist/agent/merge.js +19 -7
- package/dist/agent/merge.test.d.ts +2 -0
- package/dist/agent/merge.test.d.ts.map +1 -0
- package/dist/agent/part-list.d.ts +49 -34
- package/dist/agent/part-list.d.ts.map +1 -1
- package/dist/agent/part-list.js +42 -6
- package/dist/agent/part-list.test.d.ts +2 -0
- package/dist/agent/part-list.test.d.ts.map +1 -0
- package/dist/agent/parts.d.ts +46 -10
- package/dist/agent/parts.d.ts.map +1 -1
- package/dist/agent/parts.js +16 -6
- package/dist/agent/parts.test.d.ts +2 -0
- package/dist/agent/parts.test.d.ts.map +1 -0
- package/dist/agent/thread.d.ts +18 -0
- package/dist/agent/thread.d.ts.map +1 -1
- package/dist/agent/thread.js +8 -2
- package/dist/agent/thread.test.d.ts +2 -0
- package/dist/agent/thread.test.d.ts.map +1 -0
- package/dist/agent/transport.test.d.ts +2 -0
- package/dist/agent/transport.test.d.ts.map +1 -0
- package/dist/agent/use-agent-stream.d.ts.map +1 -1
- package/dist/agent/use-agent-stream.js +2 -2
- package/dist/agent/use-agent-stream.test.d.ts +2 -0
- package/dist/agent/use-agent-stream.test.d.ts.map +1 -0
- package/dist/agent/use-agent-thread-runs.d.ts +24 -3
- package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
- package/dist/agent/use-agent-thread-runs.foreign.test.d.ts +2 -0
- package/dist/agent/use-agent-thread-runs.foreign.test.d.ts.map +1 -0
- package/dist/agent/use-agent-thread-runs.js +55 -15
- package/dist/agent/use-agent-thread-runs.resume.test.d.ts +2 -0
- package/dist/agent/use-agent-thread-runs.resume.test.d.ts.map +1 -0
- package/dist/agent/use-agent-thread-runs.test.d.ts +2 -0
- package/dist/agent/use-agent-thread-runs.test.d.ts.map +1 -0
- package/dist/agent/use-agent-thread-runs.wedge.test.d.ts +2 -0
- package/dist/agent/use-agent-thread-runs.wedge.test.d.ts.map +1 -0
- package/dist/agent-chat/composer.d.ts +130 -8
- package/dist/agent-chat/composer.d.ts.map +1 -1
- package/dist/agent-chat/composer.js +189 -23
- package/dist/agent-chat/composer.test.d.ts +2 -0
- package/dist/agent-chat/composer.test.d.ts.map +1 -0
- package/dist/agent-chat/index.d.ts +5 -1
- package/dist/agent-chat/index.d.ts.map +1 -1
- package/dist/agent-chat/index.js +2 -0
- package/dist/agent-chat/message-affordances.d.ts +59 -0
- package/dist/agent-chat/message-affordances.d.ts.map +1 -0
- package/dist/agent-chat/message-affordances.js +7 -0
- package/dist/agent-chat/relative-time.d.ts +27 -0
- package/dist/agent-chat/relative-time.d.ts.map +1 -0
- package/dist/agent-chat/relative-time.js +27 -0
- package/dist/agent-chat/relative-time.test.d.ts +2 -0
- package/dist/agent-chat/relative-time.test.d.ts.map +1 -0
- package/dist/agent-chat/thread-detail-panel.d.ts.map +1 -1
- package/dist/agent-chat/thread-detail-panel.js +1 -1
- package/dist/agent-chat/thread-feed-row.d.ts +65 -0
- package/dist/agent-chat/thread-feed-row.d.ts.map +1 -0
- package/dist/agent-chat/thread-feed-row.js +156 -0
- package/dist/agent-chat/thread-feed-row.test.d.ts +2 -0
- package/dist/agent-chat/thread-feed-row.test.d.ts.map +1 -0
- package/dist/agent-chat/thread-feed.d.ts +34 -5
- package/dist/agent-chat/thread-feed.d.ts.map +1 -1
- package/dist/agent-chat/thread-feed.js +52 -19
- package/dist/agent-chat/thread-feed.test.d.ts +2 -0
- package/dist/agent-chat/thread-feed.test.d.ts.map +1 -0
- package/dist/agent-chat/thread-message.d.ts +183 -2
- package/dist/agent-chat/thread-message.d.ts.map +1 -1
- package/dist/agent-chat/thread-message.js +329 -37
- package/dist/agent-chat/thread-message.test.d.ts +2 -0
- package/dist/agent-chat/thread-message.test.d.ts.map +1 -0
- package/dist/agent-chat/thread-outcome-card.d.ts.map +1 -1
- package/dist/agent-chat/thread-outcome-card.js +1 -16
- package/dist/agent-chat/thread-workspace.d.ts +14 -2
- package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
- package/dist/agent-chat/thread-workspace.js +30 -4
- package/dist/agent-chat/thread-workspace.test.d.ts +2 -0
- package/dist/agent-chat/thread-workspace.test.d.ts.map +1 -0
- package/dist/agent-chat/tool-chip.test.d.ts +2 -0
- package/dist/agent-chat/tool-chip.test.d.ts.map +1 -0
- package/dist/agent-chat/virtualize.d.ts +123 -0
- package/dist/agent-chat/virtualize.d.ts.map +1 -0
- package/dist/agent-chat/virtualize.js +9 -0
- package/dist/agent-chat/virtualize.type-guard.test.d.ts +2 -0
- package/dist/agent-chat/virtualize.type-guard.test.d.ts.map +1 -0
- package/dist/charts/primitives/ChartFrame.test.d.ts +2 -0
- package/dist/charts/primitives/ChartFrame.test.d.ts.map +1 -0
- package/dist/charts/utils/domain.test.d.ts +2 -0
- package/dist/charts/utils/domain.test.d.ts.map +1 -0
- package/dist/cli/check-theme.test.d.ts +2 -0
- package/dist/cli/check-theme.test.d.ts.map +1 -0
- package/dist/cli/help.test.d.ts +2 -0
- package/dist/cli/help.test.d.ts.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/init-sync.test.d.ts +2 -0
- package/dist/cli/init-sync.test.d.ts.map +1 -0
- package/dist/cli/placement-engine.test.d.ts +2 -0
- package/dist/cli/placement-engine.test.d.ts.map +1 -0
- package/dist/cli/tarball-parity.test.d.ts +2 -0
- package/dist/cli/tarball-parity.test.d.ts.map +1 -0
- package/dist/cli/template-format.test.d.ts +2 -0
- package/dist/cli/template-format.test.d.ts.map +1 -0
- package/dist/cli/template-ledger.test.d.ts +2 -0
- package/dist/cli/template-ledger.test.d.ts.map +1 -0
- package/dist/cli/tokens-css.test.d.ts +2 -0
- package/dist/cli/tokens-css.test.d.ts.map +1 -0
- package/dist/content/article-actions.test.d.ts +2 -0
- package/dist/content/article-actions.test.d.ts.map +1 -0
- package/dist/content/article-model.test.d.ts +2 -0
- package/dist/content/article-model.test.d.ts.map +1 -0
- package/dist/content/block-split.test.d.ts +2 -0
- package/dist/content/block-split.test.d.ts.map +1 -0
- package/dist/content/fence-block.d.ts +37 -10
- package/dist/content/fence-block.d.ts.map +1 -1
- package/dist/content/fence-block.js +80 -14
- package/dist/content/heading-components.d.ts.map +1 -1
- package/dist/content/heading-components.js +7 -4
- package/dist/content/heading-components.test.d.ts +2 -0
- package/dist/content/heading-components.test.d.ts.map +1 -0
- package/dist/content/index.d.ts +6 -3
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +5 -1
- package/dist/content/markdown.d.ts +101 -14
- package/dist/content/markdown.d.ts.map +1 -1
- package/dist/content/markdown.js +141 -28
- package/dist/content/markdown.test.d.ts +2 -0
- package/dist/content/markdown.test.d.ts.map +1 -0
- package/dist/content/prose.module.css.test.d.ts +2 -0
- package/dist/content/prose.module.css.test.d.ts.map +1 -0
- package/dist/content/sanitize.d.ts +170 -0
- package/dist/content/sanitize.d.ts.map +1 -0
- package/dist/content/sanitize.js +178 -0
- package/dist/content/sanitize.test.d.ts +2 -0
- package/dist/content/sanitize.test.d.ts.map +1 -0
- package/dist/content/slug.test.d.ts +2 -0
- package/dist/content/slug.test.d.ts.map +1 -0
- package/dist/dashboard/stat-card.test.d.ts +2 -0
- package/dist/dashboard/stat-card.test.d.ts.map +1 -0
- package/dist/data/index.d.ts +5 -1
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/virtual-list.d.ts.map +1 -1
- package/dist/data/virtual.d.ts +2 -1
- package/dist/data/virtual.d.ts.map +1 -1
- package/dist/guard/check-source.test.d.ts +2 -0
- package/dist/guard/check-source.test.d.ts.map +1 -0
- package/dist/guard/guard-hook.test.d.ts +2 -0
- package/dist/guard/guard-hook.test.d.ts.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/provider/index.test.d.ts +2 -0
- package/dist/provider/index.test.d.ts.map +1 -0
- package/dist/provider/lab-theme.test.d.ts +2 -0
- package/dist/provider/lab-theme.test.d.ts.map +1 -0
- package/dist/router-tanstack/multi-search-param-store.test.d.ts +2 -0
- package/dist/router-tanstack/multi-search-param-store.test.d.ts.map +1 -0
- package/dist/styles.floor.test.d.ts +2 -0
- package/dist/styles.floor.test.d.ts.map +1 -0
- package/dist/surfaces.d.ts +5 -5
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +14 -4
- package/dist/theme/border-coverage.test.d.ts +18 -0
- package/dist/theme/border-coverage.test.d.ts.map +1 -0
- package/dist/theme/contrast.test.d.ts +2 -0
- package/dist/theme/contrast.test.d.ts.map +1 -0
- package/dist/theme/density-option.test.d.ts +2 -0
- package/dist/theme/density-option.test.d.ts.map +1 -0
- package/dist/theme/density-relations.test.d.ts +2 -0
- package/dist/theme/density-relations.test.d.ts.map +1 -0
- package/dist/theme/derive-theme.test.d.ts +2 -0
- package/dist/theme/derive-theme.test.d.ts.map +1 -0
- package/dist/theme/fonts-option.test.d.ts +2 -0
- package/dist/theme/fonts-option.test.d.ts.map +1 -0
- package/dist/theme/radius-option.test.d.ts +2 -0
- package/dist/theme/radius-option.test.d.ts.map +1 -0
- package/dist/theme/radius.test.d.ts +2 -0
- package/dist/theme/radius.test.d.ts.map +1 -0
- package/dist/theme/shadow-surfaces.test.d.ts +26 -0
- package/dist/theme/shadow-surfaces.test.d.ts.map +1 -0
- package/dist/theme/spacing.test.d.ts +2 -0
- package/dist/theme/spacing.test.d.ts.map +1 -0
- package/dist/theme/use-basalt-spacing.test.d.ts +2 -0
- package/dist/theme/use-basalt-spacing.test.d.ts.map +1 -0
- package/dist/theme-lab/derive-controls.test.d.ts +2 -0
- package/dist/theme-lab/derive-controls.test.d.ts.map +1 -0
- package/dist/theme-lab/derive-state.test.d.ts +2 -0
- package/dist/theme-lab/derive-state.test.d.ts.map +1 -0
- package/dist/theme-lab/theme-lab.test.d.ts +2 -0
- package/dist/theme-lab/theme-lab.test.d.ts.map +1 -0
- package/dist/tokens/build-fonts-css.test.d.ts +2 -0
- package/dist/tokens/build-fonts-css.test.d.ts.map +1 -0
- package/dist/tokens/build-radius-css.test.d.ts +2 -0
- package/dist/tokens/build-radius-css.test.d.ts.map +1 -0
- package/dist/tokens/density.test.d.ts +2 -0
- package/dist/tokens/density.test.d.ts.map +1 -0
- package/dist/tokens/derive-radius.test.d.ts +2 -0
- package/dist/tokens/derive-radius.test.d.ts.map +1 -0
- package/dist/tokens/derive.test.d.ts +2 -0
- package/dist/tokens/derive.test.d.ts.map +1 -0
- package/dist/tokens/palette.test.d.ts +2 -0
- package/dist/tokens/palette.test.d.ts.map +1 -0
- package/dist/vite-color-mix.test.d.ts +2 -0
- package/dist/vite-color-mix.test.d.ts.map +1 -0
- package/dist/vite.test.d.ts +2 -0
- package/dist/vite.test.d.ts.map +1 -0
- package/llms.txt +9 -9
- package/package.json +7 -2
- package/src/agent/adapter.test.ts +1640 -0
- package/src/agent/adapter.ts +945 -0
- package/src/agent/ai-sdk-transport.test.ts +117 -26
- package/src/agent/ai-sdk-transport.ts +8 -2
- package/src/agent/coalesce.test.ts +202 -9
- package/src/agent/coalesce.ts +126 -15
- package/src/agent/foreign.test.ts +5 -1
- package/src/agent/id.test.ts +101 -3
- package/src/agent/id.ts +118 -0
- package/src/agent/index.ts +8 -0
- package/src/agent/merge.test.ts +87 -0
- package/src/agent/merge.ts +57 -10
- package/src/agent/part-list.test.tsx +31 -1
- package/src/agent/part-list.tsx +77 -29
- package/src/agent/parts.test.ts +121 -4
- package/src/agent/parts.ts +78 -17
- package/src/agent/thread.test.ts +75 -2
- package/src/agent/thread.ts +25 -1
- package/src/agent/use-agent-stream.test.tsx +31 -5
- package/src/agent/use-agent-stream.ts +6 -3
- package/src/agent/use-agent-thread-runs.foreign.test.tsx +5 -0
- package/src/agent/use-agent-thread-runs.resume.test.tsx +16 -11
- package/src/agent/use-agent-thread-runs.test.tsx +356 -17
- package/src/agent/use-agent-thread-runs.ts +159 -23
- package/src/agent/use-agent-thread-runs.wedge.test.tsx +93 -7
- package/src/agent-chat/composer.test.tsx +924 -0
- package/src/agent-chat/composer.tsx +521 -52
- package/src/agent-chat/index.ts +13 -1
- package/src/agent-chat/message-affordances.ts +61 -0
- package/src/agent-chat/relative-time.test.ts +95 -0
- package/src/agent-chat/relative-time.ts +55 -0
- package/src/agent-chat/thread-detail-panel.tsx +3 -1
- package/src/agent-chat/thread-feed-row.test.tsx +510 -0
- package/src/agent-chat/thread-feed-row.tsx +274 -0
- package/src/agent-chat/thread-feed.test.tsx +241 -0
- package/src/agent-chat/thread-feed.tsx +119 -18
- package/src/agent-chat/thread-message.test.tsx +1078 -4
- package/src/agent-chat/thread-message.tsx +795 -42
- package/src/agent-chat/thread-outcome-card.tsx +1 -25
- package/src/agent-chat/thread-workspace.test.tsx +169 -0
- package/src/agent-chat/thread-workspace.tsx +70 -6
- package/src/agent-chat/virtualize.ts +132 -0
- package/src/agent-chat/virtualize.type-guard.test.ts +49 -0
- package/src/cli/index.ts +0 -1
- package/src/cli/template-format.test.ts +2 -2
- package/src/content/fence-block.tsx +242 -15
- package/src/content/heading-components.test.tsx +65 -0
- package/src/content/heading-components.tsx +26 -5
- package/src/content/index.ts +19 -3
- package/src/content/markdown.test.tsx +603 -0
- package/src/content/markdown.tsx +355 -35
- package/src/content/sanitize.test.ts +659 -0
- package/src/content/sanitize.ts +510 -0
- package/src/dashboard/stat-card.test.tsx +6 -1
- package/src/data/index.ts +5 -1
- package/src/data/virtual-list.tsx +2 -1
- package/src/data/virtual.ts +2 -1
- package/src/index.ts +18 -1
- package/src/surfaces.ts +13 -3
- package/src/theme/contrast.test.ts +1 -4
- package/src/theme/density-relations.test.ts +4 -2
- package/src/theme/fonts-option.test.ts +7 -1
- package/src/theme/shadow-surfaces.test.ts +21 -6
- package/src/theme/use-basalt-spacing.test.tsx +2 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { JSX } from 'react';
|
|
2
|
+
import type { AgentThread, ForeignPart, PartRenderer, PartRenderers, StreamStatus, TranscriptPart } from '../agent/index.js';
|
|
3
|
+
import type { ComposerProps, ComposerSubmit } from './composer.js';
|
|
4
|
+
import type { MessageAffordances } from './message-affordances.js';
|
|
5
|
+
import type { VirtualizeProps } from './virtualize.js';
|
|
6
|
+
type ThreadFeedRowBase = {
|
|
7
|
+
/**
|
|
8
|
+
* The thread this row projects. Typed against `AgentThread<TranscriptPart>` — not the plain,
|
|
9
|
+
* `AgentPart`-defaulted `AgentThread` `ThreadFeed` uses, and not a type parameter. A consumer
|
|
10
|
+
* with a registered `ForeignPart` union passes its own threads straight through with no generic
|
|
11
|
+
* argument and no cast, because `TranscriptPart` is the widest part type and arrays are
|
|
12
|
+
* structurally covariant: `AgentPart[]` and `ConsumerPart[]` both widen into `TranscriptPart[]`.
|
|
13
|
+
* Widening the row is what lets both `ThreadFeed` (non-generic, `AgentThread<AgentPart>`) and a
|
|
14
|
+
* part-registry consumer feed the same component.
|
|
15
|
+
*/
|
|
16
|
+
readonly thread: AgentThread<TranscriptPart>;
|
|
17
|
+
/** Whether this row's transcript+composer body is currently visible. */
|
|
18
|
+
readonly expanded: boolean;
|
|
19
|
+
/** Called with the thread's id when the header is clicked/activated. */
|
|
20
|
+
readonly onToggle: (id: string) => void;
|
|
21
|
+
/** The live (in-flight) assistant turn's parts, when a run is streaming for this thread. */
|
|
22
|
+
readonly liveParts?: readonly TranscriptPart[];
|
|
23
|
+
/** The live run's status — drives the in-progress indicator on the live block. */
|
|
24
|
+
readonly liveStatus?: StreamStatus;
|
|
25
|
+
/** Consumer renderers keyed by part.type, forwarded to `ThreadTranscript`. */
|
|
26
|
+
readonly renderers?: PartRenderers;
|
|
27
|
+
/** Forwarded to `ThreadTranscript` — see its own prop doc. */
|
|
28
|
+
readonly fallbackRenderer?: PartRenderer<ForeignPart>;
|
|
29
|
+
/**
|
|
30
|
+
* Per-message hover-row contract (timestamp/copy/regenerate/actions), forwarded verbatim to the
|
|
31
|
+
* row's `ThreadTranscript`. Shared with it via `message-affordances.ts` so the two cannot drift
|
|
32
|
+
* on what an unset field means.
|
|
33
|
+
*/
|
|
34
|
+
readonly affordances?: MessageAffordances;
|
|
35
|
+
/** Forwarded to `ThreadTranscript` — suppresses role label + chrome on same-speaker runs.
|
|
36
|
+
* @default true */
|
|
37
|
+
readonly groupConsecutive?: boolean;
|
|
38
|
+
/** Called with the composer's submit payload. */
|
|
39
|
+
readonly onSend: (payload: ComposerSubmit) => void;
|
|
40
|
+
/** Shown as the composer's Stop action while `liveStatus === 'streaming'`. */
|
|
41
|
+
readonly onStop?: () => void;
|
|
42
|
+
/** Forwarded to the row's `Composer`, minus the three props this component always wires itself
|
|
43
|
+
* (`onSubmit`, `onStop`, and `streaming` — the last derived from `liveStatus` so a consumer can't
|
|
44
|
+
* pass one that disagrees with the row's own live status). */
|
|
45
|
+
readonly composerProps?: Omit<ComposerProps, 'onSubmit' | 'onStop' | 'streaming'>;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* `virtualize`/`height` are forwarded straight to the row's `ThreadTranscript`, carrying the same
|
|
49
|
+
* union guard: an inline row holding a very long thread can window it, and doing so REQUIRES a
|
|
50
|
+
* `height` (the transcript then owns a fixed-height scroll node inside the expanded body, instead
|
|
51
|
+
* of the body growing to the thread's full length). Omit both and the row's transcript is
|
|
52
|
+
* content-sized, as before.
|
|
53
|
+
*/
|
|
54
|
+
export type ThreadFeedRowProps = ThreadFeedRowBase & VirtualizeProps;
|
|
55
|
+
/**
|
|
56
|
+
* One inline-expanding thread row: a header (title + relative timestamp) that toggles a lazily
|
|
57
|
+
* mounted, kept-mounted transcript + composer body. See the module doc for the mount lifecycle
|
|
58
|
+
* invariant this component exists to guarantee.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* <ThreadFeedRow thread={thread} expanded={expanded} onToggle={onToggle} onSend={onSend} />
|
|
62
|
+
*/
|
|
63
|
+
export declare function ThreadFeedRow(props: ThreadFeedRowProps): JSX.Element;
|
|
64
|
+
export {};
|
|
65
|
+
//# sourceMappingURL=thread-feed-row.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread-feed-row.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-feed-row.tsx"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAEhC,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,cAAc,EACf,MAAM,UAAU,CAAA;AAEjB,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAI/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AA6BnD,KAAK,iBAAiB,GAAG;IACvB;;;;;;;;OAQG;IACH,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,cAAc,CAAC,CAAA;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,4FAA4F;IAC5F,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,cAAc,EAAE,CAAA;IAC9C,kFAAkF;IAClF,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY,CAAA;IAClC,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAA;IAClC,8DAA8D;IAC9D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,YAAY,CAAC,WAAW,CAAC,CAAA;IACrD;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAA;IACzC;uBACmB;IACnB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;IACnC,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAA;IAClD,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IAC5B;;kEAE8D;IAC9D,QAAQ,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,UAAU,GAAG,QAAQ,GAAG,WAAW,CAAC,CAAA;CAClF,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,GAAG,eAAe,CAAA;AASpE;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,GAAG,CAAC,OAAO,CA8HpE"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Group, Stack, Text, UnstyledButton } from "@mantine/core";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { VX } from "../tokens/index.js";
|
|
5
|
+
import { Composer } from "./composer.js";
|
|
6
|
+
import { formatRelativeTime } from "./relative-time.js";
|
|
7
|
+
import { ThreadTranscript } from "./thread-message.js";
|
|
8
|
+
import { resolveVirtualize } from "./virtualize.js";
|
|
9
|
+
function ChevronGlyph({ expanded }) {
|
|
10
|
+
return /* @__PURE__ */ jsx(
|
|
11
|
+
"svg",
|
|
12
|
+
{
|
|
13
|
+
width: 14,
|
|
14
|
+
height: 14,
|
|
15
|
+
viewBox: "0 0 24 24",
|
|
16
|
+
fill: "none",
|
|
17
|
+
"aria-hidden": true,
|
|
18
|
+
style: {
|
|
19
|
+
transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
|
|
20
|
+
transition: "transform 120ms ease",
|
|
21
|
+
flexShrink: 0
|
|
22
|
+
},
|
|
23
|
+
children: /* @__PURE__ */ jsx(
|
|
24
|
+
"path",
|
|
25
|
+
{
|
|
26
|
+
d: "M9 6l6 6l-6 6",
|
|
27
|
+
stroke: "currentColor",
|
|
28
|
+
strokeWidth: 2,
|
|
29
|
+
strokeLinecap: "round",
|
|
30
|
+
strokeLinejoin: "round"
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
function rowTitle(thread) {
|
|
37
|
+
return thread.outcome?.title ?? "Untitled thread";
|
|
38
|
+
}
|
|
39
|
+
function ThreadFeedRow(props) {
|
|
40
|
+
const {
|
|
41
|
+
thread,
|
|
42
|
+
expanded,
|
|
43
|
+
onToggle,
|
|
44
|
+
liveParts,
|
|
45
|
+
liveStatus,
|
|
46
|
+
renderers,
|
|
47
|
+
fallbackRenderer,
|
|
48
|
+
affordances,
|
|
49
|
+
groupConsecutive,
|
|
50
|
+
onSend,
|
|
51
|
+
onStop,
|
|
52
|
+
composerProps
|
|
53
|
+
} = props;
|
|
54
|
+
const virtualized = resolveVirtualize(props);
|
|
55
|
+
const virtualizeProps = virtualized === null ? {} : { virtualize: virtualized.options, height: virtualized.height };
|
|
56
|
+
const [hasOpened, setHasOpened] = useState(expanded);
|
|
57
|
+
if (expanded && !hasOpened) {
|
|
58
|
+
setHasOpened(true);
|
|
59
|
+
}
|
|
60
|
+
const summary = thread.outcome?.summary ?? "";
|
|
61
|
+
const streaming = liveStatus === "streaming";
|
|
62
|
+
return /* @__PURE__ */ jsxs(
|
|
63
|
+
Box,
|
|
64
|
+
{
|
|
65
|
+
style: {
|
|
66
|
+
borderRadius: VX.radiusCard,
|
|
67
|
+
boxShadow: VX.shadowCard,
|
|
68
|
+
backgroundColor: VX.surface.panel,
|
|
69
|
+
overflow: "hidden"
|
|
70
|
+
},
|
|
71
|
+
children: [
|
|
72
|
+
/* @__PURE__ */ jsx(
|
|
73
|
+
UnstyledButton,
|
|
74
|
+
{
|
|
75
|
+
onClick: () => onToggle(thread.id),
|
|
76
|
+
w: "100%",
|
|
77
|
+
"aria-expanded": expanded,
|
|
78
|
+
style: {
|
|
79
|
+
display: "block",
|
|
80
|
+
padding: "var(--mantine-spacing-xs) var(--mantine-spacing-sm)"
|
|
81
|
+
},
|
|
82
|
+
children: /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "xs", wrap: "nowrap", align: "center", children: [
|
|
83
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1, minWidth: 0 }, children: [
|
|
84
|
+
/* @__PURE__ */ jsx(
|
|
85
|
+
Text,
|
|
86
|
+
{
|
|
87
|
+
fw: 550,
|
|
88
|
+
lineClamp: 1,
|
|
89
|
+
style: {
|
|
90
|
+
fontFamily: "var(--basalt-font-head)",
|
|
91
|
+
fontSize: VX.text.md,
|
|
92
|
+
fontStretch: "88%"
|
|
93
|
+
},
|
|
94
|
+
children: rowTitle(thread)
|
|
95
|
+
}
|
|
96
|
+
),
|
|
97
|
+
summary.length > 0 && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", lineClamp: 1, children: summary })
|
|
98
|
+
] }),
|
|
99
|
+
/* @__PURE__ */ jsx(
|
|
100
|
+
Text,
|
|
101
|
+
{
|
|
102
|
+
style: {
|
|
103
|
+
fontFamily: "var(--basalt-font-mono)",
|
|
104
|
+
fontSize: VX.text.micro,
|
|
105
|
+
color: VX.faint,
|
|
106
|
+
flexShrink: 0
|
|
107
|
+
},
|
|
108
|
+
children: formatRelativeTime(thread.updatedAt)
|
|
109
|
+
}
|
|
110
|
+
),
|
|
111
|
+
/* @__PURE__ */ jsx(ChevronGlyph, { expanded })
|
|
112
|
+
] })
|
|
113
|
+
}
|
|
114
|
+
),
|
|
115
|
+
hasOpened && /* @__PURE__ */ jsx(
|
|
116
|
+
Box,
|
|
117
|
+
{
|
|
118
|
+
"data-testid": "thread-feed-row-body",
|
|
119
|
+
style: {
|
|
120
|
+
display: expanded ? "block" : "none",
|
|
121
|
+
padding: "var(--mantine-spacing-sm)",
|
|
122
|
+
paddingTop: 0
|
|
123
|
+
},
|
|
124
|
+
children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
|
|
125
|
+
/* @__PURE__ */ jsx(
|
|
126
|
+
ThreadTranscript,
|
|
127
|
+
{
|
|
128
|
+
messages: thread.messages,
|
|
129
|
+
...liveParts !== void 0 ? { liveParts } : {},
|
|
130
|
+
...liveStatus !== void 0 ? { liveStatus } : {},
|
|
131
|
+
...renderers !== void 0 ? { renderers } : {},
|
|
132
|
+
...fallbackRenderer !== void 0 ? { fallbackRenderer } : {},
|
|
133
|
+
...affordances !== void 0 ? { affordances } : {},
|
|
134
|
+
...groupConsecutive !== void 0 ? { groupConsecutive } : {},
|
|
135
|
+
...virtualizeProps
|
|
136
|
+
}
|
|
137
|
+
),
|
|
138
|
+
/* @__PURE__ */ jsx(
|
|
139
|
+
Composer,
|
|
140
|
+
{
|
|
141
|
+
onSubmit: onSend,
|
|
142
|
+
streaming,
|
|
143
|
+
...onStop !== void 0 ? { onStop } : {},
|
|
144
|
+
...composerProps
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
] })
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
export {
|
|
155
|
+
ThreadFeedRow
|
|
156
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread-feed-row.test.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-feed-row.test.tsx"],"names":[],"mappings":""}
|
|
@@ -1,18 +1,47 @@
|
|
|
1
|
-
import type { JSX } from 'react';
|
|
1
|
+
import type { JSX, ReactNode } from 'react';
|
|
2
2
|
import type { AgentThread } from '../agent/index.js';
|
|
3
|
+
import type { ComposerSubmit } from './composer.js';
|
|
3
4
|
export type ThreadFeedProps = {
|
|
4
5
|
readonly threads: AgentThread[];
|
|
5
6
|
/** The currently open thread id, or null when none is selected. */
|
|
6
7
|
readonly activeId: string | null;
|
|
7
|
-
/** Called with a thread's id when its row is selected.
|
|
8
|
+
/** Called with a thread's id when its row is selected. Never called to report a collapse — see
|
|
9
|
+
* `variant`'s doc for how the built-in `'inline'` row's expand/collapse is decoupled from this. */
|
|
8
10
|
readonly onSelect: (id: string) => void;
|
|
11
|
+
/**
|
|
12
|
+
* Row shape: `'outcome'` (default) renders today's `ThreadOutcomeCard` inbox row, unchanged.
|
|
13
|
+
* `'inline'` renders `ThreadFeedRow`, the Slack-style inline-expanding row, wired to this feed's
|
|
14
|
+
* own `activeId` for which row is open (so at most one row is open at a time, mirroring the
|
|
15
|
+
* inbox's single-selection model) — with its OWN collapse tracking layered on top, not a raw
|
|
16
|
+
* pass-through of `onSelect` as `onToggle`. `onSelect` is documented as a selection event, and
|
|
17
|
+
* collapsing a row that remains the active thread isn't one; wiring `onToggle={onSelect}`
|
|
18
|
+
* directly (the previous shape) meant a plain `onSelect={setActiveId}` consumer could never
|
|
19
|
+
* collapse a row, since re-selecting the same id is a no-op state update. Collapsing here is
|
|
20
|
+
* local, UI-only state, reset whenever `activeId` changes to point at a DIFFERENT thread. Since
|
|
21
|
+
* this reset is a `useEffect` keyed on `activeId`, re-driving it to the SAME id from outside
|
|
22
|
+
* this component is a no-op React dep comparison, so it does not clear the override.
|
|
23
|
+
*/
|
|
24
|
+
readonly variant?: 'outcome' | 'inline';
|
|
25
|
+
/**
|
|
26
|
+
* Wires the built-in `'inline'` row's composer to a real send channel — called with the thread
|
|
27
|
+
* and the composer's submit payload. Omit it and the row's composer renders disabled (visibly
|
|
28
|
+
* inert) instead of a live control that silently discards whatever is typed into it; a consumer
|
|
29
|
+
* that needs the composer live must supply this (or `renderRow`, for full control over the row).
|
|
30
|
+
*/
|
|
31
|
+
readonly onSend?: (thread: AgentThread, payload: ComposerSubmit) => void;
|
|
32
|
+
/** Overrides row rendering entirely, for BOTH variants — when supplied, this is called for every
|
|
33
|
+
* thread instead of either built-in row. Gives full control over live-run wiring
|
|
34
|
+
* (`onStop`/`liveParts`/`liveStatus`) the built-in `'inline'` row doesn't expose; see `variant`
|
|
35
|
+
* and `onSend`. */
|
|
36
|
+
readonly renderRow?: (thread: AgentThread) => ReactNode;
|
|
9
37
|
};
|
|
10
38
|
/**
|
|
11
|
-
* A scrollable feed of thread rows, animated on add/remove/reorder.
|
|
12
|
-
* `
|
|
39
|
+
* A scrollable feed of thread rows, animated on add/remove/reorder. Row content comes from
|
|
40
|
+
* `renderRow` when supplied, else from the built-in `variant` row (`ThreadOutcomeCard` or
|
|
41
|
+
* `ThreadFeedRow`) — see `ThreadFeedProps` for the full contract.
|
|
13
42
|
*
|
|
14
43
|
* @example
|
|
15
44
|
* <ThreadFeed threads={threads} activeId={activeId} onSelect={(id) => select(id)} />
|
|
16
45
|
*/
|
|
17
|
-
export declare function ThreadFeed({ threads, activeId, onSelect }: ThreadFeedProps): JSX.Element;
|
|
46
|
+
export declare function ThreadFeed({ threads, activeId, onSelect, variant, onSend, renderRow, }: ThreadFeedProps): JSX.Element;
|
|
18
47
|
//# sourceMappingURL=thread-feed.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thread-feed.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-feed.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"thread-feed.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-feed.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAShD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,CAAA;IAC/B,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC;uGACmG;IACnG,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,KAAK,IAAI,CAAA;IACxE;;;uBAGmB;IACnB,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,SAAS,CAAA;CACxD,CAAA;AAgCD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,OAAmB,EACnB,MAAM,EACN,SAAS,GACV,EAAE,eAAe,GAAG,GAAG,CAAC,OAAO,CAkE/B"}
|
|
@@ -1,21 +1,61 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { ScrollArea, Stack } from "@mantine/core";
|
|
2
|
+
import { Box, ScrollArea, Stack } from "@mantine/core";
|
|
3
3
|
import { useReducedMotion } from "@mantine/hooks";
|
|
4
4
|
import { AnimatePresence, motion } from "motion/react";
|
|
5
|
+
import { useEffect, useState } from "react";
|
|
5
6
|
import { MOTION_SPRING } from "../motion/index.js";
|
|
7
|
+
import { ThreadFeedRow } from "./thread-feed-row.js";
|
|
6
8
|
import { ThreadOutcomeCard } from "./thread-outcome-card.js";
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
function noopSend() {
|
|
10
|
+
}
|
|
11
|
+
function defaultRow(thread, variant, activeId, collapsedId, onToggle, onSelect, onSend) {
|
|
12
|
+
if (variant === "inline") {
|
|
13
|
+
const expanded = thread.id === activeId && thread.id !== collapsedId;
|
|
14
|
+
return /* @__PURE__ */ jsx(
|
|
15
|
+
ThreadFeedRow,
|
|
12
16
|
{
|
|
13
17
|
thread,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
expanded,
|
|
19
|
+
onToggle,
|
|
20
|
+
onSend: onSend !== void 0 ? (payload) => onSend(thread, payload) : noopSend,
|
|
21
|
+
...onSend === void 0 ? { composerProps: { disabled: true } } : {}
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return /* @__PURE__ */ jsx(
|
|
26
|
+
ThreadOutcomeCard,
|
|
27
|
+
{
|
|
28
|
+
thread,
|
|
29
|
+
selected: thread.id === activeId,
|
|
30
|
+
onSelect: () => onSelect(thread.id)
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
function ThreadFeed({
|
|
35
|
+
threads,
|
|
36
|
+
activeId,
|
|
37
|
+
onSelect,
|
|
38
|
+
variant = "outcome",
|
|
39
|
+
onSend,
|
|
40
|
+
renderRow
|
|
41
|
+
}) {
|
|
42
|
+
const reduceMotion = useReducedMotion();
|
|
43
|
+
const [collapsedId, setCollapsedId] = useState(null);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
setCollapsedId(null);
|
|
46
|
+
}, [activeId]);
|
|
47
|
+
function handleInlineToggle(id) {
|
|
48
|
+
const isOpen = id === activeId && id !== collapsedId;
|
|
49
|
+
if (isOpen) {
|
|
50
|
+
setCollapsedId(id);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
setCollapsedId(null);
|
|
54
|
+
onSelect(id);
|
|
55
|
+
}
|
|
56
|
+
const rowFor = (thread) => renderRow !== void 0 ? renderRow(thread) : defaultRow(thread, variant, activeId, collapsedId, handleInlineToggle, onSelect, onSend);
|
|
57
|
+
if (reduceMotion) {
|
|
58
|
+
return /* @__PURE__ */ jsx(ScrollArea, { style: { height: "100%" }, children: /* @__PURE__ */ jsx(Stack, { gap: "sm", p: "sm", children: threads.map((thread) => /* @__PURE__ */ jsx(Box, { children: rowFor(thread) }, thread.id)) }) });
|
|
19
59
|
}
|
|
20
60
|
return /* @__PURE__ */ jsx(ScrollArea, { style: { height: "100%" }, children: /* @__PURE__ */ jsx(Stack, { gap: "sm", p: "sm", children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "popLayout", initial: false, children: threads.map((thread) => /* @__PURE__ */ jsx(
|
|
21
61
|
motion.div,
|
|
@@ -25,14 +65,7 @@ function ThreadFeed({ threads, activeId, onSelect }) {
|
|
|
25
65
|
animate: { opacity: 1, y: 0 },
|
|
26
66
|
exit: { opacity: 0, scale: 0.97 },
|
|
27
67
|
transition: MOTION_SPRING,
|
|
28
|
-
children:
|
|
29
|
-
ThreadOutcomeCard,
|
|
30
|
-
{
|
|
31
|
-
thread,
|
|
32
|
-
selected: thread.id === activeId,
|
|
33
|
-
onSelect: () => onSelect(thread.id)
|
|
34
|
-
}
|
|
35
|
-
)
|
|
68
|
+
children: rowFor(thread)
|
|
36
69
|
},
|
|
37
70
|
thread.id
|
|
38
71
|
)) }) }) });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread-feed.test.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-feed.test.tsx"],"names":[],"mappings":""}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { JSX } from 'react';
|
|
2
2
|
import type { AgentPartRenderers, ChatMessage, ForeignPart, PartRenderer, PartRenderers, StreamStatus, TranscriptPart } from '../agent/index.js';
|
|
3
|
+
import type { MessageAffordances } from './message-affordances.js';
|
|
4
|
+
import type { VirtualizeOptions, VirtualizeProps } from './virtualize.js';
|
|
3
5
|
/**
|
|
4
6
|
* Mantine overrides for `PartList`'s default headless renderers — the house style for rendering
|
|
5
7
|
* an `AgentPart` inside a chat surface.
|
|
@@ -19,6 +21,165 @@ export declare const threadPartRenderers: Partial<AgentPartRenderers>;
|
|
|
19
21
|
export declare const messageBlockRenderCounter: {
|
|
20
22
|
count: number;
|
|
21
23
|
};
|
|
24
|
+
/** Caps `applyInitialScroll`'s clobber-recovery retries (see `./virtualize`'s fourth composition
|
|
25
|
+
* rule for the full mechanism this guards). A small fixed budget, not a time/frame-based deadline:
|
|
26
|
+
* every observed case of virtual-core's `_willUpdate` clobbering this effect's jump resolves within
|
|
27
|
+
* one extra commit, so this exists purely as a backstop against a pathological DOM that never
|
|
28
|
+
* reports settled — not as the expected path. Once exhausted, the effect gives up and marks the
|
|
29
|
+
* jump permanently done wherever the last attempt landed, rather than fighting the DOM (or a user
|
|
30
|
+
* who has since scrolled away) forever. Exported (like `messageBlockRenderCounter`) purely so its
|
|
31
|
+
* test can assert the exact bound without duplicating the number; not part of the public surface. */
|
|
32
|
+
export declare const MAX_INITIAL_SCROLL_ATTEMPTS = 5;
|
|
33
|
+
type TranscriptRow = {
|
|
34
|
+
readonly key: string;
|
|
35
|
+
readonly node: JSX.Element;
|
|
36
|
+
};
|
|
37
|
+
type VirtualizedTranscriptProps = {
|
|
38
|
+
readonly rows: readonly TranscriptRow[];
|
|
39
|
+
readonly height: number | string;
|
|
40
|
+
readonly overscan: number;
|
|
41
|
+
readonly estimateSize: number;
|
|
42
|
+
readonly initialScroll: NonNullable<VirtualizeOptions['initialScroll']>;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* The degrade target: every row rendered unwindowed inside a fixed-height scroll container.
|
|
46
|
+
* This is what a `virtualize: true` transcript falls back to when `@tanstack/react-virtual` fails
|
|
47
|
+
* to resolve (peer absent) — same visual shape (a scrollable `height`-bound pane), just without
|
|
48
|
+
* windowing.
|
|
49
|
+
*
|
|
50
|
+
* NOT used as the `Suspense` fallback (see `VirtualizeSuspenseFallback` below) — mounting every
|
|
51
|
+
* real `MessageBlock` subtree for the one tick the lazy import takes to settle would make that
|
|
52
|
+
* settle a genuine unmount/remount of every rendered row, re-firing each row's effects once and
|
|
53
|
+
* risking hitting a message mid-stream during the resolve window.
|
|
54
|
+
*/
|
|
55
|
+
declare function NonVirtualizedRows({ rows, height }: VirtualizedTranscriptProps): JSX.Element;
|
|
56
|
+
/** Test-only escape hatch (mirrors `messageBlockRenderCounter` above) — proves the peer-absent
|
|
57
|
+
* degrade target itself renders every row without windowing and without throwing, without having
|
|
58
|
+
* to fight `React.lazy`'s permanent once-per-module-instance resolution cache to simulate the
|
|
59
|
+
* peer's absence at the `import()` layer (see thread-message.test.tsx's virtualization describe
|
|
60
|
+
* block for why that approach was rejected). Not exported from `agent-chat/index.ts`. */
|
|
61
|
+
export declare const nonVirtualizedRowsFallback: typeof NonVirtualizedRows;
|
|
62
|
+
/**
|
|
63
|
+
* Minimal structural slice of `Virtualizer` (virtual-core 3.17.1) that
|
|
64
|
+
* {@link resolveGuardedMeasurement} needs — `indexFromElement`/`itemSizeCache`/`options` are all
|
|
65
|
+
* public instance members, but typing against the real (generic, peer-only) `Virtualizer` class
|
|
66
|
+
* would force this test-only-exported helper to import the peer's runtime types at module scope,
|
|
67
|
+
* the exact static-import-forces-the-peer problem the surrounding `lazy(() => import(...))` exists
|
|
68
|
+
* to avoid. A structural type sidesteps that without loosening to `any`. Generic over the item
|
|
69
|
+
* element type (rather than fixed at `Element`) so it stays exactly assignable FROM a real
|
|
70
|
+
* `Virtualizer<TScrollElement, TItemElement>` at the call site below — `indexFromElement` is
|
|
71
|
+
* declared with arrow-property (not method) syntax upstream, so it is checked contravariantly, and
|
|
72
|
+
* a fixed `Element` parameter there does not structurally match a real instance's narrower
|
|
73
|
+
* `TItemElement` parameter.
|
|
74
|
+
*/
|
|
75
|
+
type MeasurementCacheHost<TItemElement extends Element> = {
|
|
76
|
+
readonly indexFromElement: (node: TItemElement) => number;
|
|
77
|
+
readonly itemSizeCache: Map<unknown, number>;
|
|
78
|
+
readonly options: {
|
|
79
|
+
readonly getItemKey: (index: number) => unknown;
|
|
80
|
+
readonly estimateSize: (index: number) => number;
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Resolves the size a `measureElement` implementation should report, given what the underlying
|
|
85
|
+
* (default or custom) measurement already produced: that value unchanged when it is a genuine
|
|
86
|
+
* measurement, or the row's own last-known size when it is `0` — never a real reading for a mounted
|
|
87
|
+
* row, so a `0` only ever means the ResizeObserver measured through a `display: none` (or otherwise
|
|
88
|
+
* unlaid-out) ancestor, e.g. a virtualized transcript sitting inside a collapsed `ThreadFeedRow`.
|
|
89
|
+
* Falling through to it unguarded is what corrupted `itemSizeCache`/`measurementsCache`: virtual-
|
|
90
|
+
* core's default `resizeItem` writes whatever `measureElement` returns with no floor, so every
|
|
91
|
+
* mounted row remeasures at 0px the instant an ancestor hides, `getTotalSize()` collapses within
|
|
92
|
+
* the same tick, and the collapse is never undone — a later real remeasurement only OVERWRITES
|
|
93
|
+
* entries the ResizeObserver happens to re-fire for, it does not restore ones that silently kept
|
|
94
|
+
* their poisoned 0 (see `virtualize.ts`'s composition-rule doc for the full mechanism and the
|
|
95
|
+
* measured numbers).
|
|
96
|
+
*
|
|
97
|
+
* Takes the already-computed `size` plus the {@link MeasurementCacheHost} slice, rather than
|
|
98
|
+
* wrapping the `measureElement` function itself, so this stays a plain, non-generic-over-a-
|
|
99
|
+
* generic-function call at its one call site below (`instance` there is `Virtualizer<TScrollElement,
|
|
100
|
+
* TItemElement>` for THAT call's concrete type arguments, not the peer's own generic
|
|
101
|
+
* `measureElement` export — wrapping the latter directly hits TS's higher-rank inference limits on
|
|
102
|
+
* a doubly-generic HOF). That shape is also what makes this unit-testable with a fake host object
|
|
103
|
+
* and no ResizeObserver/layout engine at all (happy-dom has neither). Not exported from
|
|
104
|
+
* `agent-chat/index.ts`: a test-only escape hatch, matching
|
|
105
|
+
* `messageBlockRenderCounter`/`nonVirtualizedRowsFallback` above.
|
|
106
|
+
*/
|
|
107
|
+
export declare function resolveGuardedMeasurement<TItemElement extends Element>(host: MeasurementCacheHost<TItemElement>, element: TItemElement, size: number): number;
|
|
108
|
+
/**
|
|
109
|
+
* Decides what `VirtualizedRowsInner`'s one-shot initial-scroll effect should do this render,
|
|
110
|
+
* given the inputs the effect closes over. Pure and peer/DOM-free — pulled out of the lazy-loaded
|
|
111
|
+
* closure for the same reason `resolveGuardedMeasurement` is: it stays unit-testable without a
|
|
112
|
+
* real `Virtualizer` instance or `@tanstack/react-virtual` itself.
|
|
113
|
+
*
|
|
114
|
+
* `'start'` and an empty transcript resolve to `'skip-done'` — there is nothing to scroll to, and
|
|
115
|
+
* DONE is permanent (see `virtualize.ts`'s fourth composition rule: the real scroll must fire at
|
|
116
|
+
* most once, ever, so a later append never yanks a user who has scrolled up). A non-positive
|
|
117
|
+
* `containerHeight` — the transcript's own scroll node measuring `0`, e.g. mounted behind a
|
|
118
|
+
* `display: none` ancestor per `virtualize.ts`'s third composition rule — resolves to
|
|
119
|
+
* `'skip-retry'`: NOT done, so the caller's effect (which re-runs after EVERY commit until the jump
|
|
120
|
+
* lands — see {@link applyInitialScroll}) gets another chance the next time this subtree renders,
|
|
121
|
+
* instead of permanently stranding the transcript at the top the moment it becomes visible. Only a
|
|
122
|
+
* genuinely measured, non-empty, `'end'`-targeted mount resolves to `'scroll'`.
|
|
123
|
+
*/
|
|
124
|
+
export declare function resolveInitialScrollAction(initialScroll: NonNullable<VirtualizeOptions['initialScroll']>, rowCount: number, containerHeight: number): 'scroll' | 'skip-done' | 'skip-retry';
|
|
125
|
+
/** The mutable half of the one-shot initial-scroll contract. `hasApplied` is whether it has
|
|
126
|
+
* PERMANENTLY finished — either there was nothing to do (`'start'`/empty transcript), or
|
|
127
|
+
* `scrollToEnd` fired and was subsequently CONFIRMED (via a real DOM read — see
|
|
128
|
+
* {@link applyInitialScroll}'s doc for why virtual-core's own tracked offset can't be trusted for
|
|
129
|
+
* this) to have survived whatever else committed since. `attempts` counts how many times
|
|
130
|
+
* `scrollToEnd` has been (re-)fired this mount; 0 means never attempted, and also gates whether a
|
|
131
|
+
* call re-derives {@link resolveInitialScrollAction} at all. One instance lives for the lifetime of
|
|
132
|
+
* a `VirtualizedRowsInner` mount, held in a `useRef` so it survives re-renders without itself
|
|
133
|
+
* triggering one. */
|
|
134
|
+
export type InitialScrollState = {
|
|
135
|
+
hasApplied: boolean;
|
|
136
|
+
attempts: number;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* The stateful half of "fires once, on first mount, and never again" (`virtualize.ts`'s fourth
|
|
140
|
+
* composition rule): wraps {@link resolveInitialScrollAction}'s per-call decision with the
|
|
141
|
+
* ACROSS-CALLS memory that decision alone can't carry. Exported (like `resolveGuardedMeasurement`)
|
|
142
|
+
* so `VirtualizedRowsInner`'s effect below calls this SAME function rather than a hand-mirrored
|
|
143
|
+
* copy for tests — a fake `scrollToEnd` is the only DOM/peer-free way to prove the guard, since a
|
|
144
|
+
* real `Virtualizer`'s own `followOnAppend` also calls the real DOM `scrollTo` on a later append,
|
|
145
|
+
* making that specific call indistinguishable from this one at the DOM boundary (see this
|
|
146
|
+
* function's test suite for the measurement that ruled that boundary out).
|
|
147
|
+
*
|
|
148
|
+
* Firing `scrollToEnd()` once is NOT the same as landing there: virtual-core 3.17.1's own
|
|
149
|
+
* `_willUpdate` (verified against the installed dist) writes to the SAME scroll container from
|
|
150
|
+
* `this.scrollOffset`, which updates only from the ASYNCHRONOUS native `scroll` event — never from
|
|
151
|
+
* the synchronous call that requested a scroll. A commit that runs `_willUpdate` before this
|
|
152
|
+
* effect gets a turn (element-attach) or that re-derives an anchor while `scrollOffset` still holds
|
|
153
|
+
* the pre-jump value writes that STALE offset straight back to `scrollTop`, undoing the jump within
|
|
154
|
+
* about one extra commit of it landing (see `./virtualize`'s fourth composition rule for the full
|
|
155
|
+
* trace this was diagnosed from). So `resolveInitialScrollAction`'s `'scroll'` outcome does not, by
|
|
156
|
+
* itself, set `hasApplied`: `isAtEnd` — a real `scrollTop`/`scrollHeight`/`clientHeight` read, the
|
|
157
|
+
* one piece of ground truth that is NEVER stale, unlike virtual-core's own tracked offset — is
|
|
158
|
+
* consulted on the NEXT call (i.e. the next commit, exactly the cadence this dependency-free effect
|
|
159
|
+
* already runs on) to confirm the jump survived. If it didn't, `scrollToEnd` fires again; if it did,
|
|
160
|
+
* `hasApplied` is set and the effect is inert for the rest of the mount. `MAX_INITIAL_SCROLL_ATTEMPTS`
|
|
161
|
+
* bounds this so a DOM that pathologically never reports settled — or a user who scrolls away mid-
|
|
162
|
+
* measurement — can never be fought forever; once exhausted, `hasApplied` is set regardless, wherever
|
|
163
|
+
* the last attempt left the container.
|
|
164
|
+
*
|
|
165
|
+
* `state.hasApplied` is the ONLY thing that can permanently stop this from calling `scrollToEnd`
|
|
166
|
+
* again: `'start'` and an empty transcript set it immediately (nothing to verify), and `'skip-retry'`
|
|
167
|
+
* deliberately leaves it `false` so a transcript that mounted behind a hidden ancestor gets another
|
|
168
|
+
* attempt on the caller's next commit, rather than being silently abandoned the moment it becomes
|
|
169
|
+
* visible. That retry — and the clobber-recovery above — is why the caller's effect carries NO
|
|
170
|
+
* dependency array: the commit that matters (a collapsed `ThreadFeedRow` flipping its body back to
|
|
171
|
+
* `display: block`, or virtual-core's own follow-up commit after this effect's own jump) changes
|
|
172
|
+
* neither the row count nor the virtualizer identity, so a dependency-keyed effect would never
|
|
173
|
+
* re-run for it.
|
|
174
|
+
*
|
|
175
|
+
* `getContainerHeight` is a THUNK, not a number, for the same reason: reading `offsetHeight` forces
|
|
176
|
+
* a synchronous layout, and it is read at most ONCE per mount — only on the very first call
|
|
177
|
+
* (`attempts === 0`) — never again while verifying or retrying, so a landed transcript still pays
|
|
178
|
+
* nothing for the effect it keeps re-running (a streaming transcript commits on every chunk).
|
|
179
|
+
* `isAtEnd` is cheaper (a plain property read, no forced reflow beyond what the browser already
|
|
180
|
+
* tracks) and is exactly what the verification/retry loop above consults instead.
|
|
181
|
+
*/
|
|
182
|
+
export declare function applyInitialScroll(state: InitialScrollState, initialScroll: NonNullable<VirtualizeOptions['initialScroll']>, rowCount: number, getContainerHeight: () => number, scrollToEnd: () => void, isAtEnd: () => boolean): void;
|
|
22
183
|
type ThreadTranscriptBase = {
|
|
23
184
|
/** Settled messages for the thread, oldest first. */
|
|
24
185
|
readonly messages: readonly ChatMessage<TranscriptPart>[];
|
|
@@ -31,18 +192,38 @@ type ThreadTranscriptBase = {
|
|
|
31
192
|
/** Called for a part whose type is neither an AgentPart variant nor a registered key. Defaults
|
|
32
193
|
* to a visible `UnknownPartChip` outside production, `null` in production — never throws. */
|
|
33
194
|
readonly fallbackRenderer?: PartRenderer<ForeignPart>;
|
|
195
|
+
/** Per-message hover-row affordances (timestamp/copy/regenerate/custom actions). Unset fields
|
|
196
|
+
* fall back to `DEFAULT_AFFORDANCES`. Never shown on the live/streaming message — see
|
|
197
|
+
* `MessageBlockProps.affordances`. */
|
|
198
|
+
readonly affordances?: MessageAffordances;
|
|
199
|
+
/** Suppresses the role label and surface chrome on a message whose predecessor shares its role
|
|
200
|
+
* and landed within 5 minutes — the Slack rhythm.
|
|
201
|
+
* @default true */
|
|
202
|
+
readonly groupConsecutive?: boolean;
|
|
34
203
|
};
|
|
35
|
-
export type ThreadTranscriptProps = ThreadTranscriptBase;
|
|
204
|
+
export type ThreadTranscriptProps = ThreadTranscriptBase & VirtualizeProps;
|
|
36
205
|
/**
|
|
37
206
|
* Renders a thread's settled messages, each role-labelled ("You" / "Assistant"). Each part
|
|
38
207
|
* resolves in order: a registered `renderers[part.type]` first, then basalt's own `PartList` for
|
|
39
208
|
* the built-in six, then `fallbackRenderer`. When `liveParts` is non-empty, an extra in-progress
|
|
40
209
|
* assistant block is appended at the tail.
|
|
41
210
|
*
|
|
211
|
+
* Set `virtualize` (with a required `height`) to window a long thread over
|
|
212
|
+
* `@tanstack/react-virtual` (an optional peer) instead of rendering every message. The virtualized
|
|
213
|
+
* transcript owns its own fixed-height scroll container and must NOT be nested inside
|
|
214
|
+
* `BasaltStickToBottom` — see `./virtualize`'s module doc. With the peer absent, `virtualize` still
|
|
215
|
+
* renders (unwindowed, inside the same fixed-height pane) rather than throwing. A virtualized
|
|
216
|
+
* transcript scrolls itself to the newest message once, on mount (`initialScroll: 'start'` opts
|
|
217
|
+
* out) — see `./virtualize`'s fourth composition rule.
|
|
218
|
+
*
|
|
42
219
|
* @example
|
|
43
220
|
* import { ThreadTranscript } from 'basalt-ui'
|
|
44
221
|
* <ThreadTranscript messages={thread.messages} liveParts={liveParts} liveStatus={runStatus} />
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* // Windowed, for a long thread:
|
|
225
|
+
* <ThreadTranscript messages={thread.messages} virtualize height={480} />
|
|
45
226
|
*/
|
|
46
|
-
export declare function ThreadTranscript(
|
|
227
|
+
export declare function ThreadTranscript(props: ThreadTranscriptProps): JSX.Element;
|
|
47
228
|
export {};
|
|
48
229
|
//# sourceMappingURL=thread-message.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thread-message.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-message.tsx"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,KAAK,EAEV,kBAAkB,EAClB,WAAW,EAEX,WAAW,EACX,YAAY,EACZ,aAAa,EAGb,YAAY,EAGZ,cAAc,EACf,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"thread-message.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-message.tsx"],"names":[],"mappings":"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"}
|