@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
package/src/client.ts
CHANGED
|
@@ -193,7 +193,7 @@ type SessionClientMethods = Pick<
|
|
|
193
193
|
|
|
194
194
|
/** Compact list transport is optional for scripted and older clients. */
|
|
195
195
|
export type SessionClientLike = SessionClientMethods &
|
|
196
|
-
Partial<Pick<OpenGeniClient, "listSessionSummaryPage">>;
|
|
196
|
+
Partial<Pick<OpenGeniClient, "listSessionSummaryPage" | "findGoal">>;
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
199
|
* Tenant-safe client surface required by the session-only React entry.
|
|
@@ -233,9 +233,15 @@ export type EmbeddedSessionEventClientLike = Pick<OpenGeniClient, "getSession" |
|
|
|
233
233
|
export type EmbeddedSessionReadClientLike = EmbeddedSessionEventClientLike &
|
|
234
234
|
Pick<OpenGeniClient, "getSession" | "updateSession">;
|
|
235
235
|
|
|
236
|
-
/**
|
|
236
|
+
/**
|
|
237
|
+
* Exact client surface required by {@link useGoal}. `findGoal` is optional:
|
|
238
|
+
* when present the hook reads a goal-less session as a successful `null`
|
|
239
|
+
* instead of an absorbed 404.
|
|
240
|
+
*/
|
|
237
241
|
export type EmbeddedGoalClientLike = EmbeddedSessionEventClientLike &
|
|
238
|
-
Pick<OpenGeniClient, "getGoal" | "updateGoal" | "deleteGoal"
|
|
242
|
+
Pick<OpenGeniClient, "getGoal" | "updateGoal" | "deleteGoal"> & {
|
|
243
|
+
findGoal?: OpenGeniClient["findGoal"] | undefined;
|
|
244
|
+
};
|
|
239
245
|
|
|
240
246
|
/** Exact client surface required by {@link useSessionLineage}. */
|
|
241
247
|
export type EmbeddedSessionLineageClientLike = EmbeddedSessionEventClientLike &
|
|
@@ -82,6 +82,22 @@ import { InteractionInterventionBanner } from "./interaction-intervention-banner
|
|
|
82
82
|
import { BrowserSelectControl } from "./browser-select-control";
|
|
83
83
|
import { useViewerMenuDismiss } from "./use-viewer-menu-dismiss";
|
|
84
84
|
|
|
85
|
+
/** Auto-ending a lost attached-device browser: bounded retries for transient failures only. */
|
|
86
|
+
const STALE_BROWSER_END_MAX_ATTEMPTS = 3;
|
|
87
|
+
const STALE_BROWSER_END_RETRY_BASE_MS = 5_000;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Delay before retrying an automatic end of a lost attached-device browser, or null to stop.
|
|
91
|
+
* A refusal (4xx, e.g. 409 when the server will not end it) is never retried: re-sending it on
|
|
92
|
+
* every registry refresh looped about one request per second on prod.
|
|
93
|
+
*/
|
|
94
|
+
export function staleBrowserEndRetryDelayMs(cause: unknown, failedAttempts: number): number | null {
|
|
95
|
+
const status = (cause as { status?: unknown } | null)?.status;
|
|
96
|
+
if (typeof status === "number" && status >= 400 && status < 500) return null;
|
|
97
|
+
if (failedAttempts >= STALE_BROWSER_END_MAX_ATTEMPTS) return null;
|
|
98
|
+
return STALE_BROWSER_END_RETRY_BASE_MS * 2 ** (failedAttempts - 1);
|
|
99
|
+
}
|
|
100
|
+
|
|
85
101
|
export type BrowserViewerNotification = {
|
|
86
102
|
kind: "error" | "info";
|
|
87
103
|
message: string;
|
|
@@ -190,6 +206,7 @@ export function BrowserViewer({
|
|
|
190
206
|
[registry.sessions],
|
|
191
207
|
);
|
|
192
208
|
const endedGenerationLossRef = useRef(new Set<string>());
|
|
209
|
+
const endStaleBrowserAttemptsRef = useRef(new Map<string, number>());
|
|
193
210
|
const endStaleBrowser = registry.end;
|
|
194
211
|
useEffect(() => {
|
|
195
212
|
if (!enabled) return;
|
|
@@ -203,8 +220,12 @@ export function BrowserViewer({
|
|
|
203
220
|
}
|
|
204
221
|
if (endedGenerationLossRef.current.has(session.id)) continue;
|
|
205
222
|
endedGenerationLossRef.current.add(session.id);
|
|
206
|
-
void endStaleBrowser(session.id).catch(() => {
|
|
207
|
-
|
|
223
|
+
void endStaleBrowser(session.id).catch((cause: unknown) => {
|
|
224
|
+
const attempts = (endStaleBrowserAttemptsRef.current.get(session.id) ?? 0) + 1;
|
|
225
|
+
endStaleBrowserAttemptsRef.current.set(session.id, attempts);
|
|
226
|
+
const delay = staleBrowserEndRetryDelayMs(cause, attempts);
|
|
227
|
+
if (delay === null) return;
|
|
228
|
+
setTimeout(() => endedGenerationLossRef.current.delete(session.id), delay);
|
|
208
229
|
});
|
|
209
230
|
}
|
|
210
231
|
}, [enabled, endStaleBrowser, registry.sessions]);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { CLAUDE_MARK_PATH } from "../model-mark-paths";
|
|
1
2
|
import type { SVGProps } from "react";
|
|
2
3
|
|
|
3
4
|
/** ClaudeMark: Simple Icons, CC0; https://github.com/simple-icons/simple-icons */
|
|
4
5
|
export function ClaudeMark(props: SVGProps<SVGSVGElement>) {
|
|
5
6
|
return (
|
|
6
7
|
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" {...props}>
|
|
7
|
-
<path d=
|
|
8
|
+
<path d={CLAUDE_MARK_PATH} />
|
|
8
9
|
</svg>
|
|
9
10
|
);
|
|
10
11
|
}
|
|
@@ -52,7 +52,8 @@ import {
|
|
|
52
52
|
ComposerResponsiveContext,
|
|
53
53
|
type ResponsiveBasis,
|
|
54
54
|
} from "../lib/composer-responsive-context";
|
|
55
|
-
import { composerSubmissionErrorMessage
|
|
55
|
+
import { composerSubmissionErrorMessage } from "../lib/format";
|
|
56
|
+
import { defaultChatComposerMessages, type ChatComposerMessages } from "../composer-messages";
|
|
56
57
|
import type { PickerModelRow } from "../model-policy";
|
|
57
58
|
import { useLightboxOptional } from "../timeline/screenshot-lightbox";
|
|
58
59
|
import { OPEN_WORKSTREAM_CONTROL_EVENT } from "../workstream-control-event";
|
|
@@ -107,149 +108,8 @@ export type ComposerControlLinks = {
|
|
|
107
108
|
sessionHref?: ((sessionId: string) => string) | undefined;
|
|
108
109
|
};
|
|
109
110
|
|
|
110
|
-
export
|
|
111
|
-
|
|
112
|
-
pausedPlaceholder: string;
|
|
113
|
-
inputLabel: string;
|
|
114
|
-
keyboardHint: string;
|
|
115
|
-
slashCommandBlocked: string;
|
|
116
|
-
controlChangedError: string;
|
|
117
|
-
sendFailedError: string;
|
|
118
|
-
dropFiles: string;
|
|
119
|
-
attachFiles: string;
|
|
120
|
-
pauseAriaLabel: string;
|
|
121
|
-
pauseTitle: string;
|
|
122
|
-
/** Stop control shown while a response runs (`runControl="stop"`). */
|
|
123
|
-
stopAriaLabel?: string | undefined;
|
|
124
|
-
stopTitle?: string | undefined;
|
|
125
|
-
sendMessageAriaLabel: string;
|
|
126
|
-
sendAndResumeAriaLabel: string;
|
|
127
|
-
sendTitle: string;
|
|
128
|
-
sendAndResumeTitle: string;
|
|
129
|
-
annotationNotesRequired: string;
|
|
130
|
-
workspacePaused: string;
|
|
131
|
-
pausedHere: string;
|
|
132
|
-
parentBlocker: string;
|
|
133
|
-
narrowerPause: string;
|
|
134
|
-
pausedBy: (displayName: string) => string;
|
|
135
|
-
queuedAhead: (count: number) => string;
|
|
136
|
-
resumingAndSending: string;
|
|
137
|
-
nextMessageResumes: string;
|
|
138
|
-
resumeThisWorkstream: string;
|
|
139
|
-
resumeShort: string;
|
|
140
|
-
hidePauseDetails: string;
|
|
141
|
-
showPauseDetails: string;
|
|
142
|
-
pauseReasonsLabel: string;
|
|
143
|
-
pausedByLabel: string;
|
|
144
|
-
alsoPausedByLabel: string;
|
|
145
|
-
resumeWorkspace: string;
|
|
146
|
-
resumeFromSession: string;
|
|
147
|
-
sessionCanRun: string;
|
|
148
|
-
stillPausedBy: (displayName: string) => string;
|
|
149
|
-
restoredResourcesLabel: string;
|
|
150
|
-
restoredFile: (fileId: string) => string;
|
|
151
|
-
removeRestoredResource: (index: number) => string;
|
|
152
|
-
uploading: string;
|
|
153
|
-
uploadFailed: string;
|
|
154
|
-
previewAttachment: (name: string) => string;
|
|
155
|
-
previewUnavailable?: string | undefined;
|
|
156
|
-
attachmentPreviewLabel: string;
|
|
157
|
-
downloadAttachment: (name: string) => string;
|
|
158
|
-
closeAttachmentPreview: string;
|
|
159
|
-
retryAttachment: (name: string) => string;
|
|
160
|
-
retryUpload: string;
|
|
161
|
-
removeAttachment: (name: string) => string;
|
|
162
|
-
confirmCommand: (name: string) => string;
|
|
163
|
-
confirmDescription: (command: SlashCommand) => string;
|
|
164
|
-
cancel: string;
|
|
165
|
-
runCommand: (name: string) => string;
|
|
166
|
-
commands: string;
|
|
167
|
-
slashCommandsLabel: string;
|
|
168
|
-
modelLabel: string;
|
|
169
|
-
close: string;
|
|
170
|
-
danger: string;
|
|
171
|
-
draftConflict: string;
|
|
172
|
-
useOtherDraft: string;
|
|
173
|
-
keepMine: string;
|
|
174
|
-
savingDraft: string;
|
|
175
|
-
formatBytes: (bytes: number) => string;
|
|
176
|
-
formatRelativeTime: (changedAt: string) => string;
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
export const defaultChatComposerMessages: ChatComposerMessages = {
|
|
180
|
-
messagePlaceholder: "Message the agent…",
|
|
181
|
-
pausedPlaceholder: "Message the agent — it will wait in the queue…",
|
|
182
|
-
inputLabel: "Message the agent",
|
|
183
|
-
// Shortcuts live in the send button's title; the footer stays quiet unless
|
|
184
|
-
// the host supplies its own hint.
|
|
185
|
-
keyboardHint: "",
|
|
186
|
-
slashCommandBlocked:
|
|
187
|
-
"That's a slash command — press Enter in the command list to run it, or edit the line to send a message.",
|
|
188
|
-
controlChangedError:
|
|
189
|
-
"This workstream was paused while you were sending. Nothing was sent, and your draft is still here.",
|
|
190
|
-
sendFailedError: "Sending failed — your draft is still here. Try again.",
|
|
191
|
-
dropFiles: "Drop files to attach",
|
|
192
|
-
attachFiles: "Attach files",
|
|
193
|
-
pauseAriaLabel: "Pause this workstream",
|
|
194
|
-
pauseTitle: "Pause this workstream; queued prompts and approvals are preserved",
|
|
195
|
-
stopAriaLabel: "Stop",
|
|
196
|
-
stopTitle: "Stop the response",
|
|
197
|
-
sendMessageAriaLabel: "Send message",
|
|
198
|
-
sendAndResumeAriaLabel: "Add message to queue",
|
|
199
|
-
sendTitle: "Queue message (Enter); steer with Cmd/Ctrl+Enter",
|
|
200
|
-
sendAndResumeTitle: "Add to queue (Enter); steer now with Cmd/Ctrl+Enter",
|
|
201
|
-
annotationNotesRequired: "Add a note to each quote before sending.",
|
|
202
|
-
workspacePaused: "Workspace paused",
|
|
203
|
-
pausedHere: "Paused here",
|
|
204
|
-
parentBlocker: "parent",
|
|
205
|
-
narrowerPause: "a narrower pause",
|
|
206
|
-
pausedBy: (displayName) => `Paused by ${displayName}`,
|
|
207
|
-
queuedAhead: (count) =>
|
|
208
|
-
`${count} waiting prompt${count === 1 ? "" : "s"}; your message joins the queue.`,
|
|
209
|
-
resumingAndSending: "Sending…",
|
|
210
|
-
nextMessageResumes: "Messages wait in the queue until you resume.",
|
|
211
|
-
resumeThisWorkstream: "Resume this workstream",
|
|
212
|
-
resumeShort: "Resume",
|
|
213
|
-
hidePauseDetails: "Hide pause details",
|
|
214
|
-
showPauseDetails: "Show pause details",
|
|
215
|
-
pauseReasonsLabel: "Reasons this workstream is paused",
|
|
216
|
-
pausedByLabel: "Paused by ",
|
|
217
|
-
alsoPausedByLabel: "Also paused by ",
|
|
218
|
-
resumeWorkspace: "Resume workspace",
|
|
219
|
-
resumeFromSession: "Resume from this session",
|
|
220
|
-
sessionCanRun: "This session will be able to run",
|
|
221
|
-
stillPausedBy: (displayName) => `Still paused by ${displayName}`,
|
|
222
|
-
restoredResourcesLabel: "Restored prompt resources",
|
|
223
|
-
restoredFile: (fileId) => `File ${fileId.slice(0, 8)}`,
|
|
224
|
-
removeRestoredResource: (index) => `Remove restored resource ${index + 1}`,
|
|
225
|
-
uploading: "Uploading",
|
|
226
|
-
uploadFailed: "Upload failed",
|
|
227
|
-
previewAttachment: (name) => `Preview ${name}`,
|
|
228
|
-
previewUnavailable: "Preview unavailable",
|
|
229
|
-
attachmentPreviewLabel: "Attachment preview",
|
|
230
|
-
downloadAttachment: (name) => `Download ${name}`,
|
|
231
|
-
closeAttachmentPreview: "Close",
|
|
232
|
-
retryAttachment: (name) => `Retry ${name}`,
|
|
233
|
-
retryUpload: "Retry upload",
|
|
234
|
-
removeAttachment: (name) => `Remove ${name}`,
|
|
235
|
-
confirmCommand: (name) => `Confirm /${name}`,
|
|
236
|
-
confirmDescription: (command) => `Run /${command.name}? ${command.description}`,
|
|
237
|
-
cancel: "Cancel",
|
|
238
|
-
runCommand: (name) => `Run /${name}`,
|
|
239
|
-
commands: "Commands",
|
|
240
|
-
slashCommandsLabel: "Slash commands",
|
|
241
|
-
modelLabel: "Model",
|
|
242
|
-
close: "Close",
|
|
243
|
-
danger: "danger",
|
|
244
|
-
draftConflict: "This draft changed in another tab. Your local draft is still here.",
|
|
245
|
-
useOtherDraft: "Use other draft",
|
|
246
|
-
keepMine: "Keep mine",
|
|
247
|
-
// Kept for embedder message overrides / back-compat. Routine autosave is
|
|
248
|
-
// silent — only draft conflicts surface under the composer.
|
|
249
|
-
savingDraft: "Saving draft…",
|
|
250
|
-
formatBytes,
|
|
251
|
-
formatRelativeTime,
|
|
252
|
-
};
|
|
111
|
+
export { defaultChatComposerMessages } from "../composer-messages";
|
|
112
|
+
export type { ChatComposerMessages } from "../composer-messages";
|
|
253
113
|
|
|
254
114
|
export type ComposerSubmitMode = "queue" | "steer";
|
|
255
115
|
export type ComposerSubmitBlocker =
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ComputerAction,
|
|
3
|
+
ComputerActionReceipt,
|
|
3
4
|
ComputerClipboard,
|
|
4
5
|
ComputerFrame,
|
|
5
6
|
ComputerObservation,
|
|
@@ -42,7 +43,11 @@ import {
|
|
|
42
43
|
type ComputerFrameWebSocketFactory,
|
|
43
44
|
useComputerFrameStream,
|
|
44
45
|
} from "../hooks/use-computer-frame-stream";
|
|
45
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
computerActionReceiptError,
|
|
48
|
+
isStaleComputerActionResult,
|
|
49
|
+
useComputerSession,
|
|
50
|
+
} from "../hooks/use-computer-session";
|
|
46
51
|
import { useComputerInputPosture } from "../hooks/use-computer-input-posture";
|
|
47
52
|
import { useComputerSessions } from "../hooks/use-computer-sessions";
|
|
48
53
|
import { useInteractionInterventions } from "../hooks/use-interaction-interventions";
|
|
@@ -174,6 +179,7 @@ export function ComputerViewer({
|
|
|
174
179
|
|
|
175
180
|
const notifyError = useCallback(
|
|
176
181
|
(cause: unknown, fallback: string) => {
|
|
182
|
+
if (isStaleComputerActionResult(cause)) return;
|
|
177
183
|
onNotify?.({
|
|
178
184
|
kind: "error",
|
|
179
185
|
message: cause instanceof Error ? cause.message : fallback,
|
|
@@ -490,19 +496,24 @@ export function ComputerViewer({
|
|
|
490
496
|
]);
|
|
491
497
|
|
|
492
498
|
const perform = useCallback(
|
|
493
|
-
async (
|
|
499
|
+
async (
|
|
500
|
+
action: ComputerAction,
|
|
501
|
+
frame: ComputerFrame | null,
|
|
502
|
+
operationId?: string,
|
|
503
|
+
): Promise<ComputerActionReceipt> => {
|
|
494
504
|
if (computer.controlError) throw computer.controlError;
|
|
495
505
|
if (inputDenied) throw new Error("Desktop is view only. Refresh to check desktop controls.");
|
|
496
506
|
let receipt;
|
|
497
507
|
if (action.type === "pointer") {
|
|
498
508
|
if (!frame) throw new Error("Desktop view is not ready for pointer input.");
|
|
499
|
-
receipt = await actFromFrame(action, frame);
|
|
509
|
+
receipt = await actFromFrame(action, frame, operationId);
|
|
500
510
|
} else {
|
|
501
|
-
receipt = await act(action);
|
|
511
|
+
receipt = await act(action, operationId);
|
|
502
512
|
}
|
|
503
|
-
if (receipt.state !== "completed") {
|
|
504
|
-
throw
|
|
513
|
+
if (!isStaleComputerActionResult(receipt) && receipt.state !== "completed") {
|
|
514
|
+
throw computerActionReceiptError(receipt);
|
|
505
515
|
}
|
|
516
|
+
return receipt;
|
|
506
517
|
},
|
|
507
518
|
[act, actFromFrame, computer.controlError, inputDenied],
|
|
508
519
|
);
|
|
@@ -519,14 +530,15 @@ export function ComputerViewer({
|
|
|
519
530
|
return;
|
|
520
531
|
event.preventDefault();
|
|
521
532
|
event.stopPropagation();
|
|
522
|
-
void
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
533
|
+
void (async () => {
|
|
534
|
+
const receipt = await perform({ type: "clipboard", operation: "copy" }, null);
|
|
535
|
+
if (isStaleComputerActionResult(receipt)) return;
|
|
536
|
+
const clipboard = await computer.readClipboard();
|
|
537
|
+
if (isStaleComputerActionResult(receipt) || isStaleComputerActionResult(clipboard)) return;
|
|
538
|
+
if (clipboard.text && !(await copyTextToClipboard(clipboard.text))) {
|
|
539
|
+
throw new Error("Desktop text could not be copied to the local clipboard");
|
|
540
|
+
}
|
|
541
|
+
})().catch((cause) => notifyError(cause, "Could not copy from the desktop."));
|
|
530
542
|
},
|
|
531
543
|
[computer, notifyError, perform, rfbInputEnabled, rfbStream],
|
|
532
544
|
);
|
|
@@ -543,8 +555,11 @@ export function ComputerViewer({
|
|
|
543
555
|
// ownership is asynchronous, so a successful write response alone does
|
|
544
556
|
// not prove that the graphical seat can already serve the selection.
|
|
545
557
|
void (async () => {
|
|
546
|
-
await perform({ type: "clipboard", operation: "write", text }, null);
|
|
547
|
-
|
|
558
|
+
const receipt = await perform({ type: "clipboard", operation: "write", text }, null);
|
|
559
|
+
if (isStaleComputerActionResult(receipt)) return;
|
|
560
|
+
const clipboard = await computer.readClipboard();
|
|
561
|
+
if (isStaleComputerActionResult(receipt) || isStaleComputerActionResult(clipboard)) return;
|
|
562
|
+
assertExactComputerClipboard(clipboard, text);
|
|
548
563
|
await perform({ type: "clipboard", operation: "paste" }, null);
|
|
549
564
|
})().catch((cause) => notifyError(cause, "Could not paste into the desktop."));
|
|
550
565
|
return true;
|
|
@@ -750,6 +765,10 @@ export function ComputerViewer({
|
|
|
750
765
|
pointerInput={
|
|
751
766
|
attachmentInputAllowed && computer.session?.capabilities?.pointerInput === true
|
|
752
767
|
}
|
|
768
|
+
pointerClickContinuation={
|
|
769
|
+
attachmentInputAllowed &&
|
|
770
|
+
computer.session?.capabilities?.pointerClickContinuation === true
|
|
771
|
+
}
|
|
753
772
|
keyboardInput={
|
|
754
773
|
attachmentInputAllowed && computer.session?.capabilities?.keyboardInput === true
|
|
755
774
|
}
|
|
@@ -1194,6 +1213,12 @@ function ComputerLifecyclePanel(props: {
|
|
|
1194
1213
|
);
|
|
1195
1214
|
}
|
|
1196
1215
|
|
|
1216
|
+
type ComputerQueuedAction = {
|
|
1217
|
+
operationId: string;
|
|
1218
|
+
started: Promise<boolean>;
|
|
1219
|
+
completion: Promise<void>;
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1197
1222
|
function ComputerViewport(props: {
|
|
1198
1223
|
frame: ComputerFrame | null;
|
|
1199
1224
|
observation: ComputerObservation | null;
|
|
@@ -1208,8 +1233,13 @@ function ComputerViewport(props: {
|
|
|
1208
1233
|
backgroundInput: boolean;
|
|
1209
1234
|
clipboardEnabled: boolean;
|
|
1210
1235
|
pointerInput: boolean;
|
|
1236
|
+
pointerClickContinuation: boolean;
|
|
1211
1237
|
keyboardInput: boolean;
|
|
1212
|
-
onAction: (
|
|
1238
|
+
onAction: (
|
|
1239
|
+
action: ComputerAction,
|
|
1240
|
+
frame: ComputerFrame | null,
|
|
1241
|
+
operationId?: string,
|
|
1242
|
+
) => Promise<ComputerActionReceipt>;
|
|
1213
1243
|
onReadClipboard: () => Promise<ComputerClipboard>;
|
|
1214
1244
|
onReconnect: () => void;
|
|
1215
1245
|
onError: (cause: unknown) => void;
|
|
@@ -1224,6 +1254,11 @@ function ComputerViewport(props: {
|
|
|
1224
1254
|
x: number;
|
|
1225
1255
|
y: number;
|
|
1226
1256
|
frame: ComputerFrame;
|
|
1257
|
+
clientX: number;
|
|
1258
|
+
clientY: number;
|
|
1259
|
+
canvasRect: Pick<DOMRect, "left" | "top" | "width" | "height">;
|
|
1260
|
+
pending: boolean;
|
|
1261
|
+
delivery: ComputerQueuedAction | null;
|
|
1227
1262
|
} | null>(null);
|
|
1228
1263
|
const wheelRef = useRef<{
|
|
1229
1264
|
x: number;
|
|
@@ -1394,24 +1429,48 @@ function ComputerViewport(props: {
|
|
|
1394
1429
|
action: ComputerAction,
|
|
1395
1430
|
frame: ComputerFrame | null,
|
|
1396
1431
|
after?: (isCurrent: () => boolean) => Promise<void>,
|
|
1432
|
+
continuation?: ComputerQueuedAction,
|
|
1397
1433
|
) => {
|
|
1398
1434
|
const epoch = actionQueueEpochRef.current;
|
|
1399
1435
|
const dispatch = actionRef.current;
|
|
1400
1436
|
const isCurrent = () => mountedRef.current && epoch === actionQueueEpochRef.current;
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1437
|
+
const operationId = crypto.randomUUID();
|
|
1438
|
+
let markStarted!: (value: boolean) => void;
|
|
1439
|
+
const started = new Promise<boolean>((resolve) => {
|
|
1440
|
+
markStarted = resolve;
|
|
1441
|
+
});
|
|
1442
|
+
const previous = actionTailRef.current;
|
|
1443
|
+
// Only the real second click may start before its first HTTP receipt.
|
|
1444
|
+
// The controller queue and native causal proof still serialize delivery.
|
|
1445
|
+
const ready =
|
|
1446
|
+
continuation && previous === continuation.completion
|
|
1447
|
+
? continuation.started
|
|
1448
|
+
: previous.then(() => true);
|
|
1449
|
+
const delivery = ready
|
|
1450
|
+
.then(async (firstStarted) => {
|
|
1451
|
+
if (!firstStarted || !isCurrent()) {
|
|
1452
|
+
markStarted(false);
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
const pending = dispatch(action, frame, operationId);
|
|
1456
|
+
markStarted(true);
|
|
1457
|
+
const receipt = await pending;
|
|
1458
|
+
const isCurrentResult = () => isCurrent() && !isStaleComputerActionResult(receipt);
|
|
1459
|
+
if (!isCurrentResult()) return;
|
|
1460
|
+
await after?.(isCurrentResult);
|
|
1408
1461
|
})
|
|
1409
1462
|
.catch((cause) => {
|
|
1410
|
-
|
|
1463
|
+
markStarted(false);
|
|
1464
|
+
if (!isCurrent() || isStaleComputerActionResult(cause)) return;
|
|
1411
1465
|
actionQueueEpochRef.current += 1;
|
|
1412
1466
|
clearBufferedInput();
|
|
1413
1467
|
errorRef.current(cause);
|
|
1414
1468
|
});
|
|
1469
|
+
const completion = continuation
|
|
1470
|
+
? Promise.all([previous, delivery]).then(() => undefined)
|
|
1471
|
+
: delivery;
|
|
1472
|
+
actionTailRef.current = completion;
|
|
1473
|
+
return { operationId, started, completion };
|
|
1415
1474
|
},
|
|
1416
1475
|
[clearBufferedInput],
|
|
1417
1476
|
);
|
|
@@ -1436,6 +1495,7 @@ function ComputerViewport(props: {
|
|
|
1436
1495
|
if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
|
|
1437
1496
|
clickTimerRef.current = null;
|
|
1438
1497
|
lastClickRef.current = null;
|
|
1498
|
+
if (!pending.pending) return;
|
|
1439
1499
|
enqueue(
|
|
1440
1500
|
{
|
|
1441
1501
|
type: "pointer",
|
|
@@ -1469,7 +1529,13 @@ function ComputerViewport(props: {
|
|
|
1469
1529
|
|
|
1470
1530
|
const pointerDown = (event: PointerEvent<HTMLCanvasElement>) => {
|
|
1471
1531
|
const frame = paintedFrameRef.current;
|
|
1472
|
-
if (
|
|
1532
|
+
if (
|
|
1533
|
+
!frame ||
|
|
1534
|
+
(!props.pointerClickContinuation && props.mutating) ||
|
|
1535
|
+
!rawInputEnabled ||
|
|
1536
|
+
event.button !== 0
|
|
1537
|
+
)
|
|
1538
|
+
return;
|
|
1473
1539
|
// Preserve the keyboard sink's focus through the canvas pointer default action.
|
|
1474
1540
|
event.preventDefault();
|
|
1475
1541
|
if (keyboardInputEnabled) inputRef.current?.focus({ preventScroll: true });
|
|
@@ -1490,6 +1556,7 @@ function ComputerViewport(props: {
|
|
|
1490
1556
|
const start = pointerStartRef.current;
|
|
1491
1557
|
pointerStartRef.current = null;
|
|
1492
1558
|
if (!start || start.pointerId !== event.pointerId) return;
|
|
1559
|
+
const canvasRect = event.currentTarget.getBoundingClientRect();
|
|
1493
1560
|
const from = point(start.frame, start.x, start.y);
|
|
1494
1561
|
const to = point(start.frame, event.clientX, event.clientY);
|
|
1495
1562
|
if (!from || !to) return;
|
|
@@ -1514,8 +1581,18 @@ function ComputerViewport(props: {
|
|
|
1514
1581
|
if (
|
|
1515
1582
|
previous &&
|
|
1516
1583
|
now - previous.at < 280 &&
|
|
1517
|
-
|
|
1518
|
-
|
|
1584
|
+
previous.pending === !props.pointerClickContinuation &&
|
|
1585
|
+
(props.pointerClickContinuation
|
|
1586
|
+
? Math.hypot(previous.clientX - event.clientX, previous.clientY - event.clientY) < 6 &&
|
|
1587
|
+
previous.canvasRect.left === canvasRect.left &&
|
|
1588
|
+
previous.canvasRect.top === canvasRect.top &&
|
|
1589
|
+
previous.canvasRect.width === canvasRect.width &&
|
|
1590
|
+
previous.canvasRect.height === canvasRect.height &&
|
|
1591
|
+
sameComputerTarget(previous.frame, start.frame) &&
|
|
1592
|
+
previous.frame.width === start.frame.width &&
|
|
1593
|
+
previous.frame.height === start.frame.height
|
|
1594
|
+
: Math.hypot(previous.x - to.x, previous.y - to.y) < 6 &&
|
|
1595
|
+
sameFrameFence(previous.frame, start.frame))
|
|
1519
1596
|
) {
|
|
1520
1597
|
if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
|
|
1521
1598
|
clickTimerRef.current = null;
|
|
@@ -1524,15 +1601,47 @@ function ComputerViewport(props: {
|
|
|
1524
1601
|
{
|
|
1525
1602
|
type: "pointer",
|
|
1526
1603
|
frameId: start.frame.frameId,
|
|
1527
|
-
action: "double_click",
|
|
1604
|
+
action: props.pointerClickContinuation ? "click" : "double_click",
|
|
1605
|
+
...(props.pointerClickContinuation ? { clickCount: 2 as const } : {}),
|
|
1606
|
+
...(previous.delivery
|
|
1607
|
+
? { continuationOfOperationId: previous.delivery.operationId }
|
|
1608
|
+
: {}),
|
|
1609
|
+
x: to.x,
|
|
1610
|
+
y: to.y,
|
|
1611
|
+
},
|
|
1612
|
+
start.frame,
|
|
1613
|
+
undefined,
|
|
1614
|
+
previous.delivery ?? undefined,
|
|
1615
|
+
);
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
flushPendingClick();
|
|
1619
|
+
lastClickRef.current = {
|
|
1620
|
+
at: now,
|
|
1621
|
+
x: to.x,
|
|
1622
|
+
y: to.y,
|
|
1623
|
+
frame: start.frame,
|
|
1624
|
+
clientX: event.clientX,
|
|
1625
|
+
clientY: event.clientY,
|
|
1626
|
+
canvasRect,
|
|
1627
|
+
pending: !props.pointerClickContinuation,
|
|
1628
|
+
delivery: null,
|
|
1629
|
+
};
|
|
1630
|
+
if (props.pointerClickContinuation) {
|
|
1631
|
+
const delivery = enqueue(
|
|
1632
|
+
{
|
|
1633
|
+
type: "pointer",
|
|
1634
|
+
frameId: start.frame.frameId,
|
|
1635
|
+
action: "click",
|
|
1636
|
+
clickCount: 1,
|
|
1528
1637
|
x: to.x,
|
|
1529
1638
|
y: to.y,
|
|
1530
1639
|
},
|
|
1531
1640
|
start.frame,
|
|
1532
1641
|
);
|
|
1642
|
+
if (lastClickRef.current) lastClickRef.current.delivery = delivery;
|
|
1533
1643
|
return;
|
|
1534
1644
|
}
|
|
1535
|
-
lastClickRef.current = { at: now, x: to.x, y: to.y, frame: start.frame };
|
|
1536
1645
|
clickTimerRef.current = setTimeout(() => {
|
|
1537
1646
|
clickTimerRef.current = null;
|
|
1538
1647
|
lastClickRef.current = null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GROK_MARK_PATH } from "../model-mark-paths";
|
|
1
2
|
import type { SVGProps } from "react";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -27,7 +28,7 @@ import type { SVGProps } from "react";
|
|
|
27
28
|
export function GrokMark(props: SVGProps<SVGSVGElement>) {
|
|
28
29
|
return (
|
|
29
30
|
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" {...props}>
|
|
30
|
-
<path d=
|
|
31
|
+
<path d={GROK_MARK_PATH} />
|
|
31
32
|
</svg>
|
|
32
33
|
);
|
|
33
34
|
}
|