@opengeni/react 3.7.0-canary.0 → 3.7.0-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifacts.js +24 -5
- package/dist/artifacts.js.map +1 -1
- package/dist/{browser-viewer-E5DDEFQ4.js → browser-viewer-NTD6UIPR.js} +3 -3
- package/dist/{chunk-W2RGYBCV.js → chunk-24M4YBCL.js} +4 -4
- package/dist/{chunk-DOK4KZF2.js → chunk-5IXTJTK3.js} +1322 -392
- package/dist/chunk-5IXTJTK3.js.map +1 -0
- package/dist/chunk-5PQJOCX5.js +902 -0
- package/dist/chunk-5PQJOCX5.js.map +1 -0
- package/dist/{chunk-R55HFTT3.js → chunk-INGB2N5R.js} +4305 -4019
- package/dist/chunk-INGB2N5R.js.map +1 -0
- package/dist/{chunk-FJBOU2TH.js → chunk-MOWYEBCQ.js} +1728 -12
- package/dist/chunk-MOWYEBCQ.js.map +1 -0
- package/dist/chunk-PAJU5KKL.js +331 -0
- package/dist/chunk-PAJU5KKL.js.map +1 -0
- package/dist/{chunk-VTOREEXF.js → chunk-PLYRL5ER.js} +24 -2
- package/dist/{chunk-VTOREEXF.js.map → chunk-PLYRL5ER.js.map} +1 -1
- package/dist/{chunk-YVCHAQZY.js → chunk-ZFOFY5ST.js} +27 -349
- package/dist/chunk-ZFOFY5ST.js.map +1 -0
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +3 -2
- package/dist/components/session-chrome.d.ts +37 -4
- package/dist/components/session-commands-panel.d.ts +6 -0
- package/dist/components/session-conversation.d.ts +14 -0
- package/dist/components/timeline-anchor.d.ts +21 -0
- package/dist/composer.js +2 -3
- package/dist/conversation-timeline.d.ts +5 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +716 -914
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +2 -2
- package/dist/lib/format.d.ts +5 -0
- package/dist/machines.js +3 -2
- package/dist/session-ui.d.ts +3 -0
- package/dist/session-ui.js +14 -4
- package/dist/session.js +19 -19
- package/dist/timeline/types.d.ts +3 -1
- package/package.json +2 -2
- package/src/components/artifacts/published-html-artifact-frame.tsx +27 -4
- package/src/components/machine-input-display.ts +6 -0
- package/src/components/message-timeline.tsx +320 -197
- package/src/components/session-chrome.tsx +498 -125
- package/src/components/session-commands-panel.tsx +113 -0
- package/src/components/session-conversation.tsx +141 -0
- package/src/components/timeline-anchor.tsx +91 -0
- package/src/conversation-timeline.ts +83 -0
- package/src/hooks/use-session-background-commands.ts +21 -10
- package/src/hooks/use-session-events.ts +36 -14
- package/src/hooks/use-session.ts +23 -2
- package/src/index.ts +4 -0
- package/src/lib/format.ts +32 -0
- package/src/session-ui.ts +3 -0
- package/src/timeline/projection.ts +6 -12
- package/src/timeline/types.ts +4 -0
- package/styles/compiled.css +495 -411
- package/styles/index.css +23 -0
- package/dist/chunk-7EAFGICR.js +0 -302
- package/dist/chunk-7EAFGICR.js.map +0 -1
- package/dist/chunk-C6WDLHBA.js +0 -2590
- package/dist/chunk-C6WDLHBA.js.map +0 -1
- package/dist/chunk-DOK4KZF2.js.map +0 -1
- package/dist/chunk-FJBOU2TH.js.map +0 -1
- package/dist/chunk-R55HFTT3.js.map +0 -1
- package/dist/chunk-YVCHAQZY.js.map +0 -1
- /package/dist/{browser-viewer-E5DDEFQ4.js.map → browser-viewer-NTD6UIPR.js.map} +0 -0
- /package/dist/{chunk-W2RGYBCV.js.map → chunk-24M4YBCL.js.map} +0 -0
|
@@ -28,12 +28,20 @@
|
|
|
28
28
|
* Inbox has no product dismiss API; pass `onDismissIncoming` when the host
|
|
29
29
|
* wants a visible action (dev harness may use a local dummy).
|
|
30
30
|
*
|
|
31
|
+
* Queue opens by default when chrome is idle and at least one authoritative
|
|
32
|
+
* prompt is queued. Closing the queue dismisses that session's offer until
|
|
33
|
+
* occupancy drains; a different session on the same chrome instance may
|
|
34
|
+
* offer again. An in-flight optimistic Send never opens the drawer —
|
|
35
|
+
* admission stays a paint-only receipt on the chip so layout does not jump
|
|
36
|
+
* under the pointer.
|
|
37
|
+
*
|
|
31
38
|
* Segment switches keep the panel shell mounted and crossfade content. The
|
|
32
39
|
* shell uses one CSS grid-track transition for deliberate open/close actions;
|
|
33
40
|
* live queue reconciliation never feeds measurements back into layout.
|
|
34
41
|
*/
|
|
35
42
|
import type { SessionGoal, SessionPendingInputPreview, SessionTurn } from "@opengeni/sdk";
|
|
36
43
|
import {
|
|
44
|
+
ActivityIcon,
|
|
37
45
|
AudioLinesIcon,
|
|
38
46
|
ArrowDownIcon,
|
|
39
47
|
ArrowUpIcon,
|
|
@@ -46,9 +54,11 @@ import {
|
|
|
46
54
|
PencilIcon,
|
|
47
55
|
PlayIcon,
|
|
48
56
|
Trash2Icon,
|
|
57
|
+
TerminalIcon,
|
|
49
58
|
TriangleAlertIcon,
|
|
50
59
|
XIcon,
|
|
51
|
-
|
|
60
|
+
CornerDownRightIcon,
|
|
61
|
+
TargetIcon,
|
|
52
62
|
} from "lucide-react";
|
|
53
63
|
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
54
64
|
import { useEffect, useId, useMemo, useRef, useState, type ReactNode } from "react";
|
|
@@ -61,7 +71,13 @@ import { formatClockTime } from "../lib/format";
|
|
|
61
71
|
import { requestQueueDraftEdit } from "./queue-draft-policy";
|
|
62
72
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
|
|
63
73
|
|
|
64
|
-
export type SessionChromeSignalId =
|
|
74
|
+
export type SessionChromeSignalId =
|
|
75
|
+
| "incoming"
|
|
76
|
+
| "steering"
|
|
77
|
+
| "queue"
|
|
78
|
+
| "goal"
|
|
79
|
+
| "agents"
|
|
80
|
+
| "commands";
|
|
65
81
|
|
|
66
82
|
export type SessionChromeSignalTone = "neutral" | "accent" | "waiting" | "running";
|
|
67
83
|
|
|
@@ -72,12 +88,16 @@ export type SessionChromeAgentsSignal = {
|
|
|
72
88
|
};
|
|
73
89
|
|
|
74
90
|
export type SessionChromeProps = {
|
|
91
|
+
compact?: boolean;
|
|
75
92
|
queue: UseTurnQueueResult;
|
|
76
93
|
/** Needed for queue edit → composer checkout. Omit with `readOnly`. */
|
|
77
94
|
composer?: ComposerState | undefined;
|
|
78
95
|
goal?: UseGoalResult | null | undefined;
|
|
79
96
|
/** Expanded agents body (host supplies tree / list). */
|
|
80
97
|
agentsPanel?: ReactNode;
|
|
98
|
+
/** Active commands only. The host mounts the body only when opened. */
|
|
99
|
+
commandsPanel?: ReactNode;
|
|
100
|
+
commandsCount?: number | undefined;
|
|
81
101
|
/** Chip summary; when `count > 0` the agents segment appears. */
|
|
82
102
|
agentsSignal?: SessionChromeAgentsSignal | undefined;
|
|
83
103
|
/**
|
|
@@ -111,7 +131,7 @@ type QueuedTurnPresentation = {
|
|
|
111
131
|
const GOAL_LABEL: Record<GoalPillState, string> = {
|
|
112
132
|
pursuing: "Pursuing",
|
|
113
133
|
waiting: "Waiting",
|
|
114
|
-
scheduled: "
|
|
134
|
+
scheduled: "Waiting",
|
|
115
135
|
blocked: "Blocked",
|
|
116
136
|
held: "Held",
|
|
117
137
|
paused: "Paused",
|
|
@@ -172,10 +192,10 @@ export function sessionChromeGoalPillExplanation(
|
|
|
172
192
|
? (GOAL_PAUSED_REASON_EXPLANATION[record.pausedReason] ?? null)
|
|
173
193
|
: null;
|
|
174
194
|
}
|
|
175
|
-
if (state === "scheduled"
|
|
176
|
-
return continuation
|
|
177
|
-
? `
|
|
178
|
-
: "
|
|
195
|
+
if (state === "scheduled") {
|
|
196
|
+
return continuation?.nextAttemptAt
|
|
197
|
+
? `Continues at ${formatClockTime(continuation.nextAttemptAt)}.`
|
|
198
|
+
: "Waiting to continue automatically.";
|
|
179
199
|
}
|
|
180
200
|
if (state === "held" && continuation?.reason === "held_for_input") {
|
|
181
201
|
const reason = continuation.holdReason?.trim();
|
|
@@ -203,6 +223,82 @@ function isSteeringTurn(turn: SessionTurn): boolean {
|
|
|
203
223
|
return turn.metadata.delivery === "steer";
|
|
204
224
|
}
|
|
205
225
|
|
|
226
|
+
function isAuthoritativeQueuedTurn(
|
|
227
|
+
turn: SessionTurn,
|
|
228
|
+
mutationFor: UseTurnQueueResult["mutationFor"],
|
|
229
|
+
): boolean {
|
|
230
|
+
return !isSteeringTurn(turn) && mutationFor(turn.id) !== "steer";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function countAuthoritativeQueuedTurns(
|
|
234
|
+
turns: readonly SessionTurn[],
|
|
235
|
+
mutationFor: UseTurnQueueResult["mutationFor"],
|
|
236
|
+
): number {
|
|
237
|
+
return turns.filter((turn) => isAuthoritativeQueuedTurn(turn, mutationFor)).length;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isOptimisticQueuedMessage(
|
|
241
|
+
message: ComposerOptimisticMessage,
|
|
242
|
+
queuedTurnIds: ReadonlySet<string>,
|
|
243
|
+
snapshot: UseTurnQueueResult["snapshot"],
|
|
244
|
+
): boolean {
|
|
245
|
+
return (
|
|
246
|
+
message.delivery === "send" &&
|
|
247
|
+
message.destination === "queue" &&
|
|
248
|
+
(!message.turnId || !queuedTurnIds.has(message.turnId)) &&
|
|
249
|
+
!(
|
|
250
|
+
message.turnId &&
|
|
251
|
+
message.appliedQueueVersion !== null &&
|
|
252
|
+
message.appliedQueueVersion !== undefined &&
|
|
253
|
+
snapshot &&
|
|
254
|
+
snapshot.version >= message.appliedQueueVersion
|
|
255
|
+
)
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function countOptimisticQueuedMessages(
|
|
260
|
+
messages: readonly ComposerOptimisticMessage[] | undefined,
|
|
261
|
+
queuedTurnIds: ReadonlySet<string>,
|
|
262
|
+
snapshot: UseTurnQueueResult["snapshot"],
|
|
263
|
+
): number {
|
|
264
|
+
return (messages ?? []).filter((message) =>
|
|
265
|
+
isOptimisticQueuedMessage(message, queuedTurnIds, snapshot),
|
|
266
|
+
).length;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* First uncontrolled segment. An existing authoritative queue opens itself
|
|
271
|
+
* when the host did not pick another default; optimistic-only occupancy
|
|
272
|
+
* stays collapsed so a live Send does not shove a drawer under the pointer.
|
|
273
|
+
*/
|
|
274
|
+
export function sessionChromeInitialActive(input: {
|
|
275
|
+
defaultActive: SessionChromeSignalId | null;
|
|
276
|
+
authoritativeQueuedCount: number;
|
|
277
|
+
}): SessionChromeSignalId | null {
|
|
278
|
+
if (input.defaultActive != null) return input.defaultActive;
|
|
279
|
+
return input.authoritativeQueuedCount >= 1 ? "queue" : null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Whether idle chrome should offer the queue panel. A dismissed session
|
|
284
|
+
* stays collapsed until occupancy drains. Controlled hosts own this.
|
|
285
|
+
*/
|
|
286
|
+
export function sessionChromeShouldOfferQueue(input: {
|
|
287
|
+
controlled: boolean;
|
|
288
|
+
active: SessionChromeSignalId | null;
|
|
289
|
+
activityOpen: boolean;
|
|
290
|
+
authoritativeQueuedCount: number;
|
|
291
|
+
suppressed: boolean;
|
|
292
|
+
}): boolean {
|
|
293
|
+
return (
|
|
294
|
+
!input.controlled &&
|
|
295
|
+
input.active === null &&
|
|
296
|
+
!input.activityOpen &&
|
|
297
|
+
input.authoritativeQueuedCount >= 1 &&
|
|
298
|
+
!input.suppressed
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
206
302
|
function objectValue(value: unknown): Record<string, unknown> | null {
|
|
207
303
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
208
304
|
? (value as Record<string, unknown>)
|
|
@@ -228,6 +324,7 @@ export function sessionChromeGoalPillState(
|
|
|
228
324
|
// next evaluation at `nextAttemptAt`) is an ordinary scheduled state.
|
|
229
325
|
if (continuation.state === "scheduled") return "scheduled";
|
|
230
326
|
if (continuation.state === "blocked") {
|
|
327
|
+
if (continuation.reason === "human_turn_running") return "waiting";
|
|
231
328
|
// `held_for_input` is the agent's own wait_for_input hold (waiting for child
|
|
232
329
|
// results / external input until a deadline); it shares the Held pill.
|
|
233
330
|
return continuation.reason === "workstream_paused" || continuation.reason === "held_for_input"
|
|
@@ -288,6 +385,12 @@ function pendingKindLabel(kind: SessionPendingInputPreview["kind"]): string {
|
|
|
288
385
|
return "Goal wake";
|
|
289
386
|
case "agent_message":
|
|
290
387
|
return "Update";
|
|
388
|
+
case "background_command_result":
|
|
389
|
+
return "Command result";
|
|
390
|
+
case "session_wait_timeout":
|
|
391
|
+
return "Wait ended";
|
|
392
|
+
case "media_generation_result":
|
|
393
|
+
return "Video result";
|
|
291
394
|
default:
|
|
292
395
|
return "Incoming";
|
|
293
396
|
}
|
|
@@ -307,10 +410,13 @@ function toneClass(tone: SessionChromeSignalTone, selected: boolean): string {
|
|
|
307
410
|
}
|
|
308
411
|
|
|
309
412
|
export function SessionChrome({
|
|
413
|
+
compact = false,
|
|
310
414
|
queue,
|
|
311
415
|
composer,
|
|
312
416
|
goal,
|
|
313
417
|
agentsPanel,
|
|
418
|
+
commandsPanel,
|
|
419
|
+
commandsCount = 0,
|
|
314
420
|
agentsSignal,
|
|
315
421
|
onDismissIncoming,
|
|
316
422
|
readOnly = false,
|
|
@@ -319,6 +425,9 @@ export function SessionChrome({
|
|
|
319
425
|
defaultActive = null,
|
|
320
426
|
onActiveChange,
|
|
321
427
|
}: SessionChromeProps) {
|
|
428
|
+
const [activityRequested, setActivityOpen] = useState(
|
|
429
|
+
Boolean(compact && defaultActive && ["incoming", "agents", "commands"].includes(defaultActive)),
|
|
430
|
+
);
|
|
322
431
|
const reactId = useId();
|
|
323
432
|
const panelId = `og-session-chrome-panel-${reactId}`;
|
|
324
433
|
const reduceMotion = useReducedMotion();
|
|
@@ -327,24 +436,14 @@ export function SessionChrome({
|
|
|
327
436
|
const turns = queue.queue;
|
|
328
437
|
const queueMutationFor = queue.mutationFor;
|
|
329
438
|
const queuedTurns = useMemo(
|
|
330
|
-
() => turns.filter((turn) =>
|
|
439
|
+
() => turns.filter((turn) => isAuthoritativeQueuedTurn(turn, queueMutationFor)),
|
|
331
440
|
[queueMutationFor, turns],
|
|
332
441
|
);
|
|
333
442
|
const queuedTurnIds = useMemo(() => new Set(queuedTurns.map((turn) => turn.id)), [queuedTurns]);
|
|
334
443
|
const optimisticQueued = useMemo(
|
|
335
444
|
() =>
|
|
336
|
-
(composer?.optimisticMessages ?? []).filter(
|
|
337
|
-
(message)
|
|
338
|
-
message.delivery === "send" &&
|
|
339
|
-
message.destination === "queue" &&
|
|
340
|
-
(!message.turnId || !queuedTurnIds.has(message.turnId)) &&
|
|
341
|
-
!(
|
|
342
|
-
message.turnId &&
|
|
343
|
-
message.appliedQueueVersion !== null &&
|
|
344
|
-
message.appliedQueueVersion !== undefined &&
|
|
345
|
-
queue.snapshot &&
|
|
346
|
-
queue.snapshot.version >= message.appliedQueueVersion
|
|
347
|
-
),
|
|
445
|
+
(composer?.optimisticMessages ?? []).filter((message) =>
|
|
446
|
+
isOptimisticQueuedMessage(message, queuedTurnIds, queue.snapshot),
|
|
348
447
|
),
|
|
349
448
|
[composer?.optimisticMessages, queue.snapshot, queuedTurnIds],
|
|
350
449
|
);
|
|
@@ -358,14 +457,49 @@ export function SessionChrome({
|
|
|
358
457
|
const stopping = stoppingKind !== null;
|
|
359
458
|
const canMutateQueue = !readOnly && composer !== undefined;
|
|
360
459
|
|
|
361
|
-
const
|
|
362
|
-
const elapsed = useLiveElapsed(
|
|
363
|
-
record?.createdAt,
|
|
364
|
-
Boolean(liveGoal),
|
|
365
|
-
!liveGoal ? record?.updatedAt : null,
|
|
366
|
-
);
|
|
460
|
+
const elapsed = useLiveElapsed(record?.createdAt, Boolean(record));
|
|
367
461
|
const goalState = record ? sessionChromeGoalPillState(record.status, record.continuation) : null;
|
|
368
462
|
|
|
463
|
+
const initialAuthoritativeQueuedCount = countAuthoritativeQueuedTurns(
|
|
464
|
+
queue.queue,
|
|
465
|
+
queue.mutationFor,
|
|
466
|
+
);
|
|
467
|
+
const [activeUncontrolled, setActiveUncontrolled] = useState<SessionChromeSignalId | null>(() =>
|
|
468
|
+
sessionChromeInitialActive({
|
|
469
|
+
defaultActive,
|
|
470
|
+
authoritativeQueuedCount: initialAuthoritativeQueuedCount,
|
|
471
|
+
}),
|
|
472
|
+
);
|
|
473
|
+
const active = activeControlled !== undefined ? activeControlled : activeUncontrolled;
|
|
474
|
+
const activityOpen =
|
|
475
|
+
activityRequested ||
|
|
476
|
+
Boolean(compact && active && ["incoming", "agents", "commands"].includes(active));
|
|
477
|
+
const activityVisibleRef = useRef(activityOpen);
|
|
478
|
+
activityVisibleRef.current = activityOpen;
|
|
479
|
+
// Two independent suppressions, both occupancy-scoped:
|
|
480
|
+
// - admission: a live Send while idle must not open a drawer under the pointer
|
|
481
|
+
// - dismiss: closing the queue remembers that session until occupancy drains
|
|
482
|
+
const queueOfferAdmissionSuppressedRef = useRef(
|
|
483
|
+
defaultActive == null &&
|
|
484
|
+
initialAuthoritativeQueuedCount === 0 &&
|
|
485
|
+
countOptimisticQueuedMessages(composer?.optimisticMessages, queuedTurnIds, queue.snapshot) >
|
|
486
|
+
0,
|
|
487
|
+
);
|
|
488
|
+
const queueOfferDismissedSessionIdsRef = useRef<Set<string>>(new Set());
|
|
489
|
+
const occupancySessionId = queuedTurns[0]?.sessionId ?? null;
|
|
490
|
+
const setActive = (next: SessionChromeSignalId | null) => {
|
|
491
|
+
if (active === "queue" && next === null) {
|
|
492
|
+
if (occupancySessionId) {
|
|
493
|
+
queueOfferDismissedSessionIdsRef.current.add(occupancySessionId);
|
|
494
|
+
} else {
|
|
495
|
+
queueOfferAdmissionSuppressedRef.current = true;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (activeControlled === undefined) setActiveUncontrolled(next);
|
|
499
|
+
onActiveChange?.(next);
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
const hasCommandsPanel = commandsPanel != null;
|
|
369
503
|
const signals = useMemo(() => {
|
|
370
504
|
const rows: Array<{
|
|
371
505
|
id: SessionChromeSignalId;
|
|
@@ -450,16 +584,7 @@ export function SessionChrome({
|
|
|
450
584
|
: goalState === "pursuing" || goalState === "scheduled"
|
|
451
585
|
? "accent"
|
|
452
586
|
: "neutral",
|
|
453
|
-
icon:
|
|
454
|
-
goalState === "blocked" || goalState === "invariant_broken" ? (
|
|
455
|
-
<TriangleAlertIcon className="size-3" />
|
|
456
|
-
) : goalState === "waiting" ? (
|
|
457
|
-
<Loader2Icon className="size-3 animate-og-spin" />
|
|
458
|
-
) : goalState === "paused" || goalState === "held" ? (
|
|
459
|
-
<PauseIcon className="size-3" />
|
|
460
|
-
) : (
|
|
461
|
-
<ZapIcon className="size-3" />
|
|
462
|
-
),
|
|
587
|
+
icon: <TargetIcon className="size-3" />,
|
|
463
588
|
});
|
|
464
589
|
}
|
|
465
590
|
if (agentsSignal && agentsSignal.count > 0) {
|
|
@@ -472,8 +597,22 @@ export function SessionChrome({
|
|
|
472
597
|
icon: <BotIcon className="size-3" />,
|
|
473
598
|
});
|
|
474
599
|
}
|
|
600
|
+
if (commandsCount > 0 || (active === "commands" && hasCommandsPanel)) {
|
|
601
|
+
rows.push({
|
|
602
|
+
id: "commands",
|
|
603
|
+
label:
|
|
604
|
+
commandsCount > 0
|
|
605
|
+
? `${commandsCount} command${commandsCount === 1 ? "" : "s"}`
|
|
606
|
+
: "Commands",
|
|
607
|
+
tone: commandsCount > 0 ? "running" : "neutral",
|
|
608
|
+
icon: <TerminalIcon className="size-3" />,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
475
611
|
return rows;
|
|
476
612
|
}, [
|
|
613
|
+
commandsCount,
|
|
614
|
+
hasCommandsPanel,
|
|
615
|
+
active,
|
|
477
616
|
agentsSignal,
|
|
478
617
|
elapsed,
|
|
479
618
|
goalState,
|
|
@@ -485,14 +624,6 @@ export function SessionChrome({
|
|
|
485
624
|
record,
|
|
486
625
|
]);
|
|
487
626
|
|
|
488
|
-
const [activeUncontrolled, setActiveUncontrolled] = useState<SessionChromeSignalId | null>(
|
|
489
|
-
defaultActive,
|
|
490
|
-
);
|
|
491
|
-
const active = activeControlled !== undefined ? activeControlled : activeUncontrolled;
|
|
492
|
-
const setActive = (next: SessionChromeSignalId | null) => {
|
|
493
|
-
if (activeControlled === undefined) setActiveUncontrolled(next);
|
|
494
|
-
onActiveChange?.(next);
|
|
495
|
-
};
|
|
496
627
|
const optimisticQueueKeys = optimisticQueued.map((message) => message.clientEventId).join(",");
|
|
497
628
|
const previousOptimisticQueueKeys = useRef(optimisticQueueKeys);
|
|
498
629
|
const [queueArrivalNonce, setQueueArrivalNonce] = useState(0);
|
|
@@ -505,7 +636,45 @@ export function SessionChrome({
|
|
|
505
636
|
// the pointer. A stable, paint-only receipt on the queue chip communicates
|
|
506
637
|
// destination without participating in layout.
|
|
507
638
|
setQueueArrivalNonce((current) => current + 1);
|
|
508
|
-
|
|
639
|
+
if (activeControlled === undefined && active === null) {
|
|
640
|
+
queueOfferAdmissionSuppressedRef.current = true;
|
|
641
|
+
}
|
|
642
|
+
}, [active, activeControlled, optimisticQueueKeys, optimisticQueued]);
|
|
643
|
+
const queueOccupied = queuedTurns.length > 0 || optimisticQueued.length > 0;
|
|
644
|
+
useEffect(() => {
|
|
645
|
+
if (queueOccupied) return;
|
|
646
|
+
// A later wave may offer again. A prior session's dismiss must not stick
|
|
647
|
+
// after this instance has actually gone empty.
|
|
648
|
+
queueOfferAdmissionSuppressedRef.current = false;
|
|
649
|
+
queueOfferDismissedSessionIdsRef.current.clear();
|
|
650
|
+
}, [queueOccupied]);
|
|
651
|
+
const queueOfferSuppressed =
|
|
652
|
+
queueOfferAdmissionSuppressedRef.current ||
|
|
653
|
+
(occupancySessionId != null &&
|
|
654
|
+
queueOfferDismissedSessionIdsRef.current.has(occupancySessionId));
|
|
655
|
+
useEffect(() => {
|
|
656
|
+
if (
|
|
657
|
+
!sessionChromeShouldOfferQueue({
|
|
658
|
+
controlled: activeControlled !== undefined,
|
|
659
|
+
active,
|
|
660
|
+
activityOpen,
|
|
661
|
+
authoritativeQueuedCount: queuedTurns.length,
|
|
662
|
+
suppressed: queueOfferSuppressed,
|
|
663
|
+
})
|
|
664
|
+
) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
if (activeControlled === undefined) setActiveUncontrolled("queue");
|
|
668
|
+
onActiveChange?.("queue");
|
|
669
|
+
}, [
|
|
670
|
+
active,
|
|
671
|
+
activeControlled,
|
|
672
|
+
activityOpen,
|
|
673
|
+
occupancySessionId,
|
|
674
|
+
onActiveChange,
|
|
675
|
+
queueOfferSuppressed,
|
|
676
|
+
queuedTurns.length,
|
|
677
|
+
]);
|
|
509
678
|
const [replaceDraftFor, setReplaceDraftFor] = useState<string | null>(null);
|
|
510
679
|
|
|
511
680
|
const chipRefs = useRef<Partial<Record<SessionChromeSignalId, HTMLButtonElement | null>>>({});
|
|
@@ -513,6 +682,13 @@ export function SessionChrome({
|
|
|
513
682
|
const [pill, setPill] = useState({ left: 0, top: 0, width: 0, height: 0, opacity: 0 });
|
|
514
683
|
|
|
515
684
|
const signalIds = signals.map((signal) => signal.id).join(",");
|
|
685
|
+
useEffect(() => {
|
|
686
|
+
if (
|
|
687
|
+
compact &&
|
|
688
|
+
!signals.some((signal) => ["incoming", "agents", "commands"].includes(signal.id))
|
|
689
|
+
)
|
|
690
|
+
setActivityOpen(false);
|
|
691
|
+
}, [compact, signals]);
|
|
516
692
|
useEffect(() => {
|
|
517
693
|
if (active && !signalIds.split(",").includes(active)) {
|
|
518
694
|
if (activeControlled === undefined) setActiveUncontrolled(null);
|
|
@@ -535,7 +711,10 @@ export function SessionChrome({
|
|
|
535
711
|
return;
|
|
536
712
|
}
|
|
537
713
|
const chip = chipRefs.current[active];
|
|
538
|
-
if (!chip)
|
|
714
|
+
if (!chip) {
|
|
715
|
+
setPill((prev) => ({ ...prev, opacity: 0 }));
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
539
718
|
// Measure against the chip's own box so a wrapped multi-row rail never
|
|
540
719
|
// stretches the highlight into a tall stripe across every signal.
|
|
541
720
|
const railBox = rail.getBoundingClientRect();
|
|
@@ -645,6 +824,8 @@ export function SessionChrome({
|
|
|
645
824
|
/>
|
|
646
825
|
) : active === "goal" && record && goalState && goal ? (
|
|
647
826
|
<GoalPanel goal={goal} state={goalState} elapsed={elapsed} readOnly={readOnly} />
|
|
827
|
+
) : active === "commands" ? (
|
|
828
|
+
<div data-og-session-chrome-panel="commands">{commandsPanel}</div>
|
|
648
829
|
) : active === "agents" ? (
|
|
649
830
|
<div data-og-session-chrome-panel="agents">
|
|
650
831
|
{agentsPanel ?? <p className="text-og-xs text-og-fg-muted">No agent details.</p>}
|
|
@@ -666,15 +847,21 @@ export function SessionChrome({
|
|
|
666
847
|
)}
|
|
667
848
|
style={{
|
|
668
849
|
borderRadius: "var(--_og-session-chrome-radius)",
|
|
669
|
-
background:
|
|
670
|
-
? "
|
|
671
|
-
:
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
850
|
+
background: compact
|
|
851
|
+
? "transparent"
|
|
852
|
+
: open
|
|
853
|
+
? "var(--_og-session-chrome-surface-open)"
|
|
854
|
+
: "var(--_og-session-chrome-surface)",
|
|
855
|
+
borderColor: compact
|
|
856
|
+
? "transparent"
|
|
857
|
+
: open
|
|
858
|
+
? "var(--_og-session-chrome-border-open)"
|
|
859
|
+
: "var(--_og-session-chrome-border)",
|
|
860
|
+
boxShadow: compact
|
|
861
|
+
? "none"
|
|
862
|
+
: open
|
|
863
|
+
? "var(--og-session-chrome-shadow-open)"
|
|
864
|
+
: "var(--og-session-chrome-shadow)",
|
|
678
865
|
transitionDuration: "var(--og-session-chrome-duration)",
|
|
679
866
|
transitionTimingFunction: "var(--_og-session-chrome-ease)",
|
|
680
867
|
}}
|
|
@@ -690,9 +877,9 @@ export function SessionChrome({
|
|
|
690
877
|
>
|
|
691
878
|
<div
|
|
692
879
|
ref={railRef}
|
|
693
|
-
className="relative flex flex-
|
|
880
|
+
className={cn("relative flex items-center", compact ? "flex-nowrap" : "flex-wrap")}
|
|
694
881
|
style={{
|
|
695
|
-
gap: "var(--og-session-chrome-chip-gap)",
|
|
882
|
+
gap: compact ? "10px" : "var(--og-session-chrome-chip-gap)",
|
|
696
883
|
}}
|
|
697
884
|
>
|
|
698
885
|
<motion.div
|
|
@@ -712,71 +899,193 @@ export function SessionChrome({
|
|
|
712
899
|
}}
|
|
713
900
|
transition={{ duration: shellDuration, ease }}
|
|
714
901
|
/>
|
|
715
|
-
{signals
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
902
|
+
{signals
|
|
903
|
+
.filter(
|
|
904
|
+
(signal) => !compact || !["incoming", "agents", "commands"].includes(signal.id),
|
|
905
|
+
)
|
|
906
|
+
.map((signal) => {
|
|
907
|
+
const selected = active === signal.id;
|
|
908
|
+
return (
|
|
909
|
+
<div
|
|
910
|
+
key={signal.id}
|
|
911
|
+
role="group"
|
|
912
|
+
aria-label={
|
|
913
|
+
signal.id === "queue"
|
|
914
|
+
? "Queued messages and actions"
|
|
915
|
+
: signal.id === "goal"
|
|
916
|
+
? "Goal and actions"
|
|
917
|
+
: signal.label
|
|
918
|
+
}
|
|
919
|
+
className={cn(
|
|
920
|
+
"group/signal relative z-[1] inline-flex min-w-0 max-w-full items-center rounded-og-md",
|
|
921
|
+
compact && "bg-og-surface-2/60 px-0.5",
|
|
922
|
+
compact && selected && "bg-og-surface-3",
|
|
923
|
+
)}
|
|
924
|
+
>
|
|
925
|
+
<button
|
|
926
|
+
type="button"
|
|
927
|
+
ref={(node) => {
|
|
928
|
+
chipRefs.current[signal.id] = node;
|
|
929
|
+
}}
|
|
930
|
+
aria-expanded={selected}
|
|
931
|
+
aria-controls={panelId}
|
|
932
|
+
aria-label={
|
|
933
|
+
compact && signal.id === "goal"
|
|
934
|
+
? `Goal · ${goalState === "pursuing" ? "Running" : signal.label}`
|
|
935
|
+
: selected
|
|
936
|
+
? `Close ${signal.label}`
|
|
937
|
+
: undefined
|
|
938
|
+
}
|
|
939
|
+
data-testid={`session-chrome-${signal.id}`}
|
|
940
|
+
data-og-session-chrome-signal={signal.id}
|
|
941
|
+
title={signal.title ?? (compact ? signal.label : undefined)}
|
|
942
|
+
onClick={() => {
|
|
943
|
+
if (compact) setActivityOpen(false);
|
|
944
|
+
setActive(selected ? null : signal.id);
|
|
945
|
+
}}
|
|
946
|
+
className={cn(
|
|
947
|
+
"group relative z-[1] inline-flex min-w-0 min-h-[var(--og-session-chrome-chip-min-height)] max-w-full items-center gap-1 rounded-og-md py-1 text-left text-og-xs outline-hidden",
|
|
948
|
+
// Coarse pointers keep a 44px target (session-pins acceptance).
|
|
949
|
+
"pointer-coarse:min-h-11",
|
|
950
|
+
"transition-colors duration-150 motion-reduce:transition-none",
|
|
951
|
+
"hover:text-og-fg focus-visible:bg-og-surface-3/50",
|
|
952
|
+
selected ? "text-og-fg" : "text-og-fg-muted",
|
|
953
|
+
)}
|
|
954
|
+
style={{
|
|
955
|
+
paddingInline: "var(--og-session-chrome-chip-pad-x)",
|
|
956
|
+
}}
|
|
957
|
+
>
|
|
958
|
+
{signal.id === "queue" && queueArrivalNonce > 0 && !reduceMotion ? (
|
|
959
|
+
<motion.span
|
|
960
|
+
key={queueArrivalNonce}
|
|
961
|
+
aria-hidden="true"
|
|
962
|
+
data-testid="session-chrome-queue-arrival"
|
|
963
|
+
className="pointer-events-none absolute inset-0 rounded-og-md bg-og-accent-soft"
|
|
964
|
+
initial={{ opacity: 0 }}
|
|
965
|
+
animate={{ opacity: [0, 0.38, 0] }}
|
|
966
|
+
transition={{ duration: 0.72, times: [0, 0.18, 1], ease }}
|
|
967
|
+
/>
|
|
968
|
+
) : null}
|
|
969
|
+
<span className={cn("shrink-0", toneClass(signal.tone, selected))}>
|
|
970
|
+
{signal.icon}
|
|
762
971
|
</span>
|
|
763
|
-
<span
|
|
764
|
-
{
|
|
972
|
+
<span
|
|
973
|
+
className={cn(
|
|
974
|
+
"font-medium text-og-fg",
|
|
975
|
+
compact ? "min-w-0 truncate" : "shrink-0",
|
|
976
|
+
)}
|
|
977
|
+
>
|
|
978
|
+
{compact && signal.id === "goal" ? "Goal · " : null}
|
|
979
|
+
{compact && signal.id === "goal"
|
|
980
|
+
? goalState === "pursuing"
|
|
981
|
+
? "Running"
|
|
982
|
+
: signal.label
|
|
983
|
+
: compact && signal.id === "queue"
|
|
984
|
+
? `${queuedTurns.length + optimisticQueued.length} queued`
|
|
985
|
+
: signal.label}
|
|
765
986
|
</span>
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
987
|
+
{signal.detail && !compact ? (
|
|
988
|
+
<>
|
|
989
|
+
<span aria-hidden className="shrink-0 text-og-fg-subtle/60">
|
|
990
|
+
·
|
|
991
|
+
</span>
|
|
992
|
+
<span className="min-w-0 max-w-[8.5rem] truncate text-og-fg sm:max-w-[12rem]">
|
|
993
|
+
{signal.detail}
|
|
994
|
+
</span>
|
|
995
|
+
</>
|
|
996
|
+
) : null}
|
|
997
|
+
{selected && !compact ? (
|
|
998
|
+
<span
|
|
999
|
+
data-testid="session-chrome-close"
|
|
1000
|
+
className="ml-0.5 inline-flex size-4 shrink-0 items-center justify-center rounded-og-sm text-og-fg-subtle transition-colors group-hover:text-og-fg pointer-coarse:size-5"
|
|
1001
|
+
aria-hidden
|
|
1002
|
+
>
|
|
1003
|
+
<XIcon className="size-3" />
|
|
1004
|
+
</span>
|
|
1005
|
+
) : null}
|
|
1006
|
+
</button>
|
|
1007
|
+
{signal.id === "goal" && goal && record && !readOnly ? (
|
|
1008
|
+
<div className={cn("flex shrink-0 items-center pr-1", compact && "pl-0.5")}>
|
|
1009
|
+
{record.status === "active" || record.status === "paused" ? (
|
|
1010
|
+
<IconAction
|
|
1011
|
+
label={record.status === "paused" ? "Resume goal" : "Pause goal"}
|
|
1012
|
+
tip={record.status === "paused" ? "Resume goal" : "Pause goal"}
|
|
1013
|
+
disabled={goal.updating}
|
|
1014
|
+
onClick={() =>
|
|
1015
|
+
void (record.status === "paused"
|
|
1016
|
+
? goal.resume()
|
|
1017
|
+
: goal.pause("Paused from session chrome"))
|
|
1018
|
+
}
|
|
1019
|
+
>
|
|
1020
|
+
{goal.updating ? (
|
|
1021
|
+
<Loader2Icon className="size-3 animate-og-spin" />
|
|
1022
|
+
) : record.status === "paused" ? (
|
|
1023
|
+
<PlayIcon className="size-3" />
|
|
1024
|
+
) : (
|
|
1025
|
+
<PauseIcon className="size-3" />
|
|
1026
|
+
)}
|
|
1027
|
+
</IconAction>
|
|
1028
|
+
) : null}
|
|
1029
|
+
<IconAction
|
|
1030
|
+
label="Clear goal"
|
|
1031
|
+
tip="Clear goal"
|
|
1032
|
+
danger
|
|
1033
|
+
disabled={goal.updating}
|
|
1034
|
+
onClick={() => void goal.deleteGoal()}
|
|
1035
|
+
>
|
|
1036
|
+
<Trash2Icon className="size-3" />
|
|
1037
|
+
</IconAction>
|
|
1038
|
+
</div>
|
|
1039
|
+
) : signal.id === "queue" && canMutateQueue && queuedTurns[0] ? (
|
|
1040
|
+
<div className={cn("flex shrink-0 items-center pr-1", compact && "pl-0.5")}>
|
|
1041
|
+
<IconAction
|
|
1042
|
+
label="Steer first queued message"
|
|
1043
|
+
text="Steer"
|
|
1044
|
+
tip={QUEUE_STEER_TIP}
|
|
1045
|
+
disabled={
|
|
1046
|
+
queue.mutating || Boolean(queue.mutationFor(queuedTurns[0].id))
|
|
1047
|
+
}
|
|
1048
|
+
onClick={() => void queue.steerTurn(queuedTurns[0]!.id)}
|
|
1049
|
+
>
|
|
1050
|
+
<CornerDownRightIcon className="size-3" />
|
|
1051
|
+
</IconAction>
|
|
1052
|
+
</div>
|
|
1053
|
+
) : null}
|
|
1054
|
+
</div>
|
|
1055
|
+
);
|
|
1056
|
+
})}
|
|
1057
|
+
{compact &&
|
|
1058
|
+
signals.some((signal) => ["incoming", "agents", "commands"].includes(signal.id)) ? (
|
|
1059
|
+
<button
|
|
1060
|
+
type="button"
|
|
1061
|
+
aria-label="Session activity"
|
|
1062
|
+
title={activityOpen ? "Close activity" : "Inbox, agents and commands"}
|
|
1063
|
+
aria-controls={panelId}
|
|
1064
|
+
aria-expanded={activityOpen}
|
|
1065
|
+
onClick={() => {
|
|
1066
|
+
setActivityOpen(!activityOpen);
|
|
1067
|
+
setActive(
|
|
1068
|
+
activityOpen
|
|
1069
|
+
? null
|
|
1070
|
+
: (signals.find((signal) =>
|
|
1071
|
+
["incoming", "agents", "commands"].includes(signal.id),
|
|
1072
|
+
)?.id ?? null),
|
|
1073
|
+
);
|
|
1074
|
+
}}
|
|
1075
|
+
className={cn(
|
|
1076
|
+
"ml-auto inline-flex min-h-8 shrink-0 items-center justify-center gap-1.5 rounded-og-md px-2 text-og-xs transition-colors outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent/40 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
|
|
1077
|
+
activityOpen
|
|
1078
|
+
? "bg-og-surface-3 text-og-fg"
|
|
1079
|
+
: "text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg",
|
|
1080
|
+
)}
|
|
1081
|
+
>
|
|
1082
|
+
<ActivityIcon className="size-3.5" />
|
|
1083
|
+
<span className="sr-only">Activity</span>
|
|
1084
|
+
{incoming.length > 0 ? (
|
|
1085
|
+
<span className="size-1.5 rounded-full bg-og-accent" />
|
|
1086
|
+
) : null}
|
|
1087
|
+
</button>
|
|
1088
|
+
) : null}
|
|
780
1089
|
{stopping ? (
|
|
781
1090
|
<span
|
|
782
1091
|
role="status"
|
|
@@ -794,6 +1103,47 @@ export function SessionChrome({
|
|
|
794
1103
|
</div>
|
|
795
1104
|
</div>
|
|
796
1105
|
|
|
1106
|
+
<AnimatePresence initial={false}>
|
|
1107
|
+
{compact && activityOpen ? (
|
|
1108
|
+
<motion.div
|
|
1109
|
+
key="activity-tabs"
|
|
1110
|
+
initial={{ height: 0, opacity: 0 }}
|
|
1111
|
+
animate={{ height: "auto", opacity: 1 }}
|
|
1112
|
+
exit={{ height: 0, opacity: 0 }}
|
|
1113
|
+
transition={{ duration: shellDuration, ease }}
|
|
1114
|
+
style={{ overflow: "hidden" }}
|
|
1115
|
+
>
|
|
1116
|
+
<div className="mt-1 flex items-center gap-1 rounded-t-og-lg bg-og-surface-2/50 px-2 py-1">
|
|
1117
|
+
{signals
|
|
1118
|
+
.filter((signal) => ["incoming", "agents", "commands"].includes(signal.id))
|
|
1119
|
+
.map((signal) => (
|
|
1120
|
+
<button
|
|
1121
|
+
key={signal.id}
|
|
1122
|
+
type="button"
|
|
1123
|
+
aria-expanded={active === signal.id}
|
|
1124
|
+
onClick={() => {
|
|
1125
|
+
if (activityVisibleRef.current) setActive(signal.id);
|
|
1126
|
+
}}
|
|
1127
|
+
className={cn(
|
|
1128
|
+
"inline-flex min-h-9 items-center gap-1.5 rounded-og-md px-2 text-og-xs pointer-coarse:min-h-11",
|
|
1129
|
+
active === signal.id
|
|
1130
|
+
? "bg-og-surface-3 text-og-fg"
|
|
1131
|
+
: "text-og-fg-muted hover:text-og-fg",
|
|
1132
|
+
)}
|
|
1133
|
+
>
|
|
1134
|
+
{signal.icon}
|
|
1135
|
+
{signal.id === "incoming" ? `${incoming.length} incoming` : signal.label}
|
|
1136
|
+
</button>
|
|
1137
|
+
))}
|
|
1138
|
+
</div>
|
|
1139
|
+
</motion.div>
|
|
1140
|
+
) : null}
|
|
1141
|
+
</AnimatePresence>
|
|
1142
|
+
{goal?.mutationError ? (
|
|
1143
|
+
<p role="alert" className="px-3 pb-2 text-og-xs text-og-danger">
|
|
1144
|
+
Goal action not confirmed. {goal.mutationError.message}
|
|
1145
|
+
</p>
|
|
1146
|
+
) : null}
|
|
797
1147
|
<div
|
|
798
1148
|
id={panelId}
|
|
799
1149
|
data-og-session-chrome-panel-shell=""
|
|
@@ -811,7 +1161,14 @@ export function SessionChrome({
|
|
|
811
1161
|
>
|
|
812
1162
|
<div className="min-h-0 overflow-hidden">
|
|
813
1163
|
<div
|
|
814
|
-
className=
|
|
1164
|
+
className={cn(
|
|
1165
|
+
"relative overflow-y-auto overscroll-contain",
|
|
1166
|
+
compact
|
|
1167
|
+
? activityOpen
|
|
1168
|
+
? "rounded-b-og-lg bg-og-surface-2/50"
|
|
1169
|
+
: "mt-1 rounded-og-lg bg-og-surface-2/50"
|
|
1170
|
+
: "border-t border-og-border/50",
|
|
1171
|
+
)}
|
|
815
1172
|
style={{
|
|
816
1173
|
maxHeight: "var(--og-session-chrome-panel-max-height)",
|
|
817
1174
|
paddingInline: "var(--og-session-chrome-panel-pad-x)",
|
|
@@ -819,7 +1176,7 @@ export function SessionChrome({
|
|
|
819
1176
|
}}
|
|
820
1177
|
>
|
|
821
1178
|
<AnimatePresence initial={false}>
|
|
822
|
-
{active && panelBody ? (
|
|
1179
|
+
{active && active !== "commands" && panelBody ? (
|
|
823
1180
|
<motion.div
|
|
824
1181
|
key={active}
|
|
825
1182
|
data-og-session-chrome-panel-frame={active}
|
|
@@ -842,6 +1199,16 @@ export function SessionChrome({
|
|
|
842
1199
|
</motion.div>
|
|
843
1200
|
) : null}
|
|
844
1201
|
</AnimatePresence>
|
|
1202
|
+
{active === "commands" ? (
|
|
1203
|
+
<motion.div
|
|
1204
|
+
key="commands"
|
|
1205
|
+
initial={reduceMotion ? false : { opacity: 0 }}
|
|
1206
|
+
animate={{ opacity: 1 }}
|
|
1207
|
+
transition={{ duration: crossfadeDuration, ease }}
|
|
1208
|
+
>
|
|
1209
|
+
{panelBody}
|
|
1210
|
+
</motion.div>
|
|
1211
|
+
) : null}
|
|
845
1212
|
</div>
|
|
846
1213
|
</div>
|
|
847
1214
|
</div>
|
|
@@ -1017,6 +1384,7 @@ function QueuePanel({
|
|
|
1017
1384
|
{onSteer ? (
|
|
1018
1385
|
<IconAction
|
|
1019
1386
|
label={`Steer queued prompt ${index + 1}`}
|
|
1387
|
+
text="Steer"
|
|
1020
1388
|
tip={QUEUE_STEER_TIP}
|
|
1021
1389
|
disabled={settling || pending !== null}
|
|
1022
1390
|
onClick={() => onSteer(turn.id)}
|
|
@@ -1024,7 +1392,7 @@ function QueuePanel({
|
|
|
1024
1392
|
{pending === "steer" ? (
|
|
1025
1393
|
<Loader2Icon className="size-3 animate-og-spin" />
|
|
1026
1394
|
) : (
|
|
1027
|
-
<
|
|
1395
|
+
<CornerDownRightIcon className="size-3" />
|
|
1028
1396
|
)}
|
|
1029
1397
|
</IconAction>
|
|
1030
1398
|
) : null}
|
|
@@ -1206,7 +1574,7 @@ function GoalPanel({
|
|
|
1206
1574
|
<span>{sessionChromeGoalPillLabel(state, record)}</span>
|
|
1207
1575
|
{elapsed ? (
|
|
1208
1576
|
<span className="tabular-nums normal-case tracking-normal text-og-fg-muted">
|
|
1209
|
-
· {elapsed}
|
|
1577
|
+
· Created {elapsed} ago
|
|
1210
1578
|
</span>
|
|
1211
1579
|
) : null}
|
|
1212
1580
|
<span className="normal-case tracking-normal text-og-fg-muted">· v{record.version}</span>
|
|
@@ -1288,6 +1656,7 @@ const QUEUE_EDIT_TIP = "Edit in composer";
|
|
|
1288
1656
|
const QUEUE_DELETE_TIP = "Delete this queued prompt";
|
|
1289
1657
|
|
|
1290
1658
|
function IconAction({
|
|
1659
|
+
text,
|
|
1291
1660
|
label,
|
|
1292
1661
|
tip,
|
|
1293
1662
|
onClick,
|
|
@@ -1296,6 +1665,7 @@ function IconAction({
|
|
|
1296
1665
|
children,
|
|
1297
1666
|
}: {
|
|
1298
1667
|
label: string;
|
|
1668
|
+
text?: string;
|
|
1299
1669
|
tip: ReactNode;
|
|
1300
1670
|
onClick: () => void;
|
|
1301
1671
|
disabled?: boolean;
|
|
@@ -1311,13 +1681,16 @@ function IconAction({
|
|
|
1311
1681
|
disabled={disabled}
|
|
1312
1682
|
onClick={onClick}
|
|
1313
1683
|
className={cn(
|
|
1314
|
-
"inline-flex size-6 items-center justify-center rounded-og-sm
|
|
1684
|
+
"inline-flex size-6 items-center justify-center rounded-og-sm outline-hidden transition-colors",
|
|
1315
1685
|
"hover:bg-og-surface-2 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent/40",
|
|
1316
1686
|
"disabled:pointer-events-none disabled:opacity-40 pointer-coarse:size-9",
|
|
1687
|
+
text ? "text-og-fg" : "text-og-fg-subtle",
|
|
1688
|
+
text && "w-auto gap-1 px-1.5 text-og-xs pointer-coarse:w-auto",
|
|
1317
1689
|
danger && "hover:text-og-danger",
|
|
1318
1690
|
)}
|
|
1319
1691
|
>
|
|
1320
1692
|
{children}
|
|
1693
|
+
{text}
|
|
1321
1694
|
</button>
|
|
1322
1695
|
</TooltipTrigger>
|
|
1323
1696
|
<TooltipContent side="top">{tip}</TooltipContent>
|