@adecore/agents-react 0.38.0 → 0.40.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/agents/AgentModelLabel.d.ts +10 -0
- package/dist/agents/AgentModelLabel.js +98 -0
- package/dist/agents/model-name.js +2 -1
- package/dist/agents/provider-paths.d.ts +2 -1
- package/dist/agents/provider-paths.js +7 -3
- package/dist/chat/account-choice.js +57 -28
- package/dist/chat/bookmarks.js +2 -1
- package/dist/chat/drafts.d.ts +0 -1
- package/dist/chat/drafts.js +9 -28
- package/dist/chat/logic/picker-entries.js +2 -8
- package/dist/chat/logic/resume-compaction.js +2 -5
- package/dist/chat/logic/timeline-copy.js +2 -8
- package/dist/chat/logic/timeline.js +9 -10
- package/dist/chat/logic/workflow.js +2 -4
- package/dist/chat/subagent-conversation.js +2 -1
- package/dist/chat/ui/Composer.js +385 -360
- package/dist/chat/ui/GoalDock.js +2 -1
- package/dist/chat/ui/LimitState.js +4 -6
- package/dist/chat/ui/Pickers.d.ts +2 -1
- package/dist/chat/ui/Pickers.js +126 -123
- package/dist/chat/ui/PromptEditor.d.ts +19 -0
- package/dist/chat/ui/PromptEditor.js +115 -0
- package/dist/chat/ui/Timeline.d.ts +2 -1
- package/dist/chat/ui/Timeline.js +13 -1
- package/dist/chat/ui/composer/ComposerControls.d.ts +3 -0
- package/dist/chat/ui/composer/ComposerControls.js +235 -38
- package/dist/chat/ui/composer/key-handler.d.ts +1 -1
- package/dist/chat/ui/composer/key-handler.js +3 -11
- package/dist/chat/ui/composer/prompt-picks.d.ts +4 -0
- package/dist/chat/ui/composer/prompt-picks.js +14 -0
- package/dist/chat/ui/composer/use-composer-menus.d.ts +4 -49
- package/dist/chat/ui/composer/use-composer-menus.js +65 -519
- package/dist/chat/ui/composer/use-continuable-turn.js +0 -1
- package/dist/chat/ui/composer/use-notice.js +2 -1
- package/dist/chat/ui/composer/use-prompt-history.js +1 -1
- package/dist/chat/ui/composer/use-prompt-menus.d.ts +73 -0
- package/dist/chat/ui/composer/use-prompt-menus.js +499 -0
- package/dist/chat/ui/composer/use-take-back.js +2 -2
- package/dist/chat/ui/diff/ApprovalReview.js +1 -1
- package/dist/chat/ui/highlighter.d.ts +0 -6
- package/dist/chat/ui/highlighter.js +0 -12
- package/dist/chat/ui/icons.d.ts +0 -1
- package/dist/chat/ui/icons.js +0 -3
- package/dist/chat/ui/index.d.ts +3 -0
- package/dist/chat/ui/index.js +2 -0
- package/dist/chat/ui/rows/MessageRows.js +2 -5
- package/dist/chat/ui/rows/row-rhythm.js +2 -5
- package/dist/chat/ui/theme-watch.js +3 -7
- package/dist/host-hooks.d.ts +2 -1
- package/dist/host-hooks.js +6 -0
- package/dist/host.d.ts +17 -1
- package/dist/index.d.ts +1 -1
- package/dist/locales/en/agent-chat.d.ts +1 -0
- package/dist/locales/en/agent-chat.js +1 -0
- package/dist/locales/nl/agent-chat.d.ts +1 -0
- package/dist/locales/nl/agent-chat.js +1 -0
- package/dist/prompts/logic/usePromptSession.js +2 -1
- package/dist/providers/AddAccountForm.js +2 -1
- package/dist/providers/CliDetail.js +104 -130
- package/dist/providers/account-actions.js +6 -9
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.js +1 -0
- package/dist/state/chats.d.ts +1 -0
- package/dist/state/chats.js +6 -11
- package/dist/state/provider-accounts.d.ts +1 -1
- package/dist/state/provider-accounts.js +2 -8
- package/dist/theme.css +3 -0
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +14 -0
- package/dist/usage/ChartLogo.d.ts +5 -0
- package/dist/usage/ChartLogo.js +48 -0
- package/dist/usage/LimitsList.js +6 -16
- package/dist/usage/UsageBreakdown.js +17 -40
- package/dist/usage/UsageLimits.js +183 -211
- package/dist/usage/UsagePage.js +3 -17
- package/dist/usage/UsageSummary.js +4 -9
- package/dist/usage/limits.js +16 -31
- package/dist/usage/summary.d.ts +1 -1
- package/dist/usage/summary.js +3 -5
- package/package.json +26 -26
- package/src/agents/AccountDot.tsx +1 -0
- package/src/agents/AgentIcon.tsx +2 -0
- package/src/agents/AgentModelLabel.tsx +38 -0
- package/src/agents/ModelOptionControl.tsx +8 -0
- package/src/agents/account-limits.ts +3 -0
- package/src/agents/accounts.ts +12 -0
- package/src/agents/model-name.ts +13 -1
- package/src/agents/model-options.ts +2 -0
- package/src/agents/model-requirement.ts +4 -0
- package/src/agents/option-words.ts +2 -0
- package/src/agents/provider-paths.ts +8 -3
- package/src/agents/status-look.ts +1 -0
- package/src/chat/account-choice.ts +25 -19
- package/src/chat/actions.ts +5 -0
- package/src/chat/attachments.ts +8 -0
- package/src/chat/bookmarks.ts +13 -1
- package/src/chat/chat-client.ts +63 -0
- package/src/chat/chat-references.ts +3 -0
- package/src/chat/composer-effort.ts +5 -0
- package/src/chat/composer-send.ts +4 -0
- package/src/chat/drafts.ts +21 -17
- package/src/chat/forks.ts +4 -0
- package/src/chat/guards.ts +2 -0
- package/src/chat/index.ts +7 -0
- package/src/chat/logic/ansi.ts +47 -0
- package/src/chat/logic/bookmarks.ts +7 -0
- package/src/chat/logic/command-looks.ts +29 -0
- package/src/chat/logic/context-usage.ts +3 -0
- package/src/chat/logic/continue-after-stop.ts +5 -0
- package/src/chat/logic/effort.ts +4 -0
- package/src/chat/logic/embedded-code.ts +63 -0
- package/src/chat/logic/fork.ts +22 -0
- package/src/chat/logic/generated-image.ts +7 -0
- package/src/chat/logic/goal.ts +7 -0
- package/src/chat/logic/handback.ts +2 -0
- package/src/chat/logic/limit.ts +8 -0
- package/src/chat/logic/picker-entries.ts +16 -4
- package/src/chat/logic/resume-compaction.ts +22 -5
- package/src/chat/logic/scrubber.ts +38 -5
- package/src/chat/logic/thought.ts +5 -0
- package/src/chat/logic/thread-cards.ts +6 -0
- package/src/chat/logic/timeline-copy.ts +4 -9
- package/src/chat/logic/timeline-target.ts +15 -0
- package/src/chat/logic/timeline.ts +113 -10
- package/src/chat/logic/tools.ts +30 -0
- package/src/chat/logic/visual-height.ts +5 -0
- package/src/chat/logic/visual-viewport.ts +1 -0
- package/src/chat/logic/welcome.ts +3 -0
- package/src/chat/logic/workflow.ts +26 -7
- package/src/chat/mentions.ts +56 -0
- package/src/chat/pending-prompts.ts +16 -0
- package/src/chat/persisted-json.ts +15 -0
- package/src/chat/preferences.ts +16 -0
- package/src/chat/prompt-hold.ts +2 -0
- package/src/chat/quote.ts +7 -0
- package/src/chat/reading-width.ts +1 -0
- package/src/chat/recent-messages.ts +5 -0
- package/src/chat/resume-compaction-dismissals.ts +1 -0
- package/src/chat/stash.ts +12 -0
- package/src/chat/subagent-conversation.ts +24 -4
- package/src/chat/subagent-list.ts +14 -0
- package/src/chat/subagent-support.ts +1 -0
- package/src/chat/subagent-view.ts +9 -0
- package/src/chat/thumbnails.ts +14 -0
- package/src/chat/timeline-flash.ts +2 -0
- package/src/chat/timeline-scroll.ts +23 -0
- package/src/chat/ui/AccountPill.tsx +4 -0
- package/src/chat/ui/AnsiOutput.tsx +1 -0
- package/src/chat/ui/BookmarkMarker.tsx +5 -0
- package/src/chat/ui/BookmarkSubmenu.tsx +1 -0
- package/src/chat/ui/ChatActivity.tsx +13 -0
- package/src/chat/ui/CodeBlock.tsx +8 -0
- package/src/chat/ui/Composer.tsx +27 -3
- package/src/chat/ui/ComposerInput.tsx +22 -0
- package/src/chat/ui/DiffPool.tsx +3 -0
- package/src/chat/ui/EditDiff.tsx +5 -0
- package/src/chat/ui/GoalDock.tsx +13 -1
- package/src/chat/ui/ImageView.tsx +11 -0
- package/src/chat/ui/LimitState.tsx +23 -6
- package/src/chat/ui/Markdown.tsx +41 -0
- package/src/chat/ui/MermaidBlock.tsx +16 -0
- package/src/chat/ui/MessageActions.tsx +8 -0
- package/src/chat/ui/ModelMarks.tsx +1 -0
- package/src/chat/ui/Pickers.tsx +30 -1
- package/src/chat/ui/PromptComposer.tsx +2 -0
- package/src/chat/ui/PromptEditor.tsx +136 -0
- package/src/chat/ui/QuoteButton.tsx +12 -0
- package/src/chat/ui/ReferenceChip.tsx +3 -0
- package/src/chat/ui/ResumeCompactionDock.tsx +1 -0
- package/src/chat/ui/RowTime.tsx +1 -0
- package/src/chat/ui/RunSettings.tsx +38 -0
- package/src/chat/ui/Scrubber.tsx +14 -0
- package/src/chat/ui/SessionLine.tsx +1 -0
- package/src/chat/ui/ShellCommand.tsx +32 -0
- package/src/chat/ui/SubagentControls.tsx +7 -0
- package/src/chat/ui/SubagentInfo.tsx +1 -0
- package/src/chat/ui/SubagentStopButton.tsx +6 -0
- package/src/chat/ui/SubagentTimeline.tsx +10 -0
- package/src/chat/ui/Timeline.tsx +141 -1
- package/src/chat/ui/TimelineMenu.tsx +5 -0
- package/src/chat/ui/UnifiedDiff.tsx +6 -0
- package/src/chat/ui/UploadThumb.tsx +2 -0
- package/src/chat/ui/VisualDialogs.tsx +3 -0
- package/src/chat/ui/VisualFrame.tsx +15 -0
- package/src/chat/ui/Welcome.tsx +1 -0
- package/src/chat/ui/chip-clipboard.ts +20 -0
- package/src/chat/ui/code-lines.ts +14 -0
- package/src/chat/ui/code-theme.ts +1 -0
- package/src/chat/ui/composer/ComposerControls.tsx +70 -15
- package/src/chat/ui/composer/ComposerMenu.tsx +12 -0
- package/src/chat/ui/composer/DraftParts.tsx +2 -0
- package/src/chat/ui/composer/QueuedMessages.tsx +4 -0
- package/src/chat/ui/composer/chip-paste.ts +16 -0
- package/src/chat/ui/composer/chips.ts +6 -0
- package/src/chat/ui/composer/editor.ts +9 -0
- package/src/chat/ui/composer/insert.ts +2 -0
- package/src/chat/ui/composer/key-handler.ts +42 -9
- package/src/chat/ui/composer/keys.ts +23 -0
- package/src/chat/ui/composer/placeholder.ts +6 -0
- package/src/chat/ui/composer/prompt-picks.ts +18 -0
- package/src/chat/ui/composer/surface.ts +5 -0
- package/src/chat/ui/composer/use-chat-controls.ts +13 -0
- package/src/chat/ui/composer/use-compaction-offer.ts +3 -0
- package/src/chat/ui/composer/use-composer-menus.ts +20 -275
- package/src/chat/ui/composer/use-composer-submit.ts +27 -0
- package/src/chat/ui/composer/use-continuable-turn.ts +3 -5
- package/src/chat/ui/composer/use-draft-files.ts +24 -0
- package/src/chat/ui/composer/use-effort-stepper.ts +8 -0
- package/src/chat/ui/composer/use-notice.ts +4 -1
- package/src/chat/ui/composer/use-pending-requests.ts +3 -0
- package/src/chat/ui/composer/use-prompt-history.ts +20 -11
- package/src/chat/ui/composer/use-prompt-hold.ts +3 -0
- package/src/chat/ui/composer/use-prompt-menus.ts +361 -0
- package/src/chat/ui/composer/use-take-back.ts +20 -2
- package/src/chat/ui/diff/ApprovalReview.tsx +18 -15
- package/src/chat/ui/diff/DiffFallbackBoundary.tsx +1 -0
- package/src/chat/ui/diff/DiffOmitted.tsx +1 -0
- package/src/chat/ui/diff/DiffStack.tsx +2 -0
- package/src/chat/ui/diff/DiffUnavailable.tsx +1 -0
- package/src/chat/ui/diff/EditorEditDiff.tsx +13 -0
- package/src/chat/ui/diff/EditorPatchDiff.tsx +4 -0
- package/src/chat/ui/diff/LegacyDiffPool.tsx +2 -0
- package/src/chat/ui/diff/LegacyEditDiff.tsx +2 -0
- package/src/chat/ui/diff/LegacyUnifiedDiff.tsx +8 -0
- package/src/chat/ui/diff/RawDiff.tsx +1 -0
- package/src/chat/ui/diff/ReviewDiff.tsx +1 -0
- package/src/chat/ui/diff/diff-source-budget.ts +8 -0
- package/src/chat/ui/diff/editor-edit-model.ts +4 -0
- package/src/chat/ui/diff/review-plan.ts +9 -0
- package/src/chat/ui/diff/review-settle.ts +1 -0
- package/src/chat/ui/diff/syntax-runtime.ts +52 -0
- package/src/chat/ui/diff/use-review-stack.ts +9 -0
- package/src/chat/ui/diff-syntax.ts +12 -0
- package/src/chat/ui/diff-theme.ts +2 -0
- package/src/chat/ui/file-links.ts +2 -0
- package/src/chat/ui/find-reveal.ts +1 -0
- package/src/chat/ui/full-diff.ts +1 -0
- package/src/chat/ui/highlighter.ts +29 -15
- package/src/chat/ui/icons.tsx +2 -4
- package/src/chat/ui/index.ts +7 -0
- package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +25 -0
- package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +1 -0
- package/src/chat/ui/intelligent-ui/UiReply.tsx +63 -0
- package/src/chat/ui/intelligent-ui/block-local.ts +5 -0
- package/src/chat/ui/intelligent-ui/chart-data.ts +15 -0
- package/src/chat/ui/intelligent-ui/node-text.ts +17 -0
- package/src/chat/ui/intelligent-ui/renderers/binding.ts +1 -0
- package/src/chat/ui/intelligent-ui/renderers/chart.tsx +31 -0
- package/src/chat/ui/intelligent-ui/renderers/choices.tsx +4 -0
- package/src/chat/ui/intelligent-ui/renderers/content.tsx +18 -0
- package/src/chat/ui/intelligent-ui/renderers/data.tsx +11 -0
- package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +13 -0
- package/src/chat/ui/intelligent-ui/renderers/links.tsx +9 -0
- package/src/chat/ui/intelligent-ui/renderers/map-slots.ts +15 -0
- package/src/chat/ui/intelligent-ui/renderers/map-style.ts +15 -0
- package/src/chat/ui/intelligent-ui/renderers/map-view.tsx +48 -0
- package/src/chat/ui/intelligent-ui/renderers/map.tsx +20 -0
- package/src/chat/ui/intelligent-ui/renderers/place-point.ts +3 -0
- package/src/chat/ui/intelligent-ui/renderers/structure.tsx +3 -0
- package/src/chat/ui/intelligent-ui/renderers/text.tsx +8 -0
- package/src/chat/ui/intelligent-ui/reply-parts.ts +5 -0
- package/src/chat/ui/intelligent-ui/table-data.ts +17 -0
- package/src/chat/ui/intelligent-ui/use-in-view.ts +4 -0
- package/src/chat/ui/intelligent-ui/use-later.ts +4 -0
- package/src/chat/ui/intelligent-ui/use-truncated.ts +5 -0
- package/src/chat/ui/intelligent-ui/use-ui-links.ts +29 -0
- package/src/chat/ui/intelligent-ui/use-ui-queries.ts +60 -0
- package/src/chat/ui/markdown-blocks.ts +64 -0
- package/src/chat/ui/mermaid.ts +4 -0
- package/src/chat/ui/quote-selection.ts +10 -0
- package/src/chat/ui/rehype-chips.ts +5 -0
- package/src/chat/ui/rehype-fade.ts +2 -0
- package/src/chat/ui/remark-html-as-text.ts +5 -0
- package/src/chat/ui/reveal.ts +18 -0
- package/src/chat/ui/rows/ForksRow.tsx +5 -0
- package/src/chat/ui/rows/GeneratedImageRow.tsx +10 -0
- package/src/chat/ui/rows/MessageRows.tsx +29 -5
- package/src/chat/ui/rows/RowContainer.tsx +1 -0
- package/src/chat/ui/rows/Rows.tsx +2 -0
- package/src/chat/ui/rows/SubagentRow.tsx +13 -0
- package/src/chat/ui/rows/TaskRow.tsx +7 -0
- package/src/chat/ui/rows/VisualRow.tsx +4 -0
- package/src/chat/ui/rows/WorkRows.tsx +46 -0
- package/src/chat/ui/rows/WorkflowRow.tsx +10 -0
- package/src/chat/ui/rows/row-rhythm.ts +7 -5
- package/src/chat/ui/theme-watch.ts +8 -7
- package/src/chat/ui/thread-references.ts +7 -0
- package/src/chat/ui/use-diff-source-tokens.ts +8 -0
- package/src/chat/ui/useToggleSet.ts +3 -0
- package/src/chat/ui/visual-theme.ts +30 -0
- package/src/chat/visual-bridge.ts +23 -0
- package/src/chat/visual-viewport.ts +12 -0
- package/src/host-hooks.ts +15 -5
- package/src/host.ts +29 -1
- package/src/index.ts +5 -0
- package/src/lazy.tsx +9 -0
- package/src/locales/en/agent-chat.ts +1 -0
- package/src/locales/nl/agent-chat.ts +1 -0
- package/src/mounted-registry.ts +4 -0
- package/src/port-transport.ts +32 -0
- package/src/prompts/index.ts +2 -0
- package/src/prompts/logic/focus.ts +5 -0
- package/src/prompts/logic/keys.ts +12 -0
- package/src/prompts/logic/prompts.ts +8 -0
- package/src/prompts/logic/subjects.ts +8 -0
- package/src/prompts/logic/useFocusAfterAnswer.ts +2 -0
- package/src/prompts/logic/usePromptSession.ts +13 -1
- package/src/prompts/ui/ApprovalBody.tsx +2 -0
- package/src/prompts/ui/PromptActions.tsx +2 -0
- package/src/prompts/ui/PromptCard.tsx +6 -0
- package/src/prompts/ui/PromptView.tsx +6 -0
- package/src/prompts/ui/QuestionBody.tsx +11 -0
- package/src/providers/AccountColors.tsx +1 -0
- package/src/providers/AccountDetail.tsx +7 -0
- package/src/providers/AccountVariables.tsx +2 -0
- package/src/providers/AddAccountForm.tsx +5 -1
- package/src/providers/CliDetail.tsx +6 -5
- package/src/providers/InstallPage.tsx +11 -0
- package/src/providers/ProviderList.tsx +3 -0
- package/src/providers/ProvidersPane.tsx +18 -0
- package/src/providers/account-actions.ts +10 -8
- package/src/providers/index.ts +1 -0
- package/src/providers/install-steps.ts +3 -0
- package/src/providers/pane-target.ts +5 -0
- package/src/providers/provider-abilities.ts +1 -0
- package/src/providers/provider-groups.ts +6 -0
- package/src/providers/variables.ts +12 -0
- package/src/scope.ts +2 -0
- package/src/state/chats.ts +61 -14
- package/src/state/index.ts +2 -0
- package/src/state/provider-accounts.ts +7 -9
- package/src/state/providers.ts +1 -0
- package/src/state/usage.ts +6 -0
- package/src/storage.ts +18 -0
- package/src/testing/prompts.ts +5 -0
- package/src/theme.css +3 -0
- package/src/transport.ts +18 -0
- package/src/usage/ChartLogo.tsx +12 -0
- package/src/usage/LimitsList.tsx +7 -8
- package/src/usage/UsageBreakdown.tsx +11 -4
- package/src/usage/UsageChart.tsx +5 -0
- package/src/usage/UsageLimits.tsx +10 -7
- package/src/usage/UsageLimitsCard.tsx +2 -0
- package/src/usage/UsagePage.tsx +16 -19
- package/src/usage/UsageSummary.tsx +4 -5
- package/src/usage/UsageTiles.tsx +2 -0
- package/src/usage/format.ts +7 -0
- package/src/usage/limit-groups.ts +14 -0
- package/src/usage/limits.ts +11 -13
- package/src/usage/money.ts +1 -0
- package/src/usage/summary.ts +15 -7
|
@@ -20,6 +20,7 @@ export function TabsRenderer({ node, children, context }: UiRendererProps<UiProp
|
|
|
20
20
|
const tabs = uiChildrenOf(node, 'Tab');
|
|
21
21
|
const [picked, setPicked] = useBlockLocal<string | null>(context, node.id, () => null);
|
|
22
22
|
const value = picked !== null && tabs.some((tab) => tab.id === picked) ? picked : (tabs[0]?.id ?? null);
|
|
23
|
+
|
|
23
24
|
return (
|
|
24
25
|
<Tabs.Root value={value} onValueChange={(next) => setPicked(String(next))} className="flex flex-col gap-3">
|
|
25
26
|
<Tabs.List variant="pill" overflow="scroll" className="w-fit max-w-full rounded-xl bg-ui-tile p-0.75">
|
|
@@ -44,6 +45,7 @@ export function TabRenderer({ node, children }: UiRendererProps<UiProps<'Tab'>>)
|
|
|
44
45
|
|
|
45
46
|
export function SectionsRenderer({ node, children, context }: UiRendererProps<UiProps<'Sections'>>) {
|
|
46
47
|
const [touched, setTouched] = useBlockLocal(context, node.id, () => false);
|
|
48
|
+
|
|
47
49
|
return (
|
|
48
50
|
<SectionsContext value={{ touched, touch: () => setTouched(true) }}>
|
|
49
51
|
<div className="chat-ui-nodes flex flex-col gap-1.5">{children}</div>
|
|
@@ -56,6 +58,7 @@ export function SectionRenderer({ node, children, context }: UiRendererProps<UiP
|
|
|
56
58
|
const fold = useContext(SectionsContext);
|
|
57
59
|
const [open, setOpen] = useBlockLocal(context, node.id, () => !fold.touched);
|
|
58
60
|
const contentId = useId();
|
|
61
|
+
|
|
59
62
|
return (
|
|
60
63
|
<section className="rounded-xl bg-ui-tile">
|
|
61
64
|
<button
|
|
@@ -49,6 +49,7 @@ export function SummaryRenderer({ node, children }: UiRendererProps<UiProps<'Sum
|
|
|
49
49
|
const head = headId === undefined || headId === node.id;
|
|
50
50
|
const { tone, badge } = node.props;
|
|
51
51
|
const shown = tone ?? 'neutral';
|
|
52
|
+
|
|
52
53
|
return (
|
|
53
54
|
<div className={clsx(ICON_ROW, tone === undefined ? 'bg-ui-tile' : UI_TONE_SURFACE[tone])}>
|
|
54
55
|
{head && (
|
|
@@ -69,6 +70,7 @@ export function SummaryRenderer({ node, children }: UiRendererProps<UiProps<'Sum
|
|
|
69
70
|
/* A quiet tile whatever its tone: only the square of its icon wears the tone. */
|
|
70
71
|
export function CalloutRenderer({ node, children }: UiRendererProps<UiProps<'Callout'>>) {
|
|
71
72
|
const { tone, title } = node.props;
|
|
73
|
+
|
|
72
74
|
return (
|
|
73
75
|
<div className={clsx('chat-ui-flat bg-ui-tile text-xs', ICON_ROW)}>
|
|
74
76
|
<span aria-hidden className={clsx(ICON_SQUARE, UI_TONE_SURFACE[tone], UI_TONE_TEXT[tone])}>
|
|
@@ -84,6 +86,7 @@ export function CalloutRenderer({ node, children }: UiRendererProps<UiProps<'Cal
|
|
|
84
86
|
|
|
85
87
|
export function TagRenderer({ node, children }: UiRendererProps<UiProps<'Tag'>>) {
|
|
86
88
|
const { tone } = node.props;
|
|
89
|
+
|
|
87
90
|
return (
|
|
88
91
|
<Pill shape="tag" size="sm" tone={tone === undefined ? 'muted' : UI_TONE_PILL[tone]}>
|
|
89
92
|
{children}
|
|
@@ -96,9 +99,12 @@ export function ProgressRenderer({ node, children, context }: UiRendererProps<Ui
|
|
|
96
99
|
const { t } = useTranslation('agent-chat');
|
|
97
100
|
const { value, max } = node.props;
|
|
98
101
|
const limit = max ?? 100;
|
|
102
|
+
|
|
99
103
|
const reading =
|
|
100
104
|
value === undefined ? null : limit === 100 ? formatPercent(value) : t('blocks.progress.of', { value: formatNumber(value), max: formatNumber(limit) });
|
|
105
|
+
|
|
101
106
|
const label = uiNodeLabel(node);
|
|
107
|
+
|
|
102
108
|
return (
|
|
103
109
|
<div className="chat-ui-flat flex flex-col gap-2 rounded-xl bg-ui-tile p-3">
|
|
104
110
|
<div className="flex items-baseline justify-between gap-3 text-xs">
|
|
@@ -123,6 +129,7 @@ export function StepRenderer({ node, children, context }: UiRendererProps<UiProp
|
|
|
123
129
|
const { t } = useTranslation('agent-chat');
|
|
124
130
|
const { state, detail } = node.props;
|
|
125
131
|
const quiet = state === 'pending' || state === 'skipped';
|
|
132
|
+
|
|
126
133
|
return (
|
|
127
134
|
<li className="flex min-h-10 items-start gap-3 rounded-xl bg-ui-tile px-3 py-2.5 text-xs">
|
|
128
135
|
<span className={clsx('mt-0.5 grid size-4 shrink-0 place-items-center', STEP_TONES[state])}>
|
|
@@ -162,6 +169,7 @@ export function EventRenderer({ node, children }: UiRendererProps<UiProps<'Event
|
|
|
162
169
|
const label = [time, uiNodeLabel(node), tone === 'neutral' ? '' : t(`blocks.event.tone.${tone}`), detail ?? ''].filter((part) => part !== '').join(', ');
|
|
163
170
|
const described = detail !== undefined && detail !== '';
|
|
164
171
|
const tagged = described && detail.length <= DETAIL_AS_TAG;
|
|
172
|
+
|
|
165
173
|
return (
|
|
166
174
|
<li aria-label={label} className="flex min-h-9 items-start gap-3 py-2 text-xs">
|
|
167
175
|
{/* A time nobody could read stays as written and is no `dateTime` for a machine to read. */}
|
|
@@ -5,18 +5,23 @@ export type UiReplyPart = { kind: 'text'; text: string } | { kind: 'ui'; block:
|
|
|
5
5
|
export function uiReplyParts(text: string, blocks: readonly UiBlock[], streaming = false): UiReplyPart[] {
|
|
6
6
|
const parts: UiReplyPart[] = [];
|
|
7
7
|
let position = 0;
|
|
8
|
+
|
|
8
9
|
for (const block of [...blocks].sort((left, right) => left.start - right.start)) {
|
|
9
10
|
if (!Number.isInteger(block.start) || !Number.isInteger(block.end) || block.start < position || block.end < block.start || block.end > text.length) {
|
|
10
11
|
continue;
|
|
11
12
|
}
|
|
13
|
+
|
|
12
14
|
if (block.start > position) {
|
|
13
15
|
parts.push({ kind: 'text', text: text.slice(position, block.start) });
|
|
14
16
|
}
|
|
17
|
+
|
|
15
18
|
parts.push({ kind: 'ui', block });
|
|
16
19
|
position = streaming && !block.complete ? text.length : block.end;
|
|
17
20
|
}
|
|
21
|
+
|
|
18
22
|
if (position < text.length) {
|
|
19
23
|
parts.push({ kind: 'text', text: text.slice(position) });
|
|
20
24
|
}
|
|
25
|
+
|
|
21
26
|
return parts;
|
|
22
27
|
}
|
|
@@ -21,6 +21,7 @@ export type UiTableCell =
|
|
|
21
21
|
|
|
22
22
|
/* Rows shown before a person asks for all of them. */
|
|
23
23
|
export const UI_TABLE_ROWS = 50;
|
|
24
|
+
|
|
24
25
|
/* The furthest a JavaScript Date reaches from the epoch either way, in milliseconds. */
|
|
25
26
|
const DATE_RANGE = 8.64e15;
|
|
26
27
|
const NUMERIC_KINDS: ReadonlySet<UiColumnKind> = new Set(['number', 'bytes', 'duration']);
|
|
@@ -36,19 +37,27 @@ export function isNumericColumn(column: UiTableColumn): boolean {
|
|
|
36
37
|
*/
|
|
37
38
|
export function uiTableColumns(node: UiViewNode<UiProps<'Table'>>): UiTableColumn[] {
|
|
38
39
|
const rows = node.props.rows;
|
|
40
|
+
|
|
39
41
|
const present = (key: string): boolean => rows.some((row) => Object.hasOwn(row, key));
|
|
42
|
+
|
|
40
43
|
const declared = uiChildrenOf(node, 'Column');
|
|
44
|
+
|
|
41
45
|
if (declared.length > 0) {
|
|
42
46
|
const seen = new Set<string>();
|
|
47
|
+
|
|
43
48
|
return declared.flatMap(({ props }) => {
|
|
44
49
|
if (seen.has(props.key) || (rows.length > 0 && !present(props.key))) {
|
|
45
50
|
return [];
|
|
46
51
|
}
|
|
52
|
+
|
|
47
53
|
seen.add(props.key);
|
|
54
|
+
|
|
48
55
|
return [{ key: props.key, title: props.title ?? props.key, unit: props.unit, as: props.as ?? 'text' }];
|
|
49
56
|
});
|
|
50
57
|
}
|
|
58
|
+
|
|
51
59
|
const keys: string[] = [];
|
|
60
|
+
|
|
52
61
|
for (const row of rows) {
|
|
53
62
|
for (const key of Object.keys(row)) {
|
|
54
63
|
if (!keys.includes(key)) {
|
|
@@ -56,9 +65,11 @@ export function uiTableColumns(node: UiViewNode<UiProps<'Table'>>): UiTableColum
|
|
|
56
65
|
}
|
|
57
66
|
}
|
|
58
67
|
}
|
|
68
|
+
|
|
59
69
|
return keys.map((key) => {
|
|
60
70
|
const values = rows.map((row) => row[key]).filter((value) => value !== undefined && value !== null);
|
|
61
71
|
const numeric = values.length > 0 && values.every((value) => typeof value === 'number' && Number.isFinite(value));
|
|
72
|
+
|
|
62
73
|
return { key, title: key, as: numeric ? 'number' : 'text' };
|
|
63
74
|
});
|
|
64
75
|
}
|
|
@@ -71,10 +82,13 @@ function numberOf(value: unknown): number | null {
|
|
|
71
82
|
if (typeof value === 'number') {
|
|
72
83
|
return Number.isFinite(value) ? value : null;
|
|
73
84
|
}
|
|
85
|
+
|
|
74
86
|
if (typeof value === 'string' && value.trim() !== '') {
|
|
75
87
|
const parsed = Number(value);
|
|
88
|
+
|
|
76
89
|
return Number.isFinite(parsed) ? parsed : null;
|
|
77
90
|
}
|
|
91
|
+
|
|
78
92
|
return null;
|
|
79
93
|
}
|
|
80
94
|
|
|
@@ -83,15 +97,18 @@ export function uiTableCell(value: unknown, column: UiTableColumn): UiTableCell
|
|
|
83
97
|
if (value === undefined || value === null || value === '') {
|
|
84
98
|
return { kind: 'empty' };
|
|
85
99
|
}
|
|
100
|
+
|
|
86
101
|
switch (column.as) {
|
|
87
102
|
case 'number':
|
|
88
103
|
case 'bytes':
|
|
89
104
|
case 'duration': {
|
|
90
105
|
const number = numberOf(value);
|
|
106
|
+
|
|
91
107
|
return number === null ? { kind: 'text', text: textOf(value) } : { kind: 'number', as: column.as, value: number };
|
|
92
108
|
}
|
|
93
109
|
case 'date': {
|
|
94
110
|
const at = typeof value === 'number' ? value : typeof value === 'string' ? Date.parse(value) : Number.NaN;
|
|
111
|
+
|
|
95
112
|
return Number.isFinite(at) && Math.abs(at) <= DATE_RANGE ? { kind: 'date', at } : { kind: 'text', text: textOf(value) };
|
|
96
113
|
}
|
|
97
114
|
case 'file':
|
|
@@ -6,13 +6,17 @@ import { useEffect, useState } from 'react';
|
|
|
6
6
|
*/
|
|
7
7
|
export function useInView(element: Element | null, margin = '200px'): boolean {
|
|
8
8
|
const [inView, setInView] = useState(typeof IntersectionObserver === 'undefined');
|
|
9
|
+
|
|
9
10
|
useEffect(() => {
|
|
10
11
|
if (element === null || typeof IntersectionObserver === 'undefined') {
|
|
11
12
|
return;
|
|
12
13
|
}
|
|
14
|
+
|
|
13
15
|
const observer = new IntersectionObserver((entries) => setInView(entries.at(-1)?.isIntersecting ?? false), { rootMargin: margin });
|
|
14
16
|
observer.observe(element);
|
|
17
|
+
|
|
15
18
|
return () => observer.disconnect();
|
|
16
19
|
}, [element, margin]);
|
|
20
|
+
|
|
17
21
|
return inView;
|
|
18
22
|
}
|
|
@@ -3,15 +3,19 @@ import { useEffect, useState } from 'react';
|
|
|
3
3
|
/* True once `on` has held for `ms`, for a spinner that only shows when waiting takes long. */
|
|
4
4
|
export function useLater(on: boolean, ms: number): boolean {
|
|
5
5
|
const [late, setLate] = useState(false);
|
|
6
|
+
|
|
6
7
|
useEffect(() => {
|
|
7
8
|
if (!on) {
|
|
8
9
|
return;
|
|
9
10
|
}
|
|
11
|
+
|
|
10
12
|
const timer = setTimeout(() => setLate(true), ms);
|
|
13
|
+
|
|
11
14
|
return () => {
|
|
12
15
|
clearTimeout(timer);
|
|
13
16
|
setLate(false);
|
|
14
17
|
};
|
|
15
18
|
}, [on, ms]);
|
|
19
|
+
|
|
16
20
|
return on && late;
|
|
17
21
|
}
|
|
@@ -11,20 +11,25 @@ function cut(element: HTMLElement): boolean {
|
|
|
11
11
|
*/
|
|
12
12
|
export function useTruncated<T extends HTMLElement>(within = false): [(element: T | null) => (() => void) | undefined, boolean] {
|
|
13
13
|
const [truncated, setTruncated] = useState(false);
|
|
14
|
+
|
|
14
15
|
const measure = (element: T | null) => {
|
|
15
16
|
if (element === null || typeof ResizeObserver === 'undefined') {
|
|
16
17
|
return undefined;
|
|
17
18
|
}
|
|
19
|
+
|
|
18
20
|
const check = () => setTruncated(within ? [...element.querySelectorAll<HTMLElement>('.truncate')].some(cut) : cut(element));
|
|
21
|
+
|
|
19
22
|
check();
|
|
20
23
|
const resize = new ResizeObserver(check);
|
|
21
24
|
resize.observe(element);
|
|
22
25
|
const text = new MutationObserver(check);
|
|
23
26
|
text.observe(element, { characterData: true, childList: true, subtree: true });
|
|
27
|
+
|
|
24
28
|
return () => {
|
|
25
29
|
resize.disconnect();
|
|
26
30
|
text.disconnect();
|
|
27
31
|
};
|
|
28
32
|
};
|
|
33
|
+
|
|
29
34
|
return [measure, truncated];
|
|
30
35
|
}
|
|
@@ -26,39 +26,49 @@ export function useUiLinks(
|
|
|
26
26
|
const host = useChatHost().intelligentUi;
|
|
27
27
|
const values = uiInputValues(block, state);
|
|
28
28
|
const scope = state.scope();
|
|
29
|
+
|
|
29
30
|
const queryValues = Object.fromEntries(
|
|
30
31
|
Object.keys(block.queries)
|
|
31
32
|
.filter((name) => Object.hasOwn(scope, name))
|
|
32
33
|
.map((name) => [name, scope[name]])
|
|
33
34
|
);
|
|
35
|
+
|
|
34
36
|
let targets: Record<string, UiLinkTarget> = {};
|
|
37
|
+
|
|
35
38
|
try {
|
|
36
39
|
if (block.complete) {
|
|
37
40
|
targets = uiLinkTargets(block, values, queryValues);
|
|
38
41
|
}
|
|
39
42
|
} catch {}
|
|
43
|
+
|
|
40
44
|
const signature = JSON.stringify(targets);
|
|
41
45
|
const input = JSON.stringify(values);
|
|
42
46
|
const tickets = JSON.stringify(reads);
|
|
43
47
|
const defaultInput = JSON.stringify(uiInputValues(block, new UiState(block)));
|
|
44
48
|
const link = host?.link;
|
|
49
|
+
|
|
45
50
|
useEffect(() => {
|
|
46
51
|
asked.current.clear();
|
|
47
52
|
}, [block.id, block.revision, context.scopeId, context.chatId, context.itemId, link]);
|
|
53
|
+
|
|
48
54
|
useEffect(() => {
|
|
49
55
|
if (!link || !block.revision || context.phase !== 'final') {
|
|
50
56
|
return;
|
|
51
57
|
}
|
|
58
|
+
|
|
52
59
|
let stopped = false;
|
|
60
|
+
|
|
53
61
|
const validate = async () => {
|
|
54
62
|
const moved = Object.entries(JSON.parse(signature) as Record<string, UiLinkTarget>)
|
|
55
63
|
.slice(0, UI_HOST_LIMITS.links)
|
|
56
64
|
.filter(([nodeId, target]) => asked.current.get(nodeId) !== JSON.stringify(target));
|
|
65
|
+
|
|
57
66
|
for (const [nodeId, target] of moved) {
|
|
58
67
|
const record = (reading: ChatUiLinkReading) => {
|
|
59
68
|
asked.current.set(nodeId, JSON.stringify(target));
|
|
60
69
|
setChecked((previous) => ({ ...previous, [nodeId]: { target: JSON.stringify(target), reading } }));
|
|
61
70
|
};
|
|
71
|
+
|
|
62
72
|
try {
|
|
63
73
|
const reading = await link(context.scopeId, {
|
|
64
74
|
chatId: context.chatId,
|
|
@@ -69,51 +79,66 @@ export function useUiLinks(
|
|
|
69
79
|
values: JSON.parse(input),
|
|
70
80
|
reads: JSON.parse(tickets)
|
|
71
81
|
});
|
|
82
|
+
|
|
72
83
|
if (stopped) {
|
|
73
84
|
return;
|
|
74
85
|
}
|
|
86
|
+
|
|
75
87
|
record(reading);
|
|
76
88
|
} catch {
|
|
77
89
|
if (stopped) {
|
|
78
90
|
return;
|
|
79
91
|
}
|
|
92
|
+
|
|
80
93
|
record({ state: 'plain' });
|
|
81
94
|
}
|
|
82
95
|
}
|
|
83
96
|
};
|
|
97
|
+
|
|
84
98
|
// The first check runs at once; a later one waits until the inputs rested.
|
|
85
99
|
const timer = setTimeout(() => void validate(), asked.current.size === 0 ? 0 : LINK_SETTLE_MS);
|
|
100
|
+
|
|
86
101
|
return () => {
|
|
87
102
|
stopped = true;
|
|
88
103
|
clearTimeout(timer);
|
|
89
104
|
};
|
|
90
105
|
}, [block.id, block.revision, context.scopeId, context.chatId, context.itemId, context.phase, signature, input, tickets, link]);
|
|
106
|
+
|
|
91
107
|
const idOf = (target: UiLinkTarget) => Object.entries(targets).find(([, entry]) => JSON.stringify(entry) === JSON.stringify(target))?.[0];
|
|
108
|
+
|
|
92
109
|
const resolution = (target: UiLinkTarget): ChatUiLinkReading => {
|
|
93
110
|
const id = idOf(target);
|
|
111
|
+
|
|
94
112
|
if (id === undefined) {
|
|
95
113
|
return { state: 'plain' };
|
|
96
114
|
}
|
|
115
|
+
|
|
97
116
|
if (checked[id]?.target === JSON.stringify(target)) {
|
|
98
117
|
return checked[id].reading;
|
|
99
118
|
}
|
|
119
|
+
|
|
100
120
|
if (input === defaultInput && frozen && frozen.revision === block.revision) {
|
|
101
121
|
const firstQueries = Object.fromEntries(
|
|
102
122
|
Object.entries(frozen.readings)
|
|
103
123
|
.filter(([, reading]) => reading.state === 'fresh')
|
|
104
124
|
.map(([name, reading]) => [name, reading.value])
|
|
105
125
|
);
|
|
126
|
+
|
|
106
127
|
if (JSON.stringify(queryValues) === JSON.stringify(firstQueries)) {
|
|
107
128
|
return frozen.links?.[id] ?? { state: 'plain' };
|
|
108
129
|
}
|
|
109
130
|
}
|
|
131
|
+
|
|
110
132
|
return { state: 'plain' };
|
|
111
133
|
};
|
|
134
|
+
|
|
112
135
|
const open = (target: UiLinkTarget) => {
|
|
113
136
|
const nodeId = idOf(target);
|
|
137
|
+
|
|
114
138
|
if (!nodeId || !link || !block.revision) {
|
|
115
139
|
return;
|
|
116
140
|
}
|
|
141
|
+
|
|
117
142
|
void link(context.scopeId, {
|
|
118
143
|
chatId: context.chatId,
|
|
119
144
|
itemId: context.itemId,
|
|
@@ -125,6 +150,7 @@ export function useUiLinks(
|
|
|
125
150
|
})
|
|
126
151
|
.then((reading) => {
|
|
127
152
|
setChecked((previous) => ({ ...previous, [nodeId]: { target: JSON.stringify(target), reading } }));
|
|
153
|
+
|
|
128
154
|
if (reading.state === 'chip' && reading.target) {
|
|
129
155
|
host?.openLink?.(context.scopeId, reading);
|
|
130
156
|
}
|
|
@@ -133,8 +159,11 @@ export function useUiLinks(
|
|
|
133
159
|
setChecked((previous) => ({ ...previous, [nodeId]: { target: JSON.stringify(target), reading: { state: 'plain' } } }));
|
|
134
160
|
});
|
|
135
161
|
};
|
|
162
|
+
|
|
136
163
|
const hostOpenUrl = host?.openUrl;
|
|
164
|
+
|
|
137
165
|
const openUrl = (url: string) => hostOpenUrl?.(context.scopeId, url);
|
|
166
|
+
|
|
138
167
|
return {
|
|
139
168
|
link: link ? resolution : undefined,
|
|
140
169
|
openLink: link && host?.openLink ? open : undefined,
|
|
@@ -25,6 +25,7 @@ function successfulReading(
|
|
|
25
25
|
if (local?.identity === identity && local.successful?.inputs === signature) {
|
|
26
26
|
return local.successful.reading;
|
|
27
27
|
}
|
|
28
|
+
|
|
28
29
|
return signature === defaultInputs && initial?.state === 'fresh' ? initial : undefined;
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -54,34 +55,45 @@ export function useUiQueries(
|
|
|
54
55
|
const reading = busyInput === inputKey;
|
|
55
56
|
const defaultInputs = useMemo(() => JSON.stringify(uiInputValues(block, new UiState(block))), [block]);
|
|
56
57
|
const initial = frozen && frozen.revision === block.revision ? frozen.readings : NO_READINGS;
|
|
58
|
+
|
|
57
59
|
const successfulFor = (name: string, signature: string): ChatUiQueryReading | undefined =>
|
|
58
60
|
successfulReading(readings[name], initial[name], identity, signature, defaultInputs);
|
|
61
|
+
|
|
59
62
|
const readsFor = (values: Readonly<Record<string, unknown>>): Record<string, string> | null => {
|
|
60
63
|
const signature = JSON.stringify(values);
|
|
61
64
|
const reads: Record<string, string> = {};
|
|
65
|
+
|
|
62
66
|
for (const name of names) {
|
|
63
67
|
const local = readings[name];
|
|
64
68
|
const successful = successfulFor(name, signature);
|
|
69
|
+
|
|
65
70
|
if (!successful?.readId || (local?.identity === identity && local.reading.state === 'refused')) {
|
|
66
71
|
return null;
|
|
67
72
|
}
|
|
73
|
+
|
|
68
74
|
reads[name] = successful.readId;
|
|
69
75
|
}
|
|
76
|
+
|
|
70
77
|
return reads;
|
|
71
78
|
};
|
|
79
|
+
|
|
72
80
|
const reads = readsFor(JSON.parse(inputs));
|
|
73
81
|
const ready = reads !== null;
|
|
74
82
|
const query = host.intelligentUi?.query;
|
|
75
83
|
const subscribe = host.intelligentUi?.subscribe;
|
|
84
|
+
|
|
76
85
|
useEffect(() => {
|
|
77
86
|
const scope = state.scope();
|
|
87
|
+
|
|
78
88
|
for (const name of Object.keys(block.queries)) {
|
|
79
89
|
const successful = successfulReading(readings[name], initial[name], identity, inputs, defaultInputs);
|
|
90
|
+
|
|
80
91
|
if (successful && JSON.stringify(scope[name]) !== JSON.stringify(successful.value)) {
|
|
81
92
|
state.setQuery(name, successful.value, block);
|
|
82
93
|
}
|
|
83
94
|
}
|
|
84
95
|
}, [state, block, readings, identity, inputs, defaultInputs, initial]);
|
|
96
|
+
|
|
85
97
|
useEffect(() => {
|
|
86
98
|
if (
|
|
87
99
|
!query ||
|
|
@@ -94,6 +106,7 @@ export function useUiQueries(
|
|
|
94
106
|
) {
|
|
95
107
|
return;
|
|
96
108
|
}
|
|
109
|
+
|
|
97
110
|
let stopped = false;
|
|
98
111
|
let visible = typeof IntersectionObserver === 'undefined';
|
|
99
112
|
let busy = false;
|
|
@@ -102,33 +115,41 @@ export function useUiQueries(
|
|
|
102
115
|
// The first read of this block reads at once; a changed input waits until it rests.
|
|
103
116
|
const changed = lastRead.current.inputKey !== '' && lastRead.current.inputKey !== inputKey;
|
|
104
117
|
const settledAt = changed ? settleDeadline() : 0;
|
|
118
|
+
|
|
105
119
|
const refresh = async () => {
|
|
106
120
|
if (stopped || !visible || !documentVisible() || busy) {
|
|
107
121
|
return;
|
|
108
122
|
}
|
|
123
|
+
|
|
109
124
|
const wait = Math.max(
|
|
110
125
|
settledAt - Date.now(),
|
|
111
126
|
lastRead.current.inputKey === inputKey ? UI_HOST_LIMITS.refreshMilliseconds - (Date.now() - lastRead.current.at) : 0
|
|
112
127
|
);
|
|
128
|
+
|
|
113
129
|
if (wait > 0) {
|
|
114
130
|
if (timer === null) {
|
|
115
131
|
if (!visible || !documentVisible()) {
|
|
116
132
|
return;
|
|
117
133
|
}
|
|
134
|
+
|
|
118
135
|
timer = setTimeout(() => {
|
|
119
136
|
timer = null;
|
|
120
137
|
void refresh();
|
|
121
138
|
}, wait);
|
|
122
139
|
}
|
|
140
|
+
|
|
123
141
|
return;
|
|
124
142
|
}
|
|
143
|
+
|
|
125
144
|
busy = true;
|
|
126
145
|
lastRead.current = { inputKey, at: Date.now() };
|
|
127
146
|
setBusyInput(inputKey);
|
|
147
|
+
|
|
128
148
|
for (const name of Object.keys(block.queries).slice(0, UI_HOST_LIMITS.queries)) {
|
|
129
149
|
if (!visible || !documentVisible()) {
|
|
130
150
|
break;
|
|
131
151
|
}
|
|
152
|
+
|
|
132
153
|
try {
|
|
133
154
|
const reading = await query(context.scopeId, {
|
|
134
155
|
chatId: context.chatId,
|
|
@@ -138,12 +159,15 @@ export function useUiQueries(
|
|
|
138
159
|
query: name,
|
|
139
160
|
values: JSON.parse(inputs)
|
|
140
161
|
});
|
|
162
|
+
|
|
141
163
|
if (stopped) {
|
|
142
164
|
return;
|
|
143
165
|
}
|
|
166
|
+
|
|
144
167
|
if (reading.state === 'fresh') {
|
|
145
168
|
state.setQuery(name, reading.value, block);
|
|
146
169
|
}
|
|
170
|
+
|
|
147
171
|
setReadings((previous) => ({
|
|
148
172
|
...previous,
|
|
149
173
|
[name]: {
|
|
@@ -157,6 +181,7 @@ export function useUiQueries(
|
|
|
157
181
|
if (stopped) {
|
|
158
182
|
return;
|
|
159
183
|
}
|
|
184
|
+
|
|
160
185
|
setReadings((previous) => ({
|
|
161
186
|
...previous,
|
|
162
187
|
[name]: {
|
|
@@ -167,21 +192,27 @@ export function useUiQueries(
|
|
|
167
192
|
}));
|
|
168
193
|
}
|
|
169
194
|
}
|
|
195
|
+
|
|
170
196
|
busy = false;
|
|
197
|
+
|
|
171
198
|
if (stopped) {
|
|
172
199
|
return;
|
|
173
200
|
}
|
|
201
|
+
|
|
174
202
|
setBusyInput(null);
|
|
203
|
+
|
|
175
204
|
timer = setTimeout(() => {
|
|
176
205
|
timer = null;
|
|
177
206
|
void refresh();
|
|
178
207
|
}, UI_HOST_LIMITS.refreshMilliseconds);
|
|
179
208
|
};
|
|
209
|
+
|
|
180
210
|
const observer =
|
|
181
211
|
typeof IntersectionObserver === 'undefined'
|
|
182
212
|
? null
|
|
183
213
|
: new IntersectionObserver((entries) => {
|
|
184
214
|
visible = entries.some((entry) => entry.isIntersecting);
|
|
215
|
+
|
|
185
216
|
if (visible) {
|
|
186
217
|
void refresh();
|
|
187
218
|
} else if (timer !== null) {
|
|
@@ -189,15 +220,19 @@ export function useUiQueries(
|
|
|
189
220
|
timer = null;
|
|
190
221
|
}
|
|
191
222
|
});
|
|
223
|
+
|
|
192
224
|
if (element) {
|
|
193
225
|
observer?.observe(element);
|
|
194
226
|
}
|
|
227
|
+
|
|
195
228
|
const focus = () => {
|
|
196
229
|
void refresh();
|
|
197
230
|
};
|
|
231
|
+
|
|
198
232
|
document.addEventListener('visibilitychange', focus);
|
|
199
233
|
const unsubscribe = subscribe?.(context.scopeId, context.chatId, focus);
|
|
200
234
|
void refresh();
|
|
235
|
+
|
|
201
236
|
return () => {
|
|
202
237
|
stopped = true;
|
|
203
238
|
// A read cut short by this cleanup never reaches its own reset, which would leave the choices closed.
|
|
@@ -205,19 +240,24 @@ export function useUiQueries(
|
|
|
205
240
|
observer?.disconnect();
|
|
206
241
|
unsubscribe?.();
|
|
207
242
|
document.removeEventListener('visibilitychange', focus);
|
|
243
|
+
|
|
208
244
|
if (timer !== null) {
|
|
209
245
|
clearTimeout(timer);
|
|
210
246
|
}
|
|
211
247
|
};
|
|
212
248
|
}, [block, state, context.scopeId, context.chatId, context.itemId, context.phase, inputs, query, subscribe, element, identity, inputKey]);
|
|
249
|
+
|
|
213
250
|
const currentReadings = Object.fromEntries(
|
|
214
251
|
names.flatMap((name) => {
|
|
215
252
|
const local = readings[name];
|
|
216
253
|
const reading = local?.identity === identity ? local.reading : initial[name];
|
|
254
|
+
|
|
217
255
|
return reading ? [[name, reading]] : [];
|
|
218
256
|
})
|
|
219
257
|
);
|
|
258
|
+
|
|
220
259
|
const failed = Object.entries(currentReadings).find(([, value]) => value.state !== 'fresh');
|
|
260
|
+
|
|
221
261
|
const live: UiLiveStatus | undefined = names.length
|
|
222
262
|
? {
|
|
223
263
|
state: reading || (!ready && !failed) ? 'reading' : (failed?.[1].state ?? 'fresh'),
|
|
@@ -230,6 +270,7 @@ export function useUiQueries(
|
|
|
230
270
|
code: failed?.[1].code
|
|
231
271
|
}
|
|
232
272
|
: undefined;
|
|
273
|
+
|
|
233
274
|
return { live, reads: reads ?? {}, reading, ready, readsFor, currentReadings };
|
|
234
275
|
}
|
|
235
276
|
|
|
@@ -241,16 +282,21 @@ export function useUiLiveValues(block: UiBlock, nodes: readonly UiViewNode[], re
|
|
|
241
282
|
previous: new Map()
|
|
242
283
|
}
|
|
243
284
|
);
|
|
285
|
+
|
|
244
286
|
const dependencies = new Map<string, Set<string>>();
|
|
245
287
|
const names = new Set(Object.keys(block.queries));
|
|
288
|
+
|
|
246
289
|
const references = (value: unknown, into: Set<string>) => {
|
|
247
290
|
if (!value || typeof value !== 'object') {
|
|
248
291
|
return;
|
|
249
292
|
}
|
|
293
|
+
|
|
250
294
|
const expression = value as Record<string, unknown>;
|
|
295
|
+
|
|
251
296
|
if (expression.kind === 'reference' && names.has(String(expression.name))) {
|
|
252
297
|
into.add(String(expression.name));
|
|
253
298
|
}
|
|
299
|
+
|
|
254
300
|
for (const child of Object.values(expression)) {
|
|
255
301
|
if (Array.isArray(child)) {
|
|
256
302
|
child.forEach((item) => references(item, into));
|
|
@@ -259,6 +305,7 @@ export function useUiLiveValues(block: UiBlock, nodes: readonly UiViewNode[], re
|
|
|
259
305
|
}
|
|
260
306
|
}
|
|
261
307
|
};
|
|
308
|
+
|
|
262
309
|
const written = (nodes: readonly UiNode[], inherited = new Set<string>()) => {
|
|
263
310
|
for (const node of nodes) {
|
|
264
311
|
const related = new Set(inherited);
|
|
@@ -267,39 +314,52 @@ export function useUiLiveValues(block: UiBlock, nodes: readonly UiViewNode[], re
|
|
|
267
314
|
written(node.children, node.type === 'Each' ? related : inherited);
|
|
268
315
|
}
|
|
269
316
|
};
|
|
317
|
+
|
|
270
318
|
written(block.nodes);
|
|
271
319
|
const values = new Map<string, Record<string, unknown>>();
|
|
272
320
|
const sources = new Map<string, Set<string>>();
|
|
321
|
+
|
|
273
322
|
const collect = (nodes: readonly UiViewNode[]) => {
|
|
274
323
|
for (const node of nodes) {
|
|
275
324
|
const related = dependencies.get(node.sourceId ?? node.id);
|
|
325
|
+
|
|
276
326
|
if (related?.size && !node.error) {
|
|
277
327
|
values.set(node.id, node.props);
|
|
278
328
|
sources.set(node.id, related);
|
|
279
329
|
}
|
|
330
|
+
|
|
280
331
|
collect(node.children);
|
|
281
332
|
}
|
|
282
333
|
};
|
|
334
|
+
|
|
283
335
|
collect(nodes);
|
|
284
336
|
const token = JSON.stringify([block.revision, Object.entries(readings).map(([name, reading]) => [name, reading.state, reading.readId, reading.readAt])]);
|
|
285
337
|
const previous = committed.token === token ? committed.previous : committed.values;
|
|
338
|
+
|
|
286
339
|
if (committed.token !== token) {
|
|
287
340
|
setCommitted({ token, values, previous });
|
|
288
341
|
}
|
|
342
|
+
|
|
289
343
|
if (!names.size) {
|
|
290
344
|
return undefined;
|
|
291
345
|
}
|
|
346
|
+
|
|
292
347
|
return (id: string, prop: string, path?: readonly [number, string]): UiLiveValue | undefined => {
|
|
293
348
|
const related = sources.get(id);
|
|
349
|
+
|
|
294
350
|
if (!related) {
|
|
295
351
|
return undefined;
|
|
296
352
|
}
|
|
353
|
+
|
|
297
354
|
const get = (props?: Record<string, unknown>): unknown => {
|
|
298
355
|
const value = props?.[prop];
|
|
356
|
+
|
|
299
357
|
return path && Array.isArray(value) ? value[path[0]]?.[path[1]] : value;
|
|
300
358
|
};
|
|
359
|
+
|
|
301
360
|
const before = get(previous.get(id));
|
|
302
361
|
const value = get(values.get(id));
|
|
362
|
+
|
|
303
363
|
return {
|
|
304
364
|
previous: before,
|
|
305
365
|
changed: before !== undefined && JSON.stringify(before) !== JSON.stringify(value),
|