@adecore/agents-react 0.39.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.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 +26 -26
- 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
package/src/lazy.tsx
CHANGED
|
@@ -13,6 +13,7 @@ const openErrorListeners = new Set<(error: unknown) => void>();
|
|
|
13
13
|
*/
|
|
14
14
|
export function setLazyPrefetch(next: (load: Loader) => void): void {
|
|
15
15
|
register = next;
|
|
16
|
+
|
|
16
17
|
for (const load of loaders) {
|
|
17
18
|
next(load);
|
|
18
19
|
}
|
|
@@ -24,6 +25,7 @@ export function setLazyPrefetch(next: (load: Loader) => void): void {
|
|
|
24
25
|
*/
|
|
25
26
|
export function onLazyOpenError(listener: (error: unknown) => void): () => void {
|
|
26
27
|
openErrorListeners.add(listener);
|
|
28
|
+
|
|
27
29
|
return () => {
|
|
28
30
|
openErrorListeners.delete(listener);
|
|
29
31
|
};
|
|
@@ -41,11 +43,14 @@ export function lazyNamed<Module extends Record<Name, ComponentType<any>>, Name
|
|
|
41
43
|
): ComponentType<ComponentProps<Module[Name]>> {
|
|
42
44
|
// Through `LoadedComponent`, so a `LazyBoundary` around it draws the moment the module lands.
|
|
43
45
|
const loaded = new LoadedComponent<ComponentType<any>>(async () => (await load())[name]);
|
|
46
|
+
|
|
44
47
|
const open = (): Promise<ComponentType<any>> => loaded.load();
|
|
48
|
+
|
|
45
49
|
if (options?.prefetch !== false) {
|
|
46
50
|
loaders.push(open);
|
|
47
51
|
register?.(open);
|
|
48
52
|
}
|
|
53
|
+
|
|
49
54
|
// Only a render loads through here; a prefetch calls `open` itself, so its failures report nothing.
|
|
50
55
|
const Lazy = lazy(async () => {
|
|
51
56
|
try {
|
|
@@ -54,14 +59,18 @@ export function lazyNamed<Module extends Record<Name, ComponentType<any>>, Name
|
|
|
54
59
|
for (const listener of openErrorListeners) {
|
|
55
60
|
listener(error);
|
|
56
61
|
}
|
|
62
|
+
|
|
57
63
|
throw error;
|
|
58
64
|
}
|
|
59
65
|
});
|
|
66
|
+
|
|
60
67
|
const LazyNamed = (props: ComponentProps<Module[Name]>) => {
|
|
61
68
|
// `loaded` fills in place; compiled, the element drawn before the module landed would stay.
|
|
62
69
|
'use no memo';
|
|
63
70
|
const Component: ComponentType<any> = loaded.current ?? Lazy;
|
|
71
|
+
|
|
64
72
|
return <Component {...props} />;
|
|
65
73
|
};
|
|
74
|
+
|
|
66
75
|
return LazyNamed;
|
|
67
76
|
}
|
package/src/mounted-registry.ts
CHANGED
|
@@ -27,12 +27,15 @@ export class MountedRegistry<T extends MountedEntry> extends Map<string, T> {
|
|
|
27
27
|
*/
|
|
28
28
|
async reattachAll(reattach: (id: string, entry: T) => Promise<void>): Promise<void> {
|
|
29
29
|
const queue = [...this];
|
|
30
|
+
|
|
30
31
|
const work = async (): Promise<void> => {
|
|
31
32
|
for (let next = queue.shift(); next !== undefined; next = queue.shift()) {
|
|
32
33
|
const [id, entry] = next;
|
|
34
|
+
|
|
33
35
|
if (entry.attached || this.get(id) !== entry) {
|
|
34
36
|
continue;
|
|
35
37
|
}
|
|
38
|
+
|
|
36
39
|
try {
|
|
37
40
|
await reattach(id, entry);
|
|
38
41
|
} catch {
|
|
@@ -40,6 +43,7 @@ export class MountedRegistry<T extends MountedEntry> extends Map<string, T> {
|
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
};
|
|
46
|
+
|
|
43
47
|
await Promise.all(Array.from({ length: REATTACH_CONCURRENCY }, work));
|
|
44
48
|
}
|
|
45
49
|
}
|
package/src/port-transport.ts
CHANGED
|
@@ -34,32 +34,45 @@ export function portTransport(port: FramePort): PortTransport {
|
|
|
34
34
|
|
|
35
35
|
const answer = (id: string, frame: { ok: true; result: unknown } | { ok: false; error: { code: string; message: string } }): void => {
|
|
36
36
|
const entry = pending.get(id);
|
|
37
|
+
|
|
37
38
|
if (!entry) {
|
|
38
39
|
return;
|
|
39
40
|
}
|
|
41
|
+
|
|
40
42
|
pending.delete(id);
|
|
43
|
+
|
|
41
44
|
if (!frame.ok) {
|
|
42
45
|
entry.reject(new ChatTransportError(frame.error.code, frame.error.message));
|
|
46
|
+
|
|
43
47
|
return;
|
|
44
48
|
}
|
|
49
|
+
|
|
45
50
|
const result = AGENT_REQUEST_SCHEMAS[entry.type].result.safeParse(frame.result);
|
|
51
|
+
|
|
46
52
|
if (!result.success) {
|
|
47
53
|
entry.reject(new ChatTransportError('bad-reply', i18next.t('agent-chat:transport.badReply', { type: entry.type })));
|
|
54
|
+
|
|
48
55
|
return;
|
|
49
56
|
}
|
|
57
|
+
|
|
50
58
|
entry.resolve(result.data);
|
|
51
59
|
};
|
|
52
60
|
|
|
53
61
|
const dispatch = (event: string, payload: unknown): void => {
|
|
54
62
|
if (!isEventType(event)) {
|
|
55
63
|
console.warn('Dropped an unknown event', event);
|
|
64
|
+
|
|
56
65
|
return;
|
|
57
66
|
}
|
|
67
|
+
|
|
58
68
|
const parsed = AGENT_EVENT_SCHEMAS[event].safeParse(payload);
|
|
69
|
+
|
|
59
70
|
if (!parsed.success) {
|
|
60
71
|
console.warn(`Dropped a malformed ${event} event`);
|
|
72
|
+
|
|
61
73
|
return;
|
|
62
74
|
}
|
|
75
|
+
|
|
63
76
|
// A copy, so a handler that unsubscribes while it runs does not change the set being walked.
|
|
64
77
|
for (const handler of [...(eventHandlers.get(event) ?? [])]) {
|
|
65
78
|
(handler as (payload: unknown) => void)(parsed.data);
|
|
@@ -70,21 +83,30 @@ export function portTransport(port: FramePort): PortTransport {
|
|
|
70
83
|
if (status === 'closed') {
|
|
71
84
|
return;
|
|
72
85
|
}
|
|
86
|
+
|
|
73
87
|
const parsed = parseServerFrame(raw);
|
|
88
|
+
|
|
74
89
|
if (!parsed.ok) {
|
|
75
90
|
console.warn('Dropped a malformed frame', parsed.message);
|
|
91
|
+
|
|
76
92
|
return;
|
|
77
93
|
}
|
|
94
|
+
|
|
78
95
|
const frame = parsed.value;
|
|
96
|
+
|
|
79
97
|
if ('event' in frame) {
|
|
80
98
|
dispatch(frame.event, frame.payload);
|
|
99
|
+
|
|
81
100
|
return;
|
|
82
101
|
}
|
|
102
|
+
|
|
83
103
|
if (frame.id === null) {
|
|
84
104
|
// An answer without an id is about a frame the host could not read; nothing waits for it.
|
|
85
105
|
console.warn('The chat host refused a frame', frame);
|
|
106
|
+
|
|
86
107
|
return;
|
|
87
108
|
}
|
|
109
|
+
|
|
88
110
|
answer(frame.id, frame);
|
|
89
111
|
});
|
|
90
112
|
|
|
@@ -96,29 +118,36 @@ export function portTransport(port: FramePort): PortTransport {
|
|
|
96
118
|
if (status !== 'open') {
|
|
97
119
|
return Promise.reject(new ChatTransportError('not-connected', i18next.t('agent-chat:transport.notConnected')));
|
|
98
120
|
}
|
|
121
|
+
|
|
99
122
|
const id = String(nextId++);
|
|
123
|
+
|
|
100
124
|
const promise = new Promise<ChatRequestMap[T]['result']>((resolve, reject) => {
|
|
101
125
|
pending.set(id, { type, resolve: resolve as (result: unknown) => void, reject });
|
|
102
126
|
});
|
|
127
|
+
|
|
103
128
|
try {
|
|
104
129
|
port.send({ id, type, payload });
|
|
105
130
|
} catch {
|
|
106
131
|
// A payload the port cannot clone throws here, and the caller only listens to the promise.
|
|
107
132
|
pending.delete(id);
|
|
133
|
+
|
|
108
134
|
return Promise.reject(new ChatTransportError('not-sent', i18next.t('agent-chat:transport.notSent', { type })));
|
|
109
135
|
}
|
|
136
|
+
|
|
110
137
|
return promise;
|
|
111
138
|
},
|
|
112
139
|
on<E extends AgentEventType>(event: E, handler: (payload: ChatEventMap[E]) => void): () => void {
|
|
113
140
|
const handlers = eventHandlers.get(event) ?? new Set();
|
|
114
141
|
eventHandlers.set(event, handlers);
|
|
115
142
|
handlers.add(handler);
|
|
143
|
+
|
|
116
144
|
return () => {
|
|
117
145
|
handlers.delete(handler);
|
|
118
146
|
};
|
|
119
147
|
},
|
|
120
148
|
subscribeStatus(handler: (status: ChatTransportStatus) => void): () => void {
|
|
121
149
|
statusHandlers.add(handler);
|
|
150
|
+
|
|
122
151
|
return () => {
|
|
123
152
|
statusHandlers.delete(handler);
|
|
124
153
|
};
|
|
@@ -127,13 +156,16 @@ export function portTransport(port: FramePort): PortTransport {
|
|
|
127
156
|
if (status === 'closed') {
|
|
128
157
|
return;
|
|
129
158
|
}
|
|
159
|
+
|
|
130
160
|
status = 'closed';
|
|
131
161
|
stopListening();
|
|
132
162
|
const waiting = [...pending.values()];
|
|
133
163
|
pending.clear();
|
|
164
|
+
|
|
134
165
|
for (const entry of waiting) {
|
|
135
166
|
entry.reject(new ChatTransportError('disconnected', i18next.t('agent-chat:transport.closedBeforeAnswer')));
|
|
136
167
|
}
|
|
168
|
+
|
|
137
169
|
for (const handler of [...statusHandlers]) {
|
|
138
170
|
handler(status);
|
|
139
171
|
}
|
package/src/prompts/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export { focusPromptStart } from './logic/focus';
|
|
3
3
|
export { PROMPT_SHORTCUTS, pageKey, type PromptKeyEvent } from './logic/keys';
|
|
4
4
|
export { type PendingPrompt, type PromptAction, emptyPromptDraft, orderPrompts, type PromptAnswer, type PromptDraft } from './logic/prompts';
|
|
5
|
+
|
|
5
6
|
export {
|
|
6
7
|
type HostPrompt,
|
|
7
8
|
type PromptSubject,
|
|
@@ -13,6 +14,7 @@ export {
|
|
|
13
14
|
isBlockingSubject,
|
|
14
15
|
answerPrompt
|
|
15
16
|
} from './logic/subjects';
|
|
17
|
+
|
|
16
18
|
export { useFocusAfterAnswer, type FocusAfterAnswer } from './logic/useFocusAfterAnswer';
|
|
17
19
|
export { usePromptSession, type PromptSession } from './logic/usePromptSession';
|
|
18
20
|
export { PromptPrimary, ApprovalActions, type ApprovalButton } from './ui/PromptActions';
|
|
@@ -5,15 +5,19 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export function focusPromptStart(root: ParentNode | null | undefined): boolean {
|
|
7
7
|
const card = root?.querySelector<HTMLElement>('.prompt-card');
|
|
8
|
+
|
|
8
9
|
if (!card) {
|
|
9
10
|
return false;
|
|
10
11
|
}
|
|
12
|
+
|
|
11
13
|
const start =
|
|
12
14
|
card.querySelector<HTMLElement>('[data-prompt-entry]:not(:disabled)') ??
|
|
13
15
|
card.querySelector<HTMLElement>('[role="toolbar"] button:not(:disabled):not([data-prompt-primary]):not([data-prompt-lasting])') ??
|
|
14
16
|
card.querySelector<HTMLElement>('[data-prompt-primary]:not(:disabled)') ??
|
|
15
17
|
card.querySelector<HTMLElement>('.prompt-heading');
|
|
18
|
+
|
|
16
19
|
start?.focus({ preventScroll: true });
|
|
20
|
+
|
|
17
21
|
return start !== null;
|
|
18
22
|
}
|
|
19
23
|
|
|
@@ -21,5 +25,6 @@ export function focusPromptStart(root: ParentNode | null | undefined): boolean {
|
|
|
21
25
|
export function focusPromptHeading(root: ParentNode | null | undefined): boolean {
|
|
22
26
|
const heading = root?.querySelector<HTMLElement>('.prompt-card .prompt-heading');
|
|
23
27
|
heading?.focus({ preventScroll: true });
|
|
28
|
+
|
|
24
29
|
return !!heading;
|
|
25
30
|
}
|
|
@@ -30,6 +30,7 @@ export function stepIndex(index: number, count: number, to: ListStep): number {
|
|
|
30
30
|
if (count === 0) {
|
|
31
31
|
return -1;
|
|
32
32
|
}
|
|
33
|
+
|
|
33
34
|
switch (to) {
|
|
34
35
|
case 'first':
|
|
35
36
|
return 0;
|
|
@@ -50,19 +51,25 @@ export function choiceKey(event: PromptKeyEvent, { multiSelect, anyChosen }: { m
|
|
|
50
51
|
if (!isPlain(event)) {
|
|
51
52
|
return null;
|
|
52
53
|
}
|
|
54
|
+
|
|
53
55
|
const step = LIST_KEYS[event.key];
|
|
56
|
+
|
|
54
57
|
if (step) {
|
|
55
58
|
return { kind: 'move', to: step };
|
|
56
59
|
}
|
|
60
|
+
|
|
57
61
|
if (event.code === 'Space') {
|
|
58
62
|
return { kind: 'pick' };
|
|
59
63
|
}
|
|
64
|
+
|
|
60
65
|
if (event.key === 'Enter') {
|
|
61
66
|
if (!multiSelect) {
|
|
62
67
|
return { kind: 'pick-and-commit' };
|
|
63
68
|
}
|
|
69
|
+
|
|
64
70
|
return anyChosen ? { kind: 'commit' } : { kind: 'pick' };
|
|
65
71
|
}
|
|
72
|
+
|
|
66
73
|
return null;
|
|
67
74
|
}
|
|
68
75
|
|
|
@@ -74,13 +81,16 @@ export function answerFieldKey(
|
|
|
74
81
|
if (!isPlain(event)) {
|
|
75
82
|
return null;
|
|
76
83
|
}
|
|
84
|
+
|
|
77
85
|
if (event.key === 'ArrowUp' && belowChoices && caretAtStart) {
|
|
78
86
|
return { kind: 'to-last-choice' };
|
|
79
87
|
}
|
|
88
|
+
|
|
80
89
|
if (event.key === 'Enter') {
|
|
81
90
|
// An empty answer is not sent, and an empty line in front of the answer is never what Enter meant.
|
|
82
91
|
return hasText ? { kind: 'commit' } : { kind: 'ignore' };
|
|
83
92
|
}
|
|
93
|
+
|
|
84
94
|
return null;
|
|
85
95
|
}
|
|
86
96
|
|
|
@@ -101,9 +111,11 @@ export function pageKey(event: PromptKeyEvent, apple: boolean, typing: boolean):
|
|
|
101
111
|
if (typing || event.isComposing) {
|
|
102
112
|
return null;
|
|
103
113
|
}
|
|
114
|
+
|
|
104
115
|
if (matchesShortcut(PROMPT_SHORTCUTS.previousPrompt, event, apple)) {
|
|
105
116
|
return -1;
|
|
106
117
|
}
|
|
118
|
+
|
|
107
119
|
return matchesShortcut(PROMPT_SHORTCUTS.nextPrompt, event, apple) ? 1 : null;
|
|
108
120
|
}
|
|
109
121
|
|
|
@@ -2,17 +2,20 @@ import type { ChatApprovalItem, ChatQuestionItem, ChatQuestion } from '@adecore/
|
|
|
2
2
|
import { toggleChoice } from '../../chat/logic/answers';
|
|
3
3
|
|
|
4
4
|
export type PendingPrompt = ChatApprovalItem | ChatQuestionItem;
|
|
5
|
+
|
|
5
6
|
export type PromptAction =
|
|
6
7
|
| { kind: 'approve'; decision: 'allow' | 'allow-always' | 'deny'; message?: string }
|
|
7
8
|
| { kind: 'answer'; answers: Record<string, string> }
|
|
8
9
|
| { kind: 'dismiss' }
|
|
9
10
|
// A terminal's permission request, answered with one of the choices the host offered.
|
|
10
11
|
| { kind: 'choose'; choiceId: string };
|
|
12
|
+
|
|
11
13
|
export interface PromptAnswer {
|
|
12
14
|
choices: string[];
|
|
13
15
|
text: string;
|
|
14
16
|
custom: boolean;
|
|
15
17
|
}
|
|
18
|
+
|
|
16
19
|
export interface PromptDraft {
|
|
17
20
|
index: number;
|
|
18
21
|
answers: Record<string, PromptAnswer>;
|
|
@@ -23,9 +26,11 @@ export interface PromptDraft {
|
|
|
23
26
|
export function emptyPromptDraft(): PromptDraft {
|
|
24
27
|
return { index: 0, answers: {}, reason: '', showReason: false };
|
|
25
28
|
}
|
|
29
|
+
|
|
26
30
|
export function answerValue(answer: PromptAnswer): string {
|
|
27
31
|
return answer.custom ? answer.text.trim() : answer.choices.join(', ');
|
|
28
32
|
}
|
|
33
|
+
|
|
29
34
|
export function questionAnswer(draft: PromptDraft, question: ChatQuestion): PromptAnswer {
|
|
30
35
|
return draft.answers[question.id] ?? { choices: [], text: '', custom: question.choices.length === 0 };
|
|
31
36
|
}
|
|
@@ -46,13 +51,16 @@ export function orderPrompts<T>(prompts: readonly T[], blocking: (prompt: T) =>
|
|
|
46
51
|
|
|
47
52
|
export function nextPrompt(items: readonly PendingPrompt[], activeId: string | null): PendingPrompt | null {
|
|
48
53
|
const active = items.find((item) => item.requestId === activeId);
|
|
54
|
+
|
|
49
55
|
if (active) {
|
|
50
56
|
return active;
|
|
51
57
|
}
|
|
58
|
+
|
|
52
59
|
return orderPrompts(items, isBlockingPrompt, (item) => item.createdAt)[0] ?? null;
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
export function promptAnswers(item: ChatQuestionItem, draft: PromptDraft): Record<string, string> | null {
|
|
56
63
|
const answers = Object.fromEntries(item.questions.map((question) => [question.id, answerValue(questionAnswer(draft, question))]));
|
|
64
|
+
|
|
57
65
|
return Object.values(answers).every((answer) => answer !== '') ? answers : null;
|
|
58
66
|
}
|
|
@@ -78,8 +78,10 @@ export function approvalButtons(subject: PromptSubject, reason: string): Approva
|
|
|
78
78
|
if (subject.kind !== 'chat' || subject.item.kind !== 'approval') {
|
|
79
79
|
return [];
|
|
80
80
|
}
|
|
81
|
+
|
|
81
82
|
const { item } = subject;
|
|
82
83
|
const message = reason.trim();
|
|
84
|
+
|
|
83
85
|
return [
|
|
84
86
|
...(item.canAllowAlways && item.allowAlways
|
|
85
87
|
? [
|
|
@@ -116,18 +118,24 @@ export async function answerPrompt(
|
|
|
116
118
|
if (subject.kind === 'host') {
|
|
117
119
|
return host.prompts.answer(subject.prompt, action);
|
|
118
120
|
}
|
|
121
|
+
|
|
119
122
|
if (chat === null) {
|
|
120
123
|
throw new Error(i18next.t('agent-prompts:error.notConnected'));
|
|
121
124
|
}
|
|
125
|
+
|
|
122
126
|
const { item, nodeId } = subject;
|
|
127
|
+
|
|
123
128
|
if (action.kind === 'approve') {
|
|
124
129
|
return chat.approve(nodeId, item.requestId, action.decision, action.message);
|
|
125
130
|
}
|
|
131
|
+
|
|
126
132
|
if (action.kind === 'answer') {
|
|
127
133
|
return chat.answer(nodeId, item.requestId, action.answers);
|
|
128
134
|
}
|
|
135
|
+
|
|
129
136
|
if (action.kind === 'dismiss') {
|
|
130
137
|
return chat.dismiss(nodeId, item.id);
|
|
131
138
|
}
|
|
139
|
+
|
|
132
140
|
throw new Error(i18next.t('agent-prompts:error.noChoices'));
|
|
133
141
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useRef, useState, type MouseEvent } from 'react';
|
|
2
|
+
import { errorMessage } from '../../transport';
|
|
2
3
|
import { emptyPromptDraft, type PromptDraft } from './prompts';
|
|
3
4
|
|
|
4
5
|
interface PromptSessionOptions<T> {
|
|
@@ -42,13 +43,16 @@ export function usePromptSession<T>({ prompts, idOf, pick, disabled }: PromptSes
|
|
|
42
43
|
const busy = useRef(false);
|
|
43
44
|
const pressedRequest = useRef<string | null>(null);
|
|
44
45
|
const stillCompleted = completed.filter((id) => prompts.some((prompt) => idOf(prompt) === id));
|
|
46
|
+
|
|
45
47
|
if (stillCompleted.length !== completed.length) {
|
|
46
48
|
setCompleted(stillCompleted);
|
|
47
49
|
}
|
|
50
|
+
|
|
48
51
|
const waiting = prompts.filter((prompt) => !completed.includes(idOf(prompt)));
|
|
49
52
|
const active = pick(waiting, activeId, activeIndex);
|
|
50
53
|
const activeKey = active === null ? null : idOf(active);
|
|
51
54
|
const index = active === null ? 0 : waiting.indexOf(active);
|
|
55
|
+
|
|
52
56
|
if (index !== activeIndex) {
|
|
53
57
|
setActiveIndex(index);
|
|
54
58
|
}
|
|
@@ -60,32 +64,40 @@ export function usePromptSession<T>({ prompts, idOf, pick, disabled }: PromptSes
|
|
|
60
64
|
|
|
61
65
|
const finish = (id: string) => {
|
|
62
66
|
setCompleted((current) => [...current, id]);
|
|
67
|
+
|
|
63
68
|
setDrafts((current) => {
|
|
64
69
|
const next = { ...current };
|
|
65
70
|
delete next[id];
|
|
71
|
+
|
|
66
72
|
return next;
|
|
67
73
|
});
|
|
68
74
|
};
|
|
75
|
+
|
|
69
76
|
const act = async (prompt: T, send: () => Promise<void>): Promise<'sent' | 'failed' | 'busy'> => {
|
|
70
77
|
if (busy.current || disabled) {
|
|
71
78
|
return 'busy';
|
|
72
79
|
}
|
|
80
|
+
|
|
73
81
|
const id = idOf(prompt);
|
|
74
82
|
busy.current = true;
|
|
75
83
|
setSending(true);
|
|
76
84
|
setError(null);
|
|
85
|
+
|
|
77
86
|
try {
|
|
78
87
|
await send();
|
|
79
88
|
finish(id);
|
|
89
|
+
|
|
80
90
|
return 'sent';
|
|
81
91
|
} catch (cause) {
|
|
82
|
-
setError({ id, message:
|
|
92
|
+
setError({ id, message: errorMessage(cause) });
|
|
93
|
+
|
|
83
94
|
return 'failed';
|
|
84
95
|
} finally {
|
|
85
96
|
busy.current = false;
|
|
86
97
|
setSending(false);
|
|
87
98
|
}
|
|
88
99
|
};
|
|
100
|
+
|
|
89
101
|
return {
|
|
90
102
|
active,
|
|
91
103
|
activeId: activeKey,
|
|
@@ -32,6 +32,7 @@ function ApprovalDetails({ item }: { item: ChatApprovalItem }) {
|
|
|
32
32
|
const input = typeof item.input === 'object' && item.input !== null ? (item.input as Record<string, unknown>) : {};
|
|
33
33
|
// Some agents describe a command by the command itself, which the box under it already shows.
|
|
34
34
|
const describes = item.description && item.description.trim() !== (typeof input.command === 'string' ? input.command.trim() : null);
|
|
35
|
+
|
|
35
36
|
return (
|
|
36
37
|
<>
|
|
37
38
|
{describes && <p className="text-sm text-text-muted">{item.description}</p>}
|
|
@@ -82,6 +83,7 @@ export function ApprovalBody({
|
|
|
82
83
|
const host = useChatHost();
|
|
83
84
|
const { t } = useTranslation('agent-prompts');
|
|
84
85
|
const AnswerField = host.dictation.Textarea;
|
|
86
|
+
|
|
85
87
|
return (
|
|
86
88
|
<>
|
|
87
89
|
<ApprovalDetails item={item} />
|
|
@@ -32,6 +32,7 @@ export function ApprovalActions({
|
|
|
32
32
|
sending: boolean;
|
|
33
33
|
}) {
|
|
34
34
|
const { t } = useTranslation('agent-prompts');
|
|
35
|
+
|
|
35
36
|
return (
|
|
36
37
|
<>
|
|
37
38
|
{onAddReason && (
|
|
@@ -87,6 +88,7 @@ export function QuestionActions({
|
|
|
87
88
|
onCommit(): void;
|
|
88
89
|
}) {
|
|
89
90
|
const { t } = useTranslation(['agent-prompts', 'agent-chat']);
|
|
91
|
+
|
|
90
92
|
return (
|
|
91
93
|
<>
|
|
92
94
|
{index > 0 && (
|
|
@@ -35,6 +35,7 @@ interface PromptCardProps {
|
|
|
35
35
|
function onCardKeyDown(event: KeyboardEvent<HTMLDivElement>, apple: boolean) {
|
|
36
36
|
const card = event.currentTarget;
|
|
37
37
|
const target = event.target instanceof HTMLElement ? event.target : null;
|
|
38
|
+
|
|
38
39
|
if (!event.defaultPrevented) {
|
|
39
40
|
if (isPrimaryKey(event.nativeEvent, apple)) {
|
|
40
41
|
event.preventDefault();
|
|
@@ -46,6 +47,7 @@ function onCardKeyDown(event: KeyboardEvent<HTMLDivElement>, apple: boolean) {
|
|
|
46
47
|
} else {
|
|
47
48
|
const toolbar = target?.closest('[role="toolbar"]');
|
|
48
49
|
const step = toolbar ? toolbarKey(event.nativeEvent) : null;
|
|
50
|
+
|
|
49
51
|
if (toolbar && step !== null) {
|
|
50
52
|
event.preventDefault();
|
|
51
53
|
const buttons = [...toolbar.querySelectorAll<HTMLButtonElement>('button:not(:disabled)')];
|
|
@@ -54,6 +56,7 @@ function onCardKeyDown(event: KeyboardEvent<HTMLDivElement>, apple: boolean) {
|
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
}
|
|
59
|
+
|
|
57
60
|
if (event.defaultPrevented || staysInCard(event.nativeEvent)) {
|
|
58
61
|
event.stopPropagation();
|
|
59
62
|
}
|
|
@@ -63,6 +66,7 @@ function onCardKeyDown(event: KeyboardEvent<HTMLDivElement>, apple: boolean) {
|
|
|
63
66
|
export function PromptCard({ kind, heading, meta, top, actions, busy, notice, error, children }: PromptCardProps) {
|
|
64
67
|
const { t } = useTranslation('agent-prompts');
|
|
65
68
|
const { isApplePlatform } = useChatHost();
|
|
69
|
+
|
|
66
70
|
const card = (
|
|
67
71
|
<div
|
|
68
72
|
className="prompt-card flex min-h-0 flex-col gap-2 p-3"
|
|
@@ -96,9 +100,11 @@ export function PromptCard({ kind, heading, meta, top, actions, busy, notice, er
|
|
|
96
100
|
</div>
|
|
97
101
|
</div>
|
|
98
102
|
);
|
|
103
|
+
|
|
99
104
|
if (top === undefined) {
|
|
100
105
|
return card;
|
|
101
106
|
}
|
|
107
|
+
|
|
102
108
|
return (
|
|
103
109
|
<div className="flex min-h-0 flex-col">
|
|
104
110
|
{top}
|
|
@@ -26,23 +26,29 @@ export function PromptView(props: PromptViewProps) {
|
|
|
26
26
|
const question = item.kind === 'question' ? item.questions[Math.min(draft.index, item.questions.length - 1)]! : null;
|
|
27
27
|
const answer = question ? questionAnswer(draft, question) : null;
|
|
28
28
|
const last = item.kind === 'question' && draft.index === item.questions.length - 1;
|
|
29
|
+
|
|
29
30
|
// Whether Next or Answer may go with this draft. The keys ask it as well as the button.
|
|
30
31
|
const readyWith = (next: PromptDraft): boolean =>
|
|
31
32
|
item.kind === 'question' && !!question && !!answerValue(questionAnswer(next, question)) && !(last && !promptAnswers(item, next));
|
|
33
|
+
|
|
32
34
|
const commit = (next: PromptDraft) => {
|
|
33
35
|
if (item.kind !== 'question' || locked || !readyWith(next)) {
|
|
34
36
|
return;
|
|
35
37
|
}
|
|
38
|
+
|
|
36
39
|
if (!last) {
|
|
37
40
|
onDraft({ ...next, index: next.index + 1 });
|
|
38
41
|
} else {
|
|
39
42
|
const answers = promptAnswers(item, next);
|
|
43
|
+
|
|
40
44
|
if (answers) {
|
|
41
45
|
onAction({ kind: 'answer', answers });
|
|
42
46
|
}
|
|
43
47
|
}
|
|
44
48
|
};
|
|
49
|
+
|
|
45
50
|
const multiple = item.kind === 'question' && item.questions.length > 1;
|
|
51
|
+
|
|
46
52
|
const meta =
|
|
47
53
|
subject.source !== undefined || more > 0 || hasDraft || multiple ? (
|
|
48
54
|
<>
|
|
@@ -38,7 +38,9 @@ export function QuestionBody({
|
|
|
38
38
|
if (shownQuestion.current === question.id) {
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
+
|
|
41
42
|
shownQuestion.current = question.id;
|
|
43
|
+
|
|
42
44
|
if (document.activeElement === null || document.activeElement === document.body) {
|
|
43
45
|
(items.current[tabStop] ?? items.current[0])?.focus({ preventScroll: true });
|
|
44
46
|
}
|
|
@@ -49,10 +51,13 @@ export function QuestionBody({
|
|
|
49
51
|
multiSelect: question.multiSelect,
|
|
50
52
|
anyChosen: !answer.custom && answer.choices.length > 0
|
|
51
53
|
});
|
|
54
|
+
|
|
52
55
|
if (action === null) {
|
|
53
56
|
return;
|
|
54
57
|
}
|
|
58
|
+
|
|
55
59
|
event.preventDefault();
|
|
60
|
+
|
|
56
61
|
if (action.kind === 'move') {
|
|
57
62
|
items.current[stepIndex(index, count, action.to)]?.focus();
|
|
58
63
|
} else if (action.kind === 'commit') {
|
|
@@ -60,6 +65,7 @@ export function QuestionBody({
|
|
|
60
65
|
} else {
|
|
61
66
|
const next = pickPromptChoice(answer, question, label);
|
|
62
67
|
onAnswer(next);
|
|
68
|
+
|
|
63
69
|
if (action.kind === 'pick-and-commit') {
|
|
64
70
|
onCommit(next);
|
|
65
71
|
}
|
|
@@ -68,15 +74,19 @@ export function QuestionBody({
|
|
|
68
74
|
|
|
69
75
|
const onFieldKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
70
76
|
const field = event.currentTarget;
|
|
77
|
+
|
|
71
78
|
const action = answerFieldKey(event.nativeEvent, {
|
|
72
79
|
belowChoices: question.choices.length > 0,
|
|
73
80
|
caretAtStart: field.selectionStart === 0 && field.selectionEnd === 0,
|
|
74
81
|
hasText: field.value.trim() !== ''
|
|
75
82
|
});
|
|
83
|
+
|
|
76
84
|
if (action === null) {
|
|
77
85
|
return;
|
|
78
86
|
}
|
|
87
|
+
|
|
79
88
|
event.preventDefault();
|
|
89
|
+
|
|
80
90
|
if (action.kind === 'to-last-choice') {
|
|
81
91
|
items.current[question.choices.length - 1]?.focus();
|
|
82
92
|
} else if (action.kind === 'commit') {
|
|
@@ -91,6 +101,7 @@ export function QuestionBody({
|
|
|
91
101
|
<div role={question.multiSelect ? 'group' : 'radiogroup'} aria-label={question.question} className="flex flex-col gap-2">
|
|
92
102
|
{question.choices.map((choice, index) => {
|
|
93
103
|
const selected = !answer.custom && answer.choices.includes(choice.label);
|
|
104
|
+
|
|
94
105
|
return (
|
|
95
106
|
<button
|
|
96
107
|
key={choice.label}
|
|
@@ -4,5 +4,6 @@ import { useChatHost } from '../host';
|
|
|
4
4
|
/* The colors an account may wear, the host's palette. */
|
|
5
5
|
export function AccountColors({ value, label, onChange }: { value: string | undefined; label: string; onChange(id: string): void }) {
|
|
6
6
|
const { all, featured, label: labelOf } = useChatHost().accents;
|
|
7
|
+
|
|
7
8
|
return <AccentSwatches value={value} label={label} accents={all} featured={featured} labelOf={labelOf} onValueChange={onChange} />;
|
|
8
9
|
}
|
|
@@ -32,14 +32,19 @@ interface AccountDetailProps {
|
|
|
32
32
|
/* The name a person types, saved when they leave the field; an empty one goes back to what was there. */
|
|
33
33
|
function NameField({ value, label, onSave }: { value: string; label: string; onSave(name: string): void }) {
|
|
34
34
|
const [draft, setDraft] = useState(value);
|
|
35
|
+
|
|
35
36
|
const commit = (): void => {
|
|
36
37
|
const name = draft.trim();
|
|
38
|
+
|
|
37
39
|
if (name === '' || name === value) {
|
|
38
40
|
setDraft(value);
|
|
41
|
+
|
|
39
42
|
return;
|
|
40
43
|
}
|
|
44
|
+
|
|
41
45
|
onSave(name);
|
|
42
46
|
};
|
|
47
|
+
|
|
43
48
|
return (
|
|
44
49
|
<Input
|
|
45
50
|
className="w-55 max-w-full"
|
|
@@ -80,9 +85,11 @@ export function AccountDetail({ provider, entry, login, canOpenLogin, loginBlock
|
|
|
80
85
|
if (trouble !== null) {
|
|
81
86
|
return trouble;
|
|
82
87
|
}
|
|
88
|
+
|
|
83
89
|
if (!signedIn) {
|
|
84
90
|
return t('account.login.signedOutDetail');
|
|
85
91
|
}
|
|
92
|
+
|
|
86
93
|
return status?.plan ? t('account.login.planDetail', { plan: status.plan }) : t('account.login.detail');
|
|
87
94
|
};
|
|
88
95
|
|