@adecore/agents-react 0.39.0 → 0.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/AgentModelLabel.js +2 -1
- 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/GoalDock.js +2 -1
- package/dist/chat/ui/LimitState.js +4 -6
- 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.js +216 -253
- 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/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/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/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/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 +27 -27
- package/src/agents/AccountDot.tsx +1 -0
- package/src/agents/AgentIcon.tsx +2 -0
- package/src/agents/AgentModelLabel.tsx +4 -6
- 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 +15 -0
- 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 +26 -0
- package/src/chat/ui/PromptComposer.tsx +2 -0
- package/src/chat/ui/PromptEditor.tsx +6 -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 +119 -0
- 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 +5 -0
- 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 +39 -0
- 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 +1 -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 +2 -0
- 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 +34 -14
- 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 +4 -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 +6 -4
- package/src/host.ts +2 -0
- package/src/index.ts +3 -0
- package/src/lazy.tsx +9 -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/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/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
|
@@ -22,6 +22,7 @@ const ChecklistContext = createContext<ChecklistState>({ values: [], enabled: fa
|
|
|
22
22
|
/* The rows of a checklist as one named group. */
|
|
23
23
|
function ChecklistGroup({ pending, children }: { pending: boolean; children: ReactNode }) {
|
|
24
24
|
const { t } = useTranslation('agent-chat');
|
|
25
|
+
|
|
25
26
|
return (
|
|
26
27
|
<div role="group" aria-label={t('blocks.checklist')} className={clsx('chat-ui-nodes flex flex-col gap-1.5', pending && PENDING)}>
|
|
27
28
|
{children}
|
|
@@ -32,9 +33,11 @@ function ChecklistGroup({ pending, children }: { pending: boolean; children: Rea
|
|
|
32
33
|
export function ChecklistRenderer({ node, children, context }: UiRendererProps<UiProps<'Checklist'>>) {
|
|
33
34
|
const binding = bindingOf(node, context);
|
|
34
35
|
const values = binding?.value ?? node.props.value;
|
|
36
|
+
|
|
35
37
|
const toggle = (value: Scalar): void => {
|
|
36
38
|
binding?.onValueChange(values.includes(value) ? values.filter((entry) => entry !== value) : [...values, value]);
|
|
37
39
|
};
|
|
40
|
+
|
|
38
41
|
return (
|
|
39
42
|
<ChecklistContext value={{ values, enabled: binding !== null, toggle }}>
|
|
40
43
|
<ChecklistGroup pending={!node.complete}>{children}</ChecklistGroup>
|
|
@@ -47,11 +50,13 @@ export function ItemRenderer({ node, children }: UiRendererProps<UiProps<'Item'>
|
|
|
47
50
|
const list = useContext(ChecklistContext);
|
|
48
51
|
const { value } = node.props;
|
|
49
52
|
const checked = list.values.includes(value);
|
|
53
|
+
|
|
50
54
|
const toggleRow = (event: MouseEvent<HTMLDivElement>): void => {
|
|
51
55
|
if (list.enabled && !(event.target as Element).closest('button, a')) {
|
|
52
56
|
list.toggle(value);
|
|
53
57
|
}
|
|
54
58
|
};
|
|
59
|
+
|
|
55
60
|
return (
|
|
56
61
|
<div
|
|
57
62
|
className={clsx(
|
|
@@ -69,6 +74,7 @@ export function ItemRenderer({ node, children }: UiRendererProps<UiProps<'Item'>
|
|
|
69
74
|
|
|
70
75
|
export function SwitchRenderer({ node, children, context }: UiRendererProps<UiProps<'Switch'>>) {
|
|
71
76
|
const binding = bindingOf(node, context);
|
|
77
|
+
|
|
72
78
|
return (
|
|
73
79
|
<div
|
|
74
80
|
className={clsx('chat-ui-flat flex min-h-11 items-center justify-between gap-3 rounded-xl bg-ui-tile px-3 py-2 text-xs', !node.complete && PENDING)}
|
|
@@ -88,6 +94,7 @@ export function SwitchRenderer({ node, children, context }: UiRendererProps<UiPr
|
|
|
88
94
|
export function SliderRenderer({ node, context }: UiRendererProps<UiProps<'Slider'>>) {
|
|
89
95
|
const binding = bindingOf(node, context);
|
|
90
96
|
const { value, min, max, step, unit } = node.props;
|
|
97
|
+
|
|
91
98
|
return (
|
|
92
99
|
<Slider
|
|
93
100
|
value={binding?.value ?? value}
|
|
@@ -114,6 +121,7 @@ export function SegmentedRenderer({ node, context }: UiRendererProps<UiProps<'Se
|
|
|
114
121
|
const current = binding?.value ?? node.props.value;
|
|
115
122
|
const options = uiChildrenOf(node, 'Option').map((option, index) => ({ id: String(index), label: uiNodeLabel(option), value: option.props.value }));
|
|
116
123
|
const picked = options.find((option) => option.value === current)?.id ?? '';
|
|
124
|
+
|
|
117
125
|
return (
|
|
118
126
|
<div className={clsx(!node.complete && PENDING)}>
|
|
119
127
|
<Segmented
|
|
@@ -124,6 +132,7 @@ export function SegmentedRenderer({ node, context }: UiRendererProps<UiProps<'Se
|
|
|
124
132
|
disabled={binding === null}
|
|
125
133
|
onValueChange={(id) => {
|
|
126
134
|
const option = options[Number(id)];
|
|
135
|
+
|
|
127
136
|
if (option !== undefined) {
|
|
128
137
|
binding?.onValueChange(option.value);
|
|
129
138
|
}
|
|
@@ -151,6 +160,7 @@ export function TextFieldRenderer({ node, children, context }: UiRendererProps<U
|
|
|
151
160
|
const value = binding?.value ?? node.props.value;
|
|
152
161
|
const countId = `${id}-count`;
|
|
153
162
|
const counting = value.length >= maxLength * COUNT_FROM;
|
|
163
|
+
|
|
154
164
|
return (
|
|
155
165
|
<div className={clsx('flex flex-col gap-1.5', !node.complete && PENDING)}>
|
|
156
166
|
<label htmlFor={id} className="px-0.5 text-2xs text-text-muted">
|
|
@@ -186,6 +196,7 @@ export function SelectRenderer({ node, context }: UiRendererProps<UiProps<'Selec
|
|
|
186
196
|
const current = binding?.value ?? node.props.value;
|
|
187
197
|
const options = uiChildrenOf(node, 'Option').map((option, index) => ({ id: String(index), label: uiNodeLabel(option), value: option.props.value }));
|
|
188
198
|
const label = uiOwnLabel(node);
|
|
199
|
+
|
|
189
200
|
return (
|
|
190
201
|
<div className={clsx('flex flex-col items-start gap-1.5', !node.complete && PENDING)}>
|
|
191
202
|
{label !== '' && <span className="px-0.5 text-2xs text-text-muted">{label}</span>}
|
|
@@ -196,6 +207,7 @@ export function SelectRenderer({ node, context }: UiRendererProps<UiProps<'Selec
|
|
|
196
207
|
disabled={binding === null || options.length === 0}
|
|
197
208
|
onValueChange={(id) => {
|
|
198
209
|
const option = options[Number(id)];
|
|
210
|
+
|
|
199
211
|
if (option !== undefined) {
|
|
200
212
|
binding?.onValueChange(option.value);
|
|
201
213
|
}
|
|
@@ -210,6 +222,7 @@ export function ButtonRenderer({ node, children, context }: UiRendererProps<UiPr
|
|
|
210
222
|
const enabled = node.onAction !== undefined && node.complete && context.answer === null && node.props.disabled !== true;
|
|
211
223
|
const label = uiNodeLabel(node);
|
|
212
224
|
const [measure, truncated] = useTruncated<HTMLSpanElement>();
|
|
225
|
+
|
|
213
226
|
return (
|
|
214
227
|
<div className={clsx(!node.complete && PENDING)}>
|
|
215
228
|
<Tooltip label={label} disabled={!truncated}>
|
|
@@ -15,6 +15,7 @@ const UI_CHIP = 'bg-ui-inset font-mono text-text shadow-[inset_0_0_0_1px_var(--b
|
|
|
15
15
|
|
|
16
16
|
function baseName(path: string): string {
|
|
17
17
|
const trimmed = path.replace(/\/+$/, '');
|
|
18
|
+
|
|
18
19
|
return trimmed.slice(trimmed.lastIndexOf('/') + 1) || path;
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -85,18 +86,23 @@ function chipFace(target: UiLinkTarget, link: UiLink): { face: ReactNode; hint:
|
|
|
85
86
|
export function UiLinkChip({ target, context }: { target: UiLinkTarget; context: UiRenderContext }) {
|
|
86
87
|
const { t } = useTranslation('agent-chat');
|
|
87
88
|
const link = context.link?.(target) ?? PLAIN;
|
|
89
|
+
|
|
88
90
|
if (link.state === 'plain') {
|
|
89
91
|
const reason = uiReasonText(t, link.code, link.reason) ?? (context.link === undefined ? null : t('blocks.outsideProject'));
|
|
92
|
+
|
|
90
93
|
// Focusable only with a reason, so a keyboard reaches the tooltip.
|
|
91
94
|
const text = (
|
|
92
95
|
<span tabIndex={reason === null ? undefined : 0} className="font-mono text-code break-all text-text-muted">
|
|
93
96
|
{plainText(target, link)}
|
|
94
97
|
</span>
|
|
95
98
|
);
|
|
99
|
+
|
|
96
100
|
return reason === null ? text : <Tooltip label={reason}>{text}</Tooltip>;
|
|
97
101
|
}
|
|
102
|
+
|
|
98
103
|
const { face, hint } = chipFace(target, link);
|
|
99
104
|
const { openLink } = context;
|
|
105
|
+
|
|
100
106
|
const chip =
|
|
101
107
|
openLink === undefined ? (
|
|
102
108
|
<span className={clsx(CHIP_IN_MESSAGE, UI_CHIP, 'transition-opacity duration-120 starting:opacity-0')}>{face}</span>
|
|
@@ -109,6 +115,7 @@ export function UiLinkChip({ target, context }: { target: UiLinkTarget; context:
|
|
|
109
115
|
{face}
|
|
110
116
|
</button>
|
|
111
117
|
);
|
|
118
|
+
|
|
112
119
|
return hint === null ? chip : <Tooltip label={hint}>{chip}</Tooltip>;
|
|
113
120
|
}
|
|
114
121
|
|
|
@@ -117,6 +124,7 @@ function LinkRow({ target, context, described, children }: { target: UiLinkTarge
|
|
|
117
124
|
if (!described) {
|
|
118
125
|
return <UiLinkChip target={target} context={context} />;
|
|
119
126
|
}
|
|
127
|
+
|
|
120
128
|
return (
|
|
121
129
|
<div className="flex min-w-0 flex-wrap items-baseline gap-x-2 text-xs">
|
|
122
130
|
<span className="max-w-full min-w-0">
|
|
@@ -129,6 +137,7 @@ function LinkRow({ target, context, described, children }: { target: UiLinkTarge
|
|
|
129
137
|
|
|
130
138
|
export function FileRenderer({ node, children, context }: UiRendererProps<UiProps<'File'>>) {
|
|
131
139
|
const { path, line } = node.props;
|
|
140
|
+
|
|
132
141
|
return (
|
|
133
142
|
<LinkRow target={{ type: 'File', path, line }} context={context} described={node.children.length > 0}>
|
|
134
143
|
{children}
|
|
@@ -28,6 +28,7 @@ export class MapSlots {
|
|
|
28
28
|
if (this.held.includes(id) || this.queue.includes(id)) {
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
|
+
|
|
31
32
|
(this.held.length < this.capacity ? this.held : this.queue).push(id);
|
|
32
33
|
this.changed();
|
|
33
34
|
}
|
|
@@ -35,14 +36,18 @@ export class MapSlots {
|
|
|
35
36
|
release(id: string): void {
|
|
36
37
|
const wasHeld = this.held.includes(id);
|
|
37
38
|
const wasQueued = this.queue.includes(id);
|
|
39
|
+
|
|
38
40
|
if (!wasHeld && !wasQueued) {
|
|
39
41
|
return;
|
|
40
42
|
}
|
|
43
|
+
|
|
41
44
|
this.held.splice(0, this.held.length, ...this.held.filter((entry) => entry !== id));
|
|
42
45
|
this.queue.splice(0, this.queue.length, ...this.queue.filter((entry) => entry !== id));
|
|
46
|
+
|
|
43
47
|
while (this.held.length < this.capacity && this.queue.length > 0) {
|
|
44
48
|
this.held.push(this.queue.shift()!);
|
|
45
49
|
}
|
|
50
|
+
|
|
46
51
|
this.changed();
|
|
47
52
|
}
|
|
48
53
|
|
|
@@ -50,13 +55,17 @@ export class MapSlots {
|
|
|
50
55
|
if (this.held.includes(id)) {
|
|
51
56
|
return;
|
|
52
57
|
}
|
|
58
|
+
|
|
53
59
|
const at = this.queue.indexOf(id);
|
|
60
|
+
|
|
54
61
|
if (at >= 0) {
|
|
55
62
|
this.queue.splice(at, 1);
|
|
56
63
|
}
|
|
64
|
+
|
|
57
65
|
if (this.held.length >= this.capacity) {
|
|
58
66
|
this.queue.unshift(this.held.shift()!);
|
|
59
67
|
}
|
|
68
|
+
|
|
60
69
|
this.held.push(id);
|
|
61
70
|
this.changed();
|
|
62
71
|
}
|
|
@@ -67,6 +76,7 @@ export class MapSlots {
|
|
|
67
76
|
|
|
68
77
|
subscribe = (listener: () => void): (() => void) => {
|
|
69
78
|
this.listeners.add(listener);
|
|
79
|
+
|
|
70
80
|
return () => {
|
|
71
81
|
this.listeners.delete(listener);
|
|
72
82
|
};
|
|
@@ -84,6 +94,7 @@ let page: MapSlots | null = null;
|
|
|
84
94
|
/* The one budget of the page, made on first use. */
|
|
85
95
|
export function pageMapSlots(): MapSlots {
|
|
86
96
|
page ??= new MapSlots(MAX_LIVE_MAPS);
|
|
97
|
+
|
|
87
98
|
return page;
|
|
88
99
|
}
|
|
89
100
|
|
|
@@ -94,12 +105,16 @@ export function useMapSlot(id: string, wanted: boolean, slots: MapSlots = pageMa
|
|
|
94
105
|
() => slots.stateOf(id),
|
|
95
106
|
() => 'idle' as const
|
|
96
107
|
);
|
|
108
|
+
|
|
97
109
|
useEffect(() => {
|
|
98
110
|
if (!wanted) {
|
|
99
111
|
return;
|
|
100
112
|
}
|
|
113
|
+
|
|
101
114
|
slots.request(id);
|
|
115
|
+
|
|
102
116
|
return () => slots.release(id);
|
|
103
117
|
}, [slots, id, wanted]);
|
|
118
|
+
|
|
104
119
|
return { state, take: () => slots.take(id) };
|
|
105
120
|
}
|
|
@@ -67,13 +67,16 @@ const SPREAD_STEPS: readonly (readonly [number, number])[] = [
|
|
|
67
67
|
export function spreadOverlaps(points: readonly ScreenPoint[]): Map<string, [number, number]> {
|
|
68
68
|
const offsets = new Map<string, [number, number]>();
|
|
69
69
|
const placed: { x: number; y: number }[] = [];
|
|
70
|
+
|
|
70
71
|
for (const point of points) {
|
|
71
72
|
const free = ([dx, dy]: readonly [number, number]): boolean =>
|
|
72
73
|
placed.every((other) => Math.hypot(other.x - (point.x + dx), other.y - (point.y + dy)) >= MAP_MARKER_SIZE);
|
|
74
|
+
|
|
73
75
|
const step = [[0, 0] as const, ...SPREAD_STEPS].find(free) ?? ([0, 0] as const);
|
|
74
76
|
offsets.set(point.id, [step[0], step[1]]);
|
|
75
77
|
placed.push({ x: point.x + step[0], y: point.y + step[1] });
|
|
76
78
|
}
|
|
79
|
+
|
|
77
80
|
return offsets;
|
|
78
81
|
}
|
|
79
82
|
|
|
@@ -95,19 +98,24 @@ const TOKEN = /\{([^{}]+)\}/g;
|
|
|
95
98
|
function tokenExpression(text: string): Expression | string {
|
|
96
99
|
const parts: (Expression | string)[] = [];
|
|
97
100
|
let at = 0;
|
|
101
|
+
|
|
98
102
|
for (const match of text.matchAll(TOKEN)) {
|
|
99
103
|
if (match.index > at) {
|
|
100
104
|
parts.push(text.slice(at, match.index));
|
|
101
105
|
}
|
|
106
|
+
|
|
102
107
|
parts.push(['to-string', ['coalesce', ['get', match[1]], '']]);
|
|
103
108
|
at = match.index + match[0].length;
|
|
104
109
|
}
|
|
110
|
+
|
|
105
111
|
if (parts.length === 0) {
|
|
106
112
|
return text;
|
|
107
113
|
}
|
|
114
|
+
|
|
108
115
|
if (at < text.length) {
|
|
109
116
|
parts.push(text.slice(at));
|
|
110
117
|
}
|
|
118
|
+
|
|
111
119
|
return parts.length === 1 && typeof parts[0] !== 'string' ? parts[0] : ['concat', ...parts];
|
|
112
120
|
}
|
|
113
121
|
|
|
@@ -117,9 +125,11 @@ function tokenExpression(text: string): Expression | string {
|
|
|
117
125
|
*/
|
|
118
126
|
export function localizedTextField(original: unknown, field: string): unknown {
|
|
119
127
|
const fallback = typeof original === 'string' ? tokenExpression(original) : original;
|
|
128
|
+
|
|
120
129
|
if (typeof fallback !== 'string' && !Array.isArray(fallback)) {
|
|
121
130
|
return original;
|
|
122
131
|
}
|
|
132
|
+
|
|
123
133
|
return ['coalesce', ['get', field], fallback];
|
|
124
134
|
}
|
|
125
135
|
|
|
@@ -132,19 +142,24 @@ export function boundsOf(points: readonly MapPoint[]): LngLatBoundsLike | null {
|
|
|
132
142
|
if (points.length === 0) {
|
|
133
143
|
return null;
|
|
134
144
|
}
|
|
145
|
+
|
|
135
146
|
const lons = points.map((point) => point.lon).sort((a, b) => a - b);
|
|
136
147
|
let widest = lons[0]! + 360 - lons[lons.length - 1]!;
|
|
137
148
|
let west = lons[0]!;
|
|
138
149
|
let east = lons[lons.length - 1]!;
|
|
150
|
+
|
|
139
151
|
for (let i = 1; i < lons.length; i++) {
|
|
140
152
|
const gap = lons[i]! - lons[i - 1]!;
|
|
153
|
+
|
|
141
154
|
if (gap > widest) {
|
|
142
155
|
widest = gap;
|
|
143
156
|
west = lons[i]!;
|
|
144
157
|
east = lons[i - 1]! + 360;
|
|
145
158
|
}
|
|
146
159
|
}
|
|
160
|
+
|
|
147
161
|
const lats = points.map((point) => point.lat);
|
|
162
|
+
|
|
148
163
|
return [
|
|
149
164
|
[west, Math.min(...lats)],
|
|
150
165
|
[east, Math.max(...lats)]
|
|
@@ -44,6 +44,7 @@ let workerUrlSet: string | null = null;
|
|
|
44
44
|
*/
|
|
45
45
|
function ensureBundledWorker(hosted: string | undefined): void {
|
|
46
46
|
const url = hosted ?? new URL('maplibre-gl/dist/maplibre-gl-worker.mjs', import.meta.url).href;
|
|
47
|
+
|
|
47
48
|
if (url !== workerUrlSet) {
|
|
48
49
|
setWorkerUrl(url);
|
|
49
50
|
workerUrlSet = url;
|
|
@@ -53,6 +54,7 @@ function ensureBundledWorker(hosted: string | undefined): void {
|
|
|
53
54
|
/* The grounds of the built-in map are theme tokens, which the page resolves; the style needs plain colors. */
|
|
54
55
|
function readColors(element: HTMLElement): MapColors {
|
|
55
56
|
const style = getComputedStyle(element);
|
|
57
|
+
|
|
56
58
|
return { land: style.getPropertyValue('--map-land').trim() || 'transparent', water: style.getPropertyValue('--map-water').trim() || 'transparent' };
|
|
57
59
|
}
|
|
58
60
|
|
|
@@ -70,25 +72,31 @@ export function MapView({ markers, source, selected, onPick, onStatus }: MapView
|
|
|
70
72
|
const [map, setMap] = useState<MapLibreMap | null>(null);
|
|
71
73
|
const [outline, setOutline] = useState(false);
|
|
72
74
|
const latest = useRef({ source, onStatus, onPick });
|
|
75
|
+
|
|
73
76
|
useEffect(() => {
|
|
74
77
|
latest.current = { source, onStatus, onPick };
|
|
75
78
|
});
|
|
79
|
+
|
|
76
80
|
const language = i18n.language.split('-')[0] ?? 'en';
|
|
77
81
|
const styleKey = `${mode}:${source.state === 'tiles' ? source.styleUrl(mode) : 'outline'}`;
|
|
78
82
|
const pointsKey = JSON.stringify(markers.map((marker) => [marker.id, marker.lat, marker.lon]));
|
|
79
83
|
|
|
80
84
|
useEffect(() => {
|
|
81
85
|
const container = element.current;
|
|
86
|
+
|
|
82
87
|
if (container === null) {
|
|
83
88
|
return;
|
|
84
89
|
}
|
|
90
|
+
|
|
85
91
|
const current = latest.current.source;
|
|
86
92
|
let colors = readColors(container);
|
|
87
93
|
const offline = current.state === 'tiles' && typeof navigator !== 'undefined' && navigator.onLine === false;
|
|
88
94
|
const tiles = current.state === 'tiles' && !offline;
|
|
89
95
|
let created: MapLibreMap;
|
|
96
|
+
|
|
90
97
|
try {
|
|
91
98
|
ensureBundledWorker(workerUrl);
|
|
99
|
+
|
|
92
100
|
created = new MapLibreMap({
|
|
93
101
|
container,
|
|
94
102
|
style: current.state === 'tiles' && tiles ? current.styleUrl(mode) : baseMapStyle(colors),
|
|
@@ -106,13 +114,16 @@ export function MapView({ markers, source, selected, onPick, onStatus }: MapView
|
|
|
106
114
|
},
|
|
107
115
|
transformRequest: (url) => {
|
|
108
116
|
const now = latest.current.source;
|
|
117
|
+
|
|
109
118
|
return now.state === 'tiles' && now.transformRequest ? now.transformRequest(url) : { url };
|
|
110
119
|
}
|
|
111
120
|
});
|
|
112
121
|
} catch {
|
|
113
122
|
latest.current.onStatus('failed');
|
|
123
|
+
|
|
114
124
|
return;
|
|
115
125
|
}
|
|
126
|
+
|
|
116
127
|
created.touchZoomRotate.disableRotation();
|
|
117
128
|
const canvas = created.getCanvas();
|
|
118
129
|
canvas.tabIndex = -1;
|
|
@@ -130,16 +141,19 @@ export function MapView({ markers, source, selected, onPick, onStatus }: MapView
|
|
|
130
141
|
if (drawing === 'outline' || !wantsTiles) {
|
|
131
142
|
return;
|
|
132
143
|
}
|
|
144
|
+
|
|
133
145
|
drawing = 'outline';
|
|
134
146
|
reason = why;
|
|
135
147
|
setOutline(true);
|
|
136
148
|
created.setStyle(baseMapStyle(colors));
|
|
137
149
|
latest.current.onStatus(why);
|
|
138
150
|
};
|
|
151
|
+
|
|
139
152
|
const drawTiles = (): void => {
|
|
140
153
|
if (current.state !== 'tiles') {
|
|
141
154
|
return;
|
|
142
155
|
}
|
|
156
|
+
|
|
143
157
|
drawing = 'tiles';
|
|
144
158
|
reason = null;
|
|
145
159
|
styleLoaded = false;
|
|
@@ -149,74 +163,97 @@ export function MapView({ markers, source, selected, onPick, onStatus }: MapView
|
|
|
149
163
|
created.setStyle(current.styleUrl(mode));
|
|
150
164
|
latest.current.onStatus('loading');
|
|
151
165
|
};
|
|
166
|
+
|
|
152
167
|
const unreachable = (): 'offline' | 'unavailable' => (typeof navigator !== 'undefined' && navigator.onLine === false ? 'offline' : 'unavailable');
|
|
153
168
|
|
|
154
169
|
created.on('style.load', () => {
|
|
155
170
|
if (drawing !== 'tiles') {
|
|
156
171
|
return;
|
|
157
172
|
}
|
|
173
|
+
|
|
158
174
|
styleLoaded = true;
|
|
175
|
+
|
|
159
176
|
if (restoring) {
|
|
160
177
|
restoring = false;
|
|
161
178
|
latest.current.onStatus('tiles');
|
|
162
179
|
}
|
|
180
|
+
|
|
163
181
|
const field = current.state === 'tiles' ? current.labelField?.(language) : undefined;
|
|
182
|
+
|
|
164
183
|
if (field === undefined) {
|
|
165
184
|
return;
|
|
166
185
|
}
|
|
186
|
+
|
|
167
187
|
for (const layer of created.getStyle().layers) {
|
|
168
188
|
const textField = layer.type === 'symbol' ? layer.layout?.['text-field'] : undefined;
|
|
189
|
+
|
|
169
190
|
if (textField !== undefined) {
|
|
170
191
|
created.setLayoutProperty(layer.id, 'text-field', localizedTextField(textField, field) as never);
|
|
171
192
|
}
|
|
172
193
|
}
|
|
173
194
|
});
|
|
195
|
+
|
|
174
196
|
created.on('load', () => {
|
|
175
197
|
if (drawing === 'tiles') {
|
|
176
198
|
latest.current.onStatus('tiles');
|
|
177
199
|
}
|
|
178
200
|
});
|
|
201
|
+
|
|
179
202
|
created.on('sourcedata', (event) => {
|
|
180
203
|
if ('tile' in event) {
|
|
181
204
|
tileErrors = 0;
|
|
182
205
|
}
|
|
183
206
|
});
|
|
207
|
+
|
|
184
208
|
created.on('error', (event) => {
|
|
185
209
|
if (drawing !== 'tiles') {
|
|
186
210
|
return;
|
|
187
211
|
}
|
|
212
|
+
|
|
188
213
|
// Before the style is here there is nothing to draw. After it, a tile the host refuses (a ticket
|
|
189
214
|
// that ran out) or several in a row leave an empty map; a single one the map retries itself.
|
|
190
215
|
const status = (event.error as { status?: number } | undefined)?.status;
|
|
216
|
+
|
|
191
217
|
if (!styleLoaded || status === 401 || status === 403 || ++tileErrors >= TILE_ERRORS_IN_A_ROW) {
|
|
192
218
|
drawOutline(unreachable());
|
|
193
219
|
}
|
|
194
220
|
});
|
|
221
|
+
|
|
195
222
|
const goOffline = (): void => drawOutline('offline');
|
|
223
|
+
|
|
196
224
|
const goOnline = (): void => {
|
|
197
225
|
if (reason === 'offline') {
|
|
198
226
|
drawTiles();
|
|
199
227
|
}
|
|
200
228
|
};
|
|
229
|
+
|
|
201
230
|
const contextLost = (): void => latest.current.onStatus('lost');
|
|
231
|
+
|
|
202
232
|
if (wantsTiles) {
|
|
203
233
|
window.addEventListener('offline', goOffline);
|
|
204
234
|
window.addEventListener('online', goOnline);
|
|
205
235
|
}
|
|
236
|
+
|
|
206
237
|
canvas.addEventListener('webglcontextlost', contextLost);
|
|
238
|
+
|
|
207
239
|
// Colors that change within the side repaint the outline in place: a style that differs only in its paint is
|
|
208
240
|
// applied as a diff, so the map, its camera and its markers stay. The tiles draw colors of their own.
|
|
209
241
|
const stopWatching = watchTheme(container.ownerDocument, () => {
|
|
210
242
|
const next = readColors(container);
|
|
243
|
+
|
|
211
244
|
if (next.land === colors.land && next.water === colors.water) {
|
|
212
245
|
return;
|
|
213
246
|
}
|
|
247
|
+
|
|
214
248
|
colors = next;
|
|
249
|
+
|
|
215
250
|
if (drawing === 'outline') {
|
|
216
251
|
created.setStyle(baseMapStyle(colors));
|
|
217
252
|
}
|
|
218
253
|
});
|
|
254
|
+
|
|
219
255
|
setMap(created);
|
|
256
|
+
|
|
220
257
|
return () => {
|
|
221
258
|
stopWatching();
|
|
222
259
|
window.removeEventListener('offline', goOffline);
|
|
@@ -231,19 +268,23 @@ export function MapView({ markers, source, selected, onPick, onStatus }: MapView
|
|
|
231
268
|
|
|
232
269
|
useEffect(() => {
|
|
233
270
|
const bounds = boundsOf(markers);
|
|
271
|
+
|
|
234
272
|
if (map === null || bounds === null) {
|
|
235
273
|
return;
|
|
236
274
|
}
|
|
275
|
+
|
|
237
276
|
map.fitBounds(bounds, { ...mapFitOptions(outline), duration: 0 });
|
|
238
277
|
// The points are compared by their coordinates, so a render that rebuilt the same list does not move the map.
|
|
239
278
|
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
240
279
|
}, [map, pointsKey, outline]);
|
|
241
280
|
|
|
242
281
|
const pickable = onPick !== null;
|
|
282
|
+
|
|
243
283
|
useEffect(() => {
|
|
244
284
|
if (map === null) {
|
|
245
285
|
return;
|
|
246
286
|
}
|
|
287
|
+
|
|
247
288
|
const drawn = markers.map((marker) => {
|
|
248
289
|
const node = document.createElement('div');
|
|
249
290
|
node.setAttribute('aria-hidden', 'true');
|
|
@@ -251,18 +292,24 @@ export function MapView({ markers, source, selected, onPick, onStatus }: MapView
|
|
|
251
292
|
node.className = `${MARKER} ${marker.id === selected ? PICKED : ''} ${pickable ? 'cursor-pointer' : ''}`;
|
|
252
293
|
node.textContent = String(marker.number);
|
|
253
294
|
node.addEventListener('click', () => latest.current.onPick?.(marker.id));
|
|
295
|
+
|
|
254
296
|
return { marker, drawing: new Marker({ element: node }).setLngLat([marker.lon, marker.lat]).addTo(map) };
|
|
255
297
|
});
|
|
298
|
+
|
|
256
299
|
const spread = (): void => {
|
|
257
300
|
const offsets = spreadOverlaps(drawn.map(({ marker }) => ({ id: marker.id, ...map.project([marker.lon, marker.lat]) })));
|
|
301
|
+
|
|
258
302
|
for (const { marker, drawing } of drawn) {
|
|
259
303
|
drawing.setOffset(offsets.get(marker.id) ?? [0, 0]);
|
|
260
304
|
}
|
|
261
305
|
};
|
|
306
|
+
|
|
262
307
|
spread();
|
|
263
308
|
map.on('zoomend', spread);
|
|
309
|
+
|
|
264
310
|
return () => {
|
|
265
311
|
map.off('zoomend', spread);
|
|
312
|
+
|
|
266
313
|
for (const { drawing } of drawn) {
|
|
267
314
|
drawing.remove();
|
|
268
315
|
}
|
|
@@ -272,6 +319,7 @@ export function MapView({ markers, source, selected, onPick, onStatus }: MapView
|
|
|
272
319
|
|
|
273
320
|
const showAll = (): void => {
|
|
274
321
|
const bounds = boundsOf(markers);
|
|
322
|
+
|
|
275
323
|
if (map !== null && bounds !== null) {
|
|
276
324
|
map.fitBounds(bounds, mapFitOptions(outline));
|
|
277
325
|
}
|
|
@@ -47,6 +47,7 @@ function NumberChip({ number, selected }: { number: number; selected: boolean })
|
|
|
47
47
|
function PlaceRow({ listed, number, selected, onPick }: { listed: ListedPlace; number: number; selected: boolean; onPick: (() => void) | null }) {
|
|
48
48
|
const { t } = useTranslation('agent-chat');
|
|
49
49
|
const { detail, address } = listed.node.props;
|
|
50
|
+
|
|
50
51
|
const body = (
|
|
51
52
|
<>
|
|
52
53
|
<NumberChip number={number} selected={selected} />
|
|
@@ -59,7 +60,9 @@ function PlaceRow({ listed, number, selected, onPick }: { listed: ListedPlace; n
|
|
|
59
60
|
</span>
|
|
60
61
|
</>
|
|
61
62
|
);
|
|
63
|
+
|
|
62
64
|
const row = 'flex h-full w-full min-h-12 items-start gap-2.5 rounded-xl bg-ui-tile px-3 py-2 text-left';
|
|
65
|
+
|
|
63
66
|
return (
|
|
64
67
|
<li>
|
|
65
68
|
{onPick === null ? (
|
|
@@ -83,12 +86,15 @@ function noteFor(t: TFunction<'agent-chat'>, source: UiMapSource | null, status:
|
|
|
83
86
|
if (status === 'offline') {
|
|
84
87
|
return t('blocks.map.offline');
|
|
85
88
|
}
|
|
89
|
+
|
|
86
90
|
if (status === 'unavailable' || status === 'failed' || status === 'lost') {
|
|
87
91
|
return t('blocks.map.unavailable');
|
|
88
92
|
}
|
|
93
|
+
|
|
89
94
|
if (source?.state === 'off') {
|
|
90
95
|
return t(source.reason === 'disabled' ? 'blocks.map.disabled' : source.reason === 'signed-out' ? 'blocks.map.signedOut' : 'blocks.map.unavailable');
|
|
91
96
|
}
|
|
97
|
+
|
|
92
98
|
return source?.state === 'tiles' && status === 'tiles' ? (source.attribution ?? null) : null;
|
|
93
99
|
}
|
|
94
100
|
|
|
@@ -117,6 +123,7 @@ function MapFrame({ markers, source, selected, onPick }: FrameProps) {
|
|
|
117
123
|
const drawn = slot.state === 'granted' && !broken;
|
|
118
124
|
const waiting = useLater(drawn && status === 'loading', LOADING_SPINNER_MS);
|
|
119
125
|
const note = noteFor(t, source, status);
|
|
126
|
+
|
|
120
127
|
const quiet = (text: string, action?: { label: string; run(): void }) => (
|
|
121
128
|
<div className="grid h-full place-items-center content-center justify-items-center gap-2 p-3 text-center">
|
|
122
129
|
<p className="text-xs text-text-faint">{text}</p>
|
|
@@ -127,11 +134,14 @@ function MapFrame({ markers, source, selected, onPick }: FrameProps) {
|
|
|
127
134
|
)}
|
|
128
135
|
</div>
|
|
129
136
|
);
|
|
137
|
+
|
|
130
138
|
const retry = () => {
|
|
131
139
|
setStatus('loading');
|
|
132
140
|
setAttempt((previous) => previous + 1);
|
|
133
141
|
};
|
|
142
|
+
|
|
134
143
|
let body: ReactNode = null;
|
|
144
|
+
|
|
135
145
|
if (status === 'failed') {
|
|
136
146
|
body = quiet(t('blocks.map.unavailable'));
|
|
137
147
|
} else if (status === 'lost') {
|
|
@@ -154,6 +164,7 @@ function MapFrame({ markers, source, selected, onPick }: FrameProps) {
|
|
|
154
164
|
</DiffFallbackBoundary>
|
|
155
165
|
);
|
|
156
166
|
}
|
|
167
|
+
|
|
157
168
|
return (
|
|
158
169
|
<>
|
|
159
170
|
<div ref={setElement} className="relative h-50 overflow-hidden rounded-xl bg-ui-tile @min-[420px]:h-60">
|
|
@@ -184,33 +195,42 @@ export function MapRenderer({ node, children, context }: UiRendererProps<UiProps
|
|
|
184
195
|
const binding = bindingOf(node, context);
|
|
185
196
|
const current = binding?.value ?? node.props.value;
|
|
186
197
|
const maps = useChatHost().maps;
|
|
198
|
+
|
|
187
199
|
const places: ListedPlace[] = uiChildrenOf(node, 'Place').map((place) => ({
|
|
188
200
|
node: place,
|
|
189
201
|
name: uiNodeLabel(place),
|
|
190
202
|
point: placePoint(place.props, place.id, context)
|
|
191
203
|
}));
|
|
204
|
+
|
|
192
205
|
const markers = places.flatMap((listed, index) =>
|
|
193
206
|
listed.point ? [{ id: listed.node.id, number: index + 1, lat: listed.point.lat, lon: listed.point.lon }] : []
|
|
194
207
|
);
|
|
208
|
+
|
|
195
209
|
const selectedId = places.find((listed) => listed.node.props.value !== undefined && listed.node.props.value === current)?.node.id ?? null;
|
|
210
|
+
|
|
196
211
|
const pick = (id: string): void => {
|
|
197
212
|
const value = places.find((listed) => listed.node.id === id)?.node.props.value;
|
|
213
|
+
|
|
198
214
|
if (value !== undefined) {
|
|
199
215
|
binding?.onValueChange(value);
|
|
200
216
|
}
|
|
201
217
|
};
|
|
218
|
+
|
|
202
219
|
const onPick = binding !== null ? pick : null;
|
|
220
|
+
|
|
203
221
|
const frame = maps ? (
|
|
204
222
|
<HostedFrame maps={maps} scopeId={context.scopeId} markers={markers} selected={selectedId} onPick={onPick} />
|
|
205
223
|
) : (
|
|
206
224
|
<MapFrame markers={markers} source={null} selected={selectedId} onPick={onPick} />
|
|
207
225
|
);
|
|
226
|
+
|
|
208
227
|
return (
|
|
209
228
|
<div className={clsx('@container flex flex-col gap-1.5', !node.complete && PENDING)}>
|
|
210
229
|
{frame}
|
|
211
230
|
<ol aria-label={t('blocks.map.label', { count: places.length })} className="grid gap-1.5 @min-[420px]:grid-cols-2">
|
|
212
231
|
{places.map((listed, index) => {
|
|
213
232
|
const { value } = listed.node.props;
|
|
233
|
+
|
|
214
234
|
return (
|
|
215
235
|
<PlaceRow
|
|
216
236
|
key={listed.node.id}
|
|
@@ -8,9 +8,12 @@ import type { UiPlacePoint, UiRenderContext } from '../render-context';
|
|
|
8
8
|
export function placePoint(props: UiProps<'Place'>, nodeId: string, context: Pick<UiRenderContext, 'place'>): UiPlacePoint | null | undefined {
|
|
9
9
|
const { lat, lon, address } = props;
|
|
10
10
|
const written = lat !== undefined && lon !== undefined ? { lat, lon } : undefined;
|
|
11
|
+
|
|
11
12
|
if (address === undefined || address.trim() === '') {
|
|
12
13
|
return written ?? null;
|
|
13
14
|
}
|
|
15
|
+
|
|
14
16
|
const found = context.place === undefined ? null : context.place(nodeId, address);
|
|
17
|
+
|
|
15
18
|
return found ?? written ?? found;
|
|
16
19
|
}
|