@opengeni/react 3.7.0-canary.7 → 3.8.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/README.md +6 -0
- package/dist/chat.js +1 -1
- package/dist/chunk-5M6VTFJ5.js +171 -0
- package/dist/chunk-5M6VTFJ5.js.map +1 -0
- package/dist/{chunk-IOLVN74D.js → chunk-6J56T3MG.js} +15 -1
- package/dist/chunk-6J56T3MG.js.map +1 -0
- package/dist/{chunk-BQGIXRKV.js → chunk-7BWFU6H4.js} +22 -4
- package/dist/chunk-7BWFU6H4.js.map +1 -0
- package/dist/{chunk-3HIF5IFC.js → chunk-7W6PVVTK.js} +63 -18
- package/dist/chunk-7W6PVVTK.js.map +1 -0
- package/dist/{chunk-ZFOFY5ST.js → chunk-NLTS7JHI.js} +16 -31
- package/dist/chunk-NLTS7JHI.js.map +1 -0
- package/dist/chunk-NOSYBS2D.js +208 -0
- package/dist/chunk-NOSYBS2D.js.map +1 -0
- package/dist/{chunk-F2CFHN3Y.js → chunk-YAIPFO5D.js} +2 -1
- package/dist/chunk-YAIPFO5D.js.map +1 -0
- package/dist/chunk-YBM6CQRU.js +292 -0
- package/dist/chunk-YBM6CQRU.js.map +1 -0
- package/dist/chunk-ZYRGEKWS.js +216 -0
- package/dist/chunk-ZYRGEKWS.js.map +1 -0
- package/dist/components/composer.d.ts +4 -2
- package/dist/components/message-timeline.d.ts +9 -2
- package/dist/components/timeline-annotation-chrome.d.ts +25 -0
- package/dist/components/timeline-annotation-layout.d.ts +42 -0
- package/dist/components/timeline-annotation-markers.d.ts +8 -0
- package/dist/components/timeline-annotation-reveal-context.d.ts +4 -0
- package/dist/components/timeline-annotation-shared.d.ts +31 -0
- package/dist/components/timeline-annotations-dialog.d.ts +1 -1
- package/dist/components/timeline-annotations.d.ts +8 -2
- package/dist/composer.js +2 -2
- package/dist/hooks/use-composer.d.ts +2 -0
- package/dist/index.js +9 -6
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +6 -6
- package/dist/machines.js +1 -1
- package/dist/{queue-surface-implementation-XL2A6PBM.js → queue-surface-implementation-LK3S4BIW.js} +5 -2
- package/dist/{queue-surface-implementation-XL2A6PBM.js.map → queue-surface-implementation-LK3S4BIW.js.map} +1 -1
- package/dist/session-ui.js +8 -5
- package/dist/session.js +1 -1
- package/dist/timeline-annotation-markers-DRSTLEM6.js +165 -0
- package/dist/timeline-annotation-markers-DRSTLEM6.js.map +1 -0
- package/dist/timeline-annotation-selection-IHLUGVND.js +236 -0
- package/dist/timeline-annotation-selection-IHLUGVND.js.map +1 -0
- package/dist/timeline-annotations-dialog-6Q53N5FG.js +262 -0
- package/dist/timeline-annotations-dialog-6Q53N5FG.js.map +1 -0
- package/package.json +2 -2
- package/src/components/chat-composer.tsx +5 -2
- package/src/components/composer.tsx +40 -5
- package/src/components/copy-button.tsx +1 -0
- package/src/components/machines-dashboard.tsx +1 -16
- package/src/components/message-timeline.tsx +357 -297
- package/src/components/timeline-annotation-chrome.tsx +183 -0
- package/src/components/timeline-annotation-layout.ts +268 -0
- package/src/components/timeline-annotation-markers.tsx +185 -0
- package/src/components/timeline-annotation-reveal-context.ts +9 -0
- package/src/components/timeline-annotation-selection.tsx +99 -54
- package/src/components/timeline-annotation-shared.ts +245 -0
- package/src/components/timeline-annotations-dialog.tsx +172 -128
- package/src/components/timeline-annotations.tsx +178 -45
- package/src/hooks/use-composer.ts +17 -0
- package/styles/compiled.css +86 -22
- package/dist/chunk-3HIF5IFC.js.map +0 -1
- package/dist/chunk-BQGIXRKV.js.map +0 -1
- package/dist/chunk-F2CFHN3Y.js.map +0 -1
- package/dist/chunk-IOLVN74D.js.map +0 -1
- package/dist/chunk-W3OUAH6M.js +0 -170
- package/dist/chunk-W3OUAH6M.js.map +0 -1
- package/dist/chunk-ZFOFY5ST.js.map +0 -1
- package/dist/timeline-annotation-selection-3L7LHGG2.js +0 -187
- package/dist/timeline-annotation-selection-3L7LHGG2.js.map +0 -1
- package/dist/timeline-annotations-dialog-U7EVLR75.js +0 -196
- package/dist/timeline-annotations-dialog-U7EVLR75.js.map +0 -1
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnnotationAccentRow,
|
|
3
|
+
AnnotationNotePreview,
|
|
4
|
+
AnnotationQuoteSourceButton,
|
|
5
|
+
TimelineAnnotationSourceRootContext,
|
|
6
|
+
useTimelineAnnotationSourceRoot
|
|
7
|
+
} from "./chunk-5M6VTFJ5.js";
|
|
8
|
+
import {
|
|
9
|
+
ANNOTATION_CARD_STACK_SCROLL_AT
|
|
10
|
+
} from "./chunk-ZYRGEKWS.js";
|
|
11
|
+
import {
|
|
12
|
+
annotationDisplayOrdinal,
|
|
13
|
+
annotationHasNote,
|
|
14
|
+
resolveAnnotationRevealRoot
|
|
15
|
+
} from "./chunk-NOSYBS2D.js";
|
|
16
|
+
import {
|
|
17
|
+
cn
|
|
18
|
+
} from "./chunk-22KP6LR5.js";
|
|
19
|
+
|
|
20
|
+
// src/components/timeline-annotations.tsx
|
|
21
|
+
import { QuoteIcon, XIcon } from "lucide-react";
|
|
22
|
+
import {
|
|
23
|
+
Suspense,
|
|
24
|
+
lazy,
|
|
25
|
+
useEffect,
|
|
26
|
+
useLayoutEffect,
|
|
27
|
+
useRef,
|
|
28
|
+
useState
|
|
29
|
+
} from "react";
|
|
30
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
31
|
+
var TimelineAnnotationsDialog = lazy(() => import("./timeline-annotations-dialog-6Q53N5FG.js"));
|
|
32
|
+
function AnnotationSourceRootBridge({
|
|
33
|
+
triggerRef,
|
|
34
|
+
children
|
|
35
|
+
}) {
|
|
36
|
+
const inheritedRoot = useTimelineAnnotationSourceRoot();
|
|
37
|
+
const conversationRootRef = useRef(null);
|
|
38
|
+
if (!inheritedRoot) {
|
|
39
|
+
conversationRootRef.current = resolveAnnotationRevealRoot(triggerRef.current);
|
|
40
|
+
}
|
|
41
|
+
useLayoutEffect(() => {
|
|
42
|
+
if (inheritedRoot) return;
|
|
43
|
+
conversationRootRef.current = resolveAnnotationRevealRoot(triggerRef.current);
|
|
44
|
+
});
|
|
45
|
+
if (inheritedRoot) return children;
|
|
46
|
+
return /* @__PURE__ */ jsx(TimelineAnnotationSourceRootContext.Provider, { value: conversationRootRef, children });
|
|
47
|
+
}
|
|
48
|
+
function TimelineAnnotationCards({
|
|
49
|
+
annotations,
|
|
50
|
+
onRevealSource,
|
|
51
|
+
className
|
|
52
|
+
}) {
|
|
53
|
+
const [unavailableId, setUnavailableId] = useState(null);
|
|
54
|
+
if (annotations.length === 0) return null;
|
|
55
|
+
return /* @__PURE__ */ jsx(
|
|
56
|
+
"div",
|
|
57
|
+
{
|
|
58
|
+
"data-og-annotation-cards": "",
|
|
59
|
+
className: cn(
|
|
60
|
+
"grid gap-2",
|
|
61
|
+
annotations.length >= ANNOTATION_CARD_STACK_SCROLL_AT && "max-h-[min(28rem,55vh)] overflow-x-hidden overflow-y-auto overscroll-contain pr-1",
|
|
62
|
+
className
|
|
63
|
+
),
|
|
64
|
+
children: annotations.map((annotation, index) => {
|
|
65
|
+
const ordinal = annotationDisplayOrdinal(annotation, index);
|
|
66
|
+
return /* @__PURE__ */ jsx("section", { "aria-label": `Annotation ${ordinal}`, children: /* @__PURE__ */ jsxs(AnnotationAccentRow, { children: [
|
|
67
|
+
/* @__PURE__ */ jsxs("p", { className: "mb-0.5 text-og-xs font-medium tabular-nums text-og-fg-subtle", children: [
|
|
68
|
+
"Annotation ",
|
|
69
|
+
ordinal
|
|
70
|
+
] }),
|
|
71
|
+
/* @__PURE__ */ jsx(
|
|
72
|
+
AnnotationQuoteSourceButton,
|
|
73
|
+
{
|
|
74
|
+
annotation,
|
|
75
|
+
lines: 2,
|
|
76
|
+
onRevealSource,
|
|
77
|
+
onUnavailable: setUnavailableId
|
|
78
|
+
}
|
|
79
|
+
),
|
|
80
|
+
unavailableId === annotation.id ? /* @__PURE__ */ jsx("p", { role: "status", className: "text-og-xs text-og-status-waiting", children: "Source is outside the loaded timeline window." }) : null,
|
|
81
|
+
/* @__PURE__ */ jsx(
|
|
82
|
+
AnnotationNotePreview,
|
|
83
|
+
{
|
|
84
|
+
note: annotation.note,
|
|
85
|
+
annotationId: annotation.id,
|
|
86
|
+
ordinal
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
] }) }, annotation.id);
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
function TimelineAnnotationsChip({
|
|
95
|
+
annotations,
|
|
96
|
+
editable = false,
|
|
97
|
+
focusAnnotationId,
|
|
98
|
+
onFocusConsumed,
|
|
99
|
+
onUpdate,
|
|
100
|
+
onRemove,
|
|
101
|
+
onRevealSource,
|
|
102
|
+
className
|
|
103
|
+
}) {
|
|
104
|
+
const focusRequested = Boolean(
|
|
105
|
+
focusAnnotationId && annotations.some((item) => item.id === focusAnnotationId)
|
|
106
|
+
);
|
|
107
|
+
const [open, setOpen] = useState(focusRequested);
|
|
108
|
+
const triggerRef = useRef(null);
|
|
109
|
+
const openedFocusId = useRef(focusRequested ? focusAnnotationId ?? null : null);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (!focusRequested || !focusAnnotationId) {
|
|
112
|
+
if (!focusRequested) openedFocusId.current = null;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (openedFocusId.current === focusAnnotationId) return;
|
|
116
|
+
openedFocusId.current = focusAnnotationId;
|
|
117
|
+
setOpen(true);
|
|
118
|
+
}, [focusAnnotationId, focusRequested]);
|
|
119
|
+
if (annotations.length === 0) return null;
|
|
120
|
+
const countLabel = `${annotations.length} ${annotations.length === 1 ? "annotation" : "annotations"}`;
|
|
121
|
+
const incomplete = annotations.some((annotation) => !annotationHasNote(annotation.note));
|
|
122
|
+
const canClear = Boolean(editable && onRemove);
|
|
123
|
+
const dismiss = (restoreFocus) => {
|
|
124
|
+
setOpen(false);
|
|
125
|
+
onFocusConsumed?.();
|
|
126
|
+
if (restoreFocus) triggerRef.current?.focus();
|
|
127
|
+
};
|
|
128
|
+
return /* @__PURE__ */ jsxs("span", { className: cn("inline-flex max-w-full items-center", className), children: [
|
|
129
|
+
/* @__PURE__ */ jsxs(
|
|
130
|
+
"span",
|
|
131
|
+
{
|
|
132
|
+
className: cn(
|
|
133
|
+
"inline-flex max-w-full items-center rounded-full border bg-og-surface-1 text-og-fg-muted",
|
|
134
|
+
incomplete ? "border-og-accent/45 bg-og-accent-soft/50" : "border-og-border"
|
|
135
|
+
),
|
|
136
|
+
children: [
|
|
137
|
+
/* @__PURE__ */ jsxs(
|
|
138
|
+
"button",
|
|
139
|
+
{
|
|
140
|
+
ref: triggerRef,
|
|
141
|
+
type: "button",
|
|
142
|
+
className: cn(
|
|
143
|
+
"inline-flex min-h-8 min-w-0 max-w-full items-center gap-1.5 rounded-full border-0 bg-transparent py-1 text-og-sm font-medium outline-hidden transition hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:min-h-[44px]",
|
|
144
|
+
canClear ? "pr-1 pl-2.5" : "px-2.5"
|
|
145
|
+
),
|
|
146
|
+
"aria-label": `Review ${countLabel}`,
|
|
147
|
+
"aria-haspopup": "dialog",
|
|
148
|
+
"aria-expanded": open,
|
|
149
|
+
onClick: () => setOpen((current) => !current),
|
|
150
|
+
children: [
|
|
151
|
+
/* @__PURE__ */ jsx(QuoteIcon, { className: "size-3.5 shrink-0 text-og-accent", "aria-hidden": "true" }),
|
|
152
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: countLabel })
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
),
|
|
156
|
+
canClear ? /* @__PURE__ */ jsx(
|
|
157
|
+
"button",
|
|
158
|
+
{
|
|
159
|
+
type: "button",
|
|
160
|
+
className: "inline-flex size-7 shrink-0 items-center justify-center rounded-full border-0 bg-transparent text-og-fg-subtle outline-hidden hover:text-og-status-failed focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:size-11",
|
|
161
|
+
"aria-label": "Remove all annotations",
|
|
162
|
+
onClick: () => {
|
|
163
|
+
for (const annotation of annotations) onRemove?.(annotation.id);
|
|
164
|
+
},
|
|
165
|
+
children: /* @__PURE__ */ jsx(XIcon, { className: "size-3.5", "aria-hidden": "true" })
|
|
166
|
+
}
|
|
167
|
+
) : null
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
),
|
|
171
|
+
open ? /* @__PURE__ */ jsx(AnnotationSourceRootBridge, { triggerRef, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
|
|
172
|
+
TimelineAnnotationsDialog,
|
|
173
|
+
{
|
|
174
|
+
annotations,
|
|
175
|
+
editable,
|
|
176
|
+
focusAnnotationId,
|
|
177
|
+
onFocusConsumed,
|
|
178
|
+
onUpdate,
|
|
179
|
+
onRemove,
|
|
180
|
+
onRevealSource,
|
|
181
|
+
triggerRef,
|
|
182
|
+
countLabel,
|
|
183
|
+
onDismiss: dismiss
|
|
184
|
+
}
|
|
185
|
+
) }) }) : null
|
|
186
|
+
] });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/components/queue-draft-policy.ts
|
|
190
|
+
function requestQueueDraftEdit(composer, confirmReplacement, editImmediately) {
|
|
191
|
+
if (composer.hasDraftContent()) {
|
|
192
|
+
confirmReplacement();
|
|
193
|
+
} else {
|
|
194
|
+
editImmediately();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/components/queue-surface-state.tsx
|
|
199
|
+
import { Loader2Icon, RotateCwIcon } from "lucide-react";
|
|
200
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
201
|
+
function EmptyQueueStateSurface({ queue }) {
|
|
202
|
+
const hasError = Boolean(queue.error || queue.mutationError);
|
|
203
|
+
return /* @__PURE__ */ jsx2(
|
|
204
|
+
"div",
|
|
205
|
+
{
|
|
206
|
+
className: "og-root mx-auto mb-2 w-full max-w-3xl shrink-0 px-4 sm:px-6",
|
|
207
|
+
"data-testid": "queue-surface",
|
|
208
|
+
children: /* @__PURE__ */ jsxs2("div", { className: "overflow-hidden rounded-lg border border-border bg-surface/80 shadow-sm", children: [
|
|
209
|
+
queue.stoppingPreviousAttempt ? /* @__PURE__ */ jsx2(QueueStoppingStatus, { queue, dividerAfter: hasError }) : null,
|
|
210
|
+
hasError ? /* @__PURE__ */ jsx2(QueueErrorAlert, { queue }) : null
|
|
211
|
+
] })
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
function QueueStoppingStatus({
|
|
216
|
+
queue,
|
|
217
|
+
dividerAfter = false
|
|
218
|
+
}) {
|
|
219
|
+
return /* @__PURE__ */ jsxs2(
|
|
220
|
+
"div",
|
|
221
|
+
{
|
|
222
|
+
role: "status",
|
|
223
|
+
"aria-live": "polite",
|
|
224
|
+
className: `flex min-h-11 items-center gap-2.5 bg-status-waiting/[0.07] px-3 py-2 text-og-control text-fg ${dividerAfter ? "border-b border-status-waiting/20" : ""}`,
|
|
225
|
+
"data-testid": "stopping-previous-attempt",
|
|
226
|
+
children: [
|
|
227
|
+
/* @__PURE__ */ jsx2(
|
|
228
|
+
Loader2Icon,
|
|
229
|
+
{
|
|
230
|
+
"aria-hidden": "true",
|
|
231
|
+
className: "size-3.5 shrink-0 animate-spin text-status-waiting motion-reduce:animate-none"
|
|
232
|
+
}
|
|
233
|
+
),
|
|
234
|
+
/* @__PURE__ */ jsxs2("span", { className: "min-w-0", children: [
|
|
235
|
+
/* @__PURE__ */ jsx2("span", { className: "font-medium", children: queue.effectiveControl?.state === "paused" ? "Stopping current attempt\u2026" : "Stopping previous attempt\u2026" }),
|
|
236
|
+
" ",
|
|
237
|
+
/* @__PURE__ */ jsx2("span", { className: "text-fg-muted", children: queue.effectiveControl?.state === "paused" ? "Queued work stays saved until you resume." : "Queued work is saved and starts automatically." })
|
|
238
|
+
] })
|
|
239
|
+
]
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
function QueueErrorAlert({
|
|
244
|
+
queue,
|
|
245
|
+
dividerBefore = false
|
|
246
|
+
}) {
|
|
247
|
+
return /* @__PURE__ */ jsx2("div", { className: `${dividerBefore ? "border-t border-border" : ""} p-2`, children: /* @__PURE__ */ jsxs2(
|
|
248
|
+
"div",
|
|
249
|
+
{
|
|
250
|
+
role: "alert",
|
|
251
|
+
className: "flex min-w-0 max-w-full flex-wrap items-start gap-2 rounded-md bg-status-failed/10 px-2 py-1.5 text-og-control text-status-failed",
|
|
252
|
+
children: [
|
|
253
|
+
/* @__PURE__ */ jsx2(
|
|
254
|
+
"span",
|
|
255
|
+
{
|
|
256
|
+
role: "region",
|
|
257
|
+
"aria-label": "Queue error details",
|
|
258
|
+
tabIndex: 0,
|
|
259
|
+
className: "max-h-[min(5rem,20dvh)] min-w-0 max-w-full flex-[1_1_5rem] overflow-auto overscroll-contain whitespace-pre-wrap rounded-sm outline-hidden [overflow-wrap:anywhere] [unicode-bidi:plaintext] focus-visible:ring-2 focus-visible:ring-ring/40",
|
|
260
|
+
"data-testid": "queue-error-message",
|
|
261
|
+
dir: "auto",
|
|
262
|
+
children: (queue.mutationError ?? queue.error)?.message
|
|
263
|
+
}
|
|
264
|
+
),
|
|
265
|
+
/* @__PURE__ */ jsx2(
|
|
266
|
+
"button",
|
|
267
|
+
{
|
|
268
|
+
type: "button",
|
|
269
|
+
onClick: () => {
|
|
270
|
+
queue.clearMutationError();
|
|
271
|
+
void queue.refresh();
|
|
272
|
+
},
|
|
273
|
+
"aria-label": "Dismiss queue error and retry",
|
|
274
|
+
title: "Retry loading the queue",
|
|
275
|
+
className: "ms-auto inline-flex size-7 shrink-0 items-center justify-center self-start rounded-md outline-hidden transition-colors hover:bg-surface-3 focus-visible:ring-2 focus-visible:ring-ring/40 motion-reduce:transition-none pointer-coarse:size-[44px]",
|
|
276
|
+
children: /* @__PURE__ */ jsx2(RotateCwIcon, { className: "size-3.5" })
|
|
277
|
+
}
|
|
278
|
+
)
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
) });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export {
|
|
285
|
+
TimelineAnnotationCards,
|
|
286
|
+
TimelineAnnotationsChip,
|
|
287
|
+
requestQueueDraftEdit,
|
|
288
|
+
EmptyQueueStateSurface,
|
|
289
|
+
QueueStoppingStatus,
|
|
290
|
+
QueueErrorAlert
|
|
291
|
+
};
|
|
292
|
+
//# sourceMappingURL=chunk-YBM6CQRU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/timeline-annotations.tsx","../src/components/queue-draft-policy.ts","../src/components/queue-surface-state.tsx"],"sourcesContent":["import type { TimelineAnnotationSource } from \"@opengeni/sdk\";\nimport { QuoteIcon, XIcon } from \"lucide-react\";\nimport {\n Suspense,\n lazy,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n type ReactNode,\n type RefObject,\n} from \"react\";\nimport { cn } from \"../lib/cn\";\nimport {\n AnnotationAccentRow,\n AnnotationNotePreview,\n AnnotationQuoteSourceButton,\n type TimelineAnnotationLike,\n} from \"./timeline-annotation-chrome\";\nimport { ANNOTATION_CARD_STACK_SCROLL_AT } from \"./timeline-annotation-layout\";\nimport {\n TimelineAnnotationSourceRootContext,\n useTimelineAnnotationSourceRoot,\n} from \"./timeline-annotation-reveal-context\";\nimport {\n annotationDisplayOrdinal,\n annotationHasNote,\n resolveAnnotationRevealRoot,\n} from \"./timeline-annotation-shared\";\n\nexport type { TimelineAnnotationLike } from \"./timeline-annotation-chrome\";\n\nconst TimelineAnnotationsDialog = lazy(() => import(\"./timeline-annotations-dialog\"));\n\nfunction AnnotationSourceRootBridge({\n triggerRef,\n children,\n}: {\n triggerRef: RefObject<HTMLElement | null>;\n children: ReactNode;\n}) {\n const inheritedRoot = useTimelineAnnotationSourceRoot();\n const conversationRootRef = useRef<HTMLElement | null>(null);\n if (!inheritedRoot) {\n conversationRootRef.current = resolveAnnotationRevealRoot(triggerRef.current);\n }\n useLayoutEffect(() => {\n if (inheritedRoot) return;\n conversationRootRef.current = resolveAnnotationRevealRoot(triggerRef.current);\n });\n if (inheritedRoot) return children;\n return (\n <TimelineAnnotationSourceRootContext.Provider value={conversationRootRef}>\n {children}\n </TimelineAnnotationSourceRootContext.Provider>\n );\n}\n\nexport function TimelineAnnotationCards({\n annotations,\n onRevealSource,\n className,\n}: {\n annotations: readonly TimelineAnnotationLike[];\n onRevealSource?: ((source: TimelineAnnotationSource) => boolean) | undefined;\n className?: string | undefined;\n}) {\n const [unavailableId, setUnavailableId] = useState<string | null>(null);\n if (annotations.length === 0) return null;\n return (\n <div\n data-og-annotation-cards=\"\"\n className={cn(\n \"grid gap-2\",\n annotations.length >= ANNOTATION_CARD_STACK_SCROLL_AT &&\n \"max-h-[min(28rem,55vh)] overflow-x-hidden overflow-y-auto overscroll-contain pr-1\",\n className,\n )}\n >\n {annotations.map((annotation, index) => {\n const ordinal = annotationDisplayOrdinal(annotation, index);\n return (\n <section key={annotation.id} aria-label={`Annotation ${ordinal}`}>\n <AnnotationAccentRow>\n <p className=\"mb-0.5 text-og-xs font-medium tabular-nums text-og-fg-subtle\">\n Annotation {ordinal}\n </p>\n <AnnotationQuoteSourceButton\n annotation={annotation}\n lines={2}\n onRevealSource={onRevealSource}\n onUnavailable={setUnavailableId}\n />\n {unavailableId === annotation.id ? (\n <p role=\"status\" className=\"text-og-xs text-og-status-waiting\">\n Source is outside the loaded timeline window.\n </p>\n ) : null}\n <AnnotationNotePreview\n note={annotation.note}\n annotationId={annotation.id}\n ordinal={ordinal}\n />\n </AnnotationAccentRow>\n </section>\n );\n })}\n </div>\n );\n}\n\nexport function TimelineAnnotationsChip({\n annotations,\n editable = false,\n focusAnnotationId,\n onFocusConsumed,\n onUpdate,\n onRemove,\n onRevealSource,\n className,\n}: {\n annotations: readonly TimelineAnnotationLike[];\n editable?: boolean | undefined;\n focusAnnotationId?: string | null | undefined;\n onFocusConsumed?: (() => void) | undefined;\n onUpdate?: ((id: string, note: string) => void) | undefined;\n onRemove?: ((id: string) => void) | undefined;\n onRevealSource?: ((source: TimelineAnnotationSource) => boolean) | undefined;\n className?: string | undefined;\n}) {\n const focusRequested = Boolean(\n focusAnnotationId && annotations.some((item) => item.id === focusAnnotationId),\n );\n const [open, setOpen] = useState(focusRequested);\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n const openedFocusId = useRef<string | null>(focusRequested ? (focusAnnotationId ?? null) : null);\n\n useEffect(() => {\n if (!focusRequested || !focusAnnotationId) {\n if (!focusRequested) openedFocusId.current = null;\n return;\n }\n if (openedFocusId.current === focusAnnotationId) return;\n openedFocusId.current = focusAnnotationId;\n setOpen(true);\n }, [focusAnnotationId, focusRequested]);\n\n if (annotations.length === 0) return null;\n const countLabel = `${annotations.length} ${annotations.length === 1 ? \"annotation\" : \"annotations\"}`;\n const incomplete = annotations.some((annotation) => !annotationHasNote(annotation.note));\n const canClear = Boolean(editable && onRemove);\n\n const dismiss = (restoreFocus: boolean) => {\n setOpen(false);\n onFocusConsumed?.();\n if (restoreFocus) triggerRef.current?.focus();\n };\n\n return (\n <span className={cn(\"inline-flex max-w-full items-center\", className)}>\n <span\n className={cn(\n \"inline-flex max-w-full items-center rounded-full border bg-og-surface-1 text-og-fg-muted\",\n incomplete ? \"border-og-accent/45 bg-og-accent-soft/50\" : \"border-og-border\",\n )}\n >\n <button\n ref={triggerRef}\n type=\"button\"\n className={cn(\n \"inline-flex min-h-8 min-w-0 max-w-full items-center gap-1.5 rounded-full border-0 bg-transparent py-1 text-og-sm font-medium outline-hidden transition hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:min-h-[44px]\",\n canClear ? \"pr-1 pl-2.5\" : \"px-2.5\",\n )}\n aria-label={`Review ${countLabel}`}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n onClick={() => setOpen((current) => !current)}\n >\n <QuoteIcon className=\"size-3.5 shrink-0 text-og-accent\" aria-hidden=\"true\" />\n <span className=\"min-w-0 truncate\">{countLabel}</span>\n </button>\n {canClear ? (\n <button\n type=\"button\"\n className=\"inline-flex size-7 shrink-0 items-center justify-center rounded-full border-0 bg-transparent text-og-fg-subtle outline-hidden hover:text-og-status-failed focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:size-11\"\n aria-label=\"Remove all annotations\"\n onClick={() => {\n for (const annotation of annotations) onRemove?.(annotation.id);\n }}\n >\n <XIcon className=\"size-3.5\" aria-hidden=\"true\" />\n </button>\n ) : null}\n </span>\n {open ? (\n <AnnotationSourceRootBridge triggerRef={triggerRef}>\n <Suspense fallback={null}>\n <TimelineAnnotationsDialog\n annotations={annotations}\n editable={editable}\n focusAnnotationId={focusAnnotationId}\n onFocusConsumed={onFocusConsumed}\n onUpdate={onUpdate}\n onRemove={onRemove}\n onRevealSource={onRevealSource}\n triggerRef={triggerRef}\n countLabel={countLabel}\n onDismiss={dismiss}\n />\n </Suspense>\n </AnnotationSourceRootBridge>\n ) : null}\n </span>\n );\n}\n\nexport default TimelineAnnotationsChip;\n","import type { ComposerState } from \"../hooks/use-composer\";\n\n/**\n * Decide whether checking out a queued prompt would replace current composer\n * content. `hasDraftContent` reads the synchronous draft refs, so this stays\n * correct even when a controlled input update has not rendered yet.\n */\nexport function requestQueueDraftEdit(\n composer: Pick<ComposerState, \"hasDraftContent\">,\n confirmReplacement: () => void,\n editImmediately: () => void,\n): void {\n if (composer.hasDraftContent()) {\n confirmReplacement();\n } else {\n editImmediately();\n }\n}\n","import { Loader2Icon, RotateCwIcon } from \"lucide-react\";\n\nimport type { UseTurnQueueResult } from \"../hooks/use-turn-queue\";\n\ntype QueueStateProps = {\n queue: UseTurnQueueResult;\n};\n\nexport function EmptyQueueStateSurface({ queue }: QueueStateProps) {\n const hasError = Boolean(queue.error || queue.mutationError);\n return (\n <div\n className=\"og-root mx-auto mb-2 w-full max-w-3xl shrink-0 px-4 sm:px-6\"\n data-testid=\"queue-surface\"\n >\n <div className=\"overflow-hidden rounded-lg border border-border bg-surface/80 shadow-sm\">\n {queue.stoppingPreviousAttempt ? (\n <QueueStoppingStatus queue={queue} dividerAfter={hasError} />\n ) : null}\n {hasError ? <QueueErrorAlert queue={queue} /> : null}\n </div>\n </div>\n );\n}\n\nexport function QueueStoppingStatus({\n queue,\n dividerAfter = false,\n}: QueueStateProps & { dividerAfter?: boolean }) {\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className={`flex min-h-11 items-center gap-2.5 bg-status-waiting/[0.07] px-3 py-2 text-og-control text-fg ${\n dividerAfter ? \"border-b border-status-waiting/20\" : \"\"\n }`}\n data-testid=\"stopping-previous-attempt\"\n >\n <Loader2Icon\n aria-hidden=\"true\"\n className=\"size-3.5 shrink-0 animate-spin text-status-waiting motion-reduce:animate-none\"\n />\n <span className=\"min-w-0\">\n <span className=\"font-medium\">\n {queue.effectiveControl?.state === \"paused\"\n ? \"Stopping current attempt…\"\n : \"Stopping previous attempt…\"}\n </span>{\" \"}\n <span className=\"text-fg-muted\">\n {queue.effectiveControl?.state === \"paused\"\n ? \"Queued work stays saved until you resume.\"\n : \"Queued work is saved and starts automatically.\"}\n </span>\n </span>\n </div>\n );\n}\n\nexport function QueueErrorAlert({\n queue,\n dividerBefore = false,\n}: QueueStateProps & { dividerBefore?: boolean }) {\n return (\n <div className={`${dividerBefore ? \"border-t border-border\" : \"\"} p-2`}>\n <div\n role=\"alert\"\n className=\"flex min-w-0 max-w-full flex-wrap items-start gap-2 rounded-md bg-status-failed/10 px-2 py-1.5 text-og-control text-status-failed\"\n >\n <span\n role=\"region\"\n aria-label=\"Queue error details\"\n tabIndex={0}\n className=\"max-h-[min(5rem,20dvh)] min-w-0 max-w-full flex-[1_1_5rem] overflow-auto overscroll-contain whitespace-pre-wrap rounded-sm outline-hidden [overflow-wrap:anywhere] [unicode-bidi:plaintext] focus-visible:ring-2 focus-visible:ring-ring/40\"\n data-testid=\"queue-error-message\"\n dir=\"auto\"\n >\n {(queue.mutationError ?? queue.error)?.message}\n </span>\n <button\n type=\"button\"\n onClick={() => {\n queue.clearMutationError();\n void queue.refresh();\n }}\n aria-label=\"Dismiss queue error and retry\"\n title=\"Retry loading the queue\"\n className=\"ms-auto inline-flex size-7 shrink-0 items-center justify-center self-start rounded-md outline-hidden transition-colors hover:bg-surface-3 focus-visible:ring-2 focus-visible:ring-ring/40 motion-reduce:transition-none pointer-coarse:size-[44px]\"\n >\n <RotateCwIcon className=\"size-3.5\" />\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["jsx","jsxs"]}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// src/components/timeline-annotation-layout.ts
|
|
2
|
+
var ANNOTATION_BADGE_GAP_PX = 32;
|
|
3
|
+
var ANNOTATION_BADGE_MIN_GAP_PX = 12;
|
|
4
|
+
var ANNOTATION_REVIEW_DIALOG_WIDTH_PX = 400;
|
|
5
|
+
var ANNOTATION_REVIEW_DIALOG_MAX_HEIGHT_PX = 32 * 16;
|
|
6
|
+
var ANNOTATION_REVIEW_DIALOG_MIN_HEIGHT_PX = 160;
|
|
7
|
+
var ANNOTATION_REVIEW_DIALOG_FLOOR_HEIGHT_PX = 80;
|
|
8
|
+
var ANNOTATION_REVIEW_MARGIN_PX = 12;
|
|
9
|
+
var ANNOTATION_NOTE_PREVIEW_CHARS = 280;
|
|
10
|
+
var ANNOTATION_CARD_STACK_SCROLL_AT = 4;
|
|
11
|
+
function clampAnnotationAxis(value, min, max) {
|
|
12
|
+
return Math.min(Math.max(value, min), Math.max(min, max));
|
|
13
|
+
}
|
|
14
|
+
function annotationBoxIntersects(a, b) {
|
|
15
|
+
return a.left <= b.right && a.right >= b.left && a.top <= b.bottom && a.bottom >= b.top;
|
|
16
|
+
}
|
|
17
|
+
function annotationViewportBox(viewportWidth, viewportHeight, scroller) {
|
|
18
|
+
const viewport = {
|
|
19
|
+
left: 0,
|
|
20
|
+
top: 0,
|
|
21
|
+
right: Math.max(1, viewportWidth),
|
|
22
|
+
bottom: Math.max(1, viewportHeight)
|
|
23
|
+
};
|
|
24
|
+
if (!scroller || scroller.right - scroller.left <= 0 || scroller.bottom - scroller.top <= 0) {
|
|
25
|
+
return viewport;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
left: Math.max(viewport.left, scroller.left),
|
|
29
|
+
top: Math.max(viewport.top, scroller.top),
|
|
30
|
+
right: Math.min(viewport.right, scroller.right),
|
|
31
|
+
bottom: Math.min(viewport.bottom, scroller.bottom)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function annotationNoteNeedsDisclosure(note) {
|
|
35
|
+
const trimmed = note.trim();
|
|
36
|
+
if (trimmed.length === 0) return false;
|
|
37
|
+
return trimmed.length > ANNOTATION_NOTE_PREVIEW_CHARS || trimmed.split("\n").length > 4;
|
|
38
|
+
}
|
|
39
|
+
function annotationAxisStops(min, max, gap) {
|
|
40
|
+
if (max <= min) return [min];
|
|
41
|
+
const stops = [];
|
|
42
|
+
for (let value = min; value <= max + 1e-6; value += gap) {
|
|
43
|
+
stops.push(value);
|
|
44
|
+
}
|
|
45
|
+
return stops;
|
|
46
|
+
}
|
|
47
|
+
function annotationPointKey(left, top) {
|
|
48
|
+
return `${Math.round(left * 100) / 100},${Math.round(top * 100) / 100}`;
|
|
49
|
+
}
|
|
50
|
+
function annotationPointFree(left, top, placed, gap) {
|
|
51
|
+
return placed.every((other) => Math.hypot(left - other.left, top - other.top) >= gap);
|
|
52
|
+
}
|
|
53
|
+
function annotationBadgePackGap(viewport, count, preferred = ANNOTATION_BADGE_GAP_PX) {
|
|
54
|
+
if (count <= 1) return preferred;
|
|
55
|
+
const width = Math.max(
|
|
56
|
+
1,
|
|
57
|
+
viewport.right - ANNOTATION_REVIEW_MARGIN_PX - (viewport.left + ANNOTATION_REVIEW_MARGIN_PX)
|
|
58
|
+
);
|
|
59
|
+
const height = Math.max(1, viewport.bottom - ANNOTATION_REVIEW_MARGIN_PX - (viewport.top + 8));
|
|
60
|
+
let gap = preferred;
|
|
61
|
+
const capacity = (step) => (Math.floor(width / step) + 1) * (Math.floor(height / step) + 1);
|
|
62
|
+
while (gap > ANNOTATION_BADGE_MIN_GAP_PX && capacity(gap) < count) {
|
|
63
|
+
gap -= 1;
|
|
64
|
+
}
|
|
65
|
+
return gap;
|
|
66
|
+
}
|
|
67
|
+
var ANNOTATION_BADGE_SHIFT_DIRS = [
|
|
68
|
+
[1, 0],
|
|
69
|
+
[1, 1],
|
|
70
|
+
[0, 1],
|
|
71
|
+
[-1, 1],
|
|
72
|
+
[-1, 0],
|
|
73
|
+
[-1, -1],
|
|
74
|
+
[0, -1],
|
|
75
|
+
[1, -1]
|
|
76
|
+
];
|
|
77
|
+
function layoutAnnotationBadges(anchors, viewport, preferredGap = ANNOTATION_BADGE_GAP_PX) {
|
|
78
|
+
const minLeft = viewport.left + ANNOTATION_REVIEW_MARGIN_PX;
|
|
79
|
+
const maxLeft = viewport.right - ANNOTATION_REVIEW_MARGIN_PX;
|
|
80
|
+
const minTop = viewport.top + 8;
|
|
81
|
+
const maxTop = viewport.bottom - ANNOTATION_REVIEW_MARGIN_PX;
|
|
82
|
+
const gap = annotationBadgePackGap(viewport, anchors.length, preferredGap);
|
|
83
|
+
const cells = annotationAxisStops(minLeft, maxLeft, gap).flatMap(
|
|
84
|
+
(left) => annotationAxisStops(minTop, maxTop, gap).map((top) => ({ left, top }))
|
|
85
|
+
);
|
|
86
|
+
const placed = [];
|
|
87
|
+
for (const anchor of [...anchors].sort((left, right) => left.ordinal - right.ordinal)) {
|
|
88
|
+
const originLeft = clampAnnotationAxis(anchor.left, minLeft, maxLeft);
|
|
89
|
+
const originTop = clampAnnotationAxis(anchor.top, minTop, maxTop);
|
|
90
|
+
const seen = /* @__PURE__ */ new Set();
|
|
91
|
+
const candidates = [];
|
|
92
|
+
const pushCandidate = (left, top) => {
|
|
93
|
+
if (left < minLeft || left > maxLeft || top < minTop || top > maxTop) return;
|
|
94
|
+
const key = annotationPointKey(left, top);
|
|
95
|
+
if (seen.has(key)) return;
|
|
96
|
+
seen.add(key);
|
|
97
|
+
candidates.push({ left, top });
|
|
98
|
+
};
|
|
99
|
+
pushCandidate(originLeft, originTop);
|
|
100
|
+
for (const [dx, dy] of ANNOTATION_BADGE_SHIFT_DIRS) {
|
|
101
|
+
const colliders = [...placed].sort(
|
|
102
|
+
(left, right) => right.left * dx + right.top * dy - (left.left * dx + left.top * dy)
|
|
103
|
+
);
|
|
104
|
+
for (const collider of colliders) {
|
|
105
|
+
pushCandidate(collider.left + dx * gap, collider.top + dy * gap);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const maxRadius = Math.ceil(Math.max(maxLeft - minLeft, maxTop - minTop) / Math.max(gap, 1)) + 1;
|
|
109
|
+
for (let radius = 1; radius <= maxRadius; radius++) {
|
|
110
|
+
const ring = [];
|
|
111
|
+
for (let dx = -radius; dx <= radius; dx++) {
|
|
112
|
+
for (let dy = -radius; dy <= radius; dy++) {
|
|
113
|
+
if (Math.max(Math.abs(dx), Math.abs(dy)) !== radius) continue;
|
|
114
|
+
ring.push([dx, dy]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
ring.sort((left, right) => {
|
|
118
|
+
const leftDir = (left[0] < 0 ? 2 : 0) + (left[1] < 0 ? 1 : 0);
|
|
119
|
+
const rightDir = (right[0] < 0 ? 2 : 0) + (right[1] < 0 ? 1 : 0);
|
|
120
|
+
if (leftDir !== rightDir) return leftDir - rightDir;
|
|
121
|
+
const hypot = Math.hypot(left[0], left[1]) - Math.hypot(right[0], right[1]);
|
|
122
|
+
if (hypot !== 0) return hypot;
|
|
123
|
+
if (left[0] !== right[0]) return right[0] - left[0];
|
|
124
|
+
return left[1] - right[1];
|
|
125
|
+
});
|
|
126
|
+
for (const [dx, dy] of ring) {
|
|
127
|
+
pushCandidate(originLeft + dx * gap, originTop + dy * gap);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const cell of cells) {
|
|
131
|
+
pushCandidate(cell.left, cell.top);
|
|
132
|
+
}
|
|
133
|
+
const free = candidates.find(
|
|
134
|
+
(point) => annotationPointFree(point.left, point.top, placed, gap)
|
|
135
|
+
);
|
|
136
|
+
let chosen = free ?? candidates[0];
|
|
137
|
+
if (!free && placed.length > 0) {
|
|
138
|
+
let bestMin = Number.NEGATIVE_INFINITY;
|
|
139
|
+
for (const point of candidates) {
|
|
140
|
+
const minDist = placed.reduce(
|
|
141
|
+
(current, other) => Math.min(current, Math.hypot(point.left - other.left, point.top - other.top)),
|
|
142
|
+
Number.POSITIVE_INFINITY
|
|
143
|
+
);
|
|
144
|
+
if (minDist > bestMin) {
|
|
145
|
+
bestMin = minDist;
|
|
146
|
+
chosen = point;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
placed.push({ ...anchor, left: chosen.left, top: chosen.top });
|
|
151
|
+
}
|
|
152
|
+
return placed;
|
|
153
|
+
}
|
|
154
|
+
function scrollAnnotationRowIntoList(list, row) {
|
|
155
|
+
if (!list || !row || !list.contains(row)) return;
|
|
156
|
+
const listRect = list.getBoundingClientRect();
|
|
157
|
+
const rowRect = row.getBoundingClientRect();
|
|
158
|
+
if (rowRect.top < listRect.top) {
|
|
159
|
+
list.scrollTop -= listRect.top - rowRect.top;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (rowRect.bottom > listRect.bottom) {
|
|
163
|
+
list.scrollTop += rowRect.bottom - listRect.bottom;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function clampAnnotationDialogPlacement(input) {
|
|
167
|
+
const margin = ANNOTATION_REVIEW_MARGIN_PX;
|
|
168
|
+
const panelWidth = Math.min(
|
|
169
|
+
input.panelWidth ?? ANNOTATION_REVIEW_DIALOG_WIDTH_PX,
|
|
170
|
+
Math.max(1, input.viewportWidth - margin * 2)
|
|
171
|
+
);
|
|
172
|
+
const left = Math.min(
|
|
173
|
+
Math.max(margin, input.triggerLeft),
|
|
174
|
+
Math.max(margin, input.viewportWidth - panelWidth - margin)
|
|
175
|
+
);
|
|
176
|
+
const spaceBelow = input.viewportHeight - input.triggerBottom - margin;
|
|
177
|
+
const spaceAbove = input.triggerTop - margin;
|
|
178
|
+
const above = spaceBelow < Math.min(input.contentHeight, 200) && spaceAbove > spaceBelow;
|
|
179
|
+
const viewportBudget = Math.max(
|
|
180
|
+
ANNOTATION_REVIEW_DIALOG_FLOOR_HEIGHT_PX,
|
|
181
|
+
input.viewportHeight - margin * 2
|
|
182
|
+
);
|
|
183
|
+
const available = Math.max(
|
|
184
|
+
Math.min(ANNOTATION_REVIEW_DIALOG_MIN_HEIGHT_PX, viewportBudget),
|
|
185
|
+
above ? spaceAbove : spaceBelow
|
|
186
|
+
);
|
|
187
|
+
let maxHeight = Math.min(
|
|
188
|
+
Math.max(input.contentHeight, Math.min(ANNOTATION_REVIEW_DIALOG_MIN_HEIGHT_PX, viewportBudget)),
|
|
189
|
+
available,
|
|
190
|
+
Math.floor(input.viewportHeight * 0.7) || viewportBudget,
|
|
191
|
+
ANNOTATION_REVIEW_DIALOG_MAX_HEIGHT_PX,
|
|
192
|
+
viewportBudget
|
|
193
|
+
);
|
|
194
|
+
let top = above ? Math.max(margin, input.triggerTop - 8 - maxHeight) : Math.min(
|
|
195
|
+
input.triggerBottom + 8,
|
|
196
|
+
Math.max(margin, input.viewportHeight - margin - maxHeight)
|
|
197
|
+
);
|
|
198
|
+
top = Math.max(margin, top);
|
|
199
|
+
maxHeight = Math.min(
|
|
200
|
+
maxHeight,
|
|
201
|
+
Math.max(ANNOTATION_REVIEW_DIALOG_FLOOR_HEIGHT_PX, input.viewportHeight - margin - top)
|
|
202
|
+
);
|
|
203
|
+
return { left, top, maxHeight, above };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export {
|
|
207
|
+
ANNOTATION_REVIEW_DIALOG_WIDTH_PX,
|
|
208
|
+
ANNOTATION_CARD_STACK_SCROLL_AT,
|
|
209
|
+
annotationBoxIntersects,
|
|
210
|
+
annotationViewportBox,
|
|
211
|
+
annotationNoteNeedsDisclosure,
|
|
212
|
+
layoutAnnotationBadges,
|
|
213
|
+
scrollAnnotationRowIntoList,
|
|
214
|
+
clampAnnotationDialogPlacement
|
|
215
|
+
};
|
|
216
|
+
//# sourceMappingURL=chunk-ZYRGEKWS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/timeline-annotation-layout.ts"],"sourcesContent":["export type AnnotationBox = {\n left: number;\n right: number;\n top: number;\n bottom: number;\n};\n\nexport type AnnotationBadgeAnchor = {\n id: string;\n ordinal: number;\n left: number;\n top: number;\n incomplete: boolean;\n};\n\nexport const ANNOTATION_BADGE_GAP_PX = 32;\nexport const ANNOTATION_BADGE_MIN_GAP_PX = 12;\nexport const ANNOTATION_REVIEW_DIALOG_WIDTH_PX = 400;\nexport const ANNOTATION_REVIEW_DIALOG_MAX_HEIGHT_PX = 32 * 16;\nexport const ANNOTATION_REVIEW_DIALOG_MIN_HEIGHT_PX = 160;\nexport const ANNOTATION_REVIEW_DIALOG_FLOOR_HEIGHT_PX = 80;\nexport const ANNOTATION_REVIEW_MARGIN_PX = 12;\nexport const ANNOTATION_NOTE_PREVIEW_CHARS = 280;\nexport const ANNOTATION_CARD_STACK_SCROLL_AT = 4;\n\nfunction clampAnnotationAxis(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), Math.max(min, max));\n}\n\nexport function annotationBoxIntersects(a: AnnotationBox, b: AnnotationBox): boolean {\n return a.left <= b.right && a.right >= b.left && a.top <= b.bottom && a.bottom >= b.top;\n}\n\nexport function annotationViewportBox(\n viewportWidth: number,\n viewportHeight: number,\n scroller?: AnnotationBox | null,\n): AnnotationBox {\n const viewport: AnnotationBox = {\n left: 0,\n top: 0,\n right: Math.max(1, viewportWidth),\n bottom: Math.max(1, viewportHeight),\n };\n if (!scroller || scroller.right - scroller.left <= 0 || scroller.bottom - scroller.top <= 0) {\n return viewport;\n }\n return {\n left: Math.max(viewport.left, scroller.left),\n top: Math.max(viewport.top, scroller.top),\n right: Math.min(viewport.right, scroller.right),\n bottom: Math.min(viewport.bottom, scroller.bottom),\n };\n}\n\nexport function annotationNoteNeedsDisclosure(note: string): boolean {\n const trimmed = note.trim();\n if (trimmed.length === 0) return false;\n return trimmed.length > ANNOTATION_NOTE_PREVIEW_CHARS || trimmed.split(\"\\n\").length > 4;\n}\n\nfunction annotationAxisStops(min: number, max: number, gap: number): number[] {\n if (max <= min) return [min];\n const stops: number[] = [];\n for (let value = min; value <= max + 1e-6; value += gap) {\n stops.push(value);\n }\n return stops;\n}\n\nfunction annotationPointKey(left: number, top: number): string {\n return `${Math.round(left * 100) / 100},${Math.round(top * 100) / 100}`;\n}\n\nfunction annotationPointFree(\n left: number,\n top: number,\n placed: readonly AnnotationBadgeAnchor[],\n gap: number,\n): boolean {\n return placed.every((other) => Math.hypot(left - other.left, top - other.top) >= gap);\n}\n\nexport function annotationBadgePackGap(\n viewport: AnnotationBox,\n count: number,\n preferred = ANNOTATION_BADGE_GAP_PX,\n): number {\n if (count <= 1) return preferred;\n const width = Math.max(\n 1,\n viewport.right - ANNOTATION_REVIEW_MARGIN_PX - (viewport.left + ANNOTATION_REVIEW_MARGIN_PX),\n );\n const height = Math.max(1, viewport.bottom - ANNOTATION_REVIEW_MARGIN_PX - (viewport.top + 8));\n let gap = preferred;\n const capacity = (step: number) =>\n (Math.floor(width / step) + 1) * (Math.floor(height / step) + 1);\n while (gap > ANNOTATION_BADGE_MIN_GAP_PX && capacity(gap) < count) {\n gap -= 1;\n }\n return gap;\n}\n\nconst ANNOTATION_BADGE_SHIFT_DIRS: Array<[number, number]> = [\n [1, 0],\n [1, 1],\n [0, 1],\n [-1, 1],\n [-1, 0],\n [-1, -1],\n [0, -1],\n [1, -1],\n];\n\nexport function layoutAnnotationBadges(\n anchors: readonly AnnotationBadgeAnchor[],\n viewport: AnnotationBox,\n preferredGap = ANNOTATION_BADGE_GAP_PX,\n): AnnotationBadgeAnchor[] {\n const minLeft = viewport.left + ANNOTATION_REVIEW_MARGIN_PX;\n const maxLeft = viewport.right - ANNOTATION_REVIEW_MARGIN_PX;\n const minTop = viewport.top + 8;\n const maxTop = viewport.bottom - ANNOTATION_REVIEW_MARGIN_PX;\n const gap = annotationBadgePackGap(viewport, anchors.length, preferredGap);\n const cells = annotationAxisStops(minLeft, maxLeft, gap).flatMap((left) =>\n annotationAxisStops(minTop, maxTop, gap).map((top) => ({ left, top })),\n );\n const placed: AnnotationBadgeAnchor[] = [];\n for (const anchor of [...anchors].sort((left, right) => left.ordinal - right.ordinal)) {\n const originLeft = clampAnnotationAxis(anchor.left, minLeft, maxLeft);\n const originTop = clampAnnotationAxis(anchor.top, minTop, maxTop);\n const seen = new Set<string>();\n const candidates: Array<{ left: number; top: number }> = [];\n const pushCandidate = (left: number, top: number) => {\n if (left < minLeft || left > maxLeft || top < minTop || top > maxTop) return;\n const key = annotationPointKey(left, top);\n if (seen.has(key)) return;\n seen.add(key);\n candidates.push({ left, top });\n };\n pushCandidate(originLeft, originTop);\n for (const [dx, dy] of ANNOTATION_BADGE_SHIFT_DIRS) {\n const colliders = [...placed].sort(\n (left, right) => right.left * dx + right.top * dy - (left.left * dx + left.top * dy),\n );\n for (const collider of colliders) {\n pushCandidate(collider.left + dx * gap, collider.top + dy * gap);\n }\n }\n const maxRadius =\n Math.ceil(Math.max(maxLeft - minLeft, maxTop - minTop) / Math.max(gap, 1)) + 1;\n for (let radius = 1; radius <= maxRadius; radius++) {\n const ring: Array<[number, number]> = [];\n for (let dx = -radius; dx <= radius; dx++) {\n for (let dy = -radius; dy <= radius; dy++) {\n if (Math.max(Math.abs(dx), Math.abs(dy)) !== radius) continue;\n ring.push([dx, dy]);\n }\n }\n ring.sort((left, right) => {\n const leftDir = (left[0] < 0 ? 2 : 0) + (left[1] < 0 ? 1 : 0);\n const rightDir = (right[0] < 0 ? 2 : 0) + (right[1] < 0 ? 1 : 0);\n if (leftDir !== rightDir) return leftDir - rightDir;\n const hypot = Math.hypot(left[0], left[1]) - Math.hypot(right[0], right[1]);\n if (hypot !== 0) return hypot;\n if (left[0] !== right[0]) return right[0] - left[0];\n return left[1] - right[1];\n });\n for (const [dx, dy] of ring) {\n pushCandidate(originLeft + dx * gap, originTop + dy * gap);\n }\n }\n for (const cell of cells) {\n pushCandidate(cell.left, cell.top);\n }\n const free = candidates.find((point) =>\n annotationPointFree(point.left, point.top, placed, gap),\n );\n let chosen = free ?? candidates[0]!;\n if (!free && placed.length > 0) {\n let bestMin = Number.NEGATIVE_INFINITY;\n for (const point of candidates) {\n const minDist = placed.reduce(\n (current, other) =>\n Math.min(current, Math.hypot(point.left - other.left, point.top - other.top)),\n Number.POSITIVE_INFINITY,\n );\n if (minDist > bestMin) {\n bestMin = minDist;\n chosen = point;\n }\n }\n }\n placed.push({ ...anchor, left: chosen.left, top: chosen.top });\n }\n return placed;\n}\n\nexport function scrollAnnotationRowIntoList(\n list: HTMLElement | null | undefined,\n row: HTMLElement | null | undefined,\n): void {\n if (!list || !row || !list.contains(row)) return;\n const listRect = list.getBoundingClientRect();\n const rowRect = row.getBoundingClientRect();\n if (rowRect.top < listRect.top) {\n list.scrollTop -= listRect.top - rowRect.top;\n return;\n }\n if (rowRect.bottom > listRect.bottom) {\n list.scrollTop += rowRect.bottom - listRect.bottom;\n }\n}\n\nexport function clampAnnotationDialogPlacement(input: {\n triggerLeft: number;\n triggerTop: number;\n triggerBottom: number;\n contentHeight: number;\n viewportWidth: number;\n viewportHeight: number;\n panelWidth?: number;\n}): {\n left: number;\n top: number;\n maxHeight: number;\n above: boolean;\n} {\n const margin = ANNOTATION_REVIEW_MARGIN_PX;\n const panelWidth = Math.min(\n input.panelWidth ?? ANNOTATION_REVIEW_DIALOG_WIDTH_PX,\n Math.max(1, input.viewportWidth - margin * 2),\n );\n const left = Math.min(\n Math.max(margin, input.triggerLeft),\n Math.max(margin, input.viewportWidth - panelWidth - margin),\n );\n const spaceBelow = input.viewportHeight - input.triggerBottom - margin;\n const spaceAbove = input.triggerTop - margin;\n const above = spaceBelow < Math.min(input.contentHeight, 200) && spaceAbove > spaceBelow;\n const viewportBudget = Math.max(\n ANNOTATION_REVIEW_DIALOG_FLOOR_HEIGHT_PX,\n input.viewportHeight - margin * 2,\n );\n const available = Math.max(\n Math.min(ANNOTATION_REVIEW_DIALOG_MIN_HEIGHT_PX, viewportBudget),\n above ? spaceAbove : spaceBelow,\n );\n let maxHeight = Math.min(\n Math.max(input.contentHeight, Math.min(ANNOTATION_REVIEW_DIALOG_MIN_HEIGHT_PX, viewportBudget)),\n available,\n Math.floor(input.viewportHeight * 0.7) || viewportBudget,\n ANNOTATION_REVIEW_DIALOG_MAX_HEIGHT_PX,\n viewportBudget,\n );\n let top = above\n ? Math.max(margin, input.triggerTop - 8 - maxHeight)\n : Math.min(\n input.triggerBottom + 8,\n Math.max(margin, input.viewportHeight - margin - maxHeight),\n );\n top = Math.max(margin, top);\n maxHeight = Math.min(\n maxHeight,\n Math.max(ANNOTATION_REVIEW_DIALOG_FLOOR_HEIGHT_PX, input.viewportHeight - margin - top),\n );\n return { left, top, maxHeight, above };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -9,7 +9,7 @@ import type { PickerModelRow } from "../model-policy.js";
|
|
|
9
9
|
import { OPEN_WORKSTREAM_CONTROL_EVENT } from "../workstream-control-event.js";
|
|
10
10
|
export { OPEN_WORKSTREAM_CONTROL_EVENT };
|
|
11
11
|
export type { ResponsiveBasis } from "../lib/composer-responsive-context.js";
|
|
12
|
-
export type ComposerDelivery = Pick<ComposerState, "value" | "setValue" | "send" | "steer" | "sending" | "canSend" | "error" | "clearError">;
|
|
12
|
+
export type ComposerDelivery = Pick<ComposerState, "value" | "setValue" | "send" | "steer" | "sending" | "canSend" | "error" | "clearError" | "annotations" | "requestAnnotationReview">;
|
|
13
13
|
export type ComposerDraftState = Pick<ComposerState, "draftConflict" | "draftSaving" | "resolveDraftConflict" | "restoredResources" | "removeRestoredResource">;
|
|
14
14
|
export type ComposerControlState = Pick<ComposerState, "pause" | "pausing" | "resume" | "resumeScope" | "resuming">;
|
|
15
15
|
export type ComposerControlLinks = {
|
|
@@ -32,6 +32,7 @@ export type ChatComposerMessages = {
|
|
|
32
32
|
sendAndResumeAriaLabel: string;
|
|
33
33
|
sendTitle: string;
|
|
34
34
|
sendAndResumeTitle: string;
|
|
35
|
+
annotationNotesRequired: string;
|
|
35
36
|
workspacePaused: string;
|
|
36
37
|
pausedHere: string;
|
|
37
38
|
parentBlocker: string;
|
|
@@ -82,7 +83,7 @@ export type ChatComposerMessages = {
|
|
|
82
83
|
};
|
|
83
84
|
export declare const defaultChatComposerMessages: ChatComposerMessages;
|
|
84
85
|
export type ComposerSubmitMode = "queue" | "steer";
|
|
85
|
-
export type ComposerSubmitBlocker = "disabled" | "attachment" | "sending" | "command" | "empty" | null;
|
|
86
|
+
export type ComposerSubmitBlocker = "disabled" | "attachment" | "sending" | "command" | "annotations" | "empty" | null;
|
|
86
87
|
export type UseChatComposerControllerOptions = {
|
|
87
88
|
delivery: ComposerDelivery;
|
|
88
89
|
draft?: ComposerDraftState | undefined;
|
|
@@ -141,6 +142,7 @@ export declare function useChatComposerController({ delivery, draft, control, ef
|
|
|
141
142
|
sendAndResumeAriaLabel: string;
|
|
142
143
|
sendTitle: string;
|
|
143
144
|
sendAndResumeTitle: string;
|
|
145
|
+
annotationNotesRequired: string;
|
|
144
146
|
workspacePaused: string;
|
|
145
147
|
pausedHere: string;
|
|
146
148
|
parentBlocker: string;
|
|
@@ -9,6 +9,8 @@ export type MessageTimelineProps = {
|
|
|
9
9
|
items?: TimelineItem[] | undefined;
|
|
10
10
|
/** Current session status (reserved; tip "Working…" chrome removed for now). */
|
|
11
11
|
status?: SessionStatus | null | undefined;
|
|
12
|
+
/** Host-owned controls beside Copy and the timestamp on settled message rows. */
|
|
13
|
+
renderMessageActions?: ((item: AgentMessageItem | UserMessageItem) => ReactNode) | undefined;
|
|
12
14
|
/** Plug a markdown renderer for message bodies (e.g. streamdown). */
|
|
13
15
|
renderMessageText?: ((text: string, item: AgentMessageItem | UserMessageItem) => ReactNode) | undefined;
|
|
14
16
|
/** Drill into a spawned worker session. */
|
|
@@ -67,6 +69,10 @@ export type MessageTimelineProps = {
|
|
|
67
69
|
autoFollow?: boolean | undefined;
|
|
68
70
|
/** Capture a same-row text selection into the host's canonical composer draft. */
|
|
69
71
|
onAnnotate?: ((annotation: DraftTimelineAnnotation) => void) | undefined;
|
|
72
|
+
/** Composer draft quotes currently attached to the next send. */
|
|
73
|
+
draftAnnotations?: readonly DraftTimelineAnnotation[] | undefined;
|
|
74
|
+
/** Open the composer review list for one numbered draft badge. */
|
|
75
|
+
onDraftAnnotationSelect?: ((id: string) => void) | undefined;
|
|
70
76
|
/** Older durable history exists above the current window (see useSessionEvents). */
|
|
71
77
|
hasOlder?: boolean | undefined;
|
|
72
78
|
/** An older window is being fetched; shows the quiet top shimmer. */
|
|
@@ -103,7 +109,7 @@ export type MessageTimelineProps = {
|
|
|
103
109
|
* cards, goal markers, and status transitions. Owns stick-to-bottom scrolling
|
|
104
110
|
* with a "jump to latest" affordance when the reader scrolls back.
|
|
105
111
|
*/
|
|
106
|
-
export declare function MessageTimeline({ events, items, status: _status, renderMessageText, onOpenSession, onMemoryClick, onReconnect, shouldRenderAuthNeeded, resolveProviderLogo, toolRegistry, loadRetainedScreenshot, loadRetainedArtifact, loadVideoArtifactPlayback, computeLabel, turnSummary, autoFollow, onAnnotate, hasOlder, loadingOlder, onLoadOlder, onJumpToStart, loadingOldest, hasNewer, loadingNewer, onLoadNewer, onJumpToLatest, trailingState, emptyState, className, }: MessageTimelineProps): import("react").JSX.Element;
|
|
112
|
+
export declare function MessageTimeline({ events, items, status: _status, renderMessageActions, renderMessageText, onOpenSession, onMemoryClick, onReconnect, shouldRenderAuthNeeded, resolveProviderLogo, toolRegistry, loadRetainedScreenshot, loadRetainedArtifact, loadVideoArtifactPlayback, computeLabel, turnSummary, autoFollow, onAnnotate, draftAnnotations, onDraftAnnotationSelect, hasOlder, loadingOlder, onLoadOlder, onJumpToStart, loadingOldest, hasNewer, loadingNewer, onLoadNewer, onJumpToLatest, trailingState, emptyState, className, }: MessageTimelineProps): import("react").JSX.Element;
|
|
107
113
|
/**
|
|
108
114
|
* Settled turns lift the final agent answer out as a sibling group. Include it
|
|
109
115
|
* in "Copy turn" when present so the chip copies the full assistant reply.
|
|
@@ -115,8 +121,9 @@ export declare function trailingAgentTextAfterTurn(group: TimelineGroup, next: T
|
|
|
115
121
|
* landmark, notice). Exported so the component demo draws the EXACT same rows as
|
|
116
122
|
* the live app — no forked bubble/goal markup.
|
|
117
123
|
*/
|
|
118
|
-
export declare function TimelineRow({ item, renderMessageText, onReconnect, resolveProviderLogo, onOpenSession, loadVideoArtifactPlayback, }: {
|
|
124
|
+
export declare function TimelineRow({ item, renderMessageActions, renderMessageText, onReconnect, resolveProviderLogo, onOpenSession, loadVideoArtifactPlayback, }: {
|
|
119
125
|
item: TimelineItem;
|
|
126
|
+
renderMessageActions?: ((item: AgentMessageItem | UserMessageItem) => ReactNode) | undefined;
|
|
120
127
|
renderMessageText?: ((text: string, item: AgentMessageItem | UserMessageItem) => ReactNode) | undefined;
|
|
121
128
|
onReconnect?: ((item: AuthNeededItem) => void | Promise<void>) | undefined;
|
|
122
129
|
resolveProviderLogo?: ((providerDomain: string) => string | null | undefined) | undefined;
|