@opengeni/react 0.37.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/README.md +88 -2
- package/dist/{chunk-7CJOAW3V.js → chunk-EB67J347.js} +1 -135
- package/dist/chunk-EB67J347.js.map +1 -0
- package/dist/chunk-EKZH6IDG.js +141 -0
- package/dist/chunk-EKZH6IDG.js.map +1 -0
- package/dist/{chunk-FSPDND3P.js → chunk-JALF5FI3.js} +8 -6
- package/dist/{chunk-FSPDND3P.js.map → chunk-JALF5FI3.js.map} +1 -1
- package/dist/{chunk-UCZPNXV3.js → chunk-KR2SK5GJ.js} +1292 -711
- package/dist/chunk-KR2SK5GJ.js.map +1 -0
- package/dist/{chunk-IP22SLO6.js → chunk-OMCFRWHL.js} +28 -441
- package/dist/chunk-OMCFRWHL.js.map +1 -0
- package/dist/{chunk-HJ4OQVGW.js → chunk-Q2NCKWTK.js} +10 -5
- package/dist/chunk-Q2NCKWTK.js.map +1 -0
- package/dist/{chunk-ALA3UGWB.js → chunk-SXIQK54Z.js} +8 -6
- package/dist/{chunk-ALA3UGWB.js.map → chunk-SXIQK54Z.js.map} +1 -1
- package/dist/chunk-WZT5G5OR.js +433 -0
- package/dist/chunk-WZT5G5OR.js.map +1 -0
- package/dist/client.d.ts +2 -0
- package/dist/components/chat-composer.d.ts +5 -1
- package/dist/components/human-input-form.d.ts +16 -6
- package/dist/components/human-input-surface.d.ts +19 -0
- package/dist/components/machine-input-display.d.ts +12 -0
- package/dist/components/message-timeline.d.ts +6 -1
- package/dist/components/session-chrome.d.ts +1 -0
- package/dist/components/tip-follow.d.ts +30 -0
- package/dist/composer.js +14 -11
- package/dist/hooks/use-composer.d.ts +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2246 -2236
- package/dist/index.js.map +1 -1
- package/dist/machines.js +4 -3
- package/dist/realtime/dropdown-menu.d.ts +16 -0
- package/dist/realtime/realtime-control.d.ts +126 -0
- package/dist/realtime.d.ts +11 -0
- package/dist/realtime.js +1023 -0
- package/dist/realtime.js.map +1 -0
- package/dist/session-context.d.ts +7 -1
- package/dist/session-ui.d.ts +2 -0
- package/dist/session-ui.js +5 -3
- package/dist/session.js +4 -3
- package/dist/timeline/compute-label.d.ts +2 -0
- package/dist/timeline/index.d.ts +1 -0
- package/package.json +7 -2
- package/src/client.ts +13 -0
- package/src/components/chat-composer.tsx +11 -2
- package/src/components/composer-transcription-control.tsx +2 -2
- package/src/components/composer.tsx +14 -5
- package/src/components/human-input-form.tsx +506 -186
- package/src/components/human-input-surface.tsx +80 -0
- package/src/components/machine-input-display.ts +83 -0
- package/src/components/message-timeline.tsx +348 -308
- package/src/components/model-policy-picker.tsx +8 -3
- package/src/components/sandbox-files.tsx +1 -1
- package/src/components/session-chrome.tsx +3 -1
- package/src/components/tip-follow.ts +127 -6
- package/src/hooks/use-composer.ts +16 -13
- package/src/index.ts +4 -0
- package/src/realtime/dropdown-menu.tsx +123 -0
- package/src/realtime/realtime-control.tsx +1178 -0
- package/src/realtime.ts +27 -0
- package/src/session-context.ts +16 -0
- package/src/session-ui.ts +2 -0
- package/src/timeline/compute-label.tsx +18 -0
- package/src/timeline/index.ts +3 -0
- package/src/timeline/tool-renderers.tsx +232 -43
- package/styles/index.css +61 -0
- package/styles/tokens.css +2 -0
- package/dist/chunk-7CJOAW3V.js.map +0 -1
- package/dist/chunk-HJ4OQVGW.js.map +0 -1
- package/dist/chunk-IP22SLO6.js.map +0 -1
- package/dist/chunk-UCZPNXV3.js.map +0 -1
|
@@ -452,6 +452,11 @@ export function ModelPolicyPickerMenu(
|
|
|
452
452
|
<p className="px-2.5 pt-1 pb-1 text-xs font-medium tracking-wide text-og-fg-subtle uppercase">
|
|
453
453
|
{messages.thinking}
|
|
454
454
|
</p>
|
|
455
|
+
{focusModel.catalog.capabilities?.inputModalities.includes("image") === false ? (
|
|
456
|
+
<p className="px-2.5 pb-1.5 text-xs leading-relaxed text-og-fg-subtle">
|
|
457
|
+
Unsupported attachments stay in the session but are hidden from this model.
|
|
458
|
+
</p>
|
|
459
|
+
) : null}
|
|
455
460
|
<div className="flex flex-col gap-0.5">
|
|
456
461
|
{effortOptionsForModel(focusModel.catalog).map((effort) => {
|
|
457
462
|
const selected = activeModel && effort === props.effort;
|
|
@@ -540,7 +545,7 @@ export function ModelPolicyPicker(props: ModelPolicyPickerProps) {
|
|
|
540
545
|
disabled={props.disabled}
|
|
541
546
|
aria-label={messages.label}
|
|
542
547
|
className={cn(
|
|
543
|
-
"inline-flex h-8 max-w-64 items-center gap-1 rounded-full border border-transparent px-2.5 text-xs text-og-fg-muted outline-none transition-colors hover:border-og-border hover:bg-og-surface-2 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50",
|
|
548
|
+
"inline-flex h-8 max-w-64 items-center gap-1 rounded-full border border-transparent px-2.5 text-xs text-og-fg-muted outline-none transition-colors hover:border-og-border hover:bg-og-surface-2 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50 max-sm:h-7 max-sm:max-w-[9rem] max-sm:px-2",
|
|
544
549
|
props.className,
|
|
545
550
|
)}
|
|
546
551
|
>
|
|
@@ -552,10 +557,10 @@ export function ModelPolicyPicker(props: ModelPolicyPickerProps) {
|
|
|
552
557
|
className="text-og-fg"
|
|
553
558
|
/>
|
|
554
559
|
<span className="truncate font-medium text-og-fg">{selected?.label ?? props.model}</span>
|
|
555
|
-
<span>{labelReasoningEffort(props.effort)}</span>
|
|
560
|
+
<span className="max-sm:hidden">{labelReasoningEffort(props.effort)}</span>
|
|
556
561
|
{props.latencyMode === "fast" ? (
|
|
557
562
|
<ZapIcon
|
|
558
|
-
className="size-3.5 shrink-0 text-og-fg"
|
|
563
|
+
className="size-3.5 shrink-0 fill-current stroke-current text-og-fg"
|
|
559
564
|
aria-label={messages.fast}
|
|
560
565
|
data-testid="model-picker-fast-icon"
|
|
561
566
|
/>
|
|
@@ -382,7 +382,7 @@ function WakeButton({ children, onClick }: { children: ReactNode; onClick: () =>
|
|
|
382
382
|
<button
|
|
383
383
|
type="button"
|
|
384
384
|
onClick={onClick}
|
|
385
|
-
className="mt-1 inline-flex min-h-11 items-center justify-center rounded-og-md bg-og-accent px-3 py-2 text-og-sm font-medium text-og-
|
|
385
|
+
className="mt-1 inline-flex min-h-11 items-center justify-center rounded-og-md bg-og-accent-deep px-3 py-2 text-og-sm font-medium text-og-accent-fg shadow-sm transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent focus-visible:ring-offset-2 focus-visible:ring-offset-og-bg"
|
|
386
386
|
>
|
|
387
387
|
{children}
|
|
388
388
|
</button>
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* | `--og-session-chrome-chip-pad-x` / `--og-session-chrome-chip-gap` | Chip padding / rail gap |
|
|
19
19
|
* | `--og-session-chrome-rail-pad` | Outer rail inset |
|
|
20
20
|
* | `--og-session-chrome-panel-pad-x` / `-y` | Expanded panel padding |
|
|
21
|
+
* | `--og-session-chrome-panel-max-height` | Cap for expanded body (scrolls inside) |
|
|
21
22
|
* | `--og-session-chrome-duration` / `--og-session-chrome-ease` | Expand + pill motion |
|
|
22
23
|
* | `--og-session-chrome-crossfade-duration` | Segment content opacity crossfade |
|
|
23
24
|
* | `--og-session-chrome-row-hover` | Inbox / queue row hover wash |
|
|
@@ -664,8 +665,9 @@ export function SessionChrome({
|
|
|
664
665
|
>
|
|
665
666
|
<div
|
|
666
667
|
ref={panelBodyRef}
|
|
667
|
-
className="relative border-t border-og-border/50"
|
|
668
|
+
className="relative overflow-y-auto overscroll-contain border-t border-og-border/50"
|
|
668
669
|
style={{
|
|
670
|
+
maxHeight: "var(--og-session-chrome-panel-max-height)",
|
|
669
671
|
paddingInline: "var(--og-session-chrome-panel-pad-x)",
|
|
670
672
|
paddingBlock: "var(--og-session-chrome-panel-pad-y)",
|
|
671
673
|
}}
|
|
@@ -81,6 +81,13 @@ export const TIP_FOLLOW_VELOCITY_ARM_DEBT_PX = 72;
|
|
|
81
81
|
export const TIP_FOLLOW_VELOCITY_DECAY_MS = _t(180);
|
|
82
82
|
/** Reader-up pixels above clamp budget that count as leaving the tip. */
|
|
83
83
|
export const TIP_FOLLOW_READER_UP_EPS_PX = 2;
|
|
84
|
+
/**
|
|
85
|
+
* Browsers snap scrollTop writes to device pixels (whole px at dpr 1). A DOM
|
|
86
|
+
* position within this window of the camera's own fractional position is that
|
|
87
|
+
* quantization echo, not external motion — keep the fraction so sub-pixel
|
|
88
|
+
* settle steps accumulate. Anything larger (clamp, user, glue) re-bases.
|
|
89
|
+
*/
|
|
90
|
+
export const TIP_FOLLOW_QUANTIZE_WINDOW_PX = 1;
|
|
84
91
|
|
|
85
92
|
/** Test-only override for scrollend feature detection (`null` = probe DOM). */
|
|
86
93
|
let scrollEndSupportOverride: boolean | null = null;
|
|
@@ -122,11 +129,20 @@ export type TipFollowState = {
|
|
|
122
129
|
hotUntil: number;
|
|
123
130
|
lastTs: number;
|
|
124
131
|
lastHeight: number;
|
|
132
|
+
/** Last observed scroller clientHeight — viewport shrink heats like tip growth. */
|
|
133
|
+
lastClientHeight: number;
|
|
125
134
|
lastGrowthAt: number;
|
|
126
135
|
/** EMA of recent height growth (px/s); nudges τ / ceiling under bursts. */
|
|
127
136
|
growthVelocity: number;
|
|
128
137
|
/** Camera velocity toward tip (px/s). Smoothed — never jumps with debt. */
|
|
129
138
|
scrollVelocity: number;
|
|
139
|
+
/**
|
|
140
|
+
* Fractional camera position. The DOM quantizes scrollTop to device pixels,
|
|
141
|
+
* so settle steps under 1px would be discarded every frame if the camera
|
|
142
|
+
* re-based on the DOM — it then parks 20-50px short of the tip forever.
|
|
143
|
+
* `null` = no fraction in flight; adopt the DOM position.
|
|
144
|
+
*/
|
|
145
|
+
cameraTop: number | null;
|
|
130
146
|
};
|
|
131
147
|
|
|
132
148
|
export type TipFollowStepInput = {
|
|
@@ -150,9 +166,11 @@ export function createTipFollowState(): TipFollowState {
|
|
|
150
166
|
hotUntil: 0,
|
|
151
167
|
lastTs: 0,
|
|
152
168
|
lastHeight: 0,
|
|
169
|
+
lastClientHeight: 0,
|
|
153
170
|
lastGrowthAt: 0,
|
|
154
171
|
growthVelocity: 0,
|
|
155
172
|
scrollVelocity: 0,
|
|
173
|
+
cameraTop: null,
|
|
156
174
|
};
|
|
157
175
|
}
|
|
158
176
|
|
|
@@ -162,6 +180,7 @@ export function tipFollowCancel(state: TipFollowState): TipFollowState {
|
|
|
162
180
|
running: false,
|
|
163
181
|
lastTs: 0,
|
|
164
182
|
scrollVelocity: 0,
|
|
183
|
+
cameraTop: null,
|
|
165
184
|
};
|
|
166
185
|
}
|
|
167
186
|
|
|
@@ -196,6 +215,49 @@ export function tipFollowNoteGrowth(
|
|
|
196
215
|
};
|
|
197
216
|
}
|
|
198
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Record scroller viewport height. A shrink (composer / SessionChrome /
|
|
220
|
+
* window) raises tip debt (`scrollHeight - clientHeight`) without growing
|
|
221
|
+
* content — the same debt content-growth would create. Heat the camera so
|
|
222
|
+
* catch-up does not fall into cold soft-settle (~42px/s) and leave the tip
|
|
223
|
+
* parked under the chrome.
|
|
224
|
+
*/
|
|
225
|
+
export function tipFollowNoteViewportShrink(
|
|
226
|
+
state: TipFollowState,
|
|
227
|
+
clientHeight: number,
|
|
228
|
+
now: number,
|
|
229
|
+
): TipFollowState {
|
|
230
|
+
const previous = state.lastClientHeight;
|
|
231
|
+
if (previous <= 0) {
|
|
232
|
+
return { ...state, lastClientHeight: clientHeight };
|
|
233
|
+
}
|
|
234
|
+
const shrink = previous - clientHeight;
|
|
235
|
+
if (shrink <= 0) {
|
|
236
|
+
return { ...state, lastClientHeight: clientHeight };
|
|
237
|
+
}
|
|
238
|
+
if (shrink <= TIP_FOLLOW_SHRINK_EPS_PX) {
|
|
239
|
+
// Sub-eps shrink: HOLD the baseline (deadband with memory). An animated
|
|
240
|
+
// chrome/composer ease-out delivers many ≤eps frames; adopting each one
|
|
241
|
+
// leaked the whole tail into cold debt with no glue and no heat.
|
|
242
|
+
return state;
|
|
243
|
+
}
|
|
244
|
+
const dt = state.lastGrowthAt > 0 ? now - state.lastGrowthAt : 0;
|
|
245
|
+
let growthVelocity = state.growthVelocity;
|
|
246
|
+
if (dt > 0) {
|
|
247
|
+
const instant = (shrink / dt) * 1000;
|
|
248
|
+
growthVelocity = growthVelocity * 0.65 + instant * 0.35;
|
|
249
|
+
} else {
|
|
250
|
+
growthVelocity = Math.max(growthVelocity, shrink * (1000 / TIP_FOLLOW_HOT_IDLE_MS));
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
...state,
|
|
254
|
+
lastClientHeight: clientHeight,
|
|
255
|
+
lastGrowthAt: now,
|
|
256
|
+
growthVelocity,
|
|
257
|
+
hotUntil: now + TIP_FOLLOW_HOT_IDLE_MS,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
199
261
|
/**
|
|
200
262
|
* Adaptive τ: small debt → calm; large debt / armed velocity → catch-up.
|
|
201
263
|
* While hot, floor urgency so live streams are not stuck on calm τ.
|
|
@@ -376,6 +438,29 @@ export function tipFollowCompensateShrink(
|
|
|
376
438
|
return Math.max(0, Math.min(maxScroll, scrollTop - delta));
|
|
377
439
|
}
|
|
378
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Viewport shrank (SessionChrome / composer / window). maxScroll rises by ≈Δc;
|
|
443
|
+
* keep the same tip distance: scrollTop += Δc. Without this, pinned follow only
|
|
444
|
+
* sees new tip debt and soft-settles under the chrome.
|
|
445
|
+
*/
|
|
446
|
+
export function tipFollowCompensateViewportShrink(
|
|
447
|
+
scrollTop: number,
|
|
448
|
+
previousClientHeight: number,
|
|
449
|
+
nextClientHeight: number,
|
|
450
|
+
scrollHeight: number,
|
|
451
|
+
epsPx: number = TIP_FOLLOW_SHRINK_EPS_PX,
|
|
452
|
+
): number {
|
|
453
|
+
if (previousClientHeight <= 0) {
|
|
454
|
+
return scrollTop;
|
|
455
|
+
}
|
|
456
|
+
const shrink = previousClientHeight - nextClientHeight;
|
|
457
|
+
if (shrink <= epsPx) {
|
|
458
|
+
return scrollTop;
|
|
459
|
+
}
|
|
460
|
+
const maxScroll = Math.max(0, scrollHeight - nextClientHeight);
|
|
461
|
+
return Math.max(0, Math.min(maxScroll, scrollTop + shrink));
|
|
462
|
+
}
|
|
463
|
+
|
|
379
464
|
/**
|
|
380
465
|
* One camera step. Shell schedules the next rAF while `result.state.running`.
|
|
381
466
|
*/
|
|
@@ -385,12 +470,39 @@ export function tipFollowStep(
|
|
|
385
470
|
): TipFollowStepResult {
|
|
386
471
|
const { scrollTop, scrollHeight, clientHeight, now, pinned, reducedMotion, revealed } = input;
|
|
387
472
|
const previousHeight = state.lastHeight;
|
|
473
|
+
const previousClient = state.lastClientHeight;
|
|
388
474
|
const frameGrowth =
|
|
389
475
|
previousHeight > 0 && scrollHeight > previousHeight ? scrollHeight - previousHeight : 0;
|
|
390
|
-
const
|
|
476
|
+
const frameViewportShrink =
|
|
477
|
+
previousClient > 0 && clientHeight < previousClient - TIP_FOLLOW_SHRINK_EPS_PX
|
|
478
|
+
? previousClient - clientHeight
|
|
479
|
+
: 0;
|
|
480
|
+
const grew = frameGrowth > 0 || frameViewportShrink > 0;
|
|
391
481
|
let noted = tipFollowNoteGrowth(state, scrollHeight, now);
|
|
482
|
+
noted = tipFollowNoteViewportShrink(noted, clientHeight, now);
|
|
483
|
+
// Sub-device-pixel writes are floored by the engine: a DOM position within
|
|
484
|
+
// the quantization window of our own fractional camera is the echo of our
|
|
485
|
+
// last write, not external motion — keep the fraction so settle steps
|
|
486
|
+
// accumulate. Larger disagreement (clamp / reader / snap) re-bases.
|
|
487
|
+
let cameraTop = scrollTop;
|
|
488
|
+
if (
|
|
489
|
+
state.cameraTop !== null &&
|
|
490
|
+
Math.abs(state.cameraTop - scrollTop) <= TIP_FOLLOW_QUANTIZE_WINDOW_PX
|
|
491
|
+
) {
|
|
492
|
+
cameraTop = state.cameraTop;
|
|
493
|
+
}
|
|
494
|
+
// Chrome/composer dock: tip-glue before ease so debt from ΔclientHeight is
|
|
495
|
+
// not left for cold soft-settle (content still under the new bar).
|
|
496
|
+
if (pinned && frameViewportShrink > 0) {
|
|
497
|
+
cameraTop = tipFollowCompensateViewportShrink(
|
|
498
|
+
cameraTop,
|
|
499
|
+
previousClient,
|
|
500
|
+
clientHeight,
|
|
501
|
+
scrollHeight,
|
|
502
|
+
);
|
|
503
|
+
}
|
|
392
504
|
const target = targetScrollTop(scrollHeight, clientHeight);
|
|
393
|
-
const debt = target -
|
|
505
|
+
const debt = target - cameraTop;
|
|
394
506
|
const hot = now < noted.hotUntil;
|
|
395
507
|
const settling = !hot && Math.abs(debt) < TIP_FOLLOW_CATCHUP_DEBT_PX;
|
|
396
508
|
|
|
@@ -426,7 +538,7 @@ export function tipFollowStep(
|
|
|
426
538
|
|
|
427
539
|
if (Math.abs(debt) <= 0.75) {
|
|
428
540
|
return {
|
|
429
|
-
scrollTop: Math.abs(debt) > 0 ? target :
|
|
541
|
+
scrollTop: Math.abs(debt) > 0 ? target : cameraTop,
|
|
430
542
|
state: {
|
|
431
543
|
...tipFollowCancel(noted),
|
|
432
544
|
growthVelocity: hot ? noted.growthVelocity : 0,
|
|
@@ -445,13 +557,19 @@ export function tipFollowStep(
|
|
|
445
557
|
let scrollVelocity =
|
|
446
558
|
(noted.scrollVelocity ?? 0) + (desiredVel - (noted.scrollVelocity ?? 0)) * velAlpha;
|
|
447
559
|
|
|
448
|
-
const maxStep = tipFollowMaxStepPx(
|
|
560
|
+
const maxStep = tipFollowMaxStepPx(
|
|
561
|
+
debt,
|
|
562
|
+
noted.growthVelocity,
|
|
563
|
+
dt,
|
|
564
|
+
frameGrowth + frameViewportShrink,
|
|
565
|
+
hot,
|
|
566
|
+
);
|
|
449
567
|
const maxVel = maxStep / dtSec;
|
|
450
568
|
if (Math.abs(scrollVelocity) > maxVel) {
|
|
451
569
|
scrollVelocity = Math.sign(scrollVelocity) * maxVel;
|
|
452
570
|
}
|
|
453
571
|
|
|
454
|
-
let next =
|
|
572
|
+
let next = cameraTop + scrollVelocity * dtSec;
|
|
455
573
|
// Never overshoot the tip — stop and kill velocity on arrival.
|
|
456
574
|
if (debt > 0 && next >= target - 0.35) {
|
|
457
575
|
next = target;
|
|
@@ -462,13 +580,16 @@ export function tipFollowStep(
|
|
|
462
580
|
}
|
|
463
581
|
next = Math.max(0, next);
|
|
464
582
|
|
|
583
|
+
const running = Math.abs(target - next) > 0.35 || Math.abs(scrollVelocity) > 1;
|
|
465
584
|
return {
|
|
466
585
|
scrollTop: next,
|
|
467
586
|
state: {
|
|
468
587
|
...noted,
|
|
469
588
|
scrollVelocity,
|
|
470
|
-
running
|
|
589
|
+
running,
|
|
471
590
|
lastTs: now,
|
|
591
|
+
// Carry the fraction the DOM will floor away; drop it once landed.
|
|
592
|
+
cameraTop: running ? next : null,
|
|
472
593
|
},
|
|
473
594
|
};
|
|
474
595
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_FILE_RESOURCE_MOUNT_ROOT,
|
|
3
|
+
type ComposerDraft,
|
|
4
|
+
type EffectiveControlResumeOption,
|
|
5
|
+
type EffectiveSessionControl,
|
|
6
|
+
type OpenGeniApiError,
|
|
7
|
+
type ResourceRef,
|
|
8
|
+
type SaveComposerDraftRequest,
|
|
9
|
+
type SendMessageInput,
|
|
10
|
+
type SessionEvent,
|
|
10
11
|
} from "@opengeni/sdk";
|
|
11
12
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
12
13
|
import { useEmbeddedSession, type EmbeddedSessionClientOverride } from "../session-context";
|
|
@@ -537,13 +538,15 @@ export function useComposer(
|
|
|
537
538
|
replaceLocal ||
|
|
538
539
|
(!localWasDirtyAtStart && localAtStart === localEditRevision.current)
|
|
539
540
|
) {
|
|
540
|
-
// Model/effort ride in sendExtras (outside
|
|
541
|
+
// Model/effort/latency ride in sendExtras (outside
|
|
542
|
+
// localEditRevision). If
|
|
541
543
|
// the picker changed during this fetch, skip onDraftApplied so a
|
|
542
|
-
// stale server
|
|
544
|
+
// stale server policy cannot undo the operator's pick.
|
|
543
545
|
const extrasNow = resolveSendExtras(sendExtrasRef.current);
|
|
544
546
|
const pickerChangedDuringFetch =
|
|
545
547
|
extrasNow.model !== extrasAtStart.model ||
|
|
546
|
-
extrasNow.reasoningEffort !== extrasAtStart.reasoningEffort
|
|
548
|
+
extrasNow.reasoningEffort !== extrasAtStart.reasoningEffort ||
|
|
549
|
+
extrasNow.latencyMode !== extrasAtStart.latencyMode;
|
|
547
550
|
valueRef.current = fetched.text;
|
|
548
551
|
restoredResourcesRef.current = fetched.resources;
|
|
549
552
|
lastSavedSignature.current = draftSignature(draftPayload(fetched));
|
|
@@ -1442,7 +1445,7 @@ function mergeResources(base: ResourceRef[], additions: ResourceRef[]): Resource
|
|
|
1442
1445
|
// different duplicate counts after server normalization.
|
|
1443
1446
|
const key =
|
|
1444
1447
|
resource.kind === "file"
|
|
1445
|
-
? `file:${resource.fileId}\u0000${resource.mountPath ??
|
|
1448
|
+
? `file:${resource.fileId}\u0000${resource.mountPath ?? `${DEFAULT_FILE_RESOURCE_MOUNT_ROOT}/${resource.fileId}`}`
|
|
1446
1449
|
: JSON.stringify(resource);
|
|
1447
1450
|
if (seen.has(key)) return false;
|
|
1448
1451
|
seen.add(key);
|
package/src/index.ts
CHANGED
|
@@ -256,6 +256,8 @@ export type {
|
|
|
256
256
|
HumanInputFormMessages,
|
|
257
257
|
HumanInputFormProps,
|
|
258
258
|
} from "./components/human-input-form";
|
|
259
|
+
export { HumanInputSurface } from "./components/human-input-surface";
|
|
260
|
+
export type { HumanInputSurfaceProps } from "./components/human-input-surface";
|
|
259
261
|
|
|
260
262
|
// Timeline projection
|
|
261
263
|
export {
|
|
@@ -445,6 +447,8 @@ export { SandboxTerminal } from "./components/sandbox-terminal";
|
|
|
445
447
|
export type { SandboxTerminalProps, XtermTheme } from "./components/sandbox-terminal";
|
|
446
448
|
export { FileBrowser } from "./components/file-browser";
|
|
447
449
|
export type { FileBrowserProps } from "./components/file-browser";
|
|
450
|
+
export { WorkbenchChanges } from "./components/workbench-changes";
|
|
451
|
+
export type { WorkbenchChangesProps } from "./components/workbench-changes";
|
|
448
452
|
export { DiffView } from "./components/diff-view";
|
|
449
453
|
export type { DiffViewProps, DiffTheme } from "./components/diff-view";
|
|
450
454
|
export { PierreDiff } from "./components/pierre-diff";
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ChevronRightIcon } from "lucide-react";
|
|
4
|
+
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
|
|
5
|
+
import type { ComponentProps } from "react";
|
|
6
|
+
import { cn } from "../lib/cn";
|
|
7
|
+
|
|
8
|
+
function DropdownMenu(props: ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
|
9
|
+
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function DropdownMenuTrigger(props: ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
|
13
|
+
return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function DropdownMenuContent({
|
|
17
|
+
className,
|
|
18
|
+
sideOffset = 4,
|
|
19
|
+
...props
|
|
20
|
+
}: ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
|
21
|
+
return (
|
|
22
|
+
<DropdownMenuPrimitive.Portal>
|
|
23
|
+
<DropdownMenuPrimitive.Content
|
|
24
|
+
data-slot="dropdown-menu-content"
|
|
25
|
+
sideOffset={sideOffset}
|
|
26
|
+
className={cn(
|
|
27
|
+
"z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
</DropdownMenuPrimitive.Portal>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function DropdownMenuItem({
|
|
37
|
+
className,
|
|
38
|
+
inset,
|
|
39
|
+
variant = "default",
|
|
40
|
+
...props
|
|
41
|
+
}: ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
|
42
|
+
inset?: boolean;
|
|
43
|
+
variant?: "default" | "destructive";
|
|
44
|
+
}) {
|
|
45
|
+
return (
|
|
46
|
+
<DropdownMenuPrimitive.Item
|
|
47
|
+
data-slot="dropdown-menu-item"
|
|
48
|
+
data-inset={inset}
|
|
49
|
+
data-variant={variant}
|
|
50
|
+
className={cn(
|
|
51
|
+
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
|
|
52
|
+
className,
|
|
53
|
+
)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function DropdownMenuSeparator({
|
|
60
|
+
className,
|
|
61
|
+
...props
|
|
62
|
+
}: ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
|
63
|
+
return (
|
|
64
|
+
<DropdownMenuPrimitive.Separator
|
|
65
|
+
data-slot="dropdown-menu-separator"
|
|
66
|
+
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function DropdownMenuSub(props: ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
|
73
|
+
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function DropdownMenuSubTrigger({
|
|
77
|
+
className,
|
|
78
|
+
inset,
|
|
79
|
+
children,
|
|
80
|
+
...props
|
|
81
|
+
}: ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & { inset?: boolean }) {
|
|
82
|
+
return (
|
|
83
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
84
|
+
data-slot="dropdown-menu-sub-trigger"
|
|
85
|
+
data-inset={inset}
|
|
86
|
+
className={cn(
|
|
87
|
+
"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
|
88
|
+
className,
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
>
|
|
92
|
+
{children}
|
|
93
|
+
<ChevronRightIcon className="ml-auto size-4" />
|
|
94
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function DropdownMenuSubContent({
|
|
99
|
+
className,
|
|
100
|
+
...props
|
|
101
|
+
}: ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
|
102
|
+
return (
|
|
103
|
+
<DropdownMenuPrimitive.SubContent
|
|
104
|
+
data-slot="dropdown-menu-sub-content"
|
|
105
|
+
className={cn(
|
|
106
|
+
"z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
107
|
+
className,
|
|
108
|
+
)}
|
|
109
|
+
{...props}
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
DropdownMenu,
|
|
116
|
+
DropdownMenuContent,
|
|
117
|
+
DropdownMenuItem,
|
|
118
|
+
DropdownMenuSeparator,
|
|
119
|
+
DropdownMenuSub,
|
|
120
|
+
DropdownMenuSubContent,
|
|
121
|
+
DropdownMenuSubTrigger,
|
|
122
|
+
DropdownMenuTrigger,
|
|
123
|
+
};
|