@opengeni/react 0.36.1 → 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 +92 -2
- package/dist/{chunk-J2RVJ6JX.js → chunk-4IJCL7YO.js} +13 -2
- package/dist/chunk-4IJCL7YO.js.map +1 -0
- 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-OKKMZDQF.js → chunk-JALF5FI3.js} +9 -7
- package/dist/{chunk-OKKMZDQF.js.map → chunk-JALF5FI3.js.map} +1 -1
- package/dist/{chunk-Z5FV355Z.js → chunk-KR2SK5GJ.js} +1382 -724
- package/dist/chunk-KR2SK5GJ.js.map +1 -0
- package/dist/{chunk-N4XEBE23.js → chunk-OMCFRWHL.js} +22 -13
- package/dist/{chunk-N4XEBE23.js.map → chunk-OMCFRWHL.js.map} +1 -1
- package/dist/{chunk-FT2TXNGZ.js → chunk-Q2NCKWTK.js} +183 -18
- package/dist/chunk-Q2NCKWTK.js.map +1 -0
- package/dist/{chunk-22RM4GMO.js → chunk-SJKT4TKW.js} +26 -2
- package/dist/chunk-SJKT4TKW.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 +7 -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/model-policy-picker.d.ts +78 -0
- package/dist/components/session-chrome.d.ts +2 -1
- package/dist/components/tip-follow.d.ts +30 -0
- package/dist/composer.d.ts +2 -0
- package/dist/composer.js +22 -5
- package/dist/hooks/use-composer.d.ts +10 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +2261 -2230
- package/dist/index.js.map +1 -1
- package/dist/machines.js +4 -3
- package/dist/model-policy.d.ts +12 -4
- package/dist/model-policy.js +5 -1
- 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 +6 -4
- package/dist/session.js +5 -4
- package/dist/timeline/compute-label.d.ts +2 -0
- package/dist/timeline/index.d.ts +1 -0
- package/dist/timeline/types.d.ts +5 -0
- package/package.json +7 -2
- package/src/client.ts +13 -0
- package/src/components/chat-composer.tsx +19 -5
- 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 +587 -0
- package/src/components/sandbox-files.tsx +1 -1
- package/src/components/session-chrome.tsx +141 -15
- package/src/components/tip-follow.ts +127 -6
- package/src/composer.ts +13 -0
- package/src/hooks/use-composer.ts +229 -29
- package/src/index.ts +19 -0
- package/src/model-policy.ts +51 -7
- 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/projection.ts +21 -1
- package/src/timeline/tool-renderers.tsx +232 -43
- package/src/timeline/types.ts +5 -0
- package/styles/index.css +61 -0
- package/styles/tokens.css +2 -0
- package/dist/chunk-22RM4GMO.js.map +0 -1
- package/dist/chunk-7CJOAW3V.js.map +0 -1
- package/dist/chunk-FT2TXNGZ.js.map +0 -1
- package/dist/chunk-J2RVJ6JX.js.map +0 -1
- package/dist/chunk-Z5FV355Z.js.map +0 -1
|
@@ -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";
|
|
@@ -225,6 +226,8 @@ export type ComposerState = {
|
|
|
225
226
|
send: (text?: string) => Promise<boolean>;
|
|
226
227
|
/** Supersede current direction with the draft. */
|
|
227
228
|
steer: (text?: string) => Promise<boolean>;
|
|
229
|
+
/** Optimistic-to-durable projection for a Steer that has not started yet. */
|
|
230
|
+
steering?: ComposerSteeringState | null | undefined;
|
|
228
231
|
sending: boolean;
|
|
229
232
|
canSend: boolean;
|
|
230
233
|
/** Pause the session without deleting its prompt queue. */
|
|
@@ -250,6 +253,59 @@ export type ComposerState = {
|
|
|
250
253
|
clearError: () => void;
|
|
251
254
|
};
|
|
252
255
|
|
|
256
|
+
export type ComposerSteeringState = {
|
|
257
|
+
phase: "submitting" | "accepted";
|
|
258
|
+
text: string;
|
|
259
|
+
clientEventId: string | null;
|
|
260
|
+
triggerEventId: string | null;
|
|
261
|
+
turnId: string | null;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const STEERING_SETTLEMENT_EVENT_TYPES = new Set([
|
|
265
|
+
"turn.started",
|
|
266
|
+
"turn.completed",
|
|
267
|
+
"turn.failed",
|
|
268
|
+
"turn.cancelled",
|
|
269
|
+
"turn.superseded",
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
function isSteeringSettlementEvent(event: SessionEvent): boolean {
|
|
273
|
+
return STEERING_SETTLEMENT_EVENT_TYPES.has(event.type);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function steeringAcceptedEvent(
|
|
277
|
+
steering: ComposerSteeringState,
|
|
278
|
+
events: readonly SessionEvent[],
|
|
279
|
+
): SessionEvent | undefined {
|
|
280
|
+
return events.find(
|
|
281
|
+
(event) =>
|
|
282
|
+
event.type === "user.message" &&
|
|
283
|
+
steering.clientEventId !== null &&
|
|
284
|
+
event.clientEventId === steering.clientEventId,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function steeringSettledByEvents(
|
|
289
|
+
steering: ComposerSteeringState,
|
|
290
|
+
events: readonly SessionEvent[],
|
|
291
|
+
): boolean {
|
|
292
|
+
const acceptedEventId =
|
|
293
|
+
steering.triggerEventId ?? steeringAcceptedEvent(steering, events)?.id ?? null;
|
|
294
|
+
return events.some((event) => {
|
|
295
|
+
if (steering.turnId && event.turnId === steering.turnId && isSteeringSettlementEvent(event)) {
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
if (event.type !== "turn.started" || !acceptedEventId) return false;
|
|
299
|
+
const payload = event.payload;
|
|
300
|
+
return (
|
|
301
|
+
typeof payload === "object" &&
|
|
302
|
+
payload !== null &&
|
|
303
|
+
"triggerEventId" in payload &&
|
|
304
|
+
payload.triggerEventId === acceptedEventId
|
|
305
|
+
);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
253
309
|
/**
|
|
254
310
|
* Draft + send + Pause/Resume state for the chat composer — the only
|
|
255
311
|
* human-to-agent input surface. The draft survives a failed send (nothing is
|
|
@@ -271,6 +327,17 @@ export function useComposer(
|
|
|
271
327
|
// a parent may switch sessionId without remounting this public hook.
|
|
272
328
|
const [stateTargetKey, setStateTargetKey] = useState(targetKey);
|
|
273
329
|
const [sending, setSending] = useState(false);
|
|
330
|
+
const [steering, setSteering] = useState<ComposerSteeringState | null>(() =>
|
|
331
|
+
initialPendingOperation?.delivery === "steer"
|
|
332
|
+
? {
|
|
333
|
+
phase: "submitting",
|
|
334
|
+
text: initialPendingOperation.input.text,
|
|
335
|
+
clientEventId: initialPendingOperation.input.clientEventId ?? null,
|
|
336
|
+
triggerEventId: null,
|
|
337
|
+
turnId: null,
|
|
338
|
+
}
|
|
339
|
+
: null,
|
|
340
|
+
);
|
|
274
341
|
const [pausing, setPausing] = useState(false);
|
|
275
342
|
const [resuming, setResuming] = useState(false);
|
|
276
343
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -282,6 +349,8 @@ export function useComposer(
|
|
|
282
349
|
() => initialShadow?.resources ?? [],
|
|
283
350
|
);
|
|
284
351
|
const pendingOperationRef = useRef<PendingComposerOperation | null>(initialPendingOperation);
|
|
352
|
+
const steeringSettlementEventsRef = useRef<SessionEvent[]>([]);
|
|
353
|
+
const steeringRef = useRef(steering);
|
|
285
354
|
const pendingClientEventId = useRef<string | null>(
|
|
286
355
|
initialPendingOperation?.input.clientEventId ?? null,
|
|
287
356
|
);
|
|
@@ -303,6 +372,9 @@ export function useComposer(
|
|
|
303
372
|
useLayoutEffect(() => {
|
|
304
373
|
onDraftAppliedRef.current = onDraftApplied;
|
|
305
374
|
}, [onDraftApplied]);
|
|
375
|
+
useLayoutEffect(() => {
|
|
376
|
+
steeringRef.current = steering;
|
|
377
|
+
}, [steering]);
|
|
306
378
|
// Read through a ref so a new extras closure (created every render by
|
|
307
379
|
// callers passing inline functions) does not invalidate `send`.
|
|
308
380
|
const sendExtrasRef = useRef(options.sendExtras);
|
|
@@ -320,6 +392,7 @@ export function useComposer(
|
|
|
320
392
|
targetGeneration.current += 1;
|
|
321
393
|
draftReadGeneration.current += 1;
|
|
322
394
|
pendingOperationRef.current = restorePendingComposerOperation(pendingOperationKey);
|
|
395
|
+
steeringSettlementEventsRef.current = [];
|
|
323
396
|
pendingClientEventId.current = pendingOperationRef.current?.input.clientEventId ?? null;
|
|
324
397
|
const shadow = pendingOperationRef.current?.newerShadow;
|
|
325
398
|
localEditRevision.current = shadow ? 1 : 0;
|
|
@@ -333,6 +406,17 @@ export function useComposer(
|
|
|
333
406
|
setStateTargetKey(targetKey);
|
|
334
407
|
setValue(shadow?.text ?? "");
|
|
335
408
|
setSending(false);
|
|
409
|
+
setSteering(
|
|
410
|
+
pendingOperationRef.current?.delivery === "steer"
|
|
411
|
+
? {
|
|
412
|
+
phase: "submitting",
|
|
413
|
+
text: pendingOperationRef.current.input.text,
|
|
414
|
+
clientEventId: pendingOperationRef.current.input.clientEventId ?? null,
|
|
415
|
+
triggerEventId: null,
|
|
416
|
+
turnId: null,
|
|
417
|
+
}
|
|
418
|
+
: null,
|
|
419
|
+
);
|
|
336
420
|
setPausing(false);
|
|
337
421
|
setResuming(false);
|
|
338
422
|
setError(null);
|
|
@@ -454,13 +538,15 @@ export function useComposer(
|
|
|
454
538
|
replaceLocal ||
|
|
455
539
|
(!localWasDirtyAtStart && localAtStart === localEditRevision.current)
|
|
456
540
|
) {
|
|
457
|
-
// Model/effort ride in sendExtras (outside
|
|
541
|
+
// Model/effort/latency ride in sendExtras (outside
|
|
542
|
+
// localEditRevision). If
|
|
458
543
|
// the picker changed during this fetch, skip onDraftApplied so a
|
|
459
|
-
// stale server
|
|
544
|
+
// stale server policy cannot undo the operator's pick.
|
|
460
545
|
const extrasNow = resolveSendExtras(sendExtrasRef.current);
|
|
461
546
|
const pickerChangedDuringFetch =
|
|
462
547
|
extrasNow.model !== extrasAtStart.model ||
|
|
463
|
-
extrasNow.reasoningEffort !== extrasAtStart.reasoningEffort
|
|
548
|
+
extrasNow.reasoningEffort !== extrasAtStart.reasoningEffort ||
|
|
549
|
+
extrasNow.latencyMode !== extrasAtStart.latencyMode;
|
|
464
550
|
valueRef.current = fetched.text;
|
|
465
551
|
restoredResourcesRef.current = fetched.resources;
|
|
466
552
|
lastSavedSignature.current = draftSignature(draftPayload(fetched));
|
|
@@ -503,18 +589,76 @@ export function useComposer(
|
|
|
503
589
|
if (!sessionId || !durableDrafts) return;
|
|
504
590
|
return registerSessionReconciler(sessionId, "composer", async () => await loadDraft(false));
|
|
505
591
|
}, [durableDrafts, loadDraft, registerSessionReconciler, sessionId]);
|
|
592
|
+
const reconcileSteering = useCallback(async (): Promise<void> => {
|
|
593
|
+
if (!sessionId || !steeringRef.current) return;
|
|
594
|
+
const ownedTargetKey = targetKey;
|
|
595
|
+
let events: SessionEvent[];
|
|
596
|
+
try {
|
|
597
|
+
events = await client.listEvents(workspaceId, sessionId, {
|
|
598
|
+
includeTypes: [
|
|
599
|
+
"user.message",
|
|
600
|
+
"turn.started",
|
|
601
|
+
"turn.completed",
|
|
602
|
+
"turn.failed",
|
|
603
|
+
"turn.cancelled",
|
|
604
|
+
"turn.superseded",
|
|
605
|
+
],
|
|
606
|
+
limit: 250,
|
|
607
|
+
payloadMode: "full",
|
|
608
|
+
});
|
|
609
|
+
} catch {
|
|
610
|
+
// Best effort: the live stream still settles steering when its event arrives.
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (targetKeyRef.current !== ownedTargetKey) return;
|
|
614
|
+
setSteering((current) => {
|
|
615
|
+
if (!current) return current;
|
|
616
|
+
if (steeringSettledByEvents(current, events)) {
|
|
617
|
+
steeringSettlementEventsRef.current = [];
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
const accepted = steeringAcceptedEvent(current, events);
|
|
621
|
+
if (!accepted || current.triggerEventId) return current;
|
|
622
|
+
return {
|
|
623
|
+
...current,
|
|
624
|
+
phase: "accepted",
|
|
625
|
+
triggerEventId: accepted.id,
|
|
626
|
+
};
|
|
627
|
+
});
|
|
628
|
+
}, [client, sessionId, targetKey, workspaceId]);
|
|
506
629
|
useSessionEventTrigger(
|
|
507
630
|
client,
|
|
508
631
|
workspaceId,
|
|
509
632
|
sessionId,
|
|
510
|
-
isComposerDraftEvent,
|
|
511
|
-
() =>
|
|
633
|
+
(event) => isComposerDraftEvent(event) || isSteeringSettlementEvent(event),
|
|
634
|
+
(event) => {
|
|
635
|
+
if (isComposerDraftEvent(event)) void loadDraft(false);
|
|
636
|
+
if (!isSteeringSettlementEvent(event)) return;
|
|
637
|
+
steeringSettlementEventsRef.current = [
|
|
638
|
+
...steeringSettlementEventsRef.current.slice(-15),
|
|
639
|
+
event,
|
|
640
|
+
];
|
|
641
|
+
setSteering((current) => {
|
|
642
|
+
if (!current || !steeringSettledByEvents(current, [event])) return current;
|
|
643
|
+
steeringSettlementEventsRef.current = [];
|
|
644
|
+
return null;
|
|
645
|
+
});
|
|
646
|
+
},
|
|
512
647
|
{
|
|
513
|
-
enabled: Boolean(sessionId) && durableDrafts,
|
|
648
|
+
enabled: Boolean(sessionId) && (durableDrafts || steering !== null),
|
|
514
649
|
...(options.events !== undefined ? { events: options.events } : {}),
|
|
515
650
|
},
|
|
651
|
+
reconcileSteering,
|
|
516
652
|
);
|
|
517
653
|
|
|
654
|
+
useEffect(() => {
|
|
655
|
+
if (!steering) return;
|
|
656
|
+
const observed = [...(options.events ?? []), ...steeringSettlementEventsRef.current];
|
|
657
|
+
if (!steeringSettledByEvents(steering, observed)) return;
|
|
658
|
+
steeringSettlementEventsRef.current = [];
|
|
659
|
+
setSteering(null);
|
|
660
|
+
}, [options.events, steering]);
|
|
661
|
+
|
|
518
662
|
const currentDraftPayload = useCallback((): SaveComposerDraftRequest | null => {
|
|
519
663
|
if (!durableDrafts || targetKeyRef.current !== targetKey) return null;
|
|
520
664
|
const base = draftRef.current;
|
|
@@ -667,6 +811,8 @@ export function useComposer(
|
|
|
667
811
|
forgetPendingComposerOperation(operationKey);
|
|
668
812
|
};
|
|
669
813
|
|
|
814
|
+
let keepSteering = pending?.delivery === "steer";
|
|
815
|
+
|
|
670
816
|
const settleAccepted = (operation: PendingComposerOperation): void => {
|
|
671
817
|
clearPending();
|
|
672
818
|
const draftWasUnchanged = valueRef.current === operation.draftAtSend;
|
|
@@ -699,30 +845,40 @@ export function useComposer(
|
|
|
699
845
|
onSent?.(operation.input.text, operation.input);
|
|
700
846
|
};
|
|
701
847
|
|
|
702
|
-
const deliver = async (operation: PendingComposerOperation)
|
|
848
|
+
const deliver = async (operation: PendingComposerOperation) => {
|
|
703
849
|
if (operation.delivery === "steer") {
|
|
704
|
-
await client.steerMessage(workspaceId, sessionId, operation.input);
|
|
705
|
-
} else {
|
|
706
|
-
await client.sendMessage(workspaceId, sessionId, operation.input);
|
|
850
|
+
return await client.steerMessage(workspaceId, sessionId, operation.input);
|
|
707
851
|
}
|
|
852
|
+
await client.sendMessage(workspaceId, sessionId, operation.input);
|
|
853
|
+
return null;
|
|
708
854
|
};
|
|
709
855
|
|
|
856
|
+
if (delivery === "steer") {
|
|
857
|
+
setSteering({
|
|
858
|
+
phase: "submitting",
|
|
859
|
+
text: rawText,
|
|
860
|
+
clientEventId: pending?.input.clientEventId ?? pendingClientEventId.current,
|
|
861
|
+
triggerEventId: null,
|
|
862
|
+
turnId: null,
|
|
863
|
+
});
|
|
864
|
+
}
|
|
710
865
|
setSending(true);
|
|
711
866
|
setError(null);
|
|
712
867
|
try {
|
|
713
868
|
if (pending) {
|
|
714
|
-
let
|
|
869
|
+
let acceptedEvent: SessionEvent | null = null;
|
|
715
870
|
try {
|
|
716
871
|
const events = await client.listEvents(workspaceId, sessionId, {
|
|
717
872
|
includeTypes: ["user.message"],
|
|
718
873
|
limit: 100,
|
|
719
874
|
payloadMode: "none",
|
|
720
875
|
});
|
|
721
|
-
|
|
722
|
-
(
|
|
723
|
-
event
|
|
724
|
-
|
|
725
|
-
|
|
876
|
+
acceptedEvent =
|
|
877
|
+
events.find(
|
|
878
|
+
(event) =>
|
|
879
|
+
event.type === "user.message" &&
|
|
880
|
+
event.clientEventId === pending.input.clientEventId,
|
|
881
|
+
) ?? null;
|
|
726
882
|
} catch (cause) {
|
|
727
883
|
if (
|
|
728
884
|
targetKeyRef.current === ownedTargetKey &&
|
|
@@ -738,7 +894,17 @@ export function useComposer(
|
|
|
738
894
|
) {
|
|
739
895
|
return false;
|
|
740
896
|
}
|
|
741
|
-
if (
|
|
897
|
+
if (acceptedEvent) {
|
|
898
|
+
if (pending.delivery === "steer") {
|
|
899
|
+
keepSteering = true;
|
|
900
|
+
setSteering({
|
|
901
|
+
phase: "accepted",
|
|
902
|
+
text: pending.input.text,
|
|
903
|
+
clientEventId: pending.input.clientEventId ?? null,
|
|
904
|
+
triggerEventId: acceptedEvent.id,
|
|
905
|
+
turnId: null,
|
|
906
|
+
});
|
|
907
|
+
}
|
|
742
908
|
settleAccepted(pending);
|
|
743
909
|
return true;
|
|
744
910
|
}
|
|
@@ -751,8 +917,19 @@ export function useComposer(
|
|
|
751
917
|
return false;
|
|
752
918
|
}
|
|
753
919
|
try {
|
|
754
|
-
await deliver(pending);
|
|
920
|
+
const result = await deliver(pending);
|
|
921
|
+
if (pending.delivery === "steer" && result) {
|
|
922
|
+
keepSteering = true;
|
|
923
|
+
setSteering({
|
|
924
|
+
phase: "accepted",
|
|
925
|
+
text: pending.input.text,
|
|
926
|
+
clientEventId: pending.input.clientEventId ?? null,
|
|
927
|
+
triggerEventId: result.accepted.id,
|
|
928
|
+
turnId: result.turn.id,
|
|
929
|
+
});
|
|
930
|
+
}
|
|
755
931
|
} catch (cause) {
|
|
932
|
+
if (pending.delivery === "steer") keepSteering = true;
|
|
756
933
|
if (
|
|
757
934
|
targetKeyRef.current === ownedTargetKey &&
|
|
758
935
|
targetGeneration.current === ownedGeneration
|
|
@@ -812,11 +989,32 @@ export function useComposer(
|
|
|
812
989
|
};
|
|
813
990
|
pendingOperationRef.current = operation;
|
|
814
991
|
rememberPendingComposerOperation(operationKey, operation);
|
|
992
|
+
if (delivery === "steer") {
|
|
993
|
+
setSteering({
|
|
994
|
+
phase: "submitting",
|
|
995
|
+
text: sendText,
|
|
996
|
+
clientEventId: input.clientEventId ?? null,
|
|
997
|
+
triggerEventId: null,
|
|
998
|
+
turnId: null,
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
815
1001
|
try {
|
|
816
|
-
await deliver(operation);
|
|
1002
|
+
const result = await deliver(operation);
|
|
1003
|
+
if (delivery === "steer" && result) {
|
|
1004
|
+
keepSteering = true;
|
|
1005
|
+
setSteering({
|
|
1006
|
+
phase: "accepted",
|
|
1007
|
+
text: sendText,
|
|
1008
|
+
clientEventId: input.clientEventId ?? null,
|
|
1009
|
+
triggerEventId: result.accepted.id,
|
|
1010
|
+
turnId: result.turn.id,
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
817
1013
|
} catch (cause) {
|
|
818
1014
|
if (!isOutcomeUnknownError(cause)) {
|
|
819
1015
|
clearPending();
|
|
1016
|
+
} else if (delivery === "steer") {
|
|
1017
|
+
keepSteering = true;
|
|
820
1018
|
}
|
|
821
1019
|
if (
|
|
822
1020
|
targetKeyRef.current === ownedTargetKey &&
|
|
@@ -840,6 +1038,7 @@ export function useComposer(
|
|
|
840
1038
|
targetGeneration.current === ownedGeneration
|
|
841
1039
|
) {
|
|
842
1040
|
setSending(false);
|
|
1041
|
+
if (delivery === "steer" && !keepSteering) setSteering(null);
|
|
843
1042
|
}
|
|
844
1043
|
}
|
|
845
1044
|
},
|
|
@@ -1095,6 +1294,7 @@ export function useComposer(
|
|
|
1095
1294
|
hasDraftContent,
|
|
1096
1295
|
send,
|
|
1097
1296
|
steer,
|
|
1297
|
+
steering: identityMatches ? steering : null,
|
|
1098
1298
|
sending: identityMatches ? sending : false,
|
|
1099
1299
|
canSend:
|
|
1100
1300
|
identityMatches &&
|
|
@@ -1245,7 +1445,7 @@ function mergeResources(base: ResourceRef[], additions: ResourceRef[]): Resource
|
|
|
1245
1445
|
// different duplicate counts after server normalization.
|
|
1246
1446
|
const key =
|
|
1247
1447
|
resource.kind === "file"
|
|
1248
|
-
? `file:${resource.fileId}\u0000${resource.mountPath ??
|
|
1448
|
+
? `file:${resource.fileId}\u0000${resource.mountPath ?? `${DEFAULT_FILE_RESOURCE_MOUNT_ROOT}/${resource.fileId}`}`
|
|
1249
1449
|
: JSON.stringify(resource);
|
|
1250
1450
|
if (seen.has(key)) return false;
|
|
1251
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 {
|
|
@@ -393,6 +395,19 @@ export { defaultChatComposerMessages } from "./components/composer";
|
|
|
393
395
|
export type { ChatComposerMessages } from "./components/composer";
|
|
394
396
|
export { ModelPicker } from "./components/model-picker";
|
|
395
397
|
export type { ModelPickerProps } from "./components/model-picker";
|
|
398
|
+
export {
|
|
399
|
+
BillingClassMark,
|
|
400
|
+
ModelPolicyPicker,
|
|
401
|
+
ModelPolicyPickerMenu,
|
|
402
|
+
PickerAnimatedPage,
|
|
403
|
+
PickerBackHeader,
|
|
404
|
+
PickerNavRow,
|
|
405
|
+
defaultModelPolicyPickerMessages,
|
|
406
|
+
} from "./components/model-policy-picker";
|
|
407
|
+
export type {
|
|
408
|
+
ModelPolicyPickerMessages,
|
|
409
|
+
ModelPolicyPickerProps,
|
|
410
|
+
} from "./components/model-policy-picker";
|
|
396
411
|
export {
|
|
397
412
|
advancedSourceSummary,
|
|
398
413
|
availabilityReasonLabel,
|
|
@@ -404,7 +419,9 @@ export {
|
|
|
404
419
|
findPickerRow,
|
|
405
420
|
groupPickerRowsByBillingClass,
|
|
406
421
|
labelLatencyMode,
|
|
422
|
+
labelReasoningEffort,
|
|
407
423
|
payerSummaryForModel,
|
|
424
|
+
projectClientModelRows,
|
|
408
425
|
projectPickerRows,
|
|
409
426
|
runnableLatencyModesForModel,
|
|
410
427
|
sortPickerRows,
|
|
@@ -430,6 +447,8 @@ export { SandboxTerminal } from "./components/sandbox-terminal";
|
|
|
430
447
|
export type { SandboxTerminalProps, XtermTheme } from "./components/sandbox-terminal";
|
|
431
448
|
export { FileBrowser } from "./components/file-browser";
|
|
432
449
|
export type { FileBrowserProps } from "./components/file-browser";
|
|
450
|
+
export { WorkbenchChanges } from "./components/workbench-changes";
|
|
451
|
+
export type { WorkbenchChangesProps } from "./components/workbench-changes";
|
|
433
452
|
export { DiffView } from "./components/diff-view";
|
|
434
453
|
export type { DiffViewProps, DiffTheme } from "./components/diff-view";
|
|
435
454
|
export { PierreDiff } from "./components/pierre-diff";
|
package/src/model-policy.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ClientModel, ReasoningEffort, WorkspaceModelCatalogModel } from "@
|
|
|
2
2
|
|
|
3
3
|
export type PickerBillingClass = "opengeni_credits" | "codex_subscription" | "byok";
|
|
4
4
|
|
|
5
|
-
export type PickerModelRow = {
|
|
5
|
+
export type PickerModelRow<TCatalog extends ClientModel = WorkspaceModelCatalogModel> = {
|
|
6
6
|
id: string;
|
|
7
7
|
label: string;
|
|
8
8
|
billingClass: PickerBillingClass;
|
|
@@ -11,7 +11,7 @@ export type PickerModelRow = {
|
|
|
11
11
|
unavailableReason: string | null;
|
|
12
12
|
provider: string;
|
|
13
13
|
providerLabel: string;
|
|
14
|
-
catalog:
|
|
14
|
+
catalog: TCatalog;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export type LatencyModeId = "standard" | "priority" | "fast";
|
|
@@ -78,7 +78,7 @@ export function effortOptionsForModel(model: ClientModel): ReasoningEffort[] {
|
|
|
78
78
|
if (!efforts || efforts.length === 0) {
|
|
79
79
|
return ["low"];
|
|
80
80
|
}
|
|
81
|
-
const order: ReasoningEffort[] = ["none", "minimal", "low", "medium", "high", "xhigh"];
|
|
81
|
+
const order: ReasoningEffort[] = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
82
82
|
return order.filter((effort) => efforts.includes(effort));
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -117,6 +117,13 @@ export function labelLatencyMode(mode: LatencyModeId): string {
|
|
|
117
117
|
return "Standard";
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
export function labelReasoningEffort(effort: ReasoningEffort): string {
|
|
121
|
+
if (effort === "xhigh") {
|
|
122
|
+
return "Extra high";
|
|
123
|
+
}
|
|
124
|
+
return effort.slice(0, 1).toUpperCase() + effort.slice(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
120
127
|
export function payerSummaryForModel(model: ClientModel): string {
|
|
121
128
|
const billing = model.billing;
|
|
122
129
|
if (!billing) {
|
|
@@ -177,7 +184,27 @@ export function projectPickerRows(models: WorkspaceModelCatalogModel[]): PickerM
|
|
|
177
184
|
});
|
|
178
185
|
}
|
|
179
186
|
|
|
180
|
-
|
|
187
|
+
/** Project the lightweight client-config model list into the same picker contract. */
|
|
188
|
+
export function projectClientModelRows(models: ClientModel[]): PickerModelRow<ClientModel>[] {
|
|
189
|
+
return models.map((catalog) => {
|
|
190
|
+
const billingClass = billingClassForModel(catalog);
|
|
191
|
+
return {
|
|
192
|
+
id: catalog.id,
|
|
193
|
+
label: catalog.label,
|
|
194
|
+
billingClass,
|
|
195
|
+
billingClassLabel: billingClassLabel(billingClass),
|
|
196
|
+
selectable: true,
|
|
197
|
+
unavailableReason: null,
|
|
198
|
+
provider: catalog.provider,
|
|
199
|
+
providerLabel: catalog.providerLabel,
|
|
200
|
+
catalog,
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function sortPickerRows<TCatalog extends ClientModel>(
|
|
206
|
+
rows: PickerModelRow<TCatalog>[],
|
|
207
|
+
): PickerModelRow<TCatalog>[] {
|
|
181
208
|
return [...rows].sort((left, right) => {
|
|
182
209
|
const classDelta =
|
|
183
210
|
BILLING_CLASS_ORDER.indexOf(left.billingClass) -
|
|
@@ -192,18 +219,35 @@ export function sortPickerRows(rows: PickerModelRow[]): PickerModelRow[] {
|
|
|
192
219
|
});
|
|
193
220
|
}
|
|
194
221
|
|
|
195
|
-
export function findPickerRow
|
|
222
|
+
export function findPickerRow<TCatalog extends ClientModel>(
|
|
223
|
+
rows: PickerModelRow<TCatalog>[],
|
|
224
|
+
modelId: string,
|
|
225
|
+
): PickerModelRow<TCatalog> | null {
|
|
196
226
|
return rows.find((row) => row.id === modelId) ?? null;
|
|
197
227
|
}
|
|
198
228
|
|
|
199
229
|
export function groupPickerRowsByBillingClass(
|
|
200
230
|
rows: PickerModelRow[],
|
|
201
|
-
): Array<{ billingClass: PickerBillingClass; label: string; rows: PickerModelRow[] }
|
|
231
|
+
): Array<{ billingClass: PickerBillingClass; label: string; rows: PickerModelRow[] }>;
|
|
232
|
+
export function groupPickerRowsByBillingClass<TCatalog extends ClientModel>(
|
|
233
|
+
rows: PickerModelRow<TCatalog>[],
|
|
234
|
+
): Array<{
|
|
235
|
+
billingClass: PickerBillingClass;
|
|
236
|
+
label: string;
|
|
237
|
+
rows: PickerModelRow<TCatalog>[];
|
|
238
|
+
}>;
|
|
239
|
+
export function groupPickerRowsByBillingClass<TCatalog extends ClientModel>(
|
|
240
|
+
rows: PickerModelRow<TCatalog>[],
|
|
241
|
+
): Array<{
|
|
242
|
+
billingClass: PickerBillingClass;
|
|
243
|
+
label: string;
|
|
244
|
+
rows: PickerModelRow<TCatalog>[];
|
|
245
|
+
}> {
|
|
202
246
|
const sorted = sortPickerRows(rows);
|
|
203
247
|
const groups: Array<{
|
|
204
248
|
billingClass: PickerBillingClass;
|
|
205
249
|
label: string;
|
|
206
|
-
rows: PickerModelRow[];
|
|
250
|
+
rows: PickerModelRow<TCatalog>[];
|
|
207
251
|
}> = [];
|
|
208
252
|
for (const row of sorted) {
|
|
209
253
|
const existing = groups.find((group) => group.billingClass === row.billingClass);
|
|
@@ -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
|
+
};
|