@opengeni/react 1.4.2 → 1.4.5-canary.37458084490001
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/README.md +17 -6
- package/dist/artifacts.d.ts +1 -0
- package/dist/artifacts.js +22 -15
- package/dist/artifacts.js.map +1 -1
- package/dist/{browser-viewer-WGJ43YGI.js → browser-viewer-CV23VIHB.js} +8 -6
- package/dist/chunk-3ZC6QPOM.js +174 -0
- package/dist/chunk-3ZC6QPOM.js.map +1 -0
- package/dist/{chunk-YVFRBXST.js → chunk-4BCVCJSU.js} +2 -2
- package/dist/{chunk-JHTIMBOL.js → chunk-5JSUNNQN.js} +26 -4
- package/dist/{chunk-JHTIMBOL.js.map → chunk-5JSUNNQN.js.map} +1 -1
- package/dist/{chunk-MDIXFKA3.js → chunk-6FOTSVWU.js} +303 -109
- package/dist/chunk-6FOTSVWU.js.map +1 -0
- package/dist/{chunk-IUHHPMAX.js → chunk-7RRBX5EM.js} +14 -85
- package/dist/chunk-7RRBX5EM.js.map +1 -0
- package/dist/{chunk-L2J3PU7A.js → chunk-7ZB7I3KP.js} +24 -98
- package/dist/chunk-7ZB7I3KP.js.map +1 -0
- package/dist/{chunk-LVS3MH5T.js → chunk-ATIRUHBZ.js} +128 -10
- package/dist/chunk-ATIRUHBZ.js.map +1 -0
- package/dist/chunk-BOFEQC6K.js +162 -0
- package/dist/chunk-BOFEQC6K.js.map +1 -0
- package/dist/{chunk-VCSERYXH.js → chunk-CESBPQP5.js} +309 -162
- package/dist/chunk-CESBPQP5.js.map +1 -0
- package/dist/{chunk-IYQCWGU5.js → chunk-GHJU7XG7.js} +35 -62
- package/dist/chunk-GHJU7XG7.js.map +1 -0
- package/dist/{chunk-G6YRCIYJ.js → chunk-HNVWMDNN.js} +55 -60
- package/dist/chunk-HNVWMDNN.js.map +1 -0
- package/dist/{chunk-PHBFJCEU.js → chunk-I5EGRXXG.js} +110 -7
- package/dist/chunk-I5EGRXXG.js.map +1 -0
- package/dist/{chunk-PBCZL4JP.js → chunk-INIZSNXT.js} +2 -2
- package/dist/{chunk-XS67YG5R.js → chunk-IX3K4THH.js} +24 -3
- package/dist/chunk-IX3K4THH.js.map +1 -0
- package/dist/chunk-IZV5FTJC.js +55 -0
- package/dist/chunk-IZV5FTJC.js.map +1 -0
- package/dist/chunk-JDJJMBB3.js +1883 -0
- package/dist/chunk-JDJJMBB3.js.map +1 -0
- package/dist/{chunk-C5BF4L26.js → chunk-KHF2VRXF.js} +7 -7
- package/dist/{chunk-QMMCJGFI.js → chunk-L5OKM2VX.js} +2 -2
- package/dist/chunk-LHF6BVM4.js +83 -0
- package/dist/chunk-LHF6BVM4.js.map +1 -0
- package/dist/chunk-N7S72EXK.js +25 -0
- package/dist/chunk-N7S72EXK.js.map +1 -0
- package/dist/{chunk-CVYCLFMH.js → chunk-PEHTX3V7.js} +19 -1
- package/dist/chunk-PEHTX3V7.js.map +1 -0
- package/dist/chunk-R5R5RQQD.js +51 -0
- package/dist/chunk-R5R5RQQD.js.map +1 -0
- package/dist/chunk-RI42H5IU.js +326 -0
- package/dist/chunk-RI42H5IU.js.map +1 -0
- package/dist/{chunk-JH7V627O.js → chunk-SHLZMAJN.js} +2 -11
- package/dist/chunk-SHLZMAJN.js.map +1 -0
- package/dist/{chunk-CREMJMVN.js → chunk-TZQSHCZN.js} +692 -1996
- package/dist/chunk-TZQSHCZN.js.map +1 -0
- package/dist/chunk-ZIB4H5LN.js +101 -0
- package/dist/chunk-ZIB4H5LN.js.map +1 -0
- package/dist/client.d.ts +9 -3
- package/dist/clipboard.js +1 -1
- package/dist/components/browser-viewer.d.ts +6 -0
- package/dist/components/composer.d.ts +3 -69
- package/dist/components/human-input-form.d.ts +3 -32
- package/dist/components/markdown.d.ts +8 -0
- package/dist/components/model-policy-picker.d.ts +4 -37
- package/dist/components/opper-mark.d.ts +7 -0
- package/dist/components/provider-recovery-notice.d.ts +15 -0
- package/dist/components/remark-soft-line-breaks.d.ts +38 -0
- package/dist/components/sandbox-workspace.d.ts +13 -0
- package/dist/components/session-chrome.d.ts +5 -3
- package/dist/components/session-conversation.d.ts +15 -3
- package/dist/composer-messages.d.ts +74 -0
- package/dist/composer-messages.js +9 -0
- package/dist/composer.js +15 -10
- package/dist/{computer-viewer-46UIN7F3.js → computer-viewer-K4CKYRX6.js} +4 -4
- package/dist/connect-setup.d.ts +2 -0
- package/dist/connect.d.ts +1 -1
- package/dist/connect.js +21 -6
- package/dist/connect.js.map +1 -1
- package/dist/{embedded-voice-NIL4RDX3.js → embedded-voice-3ZFWWJ6O.js} +6 -4
- package/dist/{embedded-voice-NIL4RDX3.js.map → embedded-voice-3ZFWWJ6O.js.map} +1 -1
- package/dist/hooks/shared-machines-list.d.ts +4 -0
- package/dist/hooks/use-client-config-flags.d.ts +6 -0
- package/dist/hooks/use-computer-session.d.ts +2 -0
- package/dist/hooks/use-goal.d.ts +2 -1
- package/dist/human-input-model.d.ts +40 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +116 -73
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +5 -5
- package/dist/{latest-question-2POFPYNB.js → latest-question-SWC6QEVN.js} +3 -3
- package/dist/lib/clipboard.d.ts +7 -0
- package/dist/lib/provider-recovery.d.ts +49 -0
- package/dist/machines.js +2 -2
- package/dist/model-mark-paths.d.ts +34 -0
- package/dist/model-mark-paths.js +16 -0
- package/dist/model-picker-model.d.ts +111 -0
- package/dist/model-picker-model.js +25 -0
- package/dist/model-picker-model.js.map +1 -0
- package/dist/{model-policy-picker-menu-WRNSRFQC.js → model-policy-picker-menu-NM4RPTY6.js} +6 -4
- package/dist/model-policy-picker-menu-NM4RPTY6.js.map +1 -0
- package/dist/model-policy.d.ts +10 -0
- package/dist/model-policy.js +5 -1
- package/dist/organization-model.d.ts +32 -0
- package/dist/organization-model.js +65 -0
- package/dist/organization-model.js.map +1 -0
- package/dist/{platform-activity-row-PJJVATTZ.js → platform-activity-row-NJ47YBXW.js} +7 -84
- package/dist/platform-activity-row-NJ47YBXW.js.map +1 -0
- package/dist/queue-presentation-model.d.ts +72 -0
- package/dist/queue-presentation-model.js +38 -0
- package/dist/queue-presentation-model.js.map +1 -0
- package/dist/{queue-surface-implementation-67FS7FUI.js → queue-surface-implementation-YPJFQ3O6.js} +24 -252
- package/dist/queue-surface-implementation-YPJFQ3O6.js.map +1 -0
- package/dist/realtime.js +5 -3
- package/dist/{sandbox-files-34CJJNOY.js → sandbox-files-RCVCMXG7.js} +5 -5
- package/dist/sandbox-files-RCVCMXG7.js.map +1 -0
- package/dist/sandbox-label-model.d.ts +23 -0
- package/dist/sandbox-label-model.js +28 -0
- package/dist/sandbox-label-model.js.map +1 -0
- package/dist/session-attention-model.d.ts +8 -0
- package/dist/session-attention-model.js +46 -0
- package/dist/session-attention-model.js.map +1 -0
- package/dist/session-feedback-model.d.ts +20 -0
- package/dist/session-feedback-model.js +56 -0
- package/dist/session-feedback-model.js.map +1 -0
- package/dist/session-list-model.d.ts +96 -0
- package/dist/session-list-model.js +219 -0
- package/dist/session-list-model.js.map +1 -0
- package/dist/session-status-model.d.ts +19 -0
- package/dist/session-ui.js +26 -17
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +8 -6
- package/dist/testing/session-scenarios.d.ts +10 -0
- package/dist/testing/session-scenarios.js +303 -0
- package/dist/testing/session-scenarios.js.map +1 -0
- package/dist/timeline/genie-copy.d.ts +3 -0
- package/dist/timeline/genie-loading.d.ts +11 -0
- package/dist/timeline/index.d.ts +1 -1
- package/dist/timeline/notice-presentation.d.ts +7 -0
- package/dist/timeline/patch-apply-command.d.ts +18 -0
- package/dist/timeline/platform-activity-presentation.d.ts +7 -0
- package/dist/timeline/presented-image.d.ts +6 -1
- package/dist/timeline/question-nav-model.d.ts +23 -0
- package/dist/timeline/registry.d.ts +10 -1
- package/dist/timeline/retained-loaders.d.ts +1 -1
- package/dist/timeline/tool-presentation.d.ts +172 -0
- package/dist/timeline/turn-summary-model.d.ts +90 -0
- package/dist/timeline/turn-summary.d.ts +4 -74
- package/dist/timeline/work-presentation.d.ts +42 -0
- package/dist/timeline-model.d.ts +24 -0
- package/dist/timeline-model.js +152 -0
- package/dist/timeline-model.js.map +1 -0
- package/package.json +47 -3
- package/src/artifacts.ts +1 -0
- package/src/client.ts +9 -3
- package/src/components/browser-viewer.tsx +23 -2
- package/src/components/claude-mark.tsx +2 -1
- package/src/components/composer.tsx +4 -144
- package/src/components/computer-viewer.tsx +139 -30
- package/src/components/grok-mark.tsx +2 -1
- package/src/components/human-input-form.tsx +13 -124
- package/src/components/markdown.tsx +19 -1
- package/src/components/message-timeline.tsx +45 -99
- package/src/components/model-mark.tsx +2 -1
- package/src/components/model-policy-picker-menu.tsx +30 -85
- package/src/components/model-policy-picker.tsx +15 -148
- package/src/components/opper-mark.tsx +18 -0
- package/src/components/provider-recovery-notice.tsx +41 -0
- package/src/components/queue-surface-implementation.tsx +20 -349
- package/src/components/remark-soft-line-breaks.ts +166 -0
- package/src/components/sandbox-workspace.tsx +17 -1
- package/src/components/session-chrome.tsx +114 -99
- package/src/components/session-conversation.tsx +55 -6
- package/src/components/session-list.tsx +1 -1
- package/src/components/session-status.tsx +17 -16
- package/src/composer-messages.ts +150 -0
- package/src/connect-setup.tsx +45 -11
- package/src/connect.ts +1 -1
- package/src/hooks/shared-machines-list.ts +34 -1
- package/src/hooks/use-browser-session.ts +445 -168
- package/src/hooks/use-client-config-flags.ts +8 -0
- package/src/hooks/use-computer-session.ts +334 -98
- package/src/hooks/use-goal.ts +66 -10
- package/src/human-input-model.ts +147 -0
- package/src/index.ts +16 -0
- package/src/lib/clipboard.ts +29 -0
- package/src/lib/format.ts +7 -0
- package/src/lib/provider-recovery.ts +202 -0
- package/src/model-mark-paths.ts +53 -0
- package/src/model-picker-model.ts +330 -0
- package/src/model-policy.ts +43 -2
- package/src/organization-model.ts +118 -0
- package/src/queue-presentation-model.ts +464 -0
- package/src/sandbox-label-model.ts +41 -0
- package/src/session-attention-model.ts +59 -0
- package/src/session-feedback-model.ts +95 -0
- package/src/session-list-model.ts +327 -0
- package/src/session-status-model.ts +62 -0
- package/src/testing/session-scenarios.ts +326 -0
- package/src/timeline/activity-rail.tsx +5 -17
- package/src/timeline/genie-copy.ts +19 -0
- package/src/timeline/genie-loading.tsx +13 -18
- package/src/timeline/index.ts +1 -0
- package/src/timeline/notice-presentation.ts +22 -0
- package/src/timeline/patch-apply-command.tsx +135 -0
- package/src/timeline/platform-activity-presentation.ts +122 -0
- package/src/timeline/platform-activity-row.tsx +6 -96
- package/src/timeline/presented-image.ts +16 -2
- package/src/timeline/question-nav-model.ts +28 -0
- package/src/timeline/registry.ts +11 -1
- package/src/timeline/retained-loaders.ts +9 -4
- package/src/timeline/rolling-activity.tsx +7 -19
- package/src/timeline/tool-presentation.ts +1316 -0
- package/src/timeline/tool-renderers.tsx +226 -1025
- package/src/timeline/turn-summary-model.ts +338 -0
- package/src/timeline/turn-summary.tsx +21 -326
- package/src/timeline/work-presentation.ts +139 -0
- package/src/timeline-model.ts +108 -0
- package/styles/compiled.css +8 -0
- package/styles/connect.css +4 -0
- package/dist/chunk-CREMJMVN.js.map +0 -1
- package/dist/chunk-CVYCLFMH.js.map +0 -1
- package/dist/chunk-G6YRCIYJ.js.map +0 -1
- package/dist/chunk-HXYOSSQO.js +0 -117
- package/dist/chunk-HXYOSSQO.js.map +0 -1
- package/dist/chunk-IUHHPMAX.js.map +0 -1
- package/dist/chunk-IYQCWGU5.js.map +0 -1
- package/dist/chunk-JH7V627O.js.map +0 -1
- package/dist/chunk-L2J3PU7A.js.map +0 -1
- package/dist/chunk-LVS3MH5T.js.map +0 -1
- package/dist/chunk-MDIXFKA3.js.map +0 -1
- package/dist/chunk-PHBFJCEU.js.map +0 -1
- package/dist/chunk-VCSERYXH.js.map +0 -1
- package/dist/chunk-XS67YG5R.js.map +0 -1
- package/dist/platform-activity-row-PJJVATTZ.js.map +0 -1
- package/dist/queue-surface-implementation-67FS7FUI.js.map +0 -1
- /package/dist/{browser-viewer-WGJ43YGI.js.map → browser-viewer-CV23VIHB.js.map} +0 -0
- /package/dist/{chunk-YVFRBXST.js.map → chunk-4BCVCJSU.js.map} +0 -0
- /package/dist/{chunk-PBCZL4JP.js.map → chunk-INIZSNXT.js.map} +0 -0
- /package/dist/{chunk-C5BF4L26.js.map → chunk-KHF2VRXF.js.map} +0 -0
- /package/dist/{chunk-QMMCJGFI.js.map → chunk-L5OKM2VX.js.map} +0 -0
- /package/dist/{computer-viewer-46UIN7F3.js.map → composer-messages.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-WRNSRFQC.js.map → computer-viewer-K4CKYRX6.js.map} +0 -0
- /package/dist/{latest-question-2POFPYNB.js.map → latest-question-SWC6QEVN.js.map} +0 -0
- /package/dist/{sandbox-files-34CJJNOY.js.map → model-mark-paths.js.map} +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
HumanInputAnswer,
|
|
3
2
|
HumanInputQuestion,
|
|
4
3
|
SkillRecord,
|
|
5
4
|
SessionHumanInputRequest,
|
|
@@ -9,64 +8,24 @@ import { ChevronDownIcon, ChevronUpIcon, MessageCircleQuestionIcon } from "lucid
|
|
|
9
8
|
import { useEffect, useId, useRef, useState, type FormEvent, type ReactNode } from "react";
|
|
10
9
|
import { cn } from "../lib/cn";
|
|
11
10
|
import { useErrorMessage } from "../lib/error-message";
|
|
11
|
+
import {
|
|
12
|
+
answersFromDrafts,
|
|
13
|
+
defaultHumanInputFormMessages,
|
|
14
|
+
emptyDraft,
|
|
15
|
+
initialDrafts,
|
|
16
|
+
type HumanInputAnswerDraft,
|
|
17
|
+
type HumanInputFormMessages,
|
|
18
|
+
} from "../human-input-model";
|
|
12
19
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
export {
|
|
21
|
+
answersFromDrafts,
|
|
22
|
+
defaultHumanInputFormMessages,
|
|
23
|
+
type HumanInputAnswerDraft,
|
|
24
|
+
type HumanInputFormMessages,
|
|
17
25
|
};
|
|
18
26
|
|
|
19
27
|
class SkillReviewVerificationError extends Error {}
|
|
20
28
|
|
|
21
|
-
export type HumanInputFormMessages = {
|
|
22
|
-
title: string;
|
|
23
|
-
description: string;
|
|
24
|
-
submit: string;
|
|
25
|
-
skip: string;
|
|
26
|
-
submitting: string;
|
|
27
|
-
other: string;
|
|
28
|
-
deadlineLabel: string;
|
|
29
|
-
formatDeadline: (value: string) => string;
|
|
30
|
-
required: string;
|
|
31
|
-
otherRequired: string;
|
|
32
|
-
minSelections: (count: number) => string;
|
|
33
|
-
maxSelections: (count: number) => string;
|
|
34
|
-
optional: string;
|
|
35
|
-
/** Shown when the question list overflows the card and more content is below. */
|
|
36
|
-
moreBelow: string;
|
|
37
|
-
questionCount: (count: number) => string;
|
|
38
|
-
collapse: string;
|
|
39
|
-
expand: string;
|
|
40
|
-
selectionHint: (min: number | null | undefined, max: number | null | undefined) => string | null;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export const defaultHumanInputFormMessages: HumanInputFormMessages = {
|
|
44
|
-
title: "Input required",
|
|
45
|
-
/** Multi-question chrome has no default subtitle; hosts may still override. */
|
|
46
|
-
description: "",
|
|
47
|
-
submit: "Send answers",
|
|
48
|
-
skip: "Skip",
|
|
49
|
-
submitting: "Submitting…",
|
|
50
|
-
other: "Other",
|
|
51
|
-
deadlineLabel: "Expires",
|
|
52
|
-
formatDeadline,
|
|
53
|
-
required: "This question is required.",
|
|
54
|
-
otherRequired: "Enter a value for Other.",
|
|
55
|
-
minSelections: (count) => `Choose at least ${count} option${count === 1 ? "" : "s"}.`,
|
|
56
|
-
maxSelections: (count) => `Choose no more than ${count} option${count === 1 ? "" : "s"}.`,
|
|
57
|
-
optional: "Optional",
|
|
58
|
-
moreBelow: "More below",
|
|
59
|
-
questionCount: (count) => `${count} questions`,
|
|
60
|
-
collapse: "Collapse",
|
|
61
|
-
expand: "Expand",
|
|
62
|
-
selectionHint: (min, max) => {
|
|
63
|
-
if (min != null && max != null) return `Choose ${min}–${max}.`;
|
|
64
|
-
if (min != null) return `Choose at least ${min}.`;
|
|
65
|
-
if (max != null) return `Choose up to ${max}.`;
|
|
66
|
-
return null;
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
|
|
70
29
|
export type HumanInputFormProps = {
|
|
71
30
|
/** Scoped immutable revision reader. Saving a Skill fails closed without its full preview. */
|
|
72
31
|
loadSkillReview?:
|
|
@@ -1006,73 +965,3 @@ function QuestionControls({
|
|
|
1006
965
|
</>
|
|
1007
966
|
);
|
|
1008
967
|
}
|
|
1009
|
-
|
|
1010
|
-
export function answersFromDrafts(
|
|
1011
|
-
questions: HumanInputQuestion[],
|
|
1012
|
-
drafts: Record<string, HumanInputAnswerDraft>,
|
|
1013
|
-
messageOverrides: Partial<HumanInputFormMessages> = {},
|
|
1014
|
-
): { answers: HumanInputAnswer[]; errors: Record<string, string> } {
|
|
1015
|
-
const messages = { ...defaultHumanInputFormMessages, ...messageOverrides };
|
|
1016
|
-
const answers: HumanInputAnswer[] = [];
|
|
1017
|
-
const errors: Record<string, string> = {};
|
|
1018
|
-
for (const question of questions) {
|
|
1019
|
-
const draft = drafts[question.id] ?? emptyDraft();
|
|
1020
|
-
const values = question.kind === "text" ? draft.values.filter(Boolean) : draft.values;
|
|
1021
|
-
const other = draft.otherSelected ? draft.other : "";
|
|
1022
|
-
const hasOther = Boolean(other.trim());
|
|
1023
|
-
const supplied = values.length + (hasOther ? 1 : 0);
|
|
1024
|
-
|
|
1025
|
-
// Other-selected-but-empty must win over generic "required" — otherwise the
|
|
1026
|
-
// user sees the wrong diagnosis next to a clearly selected control.
|
|
1027
|
-
if (question.kind !== "text" && draft.otherSelected && !hasOther) {
|
|
1028
|
-
errors[question.id] = messages.otherRequired;
|
|
1029
|
-
continue;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
if (question.required && supplied === 0) {
|
|
1033
|
-
errors[question.id] = messages.required;
|
|
1034
|
-
continue;
|
|
1035
|
-
}
|
|
1036
|
-
if (question.kind !== "text") {
|
|
1037
|
-
const min = question.validation?.minSelections;
|
|
1038
|
-
const max = question.kind === "single_select" ? 1 : question.validation?.maxSelections;
|
|
1039
|
-
if (min != null && supplied < min) {
|
|
1040
|
-
errors[question.id] = messages.minSelections(min);
|
|
1041
|
-
continue;
|
|
1042
|
-
}
|
|
1043
|
-
if (max != null && supplied > max) {
|
|
1044
|
-
errors[question.id] = messages.maxSelections(max);
|
|
1045
|
-
continue;
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
if (supplied > 0) {
|
|
1049
|
-
answers.push({
|
|
1050
|
-
questionId: question.id,
|
|
1051
|
-
values,
|
|
1052
|
-
...(hasOther ? { other } : {}),
|
|
1053
|
-
});
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
return { answers, errors };
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
function initialDrafts(questions: HumanInputQuestion[]): Record<string, HumanInputAnswerDraft> {
|
|
1060
|
-
return Object.fromEntries(questions.map((question) => [question.id, emptyDraft()]));
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
function emptyDraft(): HumanInputAnswerDraft {
|
|
1064
|
-
return { values: [], other: "", otherSelected: false };
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
function formatDeadline(value: string): string {
|
|
1068
|
-
const date = new Date(value);
|
|
1069
|
-
if (Number.isNaN(date.getTime())) return value;
|
|
1070
|
-
const ms = date.getTime() - Date.now();
|
|
1071
|
-
if (ms <= 0) return "deadline passed";
|
|
1072
|
-
const minutes = Math.round(ms / 60_000);
|
|
1073
|
-
if (minutes < 1) return "in under a minute";
|
|
1074
|
-
if (minutes < 60) return `in ${minutes}m`;
|
|
1075
|
-
const hours = Math.round(minutes / 60);
|
|
1076
|
-
if (hours < 48) return `in ${hours}h`;
|
|
1077
|
-
return date.toLocaleString();
|
|
1078
|
-
}
|
|
@@ -28,6 +28,7 @@ import { MOTION_INSPECT_SCALE } from "../lib/motion-inspect";
|
|
|
28
28
|
import { CopyButton } from "./copy-button";
|
|
29
29
|
import { PreviewLoading } from "./preview-loading";
|
|
30
30
|
import type { observeMarkdownTableLayout } from "./markdown-table-layout";
|
|
31
|
+
import { remarkSoftLineBreaks } from "./remark-soft-line-breaks";
|
|
31
32
|
import { softenStreamingMarkdown } from "./soften-streaming-markdown";
|
|
32
33
|
import { createStreamReveal, rehypeStreamReveal, type StreamReveal } from "./stream-reveal";
|
|
33
34
|
import { TooltipProvider } from "./tooltip";
|
|
@@ -81,6 +82,14 @@ export type MarkdownProps = {
|
|
|
81
82
|
renderInteractiveBlock?: ((block: MarkdownInteractiveBlock) => ReactNode) | undefined;
|
|
82
83
|
children: string;
|
|
83
84
|
className?: string | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* Render a single newline inside prose as a line break (`<br>`), the way chat
|
|
87
|
+
* apps show messages; CommonMark otherwise folds it into a space. Chat message
|
|
88
|
+
* bodies enable this. Leave it off for documents and other authored Markdown.
|
|
89
|
+
* Code (fenced and inline), lists, tables, and blank-line paragraphs are
|
|
90
|
+
* unaffected either way.
|
|
91
|
+
*/
|
|
92
|
+
softLineBreaks?: boolean | undefined;
|
|
84
93
|
/**
|
|
85
94
|
* While true, newly arrived source fades in through tip ink (`.og-stream-ink`):
|
|
86
95
|
* an age window over each append batch — fast streams keep a large soft band,
|
|
@@ -630,10 +639,19 @@ function MarkdownTable({ children, className, ...props }: ComponentPropsWithoutR
|
|
|
630
639
|
);
|
|
631
640
|
}
|
|
632
641
|
|
|
642
|
+
type RemarkPlugins = NonNullable<Parameters<typeof ReactMarkdown>[0]["remarkPlugins"]>;
|
|
643
|
+
const REMARK_PLUGINS: RemarkPlugins = [remarkGfm];
|
|
644
|
+
// Structural plugin types (see remark-soft-line-breaks.ts) need one cast at the seam.
|
|
645
|
+
const SOFT_LINE_BREAK_REMARK_PLUGINS = [
|
|
646
|
+
remarkGfm,
|
|
647
|
+
remarkSoftLineBreaks,
|
|
648
|
+
] as unknown as RemarkPlugins;
|
|
649
|
+
|
|
633
650
|
function MarkdownImpl({
|
|
634
651
|
children,
|
|
635
652
|
artifactHref,
|
|
636
653
|
className,
|
|
654
|
+
softLineBreaks = false,
|
|
637
655
|
streaming = false,
|
|
638
656
|
onSandboxFile,
|
|
639
657
|
renderInteractiveBlock,
|
|
@@ -760,7 +778,7 @@ function MarkdownImpl({
|
|
|
760
778
|
)}
|
|
761
779
|
>
|
|
762
780
|
<ReactMarkdown
|
|
763
|
-
remarkPlugins={
|
|
781
|
+
remarkPlugins={softLineBreaks ? SOFT_LINE_BREAK_REMARK_PLUGINS : REMARK_PLUGINS}
|
|
764
782
|
rehypePlugins={rehypePlugins}
|
|
765
783
|
components={markdownComponents}
|
|
766
784
|
urlTransform={markdownUrlTransform}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
import { QUESTION_NAV_MARGIN_PX, questionNavTarget } from "../timeline/question-nav-model";
|
|
1
2
|
import { RollingActivity } from "../timeline/rolling-activity";
|
|
3
|
+
import {
|
|
4
|
+
clusterIsSettled,
|
|
5
|
+
compactedLandmarkCount,
|
|
6
|
+
durationBetween,
|
|
7
|
+
flattenActivityItems,
|
|
8
|
+
isPreparingWork,
|
|
9
|
+
readableWorkDefaultOpen,
|
|
10
|
+
readableWorkShowsPreview,
|
|
11
|
+
readableWorkStatus,
|
|
12
|
+
} from "../timeline/work-presentation";
|
|
2
13
|
import { useErrorMessage } from "../lib/error-message";
|
|
3
14
|
import { formatElapsed } from "../timeline/turn-summary";
|
|
4
15
|
import { ActivityNoteTextContext } from "../timeline/activity-rail";
|
|
@@ -143,6 +154,11 @@ import {
|
|
|
143
154
|
readableMachineInputSource,
|
|
144
155
|
} from "./machine-input-display";
|
|
145
156
|
import { SESSION_STATUS_META, StatusDot } from "./session-status";
|
|
157
|
+
import {
|
|
158
|
+
noticeDisplayText,
|
|
159
|
+
noticeIsResolvedApproval,
|
|
160
|
+
noticeTone,
|
|
161
|
+
} from "../timeline/notice-presentation";
|
|
146
162
|
import { TimelineComputeLabelProvider } from "../timeline/compute-label";
|
|
147
163
|
import { EntranceAnimationProvider, useEntranceAnimation } from "../timeline/entrance";
|
|
148
164
|
import {
|
|
@@ -338,8 +354,6 @@ const PIN_THRESHOLD_PX = 48;
|
|
|
338
354
|
* line-sized streaming movement.
|
|
339
355
|
*/
|
|
340
356
|
const JUMP_TO_LATEST_CATCHUP_DEBT_PX = 240;
|
|
341
|
-
/** Breathing room above the question when following stops at an answer. */
|
|
342
|
-
const QUESTION_NAV_MARGIN_PX = 12;
|
|
343
357
|
/**
|
|
344
358
|
* Floating timeline navigation: two identical small round arrow buttons at fixed
|
|
345
359
|
* spots, centered on the conversation. Back to your message floats 12px below
|
|
@@ -505,25 +519,16 @@ function contentTopOf(node: HTMLElement, groupKey: string): number | null {
|
|
|
505
519
|
*/
|
|
506
520
|
type QuestionNav = { key: string; belowHeader: boolean };
|
|
507
521
|
|
|
508
|
-
/** How far a question's start must be above the viewport before navigation shows. */
|
|
509
|
-
const QUESTION_NAV_HIDDEN_PX = 24;
|
|
510
|
-
|
|
511
522
|
function readQuestionNav(node: HTMLElement): QuestionNav | null {
|
|
512
523
|
const view = node.getBoundingClientRect();
|
|
513
|
-
const prompts = [...node.querySelectorAll<HTMLElement>("[data-og-prompt]")]
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
if (!prompt || !key) return null;
|
|
522
|
-
const bounds = prompt.getBoundingClientRect();
|
|
523
|
-
if (bounds.top >= view.top - QUESTION_NAV_HIDDEN_PX || bounds.bottom > view.top) {
|
|
524
|
-
return null;
|
|
525
|
-
}
|
|
526
|
-
return { key, belowHeader: workHeaderPinned(node, view) };
|
|
524
|
+
const prompts = [...node.querySelectorAll<HTMLElement>("[data-og-prompt]")].flatMap((prompt) => {
|
|
525
|
+
const key = prompt.dataset.ogGroupKey;
|
|
526
|
+
if (!key) return [];
|
|
527
|
+
const bounds = prompt.getBoundingClientRect();
|
|
528
|
+
return [{ key, top: bounds.top, bottom: bounds.bottom }];
|
|
529
|
+
});
|
|
530
|
+
const key = questionNavTarget(prompts, { top: view.top, height: view.height });
|
|
531
|
+
return key ? { key, belowHeader: workHeaderPinned(node, view) } : null;
|
|
527
532
|
}
|
|
528
533
|
|
|
529
534
|
/** True while an expanded outer work header is stuck to the scrollport's top edge. */
|
|
@@ -633,6 +638,7 @@ export function MessageTimeline({
|
|
|
633
638
|
const body = (
|
|
634
639
|
<Markdown
|
|
635
640
|
searchTarget={searchItem?.id === item.id ? searchTarget : null}
|
|
641
|
+
softLineBreaks
|
|
636
642
|
streaming={item.kind === "agent-message" && item.streaming}
|
|
637
643
|
renderInteractiveBlock={
|
|
638
644
|
item.kind === "agent-message" ? renderInteractiveBlock : undefined
|
|
@@ -3401,18 +3407,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
3401
3407
|
switch (group.kind) {
|
|
3402
3408
|
case "activity":
|
|
3403
3409
|
if (group.work) {
|
|
3404
|
-
const
|
|
3405
|
-
const preparing =
|
|
3406
|
-
!startupDetails &&
|
|
3407
|
-
!startupDismissed &&
|
|
3408
|
-
!group.work.endedAt &&
|
|
3409
|
-
!group.work.waiting &&
|
|
3410
|
-
phases.length > 0 &&
|
|
3411
|
-
group.items.every(
|
|
3412
|
-
(item) =>
|
|
3413
|
-
item.kind === "startup-phase" || (item.kind === "reasoning" && !item.text.trim()),
|
|
3414
|
-
) &&
|
|
3415
|
-
!phases.some((item) => item.status === "failed" || item.status === "cancelled");
|
|
3410
|
+
const preparing = isPreparingWork(group, { startupDetails, startupDismissed });
|
|
3416
3411
|
if (preparing) {
|
|
3417
3412
|
// Preparation is primary, never hidden in an activity disclosure.
|
|
3418
3413
|
// Keep work.startedAt unchanged for the handoff and settled duration.
|
|
@@ -3429,22 +3424,20 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
3429
3424
|
/>
|
|
3430
3425
|
);
|
|
3431
3426
|
}
|
|
3432
|
-
const
|
|
3433
|
-
const status: TurnSummaryStatus =
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
kind: "working",
|
|
3439
|
-
since: group.work.startedAt,
|
|
3440
|
-
preview: containsPresentedImage ? undefined : (
|
|
3427
|
+
const workStatus = readableWorkStatus({ ...group, work: group.work });
|
|
3428
|
+
const status: TurnSummaryStatus =
|
|
3429
|
+
workStatus.kind === "working"
|
|
3430
|
+
? {
|
|
3431
|
+
...workStatus,
|
|
3432
|
+
preview: readableWorkShowsPreview(group) ? (
|
|
3441
3433
|
<RollingActivity
|
|
3442
3434
|
items={group.items}
|
|
3443
3435
|
toolRegistry={toolRegistry}
|
|
3444
3436
|
showCount={false}
|
|
3445
3437
|
/>
|
|
3446
|
-
),
|
|
3447
|
-
}
|
|
3438
|
+
) : undefined,
|
|
3439
|
+
}
|
|
3440
|
+
: workStatus;
|
|
3448
3441
|
return (
|
|
3449
3442
|
// Marks this work row for the live-note fold (list keys can be carried
|
|
3450
3443
|
// over from an earlier row, so they are not the work id).
|
|
@@ -3456,13 +3449,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
3456
3449
|
outcome={group.outcome}
|
|
3457
3450
|
failureText={group.failureText}
|
|
3458
3451
|
foldKey={group.id}
|
|
3459
|
-
defaultOpen={
|
|
3460
|
-
containsPresentedImage ||
|
|
3461
|
-
group.outcome === "failed" ||
|
|
3462
|
-
phases.some((item) => item.status === "failed" || item.status === "cancelled")
|
|
3463
|
-
? true
|
|
3464
|
-
: undefined
|
|
3465
|
-
}
|
|
3452
|
+
defaultOpen={readableWorkDefaultOpen(group)}
|
|
3466
3453
|
facets={turnSummary?.facets}
|
|
3467
3454
|
contextCompactionCount={compactedLandmarkCount(group.work.details)}
|
|
3468
3455
|
onOpenStateChange={
|
|
@@ -3783,15 +3770,6 @@ function renderFoldedGroups(
|
|
|
3783
3770
|
));
|
|
3784
3771
|
}
|
|
3785
3772
|
|
|
3786
|
-
function compactedLandmarkCount(groups: readonly TimelineGroup[]): number {
|
|
3787
|
-
return groups.filter(
|
|
3788
|
-
(group) =>
|
|
3789
|
-
group.kind === "item" &&
|
|
3790
|
-
group.item.kind === "context-compaction" &&
|
|
3791
|
-
group.item.phase === "compacted",
|
|
3792
|
-
).length;
|
|
3793
|
-
}
|
|
3794
|
-
|
|
3795
3773
|
/**
|
|
3796
3774
|
* Body under a turn/activity chip. Remount flashes are gated by the timeline
|
|
3797
3775
|
* seen-activity-id map (not by killing entrance): FoldBody used to force
|
|
@@ -3892,18 +3870,6 @@ function isAgentProgress(next: TimelineGroup | undefined): boolean {
|
|
|
3892
3870
|
/** No item still running or streaming — the only state safe to fold live.
|
|
3893
3871
|
Position alone is a broken proxy: a pending queued message (or any trailing
|
|
3894
3872
|
item) can sit after the ACTIVE cluster, which must never fold mid-work. */
|
|
3895
|
-
function clusterIsSettled(group: Extract<TimelineGroup, { kind: "activity" }>): boolean {
|
|
3896
|
-
return group.items.every((item) => {
|
|
3897
|
-
if (item.kind === "reasoning" || item.kind === "agent-message") {
|
|
3898
|
-
return !item.streaming;
|
|
3899
|
-
}
|
|
3900
|
-
// Memory writes and fleet observations are discrete, already-settled events.
|
|
3901
|
-
if (item.kind === "memory" || item.kind === "fleet-decision") {
|
|
3902
|
-
return true;
|
|
3903
|
-
}
|
|
3904
|
-
return item.status !== "running";
|
|
3905
|
-
});
|
|
3906
|
-
}
|
|
3907
3873
|
|
|
3908
3874
|
/** Settled activity clusters that could become nested chips under a turn. */
|
|
3909
3875
|
function foldableActivityClusterCount(groups: readonly TimelineGroup[]): number {
|
|
@@ -3916,18 +3882,6 @@ function foldableActivityClusterCount(groups: readonly TimelineGroup[]): number
|
|
|
3916
3882
|
return count;
|
|
3917
3883
|
}
|
|
3918
3884
|
|
|
3919
|
-
function flattenActivityItems(groups: readonly TimelineGroup[]): ActivityItem[] {
|
|
3920
|
-
const items: ActivityItem[] = [];
|
|
3921
|
-
for (const group of groups) {
|
|
3922
|
-
if (group.kind === "activity") {
|
|
3923
|
-
items.push(...flattenActivityItems(group.work?.details ?? []), ...group.items);
|
|
3924
|
-
} else if (group.kind === "turn") {
|
|
3925
|
-
items.push(...flattenActivityItems(group.groups));
|
|
3926
|
-
}
|
|
3927
|
-
}
|
|
3928
|
-
return items;
|
|
3929
|
-
}
|
|
3930
|
-
|
|
3931
3885
|
/** Assistant prose inside a turn fold (mid-turn narration), joined for copy. */
|
|
3932
3886
|
function collectAgentMessageText(groups: readonly TimelineGroup[]): string {
|
|
3933
3887
|
const parts: string[] = [];
|
|
@@ -4026,15 +3980,6 @@ function collectTurnCopyText(
|
|
|
4026
3980
|
return parts.join("\n\n");
|
|
4027
3981
|
}
|
|
4028
3982
|
|
|
4029
|
-
function durationBetween(startedAt: string, endedAt: string): number | undefined {
|
|
4030
|
-
const started = Date.parse(startedAt);
|
|
4031
|
-
const ended = Date.parse(endedAt);
|
|
4032
|
-
if (!Number.isFinite(started) || !Number.isFinite(ended) || ended < started) {
|
|
4033
|
-
return undefined;
|
|
4034
|
-
}
|
|
4035
|
-
return ended - started;
|
|
4036
|
-
}
|
|
4037
|
-
|
|
4038
3983
|
/* --- single rows ------------------------------------------------------------ */
|
|
4039
3984
|
|
|
4040
3985
|
/**
|
|
@@ -4235,7 +4180,7 @@ function UserMessageRow({
|
|
|
4235
4180
|
renderMessageText(item.text, item)
|
|
4236
4181
|
) : (
|
|
4237
4182
|
<UserMessageBody messageId={item.id} text={item.text}>
|
|
4238
|
-
<Markdown>{item.text}</Markdown>
|
|
4183
|
+
<Markdown softLineBreaks>{item.text}</Markdown>
|
|
4239
4184
|
</UserMessageBody>
|
|
4240
4185
|
)}
|
|
4241
4186
|
</div>
|
|
@@ -4445,7 +4390,9 @@ function AgentMessageRow({
|
|
|
4445
4390
|
const body = renderMessageText ? (
|
|
4446
4391
|
renderMessageText(item.text, item)
|
|
4447
4392
|
) : (
|
|
4448
|
-
<Markdown streaming={item.streaming}>
|
|
4393
|
+
<Markdown softLineBreaks streaming={item.streaming}>
|
|
4394
|
+
{item.text}
|
|
4395
|
+
</Markdown>
|
|
4449
4396
|
);
|
|
4450
4397
|
// While streaming, copy is still useful (current text) but keep chrome calm —
|
|
4451
4398
|
// stamp only after the message finishes (occurredAt tracks completion).
|
|
@@ -4934,11 +4881,12 @@ function NoticeRow({ item }: { item: NoticeItem }) {
|
|
|
4934
4881
|
</details>
|
|
4935
4882
|
);
|
|
4936
4883
|
}
|
|
4937
|
-
const
|
|
4884
|
+
const presentedTone = noticeTone(item);
|
|
4885
|
+
const resolvedApproval = noticeIsResolvedApproval(item);
|
|
4938
4886
|
const tone =
|
|
4939
|
-
|
|
4887
|
+
presentedTone === "failed"
|
|
4940
4888
|
? "border-og-status-failed/35 bg-og-status-failed/10 text-og-status-failed"
|
|
4941
|
-
:
|
|
4889
|
+
: presentedTone === "waiting"
|
|
4942
4890
|
? WAITING_PILL_CLASS
|
|
4943
4891
|
: NEUTRAL_PILL;
|
|
4944
4892
|
return (
|
|
@@ -4958,9 +4906,7 @@ function NoticeRow({ item }: { item: NoticeItem }) {
|
|
|
4958
4906
|
/>
|
|
4959
4907
|
)}
|
|
4960
4908
|
<div className="min-w-0 flex-1">
|
|
4961
|
-
<span className="whitespace-pre-wrap break-words">
|
|
4962
|
-
{resolvedApproval ? "You responded to this approval." : item.text}
|
|
4963
|
-
</span>
|
|
4909
|
+
<span className="whitespace-pre-wrap break-words">{noticeDisplayText(item)}</span>
|
|
4964
4910
|
{item.details ? (
|
|
4965
4911
|
<details className="mt-2 text-og-control">
|
|
4966
4912
|
<summary className="cursor-pointer font-medium">{item.details.label}</summary>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CHATGPT_MARK_PATH } from "../model-mark-paths";
|
|
1
2
|
import {
|
|
2
3
|
modelDisplayName,
|
|
3
4
|
modelLogoUrl,
|
|
@@ -15,7 +16,7 @@ import { GrokMark } from "./grok-mark";
|
|
|
15
16
|
export function ChatGptMark(props: SVGProps<SVGSVGElement>) {
|
|
16
17
|
return (
|
|
17
18
|
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" {...props}>
|
|
18
|
-
<path d=
|
|
19
|
+
<path d={CHATGPT_MARK_PATH} />
|
|
19
20
|
</svg>
|
|
20
21
|
);
|
|
21
22
|
}
|
|
@@ -9,23 +9,17 @@ import {
|
|
|
9
9
|
MENU_NOTE_CLASS,
|
|
10
10
|
MENU_SURFACE_CLASS,
|
|
11
11
|
} from "../lib/menu-styles";
|
|
12
|
+
import { labelReasoningEffort, type PickerModelRow } from "../model-policy";
|
|
12
13
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
payerSummaryForModel,
|
|
19
|
-
runnableLatencyModesForModel,
|
|
20
|
-
type PickerModelRow,
|
|
21
|
-
} from "../model-policy";
|
|
14
|
+
compactEffortLabel,
|
|
15
|
+
groupPresentationFor,
|
|
16
|
+
modelPickerChoice,
|
|
17
|
+
modelPickerMenuView,
|
|
18
|
+
} from "../model-picker-model";
|
|
22
19
|
import {
|
|
23
20
|
BillingClassMark,
|
|
24
21
|
defaultModelPolicyPickerMessages,
|
|
25
|
-
effectiveRows,
|
|
26
|
-
groupPresentationFor,
|
|
27
22
|
PickerNavRow,
|
|
28
|
-
SCOPED_BILLING_HINTS,
|
|
29
23
|
type ModelPolicyPickerProps,
|
|
30
24
|
} from "./model-policy-picker";
|
|
31
25
|
import { ModelMark } from "./model-mark";
|
|
@@ -33,57 +27,18 @@ type ClientPickerModelRow = PickerModelRow<ClientModel>;
|
|
|
33
27
|
|
|
34
28
|
/** Model selection stays flat; reasoning never becomes a navigation destination. */
|
|
35
29
|
export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
36
|
-
const messages = { ...defaultModelPolicyPickerMessages, ...props.messages };
|
|
37
30
|
const [query, setQuery] = useState("");
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const filtered = rows.filter((row) => {
|
|
42
|
-
const description = groupPresentationFor(props, row.billingClass)?.description;
|
|
43
|
-
const text =
|
|
44
|
-
`${row.label} ${row.id} ${row.providerLabel} ${row.billingClassLabel} ${description === undefined ? payerSummaryForModel(row.catalog) : (description ?? "")}`.toLowerCase();
|
|
45
|
-
return words.every((word) => text.includes(word));
|
|
46
|
-
});
|
|
47
|
-
const matchingIds = new Set(filtered.map((row) => row.id));
|
|
48
|
-
const groups = groupPickerRowsByBillingClass(rows, {
|
|
49
|
-
codexOnly: props.codexOnly === true,
|
|
50
|
-
selectedId: props.model,
|
|
51
|
-
collapseScopes: props.collapseScopes !== false,
|
|
52
|
-
})
|
|
53
|
-
.map((group) => {
|
|
54
|
-
const presentation = groupPresentationFor(props, group.billingClass);
|
|
55
|
-
const override = presentation?.description;
|
|
56
|
-
const defaultHint =
|
|
57
|
-
props.collapseScopes === false &&
|
|
58
|
-
messages.billingHints[group.billingClass] ===
|
|
59
|
-
defaultModelPolicyPickerMessages.billingHints[group.billingClass]
|
|
60
|
-
? (SCOPED_BILLING_HINTS[group.billingClass] ?? messages.billingHints[group.billingClass])
|
|
61
|
-
: messages.billingHints[group.billingClass];
|
|
62
|
-
return {
|
|
63
|
-
...group,
|
|
64
|
-
label: presentation?.label ?? group.label,
|
|
65
|
-
rows: group.rows.filter((row) => matchingIds.has(row.id)),
|
|
66
|
-
description:
|
|
67
|
-
override === undefined
|
|
68
|
-
? group.billingClass === "opengeni_credits"
|
|
69
|
-
? null
|
|
70
|
-
: defaultHint
|
|
71
|
-
: override,
|
|
72
|
-
};
|
|
73
|
-
})
|
|
74
|
-
.filter((group) => group.rows.length > 0);
|
|
31
|
+
const view = modelPickerMenuView(props, query);
|
|
32
|
+
const { messages, rows, groups } = view;
|
|
33
|
+
const selected = view.selected;
|
|
75
34
|
const choose = (row: ClientPickerModelRow) => {
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
35
|
+
if (props.disabled) return;
|
|
36
|
+
const choice = modelPickerChoice(props, row);
|
|
37
|
+
if (!choice) return;
|
|
38
|
+
if (choice.model !== null) props.onModelChange(choice.model);
|
|
79
39
|
// Hosts may commit the combined model/effort draft through this callback.
|
|
80
|
-
props.onEffortChange(effort);
|
|
81
|
-
if (
|
|
82
|
-
props.latencyMode !== "standard" &&
|
|
83
|
-
!runnableLatencyModesForModel(row.catalog).includes(props.latencyMode)
|
|
84
|
-
) {
|
|
85
|
-
props.onLatencyModeChange("standard");
|
|
86
|
-
}
|
|
40
|
+
props.onEffortChange(choice.effort);
|
|
41
|
+
if (choice.latencyMode !== null) props.onLatencyModeChange(choice.latencyMode);
|
|
87
42
|
};
|
|
88
43
|
const modelRow = (row: ClientPickerModelRow) => (
|
|
89
44
|
<PickerNavRow
|
|
@@ -214,9 +169,9 @@ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
|
214
169
|
{group.rows.map(modelRow)}
|
|
215
170
|
</section>
|
|
216
171
|
))}
|
|
217
|
-
{
|
|
172
|
+
{view.matchCount === 0 ? (
|
|
218
173
|
<p className={MENU_NOTE_CLASS}>
|
|
219
|
-
{
|
|
174
|
+
{view.searching ? messages.noMatches : messages.noModels}
|
|
220
175
|
</p>
|
|
221
176
|
) : null}
|
|
222
177
|
</>
|
|
@@ -226,17 +181,9 @@ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
|
226
181
|
<p className={MENU_NOTE_CLASS}>{messages.noModels}</p>
|
|
227
182
|
)}
|
|
228
183
|
</div>
|
|
229
|
-
{
|
|
230
|
-
selected &&
|
|
231
|
-
((props.hasImageAttachments &&
|
|
232
|
-
selected.catalog.capabilities?.inputModalities.includes("image") === false) ||
|
|
233
|
-
(effortOptionsForModel(selected.catalog).length > 1 &&
|
|
234
|
-
selected.catalog.capabilities?.reasoning.runnable !== false) ||
|
|
235
|
-
(props.allowLatencyMode !== false &&
|
|
236
|
-
runnableLatencyModesForModel(selected.catalog).includes("fast"))) ? (
|
|
184
|
+
{view.anySelectable && selected && (view.unsupportedAttachments || view.thinking) ? (
|
|
237
185
|
<div className="border-t border-og-border px-2.5 py-2.5">
|
|
238
|
-
{
|
|
239
|
-
selected.catalog.capabilities?.inputModalities.includes("image") === false ? (
|
|
186
|
+
{view.unsupportedAttachments ? (
|
|
240
187
|
<p className="pb-1.5 text-og-control leading-relaxed text-og-fg-subtle">
|
|
241
188
|
{messages.unsupportedAttachments}
|
|
242
189
|
</p>
|
|
@@ -323,15 +270,13 @@ function ConnectModelsPanel(props: {
|
|
|
323
270
|
}
|
|
324
271
|
|
|
325
272
|
function ModelThinkingControls(props: ModelPolicyPickerProps) {
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const messages =
|
|
330
|
-
const
|
|
331
|
-
const showThinking =
|
|
332
|
-
|
|
333
|
-
const showFast = supportsFast && props.allowLatencyMode !== false;
|
|
334
|
-
if (!showThinking && !showFast) return null;
|
|
273
|
+
const view = modelPickerMenuView(props, "");
|
|
274
|
+
const thinking = view.thinking;
|
|
275
|
+
if (!view.selected || !thinking) return null;
|
|
276
|
+
const messages = view.messages;
|
|
277
|
+
const efforts = thinking.efforts;
|
|
278
|
+
const showThinking = thinking.showThinking;
|
|
279
|
+
const showFast = thinking.showFast;
|
|
335
280
|
return (
|
|
336
281
|
<div className="space-y-2 text-og-control" data-testid="model-picker-reasoning">
|
|
337
282
|
<div className="flex min-h-7 items-center justify-between gap-3">
|
|
@@ -340,7 +285,7 @@ function ModelThinkingControls(props: ModelPolicyPickerProps) {
|
|
|
340
285
|
<button
|
|
341
286
|
type="button"
|
|
342
287
|
data-testid="model-picker-fast"
|
|
343
|
-
disabled={props.disabled ||
|
|
288
|
+
disabled={props.disabled || thinking.disabled}
|
|
344
289
|
aria-pressed={props.latencyMode === "fast"}
|
|
345
290
|
title={messages.fast + " · " + messages.fastRateHint}
|
|
346
291
|
onClick={() =>
|
|
@@ -357,8 +302,8 @@ function ModelThinkingControls(props: ModelPolicyPickerProps) {
|
|
|
357
302
|
{showThinking ? (
|
|
358
303
|
<RadioGroup.Root
|
|
359
304
|
aria-label={messages.thinkingEffort}
|
|
360
|
-
value={
|
|
361
|
-
disabled={props.disabled ||
|
|
305
|
+
value={thinking.value}
|
|
306
|
+
disabled={props.disabled || thinking.disabled}
|
|
362
307
|
onValueChange={(value) => props.onEffortChange(value as ReasoningEffort)}
|
|
363
308
|
orientation="horizontal"
|
|
364
309
|
className="flex flex-wrap gap-0.5 rounded-og-md bg-og-surface-2 p-0.5"
|
|
@@ -371,7 +316,7 @@ function ModelThinkingControls(props: ModelPolicyPickerProps) {
|
|
|
371
316
|
title={labelReasoningEffort(effort)}
|
|
372
317
|
className="min-h-8 min-w-10 flex-1 cursor-pointer whitespace-nowrap rounded-og-sm px-1.5 text-og-control text-og-fg-muted outline-hidden transition-colors hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent/40 data-[state=checked]:bg-og-surface-1 data-[state=checked]:text-og-fg data-[state=checked]:shadow-sm disabled:cursor-not-allowed disabled:opacity-50"
|
|
373
318
|
>
|
|
374
|
-
{effort
|
|
319
|
+
{compactEffortLabel(effort, labelReasoningEffort(effort))}
|
|
375
320
|
</RadioGroup.Item>
|
|
376
321
|
))}
|
|
377
322
|
</RadioGroup.Root>
|