reflet-sdk 0.6.8 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/{devtools-layer-DyoBn6ys.js → devtools-layer-C3rMV03Q.js} +3 -3
- package/dist/{devtools-layer-DyoBn6ys.js.map → devtools-layer-C3rMV03Q.js.map} +1 -1
- package/dist/devtools-next.js +1 -1
- package/dist/devtools-vite.js +2 -2
- package/dist/feedback.js +4 -4
- package/dist/{handler-DREUPKLQ.js → handler-B4Z6jkYX.js} +3 -3
- package/dist/{handler-DREUPKLQ.js.map → handler-B4Z6jkYX.js.map} +1 -1
- package/dist/protocol-cJVVwnPe.js +74 -0
- package/dist/protocol-cJVVwnPe.js.map +1 -0
- package/dist/{sessions-panel-Dq5j1qcu.js → sessions-panel-DPhFYhbk.js} +3555 -961
- package/dist/sessions-panel-DPhFYhbk.js.map +1 -0
- package/dist/{submit-CafYVnCb.js → submit-CahCBj1P.js} +2 -2
- package/dist/{submit-CafYVnCb.js.map → submit-CahCBj1P.js.map} +1 -1
- package/dist/{team-layer-BdcTaSSZ.js → team-layer-gMWcOdCd.js} +2 -3
- package/dist/{team-layer-BdcTaSSZ.js.map → team-layer-gMWcOdCd.js.map} +1 -1
- package/package.json +1 -1
- package/dist/protocol-Cg_cI4xS.js +0 -391
- package/dist/protocol-Cg_cI4xS.js.map +0 -1
- package/dist/sessions-panel-Dq5j1qcu.js.map +0 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { a as EDITORS,
|
|
2
|
-
import { B as clip, D as MoreIcon, G as describeElement, K as getElementRect, M as TargetIcon, R as DEFAULT_WIDGET_LABELS, T as GripIcon, W as buildSelector, Y as redactUrl, Z as inspectReactElement, _ as matchesHotkey, a as uploadBlob, c as useFloatingPosition, g as isSubmitEnter, it as releaseCapture, j as Svg, l as onViewportChange, m as startsInWidget, o as ElementPicker, p as listenToKeydown, q as REDACT_ATTRIBUTE, tt as captureViewport, u as visibleViewport, w as CloseIcon } from "./submit-
|
|
1
|
+
import { a as EDITORS, n as DEVTOOLS_REQUEST_HEADER, r as DEVTOOLS_ROUTE_BASE, t as DEVTOOLS_ENDPOINTS } from "./protocol-cJVVwnPe.js";
|
|
2
|
+
import { B as clip, D as MoreIcon, G as describeElement, K as getElementRect, M as TargetIcon, R as DEFAULT_WIDGET_LABELS, T as GripIcon, W as buildSelector, Y as redactUrl, Z as inspectReactElement, _ as matchesHotkey, a as uploadBlob, c as useFloatingPosition, g as isSubmitEnter, it as releaseCapture, j as Svg, l as onViewportChange, m as startsInWidget, o as ElementPicker, p as listenToKeydown, q as REDACT_ATTRIBUTE, tt as captureViewport, u as visibleViewport, w as CloseIcon } from "./submit-CahCBj1P.js";
|
|
3
3
|
import { t as REFLET_Z_INDEX } from "./z-index-CwokqVq_.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { createContext, useContext, useEffect, useId, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { flushSync } from "react-dom";
|
|
7
8
|
//#region src/devtools/client/notices.tsx
|
|
8
9
|
const STATUS_VISIBLE_MS = 2400;
|
|
9
10
|
const ACTION_VISIBLE_MS = 6e3;
|
|
@@ -82,10 +83,531 @@ function useRecorderSnapshot(recorder) {
|
|
|
82
83
|
return useSyncExternalStore(recorder.subscribe, recorder.getSnapshot, recorder.getSnapshot);
|
|
83
84
|
}
|
|
84
85
|
//#endregion
|
|
86
|
+
//#region src/sessions/intention-protocol.ts
|
|
87
|
+
const INTENTION_FIELDS = [
|
|
88
|
+
"need",
|
|
89
|
+
"observed",
|
|
90
|
+
"response",
|
|
91
|
+
"unknowns",
|
|
92
|
+
"setAside"
|
|
93
|
+
];
|
|
94
|
+
const INTENTION_CHANGE_KINDS = [
|
|
95
|
+
"precision",
|
|
96
|
+
"contradiction",
|
|
97
|
+
"constraint",
|
|
98
|
+
"solution",
|
|
99
|
+
"outcome"
|
|
100
|
+
];
|
|
101
|
+
const fieldValueSchema = z.union([z.string(), z.array(z.string())]);
|
|
102
|
+
const intentionChangeSchema = z.object({
|
|
103
|
+
decision: z.enum([
|
|
104
|
+
"pending",
|
|
105
|
+
"accepted",
|
|
106
|
+
"rejected"
|
|
107
|
+
]),
|
|
108
|
+
evidenceIds: z.array(z.string()),
|
|
109
|
+
field: z.enum(INTENTION_FIELDS),
|
|
110
|
+
hypothesis: z.boolean(),
|
|
111
|
+
id: z.string(),
|
|
112
|
+
kind: z.enum(INTENTION_CHANGE_KINDS),
|
|
113
|
+
operation: z.enum([
|
|
114
|
+
"replace",
|
|
115
|
+
"add",
|
|
116
|
+
"remove"
|
|
117
|
+
]),
|
|
118
|
+
reason: z.string(),
|
|
119
|
+
segmentIds: z.array(z.string()),
|
|
120
|
+
value: z.string()
|
|
121
|
+
});
|
|
122
|
+
const appliedChangeSchema = z.object({
|
|
123
|
+
at: z.number(),
|
|
124
|
+
byName: z.string().nullable(),
|
|
125
|
+
fields: z.array(z.object({
|
|
126
|
+
after: fieldValueSchema,
|
|
127
|
+
before: fieldValueSchema,
|
|
128
|
+
field: z.enum(INTENTION_FIELDS)
|
|
129
|
+
})),
|
|
130
|
+
revision: z.number()
|
|
131
|
+
});
|
|
132
|
+
const sessionContributionSchema = z.object({
|
|
133
|
+
answers: z.array(z.object({
|
|
134
|
+
at: z.number(),
|
|
135
|
+
text: z.string()
|
|
136
|
+
})),
|
|
137
|
+
applied: z.array(appliedChangeSchema),
|
|
138
|
+
baseRevision: z.number(),
|
|
139
|
+
changes: z.array(intentionChangeSchema),
|
|
140
|
+
detachedAt: z.number().nullable(),
|
|
141
|
+
id: z.string(),
|
|
142
|
+
intentionId: z.string(),
|
|
143
|
+
needsShareConsent: z.boolean(),
|
|
144
|
+
origin: z.enum(["continuation", "attach"]),
|
|
145
|
+
question: z.string().nullable(),
|
|
146
|
+
sharedAt: z.number().nullable(),
|
|
147
|
+
stale: z.boolean(),
|
|
148
|
+
state: z.enum([
|
|
149
|
+
"recording",
|
|
150
|
+
"analyzing",
|
|
151
|
+
"ready",
|
|
152
|
+
"no_change",
|
|
153
|
+
"failed"
|
|
154
|
+
]),
|
|
155
|
+
verdict: z.object({
|
|
156
|
+
reason: z.string(),
|
|
157
|
+
segmentIds: z.array(z.string()),
|
|
158
|
+
status: z.enum([
|
|
159
|
+
"resolved",
|
|
160
|
+
"needs_work",
|
|
161
|
+
"insufficient"
|
|
162
|
+
])
|
|
163
|
+
}).nullable()
|
|
164
|
+
});
|
|
165
|
+
const continuedIntentionSchema = z.object({
|
|
166
|
+
id: z.string(),
|
|
167
|
+
need: z.string(),
|
|
168
|
+
observed: z.string(),
|
|
169
|
+
question: z.string().nullable(),
|
|
170
|
+
response: z.string().nullable(),
|
|
171
|
+
revision: z.number(),
|
|
172
|
+
sessionId: z.string(),
|
|
173
|
+
sessionTitle: z.string(),
|
|
174
|
+
setAside: z.array(z.string()),
|
|
175
|
+
unknowns: z.array(z.string()),
|
|
176
|
+
verification: z.object({
|
|
177
|
+
at: z.number(),
|
|
178
|
+
sessionId: z.string().nullable(),
|
|
179
|
+
status: z.enum(["works", "needs_work"])
|
|
180
|
+
}).nullable()
|
|
181
|
+
});
|
|
182
|
+
const sessionContinuationSchema = z.object({
|
|
183
|
+
contribution: sessionContributionSchema,
|
|
184
|
+
intention: continuedIntentionSchema.nullable()
|
|
185
|
+
});
|
|
186
|
+
const contributionQuoteSchema = z.object({
|
|
187
|
+
context: z.array(z.string()),
|
|
188
|
+
segmentId: z.string(),
|
|
189
|
+
startMs: z.number(),
|
|
190
|
+
text: z.string()
|
|
191
|
+
});
|
|
192
|
+
const intentionHistoryEntrySchema = z.object({
|
|
193
|
+
applied: z.array(appliedChangeSchema),
|
|
194
|
+
detachedAt: z.number().nullable(),
|
|
195
|
+
evidenceIds: z.array(z.string()),
|
|
196
|
+
extractsHidden: z.boolean(),
|
|
197
|
+
id: z.string(),
|
|
198
|
+
intentionId: z.string(),
|
|
199
|
+
origin: z.enum(["continuation", "attach"]),
|
|
200
|
+
quotes: z.array(contributionQuoteSchema),
|
|
201
|
+
session: z.object({
|
|
202
|
+
canOpen: z.boolean(),
|
|
203
|
+
id: z.string(),
|
|
204
|
+
startedAt: z.number(),
|
|
205
|
+
title: z.string()
|
|
206
|
+
})
|
|
207
|
+
});
|
|
208
|
+
const intentionSuggestionSchema = z.object({
|
|
209
|
+
intentionId: z.string(),
|
|
210
|
+
need: z.string(),
|
|
211
|
+
needsShareConsent: z.boolean(),
|
|
212
|
+
reason: z.string(),
|
|
213
|
+
sessionId: z.string(),
|
|
214
|
+
sessionTitle: z.string()
|
|
215
|
+
});
|
|
216
|
+
const lastHandoffSchema = z.object({
|
|
217
|
+
at: z.number(),
|
|
218
|
+
need: z.string(),
|
|
219
|
+
response: z.string().nullable(),
|
|
220
|
+
revision: z.number(),
|
|
221
|
+
unknowns: z.array(z.string()).nullable()
|
|
222
|
+
});
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/sessions/protocol.ts
|
|
225
|
+
const SESSION_SURFACES = ["devtools", "app"];
|
|
226
|
+
const SESSION_GAP_REASONS = [
|
|
227
|
+
"paused",
|
|
228
|
+
"hidden",
|
|
229
|
+
"interrupted",
|
|
230
|
+
"limit"
|
|
231
|
+
];
|
|
232
|
+
const SESSION_LIMIT_KINDS = [
|
|
233
|
+
"duration",
|
|
234
|
+
"segments",
|
|
235
|
+
"events",
|
|
236
|
+
"evidence",
|
|
237
|
+
"bytes"
|
|
238
|
+
];
|
|
239
|
+
const PROPOSAL_DECISIONS = [
|
|
240
|
+
"pending",
|
|
241
|
+
"kept",
|
|
242
|
+
"later",
|
|
243
|
+
"dismissed"
|
|
244
|
+
];
|
|
245
|
+
const EVIDENCE_TRIGGERS = [
|
|
246
|
+
"start",
|
|
247
|
+
"mark",
|
|
248
|
+
"navigation",
|
|
249
|
+
"select",
|
|
250
|
+
"manual"
|
|
251
|
+
];
|
|
252
|
+
const sessionLimitsSchema = z.object({
|
|
253
|
+
audioRetentionMs: z.number(),
|
|
254
|
+
maxDurationMs: z.number(),
|
|
255
|
+
maxEvents: z.number(),
|
|
256
|
+
maxEventsPerBatch: z.number(),
|
|
257
|
+
maxEvidence: z.number(),
|
|
258
|
+
maxImageBytes: z.number(),
|
|
259
|
+
maxLocalBufferBytes: z.number(),
|
|
260
|
+
maxSegmentBytes: z.number(),
|
|
261
|
+
maxSegmentMs: z.number(),
|
|
262
|
+
maxSegments: z.number(),
|
|
263
|
+
minAutoCaptureIntervalMs: z.number(),
|
|
264
|
+
warnBeforeLimitMs: z.number()
|
|
265
|
+
});
|
|
266
|
+
const sessionIdentitySchema = z.object({
|
|
267
|
+
analysisConfigured: z.boolean(),
|
|
268
|
+
limits: sessionLimitsSchema,
|
|
269
|
+
organizationId: z.string(),
|
|
270
|
+
organizationName: z.string(),
|
|
271
|
+
transcriptionConfigured: z.boolean(),
|
|
272
|
+
user: z.object({
|
|
273
|
+
email: z.string().nullable(),
|
|
274
|
+
id: z.string(),
|
|
275
|
+
name: z.string().nullable()
|
|
276
|
+
})
|
|
277
|
+
});
|
|
278
|
+
const analysisStatusSchema = z.object({
|
|
279
|
+
analyzedRevision: z.number().nullable(),
|
|
280
|
+
error: z.string().nullable(),
|
|
281
|
+
proposalCount: z.number().nullable(),
|
|
282
|
+
state: z.enum([
|
|
283
|
+
"idle",
|
|
284
|
+
"waiting",
|
|
285
|
+
"running",
|
|
286
|
+
"succeeded",
|
|
287
|
+
"failed"
|
|
288
|
+
]),
|
|
289
|
+
transcriptRevision: z.number(),
|
|
290
|
+
updatedAt: z.number().nullable()
|
|
291
|
+
});
|
|
292
|
+
const sessionSummarySchema = z.object({
|
|
293
|
+
analysis: analysisStatusSchema,
|
|
294
|
+
authorName: z.string().nullable(),
|
|
295
|
+
collection: z.enum([
|
|
296
|
+
"recording",
|
|
297
|
+
"paused",
|
|
298
|
+
"ended"
|
|
299
|
+
]),
|
|
300
|
+
counts: z.object({
|
|
301
|
+
evidence: z.number(),
|
|
302
|
+
failedSegments: z.number(),
|
|
303
|
+
handoffs: z.number(),
|
|
304
|
+
kept: z.number(),
|
|
305
|
+
pendingSegments: z.number(),
|
|
306
|
+
proposals: z.number(),
|
|
307
|
+
segments: z.number()
|
|
308
|
+
}),
|
|
309
|
+
durationMs: z.number(),
|
|
310
|
+
endedAt: z.number().nullable(),
|
|
311
|
+
id: z.string(),
|
|
312
|
+
isMine: z.boolean(),
|
|
313
|
+
startedAt: z.number(),
|
|
314
|
+
surface: z.enum(SESSION_SURFACES),
|
|
315
|
+
title: z.string(),
|
|
316
|
+
visibility: z.enum(["private", "team"])
|
|
317
|
+
});
|
|
318
|
+
const rectSchema = z.object({
|
|
319
|
+
height: z.number(),
|
|
320
|
+
width: z.number(),
|
|
321
|
+
x: z.number(),
|
|
322
|
+
y: z.number()
|
|
323
|
+
});
|
|
324
|
+
const elementRefSchema = z.object({
|
|
325
|
+
component: z.string().nullable(),
|
|
326
|
+
label: z.string(),
|
|
327
|
+
rect: rectSchema,
|
|
328
|
+
role: z.string().nullable(),
|
|
329
|
+
selector: z.string(),
|
|
330
|
+
source: z.string().nullable()
|
|
331
|
+
});
|
|
332
|
+
const frameSchema = z.object({
|
|
333
|
+
devicePixelRatio: z.number(),
|
|
334
|
+
scrollX: z.number(),
|
|
335
|
+
scrollY: z.number(),
|
|
336
|
+
title: z.string().nullable(),
|
|
337
|
+
url: z.string(),
|
|
338
|
+
viewportHeight: z.number(),
|
|
339
|
+
viewportWidth: z.number()
|
|
340
|
+
});
|
|
341
|
+
const pointerSchema = z.object({
|
|
342
|
+
x: z.number(),
|
|
343
|
+
y: z.number()
|
|
344
|
+
});
|
|
345
|
+
z.discriminatedUnion("kind", [
|
|
346
|
+
z.object({
|
|
347
|
+
atMs: z.number(),
|
|
348
|
+
frame: frameSchema,
|
|
349
|
+
kind: z.literal("navigation")
|
|
350
|
+
}),
|
|
351
|
+
z.object({
|
|
352
|
+
atMs: z.number(),
|
|
353
|
+
element: elementRefSchema,
|
|
354
|
+
frame: frameSchema,
|
|
355
|
+
kind: z.literal("click"),
|
|
356
|
+
pointer: pointerSchema.nullable()
|
|
357
|
+
}),
|
|
358
|
+
z.object({
|
|
359
|
+
atMs: z.number(),
|
|
360
|
+
durationMs: z.number(),
|
|
361
|
+
element: elementRefSchema,
|
|
362
|
+
frame: frameSchema,
|
|
363
|
+
kind: z.literal("dwell"),
|
|
364
|
+
pointer: pointerSchema
|
|
365
|
+
}),
|
|
366
|
+
z.object({
|
|
367
|
+
atMs: z.number(),
|
|
368
|
+
kind: z.literal("scroll"),
|
|
369
|
+
scrollX: z.number(),
|
|
370
|
+
scrollY: z.number()
|
|
371
|
+
}),
|
|
372
|
+
z.object({
|
|
373
|
+
atMs: z.number(),
|
|
374
|
+
devicePixelRatio: z.number(),
|
|
375
|
+
kind: z.literal("viewport"),
|
|
376
|
+
viewportHeight: z.number(),
|
|
377
|
+
viewportWidth: z.number()
|
|
378
|
+
}),
|
|
379
|
+
z.object({
|
|
380
|
+
atMs: z.number(),
|
|
381
|
+
element: elementRefSchema.nullable(),
|
|
382
|
+
evidenceClientKey: z.string().nullable(),
|
|
383
|
+
frame: frameSchema,
|
|
384
|
+
kind: z.literal("mark"),
|
|
385
|
+
note: z.string().nullable()
|
|
386
|
+
}),
|
|
387
|
+
z.object({
|
|
388
|
+
atMs: z.number(),
|
|
389
|
+
element: elementRefSchema,
|
|
390
|
+
evidenceClientKey: z.string().nullable(),
|
|
391
|
+
frame: frameSchema,
|
|
392
|
+
kind: z.literal("select"),
|
|
393
|
+
note: z.string().nullable()
|
|
394
|
+
})
|
|
395
|
+
]);
|
|
396
|
+
const sessionGapSchema = z.object({
|
|
397
|
+
endMs: z.number().nullable(),
|
|
398
|
+
reason: z.enum(SESSION_GAP_REASONS),
|
|
399
|
+
startMs: z.number()
|
|
400
|
+
});
|
|
401
|
+
const segmentContextSchema = z.object({
|
|
402
|
+
atMs: z.number(),
|
|
403
|
+
durationMs: z.number().nullable(),
|
|
404
|
+
element: elementRefSchema.nullable(),
|
|
405
|
+
evidenceId: z.string().nullable(),
|
|
406
|
+
evidenceMatch: z.enum(["exact", "approximate"]).nullable(),
|
|
407
|
+
evidenceOffsetMs: z.number().nullable(),
|
|
408
|
+
frame: frameSchema.nullable(),
|
|
409
|
+
kind: z.enum([
|
|
410
|
+
"mark",
|
|
411
|
+
"select",
|
|
412
|
+
"click",
|
|
413
|
+
"dwell",
|
|
414
|
+
"navigation",
|
|
415
|
+
"screenshot"
|
|
416
|
+
]),
|
|
417
|
+
label: z.string(),
|
|
418
|
+
pointer: pointerSchema.nullable(),
|
|
419
|
+
url: z.string().nullable()
|
|
420
|
+
});
|
|
421
|
+
const contextChoiceSchema = z.discriminatedUnion("kind", [
|
|
422
|
+
z.object({ kind: z.literal("auto") }),
|
|
423
|
+
z.object({ kind: z.literal("unknown") }),
|
|
424
|
+
z.object({
|
|
425
|
+
atMs: z.number(),
|
|
426
|
+
eventKind: z.enum([
|
|
427
|
+
"mark",
|
|
428
|
+
"select",
|
|
429
|
+
"click",
|
|
430
|
+
"dwell",
|
|
431
|
+
"navigation"
|
|
432
|
+
]),
|
|
433
|
+
kind: z.literal("event")
|
|
434
|
+
}),
|
|
435
|
+
z.object({
|
|
436
|
+
evidenceId: z.string(),
|
|
437
|
+
kind: z.literal("evidence")
|
|
438
|
+
})
|
|
439
|
+
]);
|
|
440
|
+
const contextOptionSchema = z.object({
|
|
441
|
+
choice: contextChoiceSchema,
|
|
442
|
+
item: segmentContextSchema,
|
|
443
|
+
selected: z.boolean()
|
|
444
|
+
});
|
|
445
|
+
const sessionSegmentSchema = z.object({
|
|
446
|
+
audioExpiresAt: z.number().nullable(),
|
|
447
|
+
context: z.array(segmentContextSchema),
|
|
448
|
+
contextAmbiguous: z.boolean(),
|
|
449
|
+
contextCorrectedAt: z.number().nullable(),
|
|
450
|
+
contextOrigin: z.enum([
|
|
451
|
+
"auto",
|
|
452
|
+
"human",
|
|
453
|
+
"unknown"
|
|
454
|
+
]),
|
|
455
|
+
endMs: z.number(),
|
|
456
|
+
hasAudio: z.boolean(),
|
|
457
|
+
id: z.string(),
|
|
458
|
+
language: z.string().nullable(),
|
|
459
|
+
revision: z.number(),
|
|
460
|
+
source: z.enum(["voice", "typed"]),
|
|
461
|
+
startMs: z.number(),
|
|
462
|
+
text: z.string(),
|
|
463
|
+
textOrigin: z.enum(["transcription", "human"]),
|
|
464
|
+
transcription: z.object({
|
|
465
|
+
attempts: z.number(),
|
|
466
|
+
error: z.string().nullable(),
|
|
467
|
+
state: z.enum([
|
|
468
|
+
"pending",
|
|
469
|
+
"running",
|
|
470
|
+
"done",
|
|
471
|
+
"empty",
|
|
472
|
+
"failed",
|
|
473
|
+
"expired"
|
|
474
|
+
])
|
|
475
|
+
})
|
|
476
|
+
});
|
|
477
|
+
const evidenceLinkSchema = z.object({
|
|
478
|
+
access: z.string(),
|
|
479
|
+
label: z.string(),
|
|
480
|
+
url: z.string()
|
|
481
|
+
});
|
|
482
|
+
const sessionEvidenceSchema = z.object({
|
|
483
|
+
atMs: z.number(),
|
|
484
|
+
capturedAtMs: z.number().nullable(),
|
|
485
|
+
element: elementRefSchema.nullable(),
|
|
486
|
+
frame: frameSchema.nullable(),
|
|
487
|
+
id: z.string(),
|
|
488
|
+
image: z.object({
|
|
489
|
+
height: z.number(),
|
|
490
|
+
url: z.string().nullable(),
|
|
491
|
+
width: z.number()
|
|
492
|
+
}).nullable(),
|
|
493
|
+
kind: z.enum(["screenshot", "link"]),
|
|
494
|
+
link: evidenceLinkSchema.nullable(),
|
|
495
|
+
missingReason: z.string().nullable(),
|
|
496
|
+
note: z.string().nullable(),
|
|
497
|
+
status: z.enum(["available", "missing"]),
|
|
498
|
+
trigger: z.enum(EVIDENCE_TRIGGERS)
|
|
499
|
+
});
|
|
500
|
+
const sessionProposalSchema = z.object({
|
|
501
|
+
attachment: z.object({
|
|
502
|
+
contributionId: z.string(),
|
|
503
|
+
intentionId: z.string(),
|
|
504
|
+
need: z.string()
|
|
505
|
+
}).nullable(),
|
|
506
|
+
certainty: z.enum(["clear", "uncertain"]),
|
|
507
|
+
clarifications: z.array(z.object({
|
|
508
|
+
at: z.number(),
|
|
509
|
+
text: z.string()
|
|
510
|
+
})),
|
|
511
|
+
decision: z.enum(PROPOSAL_DECISIONS),
|
|
512
|
+
evidenceIds: z.array(z.string()),
|
|
513
|
+
feedbackId: z.string().nullable(),
|
|
514
|
+
formulation: z.enum(["ai", "human"]),
|
|
515
|
+
handedOffRevision: z.number().nullable(),
|
|
516
|
+
id: z.string(),
|
|
517
|
+
intentionSuggestion: intentionSuggestionSchema.nullable(),
|
|
518
|
+
lastHandoff: lastHandoffSchema.nullable(),
|
|
519
|
+
mergedIntoId: z.string().nullable(),
|
|
520
|
+
mergeSuggestion: z.object({
|
|
521
|
+
reason: z.string(),
|
|
522
|
+
withIds: z.array(z.string())
|
|
523
|
+
}).nullable(),
|
|
524
|
+
need: z.string(),
|
|
525
|
+
needsReview: z.boolean(),
|
|
526
|
+
observed: z.string(),
|
|
527
|
+
origin: z.enum(["ai", "human"]),
|
|
528
|
+
question: z.string().nullable(),
|
|
529
|
+
refinement: z.object({
|
|
530
|
+
error: z.string().nullable(),
|
|
531
|
+
state: z.enum([
|
|
532
|
+
"running",
|
|
533
|
+
"failed",
|
|
534
|
+
"outdated"
|
|
535
|
+
])
|
|
536
|
+
}).nullable(),
|
|
537
|
+
response: z.string().nullable(),
|
|
538
|
+
revision: z.number(),
|
|
539
|
+
segmentIds: z.array(z.string()),
|
|
540
|
+
setAside: z.array(z.string()),
|
|
541
|
+
unknowns: z.array(z.string()),
|
|
542
|
+
updatedAt: z.number(),
|
|
543
|
+
verification: z.object({
|
|
544
|
+
at: z.number(),
|
|
545
|
+
note: z.string().nullable(),
|
|
546
|
+
sessionId: z.string().nullable(),
|
|
547
|
+
status: z.enum(["works", "needs_work"])
|
|
548
|
+
}).nullable()
|
|
549
|
+
});
|
|
550
|
+
const sessionHandoffSchema = z.object({
|
|
551
|
+
brief: z.string(),
|
|
552
|
+
createdAt: z.number(),
|
|
553
|
+
deliveries: z.array(z.object({
|
|
554
|
+
at: z.number(),
|
|
555
|
+
kind: z.enum(["copied", "exported"])
|
|
556
|
+
})),
|
|
557
|
+
evidenceIds: z.array(z.string()),
|
|
558
|
+
id: z.string(),
|
|
559
|
+
proposals: z.array(z.object({
|
|
560
|
+
id: z.string(),
|
|
561
|
+
need: z.string(),
|
|
562
|
+
revision: z.number()
|
|
563
|
+
}))
|
|
564
|
+
});
|
|
565
|
+
const sessionMarkSchema = z.object({
|
|
566
|
+
atMs: z.number(),
|
|
567
|
+
kind: z.enum([
|
|
568
|
+
"mark",
|
|
569
|
+
"select",
|
|
570
|
+
"navigation"
|
|
571
|
+
]),
|
|
572
|
+
label: z.string(),
|
|
573
|
+
note: z.string().nullable(),
|
|
574
|
+
url: z.string().nullable()
|
|
575
|
+
});
|
|
576
|
+
const sessionDetailSchema = z.object({
|
|
577
|
+
continuation: sessionContinuationSchema.nullable(),
|
|
578
|
+
contributedEvidence: z.array(sessionEvidenceSchema),
|
|
579
|
+
evidence: z.array(sessionEvidenceSchema),
|
|
580
|
+
handoffs: z.array(sessionHandoffSchema),
|
|
581
|
+
history: z.array(intentionHistoryEntrySchema),
|
|
582
|
+
marks: z.array(sessionMarkSchema),
|
|
583
|
+
proposals: z.array(sessionProposalSchema),
|
|
584
|
+
segments: z.array(sessionSegmentSchema),
|
|
585
|
+
session: sessionSummarySchema.extend({
|
|
586
|
+
canEdit: z.boolean(),
|
|
587
|
+
gaps: z.array(sessionGapSchema),
|
|
588
|
+
startUrl: z.string().nullable()
|
|
589
|
+
})
|
|
590
|
+
});
|
|
591
|
+
const handoffFileSchema = z.object({
|
|
592
|
+
evidenceId: z.string(),
|
|
593
|
+
name: z.string(),
|
|
594
|
+
url: z.string()
|
|
595
|
+
});
|
|
596
|
+
const sessionApiErrorSchema = z.object({
|
|
597
|
+
error: z.string(),
|
|
598
|
+
limit: z.enum(SESSION_LIMIT_KINDS).optional()
|
|
599
|
+
});
|
|
600
|
+
//#endregion
|
|
85
601
|
//#region src/sessions/api/proposal-endpoints.ts
|
|
86
602
|
const proposalResponse = z.object({ proposal: sessionProposalSchema });
|
|
87
|
-
|
|
88
|
-
|
|
603
|
+
const intentionUpdateResponse = z.object({
|
|
604
|
+
contribution: sessionContributionSchema,
|
|
605
|
+
intention: continuedIntentionSchema.nullable()
|
|
606
|
+
});
|
|
607
|
+
const proposalPoster = ({ post }) => async (path, body) => (await post(path, body, proposalResponse)).proposal;
|
|
608
|
+
function createProposalEndpoints(requests) {
|
|
609
|
+
const { post } = requests;
|
|
610
|
+
const postProposal = proposalPoster(requests);
|
|
89
611
|
return {
|
|
90
612
|
clarifyProposal: (body) => postProposal("/api/v1/sessions/proposals/clarify", body),
|
|
91
613
|
createFeedback: (proposalId) => post("/api/v1/sessions/proposals/feedback", { proposalId }, z.object({
|
|
@@ -105,6 +627,22 @@ function createProposalEndpoints({ post }) {
|
|
|
105
627
|
verifyProposal: (body) => postProposal("/api/v1/sessions/proposals/verify", body)
|
|
106
628
|
};
|
|
107
629
|
}
|
|
630
|
+
function createIntentionEndpoints(requests) {
|
|
631
|
+
const { post } = requests;
|
|
632
|
+
const postProposal = proposalPoster(requests);
|
|
633
|
+
const postIntentionUpdate = (path, body) => post(path, body, intentionUpdateResponse);
|
|
634
|
+
return {
|
|
635
|
+
acceptIntentionChanges: (body) => postIntentionUpdate("/api/v1/sessions/intentions/accept", body),
|
|
636
|
+
answerIntentionQuestion: (body) => postIntentionUpdate("/api/v1/sessions/intentions/answer", body),
|
|
637
|
+
attachToIntention: (body) => postProposal("/api/v1/sessions/intentions/attach", body),
|
|
638
|
+
deleteProposal: async (proposalId) => {
|
|
639
|
+
await post("/api/v1/sessions/proposals/delete", { proposalId }, z.object({ deleted: z.literal(true) }));
|
|
640
|
+
},
|
|
641
|
+
detachContribution: (contributionId) => postIntentionUpdate("/api/v1/sessions/intentions/detach", { contributionId }),
|
|
642
|
+
keepSeparate: (proposalId) => postProposal("/api/v1/sessions/intentions/keep-separate", { proposalId }),
|
|
643
|
+
reopenOrRejectChange: (body) => postIntentionUpdate("/api/v1/sessions/intentions/change", body)
|
|
644
|
+
};
|
|
645
|
+
}
|
|
108
646
|
//#endregion
|
|
109
647
|
//#region src/sessions/api/recording-endpoints.ts
|
|
110
648
|
const summaryResponse = z.object({ session: sessionSummarySchema });
|
|
@@ -127,9 +665,7 @@ function createRecordingEndpoints({ post }) {
|
|
|
127
665
|
//#region src/sessions/api/review-endpoints.ts
|
|
128
666
|
function createReviewEndpoints({ get, post }) {
|
|
129
667
|
return {
|
|
130
|
-
addTypedSegment: async (body) => (await post("/api/v1/sessions/segments", body, segmentResponse)).segment,
|
|
131
668
|
analyze: async (sessionId) => (await post("/api/v1/sessions/analyze", { sessionId }, z.object({ analysis: analysisStatusSchema }))).analysis,
|
|
132
|
-
correctSegment: async (body) => (await post("/api/v1/sessions/segments/correct", body, segmentResponse)).segment,
|
|
133
669
|
deleteSession: async (sessionId) => {
|
|
134
670
|
await post("/api/v1/sessions/delete", { sessionId }, z.object({ deleted: z.literal(true) }));
|
|
135
671
|
},
|
|
@@ -137,10 +673,19 @@ function createReviewEndpoints({ get, post }) {
|
|
|
137
673
|
identity: () => get("/api/v1/sessions/identity", sessionIdentitySchema),
|
|
138
674
|
list: async () => (await get("/api/v1/sessions/list", z.object({ sessions: z.array(sessionSummarySchema) }))).sessions,
|
|
139
675
|
removeEvidence: async (evidenceId) => (await post("/api/v1/sessions/evidence/remove", { evidenceId }, z.object({ evidence: sessionEvidenceSchema }))).evidence,
|
|
140
|
-
retrySegment: async (segmentId) => (await post("/api/v1/sessions/segments/retry", { segmentId }, segmentResponse)).segment,
|
|
141
676
|
update: async (body) => (await post("/api/v1/sessions/update", body, summaryResponse)).session
|
|
142
677
|
};
|
|
143
678
|
}
|
|
679
|
+
function createSegmentEndpoints({ get, post }) {
|
|
680
|
+
const postSegment = async (path, body) => (await post(path, body, segmentResponse)).segment;
|
|
681
|
+
return {
|
|
682
|
+
addTypedSegment: (body) => postSegment("/api/v1/sessions/segments", body),
|
|
683
|
+
contextChoices: async (segmentId) => (await get("/api/v1/sessions/segments/context-choices", z.object({ options: z.array(contextOptionSchema) }), { segmentId })).options,
|
|
684
|
+
correctSegment: (body) => postSegment("/api/v1/sessions/segments/correct", body),
|
|
685
|
+
retrySegment: (segmentId) => postSegment("/api/v1/sessions/segments/retry", { segmentId }),
|
|
686
|
+
setSegmentContext: (body) => postSegment("/api/v1/sessions/segments/context", body)
|
|
687
|
+
};
|
|
688
|
+
}
|
|
144
689
|
//#endregion
|
|
145
690
|
//#region src/sessions/api/session-requests.ts
|
|
146
691
|
function createSessionRequests(transport) {
|
|
@@ -176,7 +721,9 @@ function createSessionApi(transport) {
|
|
|
176
721
|
return {
|
|
177
722
|
...createRecordingEndpoints(requests),
|
|
178
723
|
...createReviewEndpoints(requests),
|
|
179
|
-
...
|
|
724
|
+
...createSegmentEndpoints(requests),
|
|
725
|
+
...createProposalEndpoints(requests),
|
|
726
|
+
...createIntentionEndpoints(requests)
|
|
180
727
|
};
|
|
181
728
|
}
|
|
182
729
|
//#endregion
|
|
@@ -1493,7 +2040,7 @@ function captureFrame() {
|
|
|
1493
2040
|
}
|
|
1494
2041
|
//#endregion
|
|
1495
2042
|
//#region src/sessions/capture/page/interaction-recorder.ts
|
|
1496
|
-
const
|
|
2043
|
+
const MIN_DWELL_MS = 700;
|
|
1497
2044
|
const POINTER_SAMPLE_INTERVAL_MS = 100;
|
|
1498
2045
|
const SETTLE_DEBOUNCE_MS = 400;
|
|
1499
2046
|
function eventElement(event) {
|
|
@@ -1504,14 +2051,14 @@ function eventElement(event) {
|
|
|
1504
2051
|
return element === document.body || element === document.documentElement ? null : element;
|
|
1505
2052
|
}
|
|
1506
2053
|
function pointerOf(event) {
|
|
1507
|
-
return
|
|
2054
|
+
return {
|
|
1508
2055
|
x: Math.round(event.clientX),
|
|
1509
2056
|
y: Math.round(event.clientY)
|
|
1510
|
-
} : {
|
|
1511
|
-
x: 0,
|
|
1512
|
-
y: 0
|
|
1513
2057
|
};
|
|
1514
2058
|
}
|
|
2059
|
+
function isPhysicalPointerClick(event) {
|
|
2060
|
+
return event instanceof MouseEvent && event.detail > 0;
|
|
2061
|
+
}
|
|
1515
2062
|
function debounced(signal, run) {
|
|
1516
2063
|
let settleTimer;
|
|
1517
2064
|
signal.addEventListener("abort", () => window.clearTimeout(settleTimer));
|
|
@@ -1528,34 +2075,50 @@ function recordClick(options) {
|
|
|
1528
2075
|
element: describeElementRef(element, options),
|
|
1529
2076
|
frame: captureFrame(),
|
|
1530
2077
|
kind: "click",
|
|
1531
|
-
pointer: pointerOf(event)
|
|
2078
|
+
pointer: isPhysicalPointerClick(event) ? pointerOf(event) : null
|
|
1532
2079
|
});
|
|
1533
2080
|
};
|
|
1534
2081
|
}
|
|
1535
2082
|
function watchDwell(options, signal) {
|
|
1536
2083
|
let hovered = null;
|
|
1537
2084
|
let lastSampleAt = Number.NEGATIVE_INFINITY;
|
|
1538
|
-
let
|
|
1539
|
-
|
|
1540
|
-
|
|
2085
|
+
let entered = null;
|
|
2086
|
+
const settle = () => {
|
|
2087
|
+
if (!entered) return;
|
|
2088
|
+
const durationMs = options.atMs() - entered.atMs;
|
|
2089
|
+
if (durationMs >= MIN_DWELL_MS) options.onEvent({
|
|
2090
|
+
...entered,
|
|
2091
|
+
durationMs
|
|
2092
|
+
});
|
|
2093
|
+
entered = null;
|
|
2094
|
+
};
|
|
2095
|
+
signal.addEventListener("abort", settle);
|
|
2096
|
+
const onPointerMove = (event) => {
|
|
2097
|
+
if (!(event instanceof MouseEvent)) return;
|
|
1541
2098
|
const now = Date.now();
|
|
1542
2099
|
if (now - lastSampleAt < POINTER_SAMPLE_INTERVAL_MS) return;
|
|
1543
2100
|
lastSampleAt = now;
|
|
1544
2101
|
const element = eventElement(event);
|
|
1545
2102
|
if (element === hovered) return;
|
|
2103
|
+
settle();
|
|
1546
2104
|
hovered = element;
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
const atMs = options.atMs();
|
|
1550
|
-
const pointer = pointerOf(event);
|
|
1551
|
-
dwellTimer = window.setTimeout(() => options.onEvent({
|
|
1552
|
-
atMs,
|
|
1553
|
-
durationMs: DWELL_MS,
|
|
2105
|
+
if (element) entered = {
|
|
2106
|
+
atMs: options.atMs(),
|
|
1554
2107
|
element: describeElementRef(element, options),
|
|
1555
2108
|
frame: captureFrame(),
|
|
1556
2109
|
kind: "dwell",
|
|
1557
|
-
pointer
|
|
1558
|
-
}
|
|
2110
|
+
pointer: pointerOf(event)
|
|
2111
|
+
};
|
|
2112
|
+
};
|
|
2113
|
+
const onPointerLeaveWindow = (event) => {
|
|
2114
|
+
if (event instanceof MouseEvent && event.relatedTarget === null) {
|
|
2115
|
+
settle();
|
|
2116
|
+
hovered = null;
|
|
2117
|
+
}
|
|
2118
|
+
};
|
|
2119
|
+
return {
|
|
2120
|
+
onPointerLeaveWindow,
|
|
2121
|
+
onPointerMove
|
|
1559
2122
|
};
|
|
1560
2123
|
}
|
|
1561
2124
|
function startInteractionRecorder(options) {
|
|
@@ -1579,8 +2142,10 @@ function startInteractionRecorder(options) {
|
|
|
1579
2142
|
viewportHeight: window.innerHeight,
|
|
1580
2143
|
viewportWidth: window.innerWidth
|
|
1581
2144
|
}));
|
|
2145
|
+
const dwell = watchDwell(options, signal);
|
|
1582
2146
|
document.addEventListener("click", recordClick(options), listen);
|
|
1583
|
-
document.addEventListener("pointermove",
|
|
2147
|
+
document.addEventListener("pointermove", dwell.onPointerMove, listen);
|
|
2148
|
+
document.addEventListener("pointerout", dwell.onPointerLeaveWindow, listen);
|
|
1584
2149
|
window.addEventListener("scroll", recordScroll, listen);
|
|
1585
2150
|
window.addEventListener("resize", recordViewport, listen);
|
|
1586
2151
|
return { stop: () => controller.abort() };
|
|
@@ -1713,25 +2278,26 @@ function blankFieldValues(root) {
|
|
|
1713
2278
|
const NAVIGATION_SETTLE_MS = 1500;
|
|
1714
2279
|
const CAPTURE_FAILED = "The page could not be captured.";
|
|
1715
2280
|
const CAPTURE_TOO_LARGE = "The screenshot was larger than the upload limit.";
|
|
1716
|
-
|
|
2281
|
+
const STOPPED_BEFORE_CAPTURE = "Recording stopped before the screenshot was taken.";
|
|
2282
|
+
const withoutImage = (base, missingReason) => ({
|
|
2283
|
+
...base,
|
|
2284
|
+
capturedAtMs: null,
|
|
2285
|
+
image: null,
|
|
2286
|
+
missingReason
|
|
2287
|
+
});
|
|
2288
|
+
async function shoot(base, options) {
|
|
1717
2289
|
const image = await captureViewport({
|
|
1718
2290
|
excludeSelectors: SESSION_MASK_SELECTORS,
|
|
1719
2291
|
prepareClone: blankFieldValues
|
|
1720
2292
|
});
|
|
1721
2293
|
releaseCapture(image);
|
|
1722
|
-
if (!image) return
|
|
1723
|
-
|
|
1724
|
-
image: null,
|
|
1725
|
-
missingReason: CAPTURE_FAILED
|
|
1726
|
-
};
|
|
1727
|
-
if (image.blob.size > maxImageBytes) return {
|
|
1728
|
-
...base,
|
|
1729
|
-
image: null,
|
|
1730
|
-
missingReason: CAPTURE_TOO_LARGE
|
|
1731
|
-
};
|
|
2294
|
+
if (!image) return withoutImage(base, CAPTURE_FAILED);
|
|
2295
|
+
if (image.blob.size > options.limits.maxImageBytes) return withoutImage(base, CAPTURE_TOO_LARGE);
|
|
1732
2296
|
const { blob, height, width } = image;
|
|
1733
2297
|
return {
|
|
1734
2298
|
...base,
|
|
2299
|
+
capturedAtMs: options.atMs(),
|
|
2300
|
+
frame: captureFrame(),
|
|
1735
2301
|
image: {
|
|
1736
2302
|
blob,
|
|
1737
2303
|
height,
|
|
@@ -1740,31 +2306,43 @@ async function shoot(base, maxImageBytes) {
|
|
|
1740
2306
|
missingReason: null
|
|
1741
2307
|
};
|
|
1742
2308
|
}
|
|
2309
|
+
function evidenceBase(trigger, request, options) {
|
|
2310
|
+
return {
|
|
2311
|
+
atMs: options.atMs(),
|
|
2312
|
+
clientKey: crypto.randomUUID(),
|
|
2313
|
+
element: request.element ? describeElementRef(request.element, options) : null,
|
|
2314
|
+
frame: captureFrame(),
|
|
2315
|
+
note: request.note ?? null,
|
|
2316
|
+
trigger
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
1743
2319
|
function createEvidenceScheduler(options) {
|
|
1744
2320
|
const { limits } = options;
|
|
1745
2321
|
let captured = 0;
|
|
1746
2322
|
let lastCaptureAtMs = Number.NEGATIVE_INFINITY;
|
|
1747
2323
|
let navigationTimer;
|
|
1748
2324
|
let previousCapture = Promise.resolve();
|
|
2325
|
+
let collectionRun = 0;
|
|
1749
2326
|
const take = (trigger, request) => {
|
|
1750
2327
|
if (captured >= limits.maxEvidence) return null;
|
|
1751
2328
|
captured += 1;
|
|
1752
|
-
const
|
|
1753
|
-
lastCaptureAtMs = atMs;
|
|
1754
|
-
const
|
|
1755
|
-
const
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
note: request.note ?? null,
|
|
1761
|
-
trigger
|
|
2329
|
+
const base = evidenceBase(trigger, request, options);
|
|
2330
|
+
lastCaptureAtMs = base.atMs;
|
|
2331
|
+
const takenInRun = collectionRun;
|
|
2332
|
+
const isCurrent = () => takenInRun === collectionRun;
|
|
2333
|
+
const stopped = withoutImage(base, STOPPED_BEFORE_CAPTURE);
|
|
2334
|
+
const captureUnlessStopped = async () => {
|
|
2335
|
+
const evidence = isCurrent() ? await shoot(base, options) : stopped;
|
|
2336
|
+
return isCurrent() ? evidence : stopped;
|
|
1762
2337
|
};
|
|
1763
|
-
previousCapture = previousCapture.then(
|
|
1764
|
-
return clientKey;
|
|
2338
|
+
previousCapture = previousCapture.then(captureUnlessStopped).then(options.onEvidence);
|
|
2339
|
+
return base.clientKey;
|
|
1765
2340
|
};
|
|
1766
2341
|
return {
|
|
1767
|
-
cancelPending: () =>
|
|
2342
|
+
cancelPending: () => {
|
|
2343
|
+
window.clearTimeout(navigationTimer);
|
|
2344
|
+
collectionRun += 1;
|
|
2345
|
+
},
|
|
1768
2346
|
capture: (request) => take(request.trigger, request),
|
|
1769
2347
|
navigated: () => {
|
|
1770
2348
|
window.clearTimeout(navigationTimer);
|
|
@@ -1866,7 +2444,8 @@ function startPayload(draft) {
|
|
|
1866
2444
|
},
|
|
1867
2445
|
startedAt: draft.startedAt,
|
|
1868
2446
|
surface: draft.surface,
|
|
1869
|
-
...draft.title ? { title: draft.title } : {}
|
|
2447
|
+
...draft.title ? { title: draft.title } : {},
|
|
2448
|
+
...draft.continues ? { continues: draft.continues } : {}
|
|
1870
2449
|
},
|
|
1871
2450
|
kind: "start"
|
|
1872
2451
|
};
|
|
@@ -1876,6 +2455,7 @@ function evidencePayload(captured) {
|
|
|
1876
2455
|
return {
|
|
1877
2456
|
evidence: {
|
|
1878
2457
|
atMs: captured.atMs,
|
|
2458
|
+
capturedAtMs: captured.capturedAtMs ?? void 0,
|
|
1879
2459
|
clientKey: captured.clientKey,
|
|
1880
2460
|
element: element ?? void 0,
|
|
1881
2461
|
frame: captured.frame,
|
|
@@ -2252,6 +2832,7 @@ var RecordingController = class {
|
|
|
2252
2832
|
if (!voice) return;
|
|
2253
2833
|
const draft = newSessionDraft({
|
|
2254
2834
|
accountKey: this.options.account.key,
|
|
2835
|
+
continues: options.continues,
|
|
2255
2836
|
surface: this.options.surface,
|
|
2256
2837
|
title: cleanText(options.title, MAX_TITLE_CHARS) ?? ""
|
|
2257
2838
|
});
|
|
@@ -2603,6 +3184,9 @@ function shortDuration(ms) {
|
|
|
2603
3184
|
if (minutes === 0) return `${seconds} s`;
|
|
2604
3185
|
return seconds === 0 ? `${minutes} min` : `${minutes} min ${seconds} s`;
|
|
2605
3186
|
}
|
|
3187
|
+
function preciseSeconds(ms) {
|
|
3188
|
+
return `${(Math.abs(ms) / SECOND_MS).toFixed(1)} s`;
|
|
3189
|
+
}
|
|
2606
3190
|
function minutesLeft(remainingMs) {
|
|
2607
3191
|
return Math.max(1, Math.ceil(remainingMs / MINUTE_MS));
|
|
2608
3192
|
}
|
|
@@ -2626,12 +3210,13 @@ function sessionStatusLine(summary) {
|
|
|
2626
3210
|
if (isAnalysisBusy(summary)) return "Reading";
|
|
2627
3211
|
if (analysis.state === "failed") return "Analysis failed";
|
|
2628
3212
|
if (analysis.state === "idle") return "Not analyzed yet";
|
|
2629
|
-
return `${countLabel(counts.proposals, "
|
|
3213
|
+
return `${countLabel(counts.proposals, "intention")} · ${counts.kept} confirmed`;
|
|
2630
3214
|
}
|
|
2631
3215
|
function isDetailProcessing(detail) {
|
|
2632
3216
|
const transcribing = detail.segments.some((segment) => segment.transcription.state === "pending" || segment.transcription.state === "running");
|
|
2633
3217
|
const refining = detail.proposals.some((proposal) => proposal.refinement?.state === "running");
|
|
2634
|
-
|
|
3218
|
+
const comparing = detail.continuation?.contribution.state === "analyzing";
|
|
3219
|
+
return transcribing || refining || comparing || isAnalysisBusy(detail.session) || detail.session.collection !== "ended";
|
|
2635
3220
|
}
|
|
2636
3221
|
//#endregion
|
|
2637
3222
|
//#region src/sessions/ui/session-icons.tsx
|
|
@@ -2917,6 +3502,34 @@ function RecordingLayer({ onFinished }) {
|
|
|
2917
3502
|
})] });
|
|
2918
3503
|
}
|
|
2919
3504
|
//#endregion
|
|
3505
|
+
//#region src/sessions/ui/intention-styles.ts
|
|
3506
|
+
const INTENTION_STYLES = `
|
|
3507
|
+
.dt-ses-panel { display: contents; }
|
|
3508
|
+
.dt-ses-panel[hidden] { display: none; }
|
|
3509
|
+
.dt-ses-moment, .dt-ses-moment-section, .dt-ses-block, .dt-ses-change, .dt-ses-history { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
3510
|
+
.dt-ses-moment-item { display: flex; flex-direction: column; gap: 6px; padding: 8px 10px; border-radius: 10px; background: var(--rf-bg-subtle); }
|
|
3511
|
+
.dt-ses-moment-line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0; overflow-wrap: anywhere; }
|
|
3512
|
+
.dt-ses-moment-quote { display: flex; flex-direction: column; gap: 2px; margin: 0; padding-left: 10px; border-left: 2px solid var(--rf-border); overflow-wrap: anywhere; }
|
|
3513
|
+
.dt-ses-moment-notices { color: var(--rf-text-muted); font-size: 12px; }
|
|
3514
|
+
.dt-ses-moment-shot { display: flex; flex-direction: column; gap: 4px; margin: 0; max-width: 100%; }
|
|
3515
|
+
.dt-ses-shot-mark[data-match="approximate"] { border-style: dashed; }
|
|
3516
|
+
.dt-ses-shot-pointer {
|
|
3517
|
+
position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%;
|
|
3518
|
+
background: var(--rf-accent); box-shadow: 0 0 0 2px var(--rf-bg); pointer-events: none;
|
|
3519
|
+
}
|
|
3520
|
+
.dt-ses-source { margin: 0; font-size: 12px; overflow-wrap: anywhere; }
|
|
3521
|
+
.dt-ses-context-line { display: block; }
|
|
3522
|
+
.dt-ses-attached { flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px; }
|
|
3523
|
+
.dt-ses-attached p { margin: 0; }
|
|
3524
|
+
.dt-ses-history[data-removed="true"] > .dt-ses-subhead { color: var(--rf-text-muted); text-decoration: line-through; }
|
|
3525
|
+
.dt-ses-change { padding: 8px 10px; border-radius: 10px; background: var(--rf-bg-subtle); list-style: none; }
|
|
3526
|
+
.dt-ses-change[data-decision="rejected"] .dt-ses-diff { color: var(--rf-text-muted); text-decoration: line-through; }
|
|
3527
|
+
.dt-ses-diff { display: grid; grid-template-columns: auto 1fr; gap: 2px 8px; margin: 0; overflow-wrap: anywhere; }
|
|
3528
|
+
p.dt-ses-diff { display: block; }
|
|
3529
|
+
.dt-ses-diff dd { margin: 0; }
|
|
3530
|
+
.dt-card:focus-visible { outline: 2px solid var(--rf-accent); outline-offset: 2px; }
|
|
3531
|
+
`;
|
|
3532
|
+
//#endregion
|
|
2920
3533
|
//#region src/sessions/ui/sessions-styles.ts
|
|
2921
3534
|
const MONO_FONT$1 = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
|
|
2922
3535
|
const SESSIONS_STYLES = `${`
|
|
@@ -3011,7 +3624,7 @@ ${`
|
|
|
3011
3624
|
.dt-team-anchor[data-edge="top"] { top: calc(var(--rf-viewport-top, 0px) + env(safe-area-inset-top) + var(--dt-edge-gap)); }
|
|
3012
3625
|
.dt-team-card { display: flex; flex-direction: column; gap: 6px; padding: 12px 14px; border-radius: 14px; animation: dt-popover-in 200ms var(--rf-ease); }
|
|
3013
3626
|
.dt-bar > .dt-btn { margin-inline: 2px 4px; }
|
|
3014
|
-
`}`;
|
|
3627
|
+
`}${INTENTION_STYLES}`;
|
|
3015
3628
|
//#endregion
|
|
3016
3629
|
//#region src/devtools/client/clipboard.ts
|
|
3017
3630
|
/**
|
|
@@ -3661,21 +4274,28 @@ function evidenceCaption(evidence) {
|
|
|
3661
4274
|
const element = evidence.element ? ` · ${evidence.element.label}` : "";
|
|
3662
4275
|
return `${TRIGGER_LABELS[evidence.trigger]}${element}`;
|
|
3663
4276
|
}
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
4277
|
+
const hasViewport = (frame) => frame.viewportWidth > 0 && frame.viewportHeight > 0;
|
|
4278
|
+
const percent = (value, total) => `${value / total * 100}%`;
|
|
4279
|
+
function percentBox(rect, frame) {
|
|
4280
|
+
if (!hasViewport(frame)) return null;
|
|
4281
|
+
return {
|
|
4282
|
+
height: percent(rect.height, frame.viewportHeight),
|
|
4283
|
+
left: percent(rect.x, frame.viewportWidth),
|
|
4284
|
+
top: percent(rect.y, frame.viewportHeight),
|
|
4285
|
+
width: percent(rect.width, frame.viewportWidth)
|
|
4286
|
+
};
|
|
4287
|
+
}
|
|
4288
|
+
function percentPoint(point, frame) {
|
|
4289
|
+
if (!hasViewport(frame)) return null;
|
|
3668
4290
|
return {
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
top: percent(element.rect.y, frame.viewportHeight),
|
|
3672
|
-
width: percent(element.rect.width, frame.viewportWidth)
|
|
4291
|
+
left: percent(point.x, frame.viewportWidth),
|
|
4292
|
+
top: percent(point.y, frame.viewportHeight)
|
|
3673
4293
|
};
|
|
3674
4294
|
}
|
|
3675
4295
|
function EvidenceThumb({ evidence, isLarge = false }) {
|
|
3676
|
-
const { image } = evidence;
|
|
4296
|
+
const { element, frame, image } = evidence;
|
|
3677
4297
|
if (!image?.url) return null;
|
|
3678
|
-
const highlight =
|
|
4298
|
+
const highlight = element && frame ? percentBox(element.rect, frame) : null;
|
|
3679
4299
|
return /* @__PURE__ */ jsxs("span", {
|
|
3680
4300
|
className: "dt-ses-shot",
|
|
3681
4301
|
"data-large": isLarge,
|
|
@@ -3692,13 +4312,246 @@ function EvidenceThumb({ evidence, isLarge = false }) {
|
|
|
3692
4312
|
});
|
|
3693
4313
|
}
|
|
3694
4314
|
//#endregion
|
|
4315
|
+
//#region src/sessions/ui/review/continuation/intention-history.tsx
|
|
4316
|
+
const FIELD_LABELS = {
|
|
4317
|
+
need: "Need",
|
|
4318
|
+
observed: "Observed",
|
|
4319
|
+
response: "Considered response",
|
|
4320
|
+
setAside: "Set aside",
|
|
4321
|
+
unknowns: "To check"
|
|
4322
|
+
};
|
|
4323
|
+
function fieldValueText(value) {
|
|
4324
|
+
return (Array.isArray(value) ? value.join("; ") : value) || "—";
|
|
4325
|
+
}
|
|
4326
|
+
function AppliedChanges({ applied }) {
|
|
4327
|
+
if (applied.length === 0) return null;
|
|
4328
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
4329
|
+
className: "dt-ses-rows",
|
|
4330
|
+
children: applied.map((change) => /* @__PURE__ */ jsxs("li", {
|
|
4331
|
+
className: "dt-ses-row",
|
|
4332
|
+
children: [/* @__PURE__ */ jsxs("p", {
|
|
4333
|
+
className: "dt-meta",
|
|
4334
|
+
children: [
|
|
4335
|
+
"Accepted into revision ",
|
|
4336
|
+
change.revision,
|
|
4337
|
+
" by",
|
|
4338
|
+
" ",
|
|
4339
|
+
change.byName ?? "a teammate",
|
|
4340
|
+
" · ",
|
|
4341
|
+
formatDate(change.at)
|
|
4342
|
+
]
|
|
4343
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
4344
|
+
className: "dt-ses-list",
|
|
4345
|
+
children: change.fields.map((field) => /* @__PURE__ */ jsxs("li", { children: [
|
|
4346
|
+
FIELD_LABELS[field.field],
|
|
4347
|
+
": ",
|
|
4348
|
+
fieldValueText(field.before),
|
|
4349
|
+
" →",
|
|
4350
|
+
" ",
|
|
4351
|
+
fieldValueText(field.after)
|
|
4352
|
+
] }, field.field))
|
|
4353
|
+
})]
|
|
4354
|
+
}, change.revision))
|
|
4355
|
+
});
|
|
4356
|
+
}
|
|
4357
|
+
function HistoryExtracts({ evidence, quotes }) {
|
|
4358
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [quotes.map((quote) => /* @__PURE__ */ jsxs("blockquote", {
|
|
4359
|
+
className: "dt-ses-moment-quote",
|
|
4360
|
+
children: [
|
|
4361
|
+
/* @__PURE__ */ jsx("span", {
|
|
4362
|
+
className: "dt-ses-time",
|
|
4363
|
+
children: formatClock(quote.startMs)
|
|
4364
|
+
}),
|
|
4365
|
+
` “${quote.text}”`,
|
|
4366
|
+
quote.context.map((line) => /* @__PURE__ */ jsx("span", {
|
|
4367
|
+
className: "dt-meta dt-ses-context-line",
|
|
4368
|
+
children: line
|
|
4369
|
+
}, line))
|
|
4370
|
+
]
|
|
4371
|
+
}, quote.segmentId)), evidence.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
4372
|
+
className: "dt-ses-thumbs",
|
|
4373
|
+
children: evidence.map((item) => /* @__PURE__ */ jsx(EvidenceThumb, { evidence: item }, item.id))
|
|
4374
|
+
})] });
|
|
4375
|
+
}
|
|
4376
|
+
function entryTitle(entry) {
|
|
4377
|
+
const { session } = entry;
|
|
4378
|
+
return entry.origin === "continuation" ? `Continued in “${session.title}” · ${formatDate(session.startedAt)}` : `Added from “${session.title}” · ${formatDate(session.startedAt)}`;
|
|
4379
|
+
}
|
|
4380
|
+
function HistoryEntry({ context, entry }) {
|
|
4381
|
+
const evidenceIds = new Set(entry.evidenceIds);
|
|
4382
|
+
const evidence = context.detail.contributedEvidence.filter((item) => evidenceIds.has(item.id));
|
|
4383
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
4384
|
+
className: "dt-ses-history",
|
|
4385
|
+
"data-removed": entry.detachedAt !== null,
|
|
4386
|
+
children: [
|
|
4387
|
+
/* @__PURE__ */ jsx("p", {
|
|
4388
|
+
className: "dt-ses-subhead",
|
|
4389
|
+
children: entryTitle(entry)
|
|
4390
|
+
}),
|
|
4391
|
+
entry.detachedAt !== null && /* @__PURE__ */ jsxs("p", {
|
|
4392
|
+
className: "dt-meta",
|
|
4393
|
+
children: [
|
|
4394
|
+
"Removed on ",
|
|
4395
|
+
formatDate(entry.detachedAt),
|
|
4396
|
+
": what it added was taken back."
|
|
4397
|
+
]
|
|
4398
|
+
}),
|
|
4399
|
+
/* @__PURE__ */ jsx(AppliedChanges, { applied: entry.applied }),
|
|
4400
|
+
entry.extractsHidden ? /* @__PURE__ */ jsx("p", {
|
|
4401
|
+
className: "dt-meta",
|
|
4402
|
+
children: "Extracts are private to the author"
|
|
4403
|
+
}) : /* @__PURE__ */ jsx(HistoryExtracts, {
|
|
4404
|
+
evidence,
|
|
4405
|
+
quotes: entry.quotes
|
|
4406
|
+
}),
|
|
4407
|
+
entry.session.canOpen && /* @__PURE__ */ jsx("button", {
|
|
4408
|
+
className: "dt-ses-link",
|
|
4409
|
+
onClick: () => context.navigation.onOpenSession(entry.session.id),
|
|
4410
|
+
type: "button",
|
|
4411
|
+
children: "Open session"
|
|
4412
|
+
})
|
|
4413
|
+
]
|
|
4414
|
+
});
|
|
4415
|
+
}
|
|
4416
|
+
function IntentionHistory({ context, intentionId }) {
|
|
4417
|
+
const entries = context.detail.history.filter((entry) => entry.intentionId === intentionId);
|
|
4418
|
+
if (entries.length === 0) return null;
|
|
4419
|
+
return /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
|
|
4420
|
+
className: "dt-ses-label",
|
|
4421
|
+
children: "History"
|
|
4422
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
4423
|
+
className: "dt-ses-rows",
|
|
4424
|
+
children: entries.map((entry) => /* @__PURE__ */ jsx(HistoryEntry, {
|
|
4425
|
+
context,
|
|
4426
|
+
entry
|
|
4427
|
+
}, entry.id))
|
|
4428
|
+
})] });
|
|
4429
|
+
}
|
|
4430
|
+
//#endregion
|
|
4431
|
+
//#region src/sessions/ui/handoff/handoff-changes.tsx
|
|
4432
|
+
function differences(proposal, last) {
|
|
4433
|
+
return [
|
|
4434
|
+
{
|
|
4435
|
+
after: proposal.need,
|
|
4436
|
+
before: last.need,
|
|
4437
|
+
label: "Need"
|
|
4438
|
+
},
|
|
4439
|
+
{
|
|
4440
|
+
after: fieldValueText(proposal.response),
|
|
4441
|
+
before: fieldValueText(last.response),
|
|
4442
|
+
label: "Considered response"
|
|
4443
|
+
},
|
|
4444
|
+
{
|
|
4445
|
+
after: fieldValueText(proposal.unknowns),
|
|
4446
|
+
before: last.unknowns === null ? null : fieldValueText(last.unknowns),
|
|
4447
|
+
label: "To check"
|
|
4448
|
+
}
|
|
4449
|
+
].flatMap(({ after, before, label }) => before === null || before === after ? [] : [{
|
|
4450
|
+
after,
|
|
4451
|
+
before,
|
|
4452
|
+
label
|
|
4453
|
+
}]);
|
|
4454
|
+
}
|
|
4455
|
+
function ChangeSummary({ last, proposal }) {
|
|
4456
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
4457
|
+
className: "dt-ses-row",
|
|
4458
|
+
children: [
|
|
4459
|
+
/* @__PURE__ */ jsx("p", {
|
|
4460
|
+
className: "dt-ses-subhead",
|
|
4461
|
+
children: proposal.need
|
|
4462
|
+
}),
|
|
4463
|
+
/* @__PURE__ */ jsxs("p", {
|
|
4464
|
+
className: "dt-meta",
|
|
4465
|
+
children: [
|
|
4466
|
+
"Changed since the brief of ",
|
|
4467
|
+
formatDate(last.at),
|
|
4468
|
+
" (revision",
|
|
4469
|
+
" ",
|
|
4470
|
+
last.revision,
|
|
4471
|
+
" → ",
|
|
4472
|
+
proposal.revision,
|
|
4473
|
+
")"
|
|
4474
|
+
]
|
|
4475
|
+
}),
|
|
4476
|
+
/* @__PURE__ */ jsx("ul", {
|
|
4477
|
+
className: "dt-ses-list",
|
|
4478
|
+
children: differences(proposal, last).map((difference) => /* @__PURE__ */ jsxs("li", { children: [
|
|
4479
|
+
difference.label,
|
|
4480
|
+
": ",
|
|
4481
|
+
difference.before,
|
|
4482
|
+
" → ",
|
|
4483
|
+
difference.after
|
|
4484
|
+
] }, difference.label))
|
|
4485
|
+
})
|
|
4486
|
+
]
|
|
4487
|
+
});
|
|
4488
|
+
}
|
|
4489
|
+
function ChangedSinceBriefs({ detail, proposalIds }) {
|
|
4490
|
+
const selected = new Set(proposalIds);
|
|
4491
|
+
const changed = detail.proposals.flatMap((proposal) => {
|
|
4492
|
+
const last = proposal.lastHandoff;
|
|
4493
|
+
return selected.has(proposal.id) && last && last.revision < proposal.revision ? [{
|
|
4494
|
+
last,
|
|
4495
|
+
proposal
|
|
4496
|
+
}] : [];
|
|
4497
|
+
});
|
|
4498
|
+
if (changed.length === 0) return null;
|
|
4499
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
4500
|
+
className: "dt-ses-rows",
|
|
4501
|
+
children: changed.map(({ last, proposal }) => /* @__PURE__ */ jsx(ChangeSummary, {
|
|
4502
|
+
last,
|
|
4503
|
+
proposal
|
|
4504
|
+
}, proposal.id))
|
|
4505
|
+
});
|
|
4506
|
+
}
|
|
4507
|
+
function deliveryLine(handoff) {
|
|
4508
|
+
const kinds = new Set(handoff.deliveries.map((delivery) => delivery.kind));
|
|
4509
|
+
if (kinds.size === 0) return "not copied or exported";
|
|
4510
|
+
return [...kinds].join(" and ");
|
|
4511
|
+
}
|
|
4512
|
+
function intentionsLine(handoff) {
|
|
4513
|
+
const [only, ...others] = handoff.proposals;
|
|
4514
|
+
if (only && others.length === 0) return `“${only.need}” revision ${only.revision}`;
|
|
4515
|
+
return countLabel(handoff.proposals.length, "intention");
|
|
4516
|
+
}
|
|
4517
|
+
function PreviousBriefs({ handoffs }) {
|
|
4518
|
+
if (handoffs.length === 0) return null;
|
|
4519
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
4520
|
+
className: "dt-ses-form",
|
|
4521
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
4522
|
+
className: "dt-ses-label",
|
|
4523
|
+
children: "Previous briefs"
|
|
4524
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
4525
|
+
className: "dt-ses-rows",
|
|
4526
|
+
children: handoffs.map((handoff) => {
|
|
4527
|
+
const date = formatDate(handoff.createdAt);
|
|
4528
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsxs("summary", { children: [
|
|
4529
|
+
date,
|
|
4530
|
+
" · ",
|
|
4531
|
+
intentionsLine(handoff),
|
|
4532
|
+
" · ",
|
|
4533
|
+
deliveryLine(handoff)
|
|
4534
|
+
] }), /* @__PURE__ */ jsx("textarea", {
|
|
4535
|
+
"aria-label": `Brief of ${date}`,
|
|
4536
|
+
className: "dt-field dt-ses-brief",
|
|
4537
|
+
readOnly: true,
|
|
4538
|
+
rows: 8,
|
|
4539
|
+
value: handoff.brief
|
|
4540
|
+
})] }) }, handoff.id);
|
|
4541
|
+
})
|
|
4542
|
+
})]
|
|
4543
|
+
});
|
|
4544
|
+
}
|
|
4545
|
+
//#endregion
|
|
3695
4546
|
//#region src/sessions/ui/handoff/handoff-selection.tsx
|
|
3696
4547
|
function keptProposals(detail) {
|
|
3697
4548
|
return detail.proposals.filter((proposal) => proposal.decision === "kept" && proposal.mergedIntoId === null);
|
|
3698
4549
|
}
|
|
3699
4550
|
function evidenceOf(detail, proposals) {
|
|
3700
|
-
const
|
|
3701
|
-
|
|
4551
|
+
const intentionIds = new Set(proposals.map((proposal) => proposal.id));
|
|
4552
|
+
const contributed = detail.history.filter((entry) => intentionIds.has(entry.intentionId) && entry.detachedAt === null);
|
|
4553
|
+
const cited = /* @__PURE__ */ new Set([...proposals.flatMap((proposal) => proposal.evidenceIds), ...contributed.flatMap((entry) => entry.evidenceIds)]);
|
|
4554
|
+
return [...detail.evidence, ...detail.contributedEvidence].filter((item) => cited.has(item.id) && item.status === "available");
|
|
3702
4555
|
}
|
|
3703
4556
|
function initialSelection(detail) {
|
|
3704
4557
|
const proposals = keptProposals(detail);
|
|
@@ -3746,7 +4599,7 @@ function HandoffChoices({ detail, onChange, selection }) {
|
|
|
3746
4599
|
const chosenProposalIds = new Set(selection.proposalIds);
|
|
3747
4600
|
const chosenEvidenceIds = new Set(selection.evidenceIds);
|
|
3748
4601
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ChoiceGroup, {
|
|
3749
|
-
legend: "
|
|
4602
|
+
legend: "Intentions",
|
|
3750
4603
|
children: proposals.map((proposal) => /* @__PURE__ */ jsx(CheckRow, {
|
|
3751
4604
|
isChecked: chosenProposalIds.has(proposal.id),
|
|
3752
4605
|
label: proposal.need,
|
|
@@ -3883,6 +4736,10 @@ function SelectionStep({ detail, handoff }) {
|
|
|
3883
4736
|
onChange: handoff.select,
|
|
3884
4737
|
selection: handoff.selection
|
|
3885
4738
|
}),
|
|
4739
|
+
/* @__PURE__ */ jsx(ChangedSinceBriefs, {
|
|
4740
|
+
detail,
|
|
4741
|
+
proposalIds: handoff.selection.proposalIds
|
|
4742
|
+
}),
|
|
3886
4743
|
/* @__PURE__ */ jsxs("div", {
|
|
3887
4744
|
className: "dt-actions",
|
|
3888
4745
|
children: [/* @__PURE__ */ jsx("button", {
|
|
@@ -3933,196 +4790,60 @@ function HandoffView({ detail, onClose, review }) {
|
|
|
3933
4790
|
/* @__PURE__ */ jsx("p", {
|
|
3934
4791
|
className: "dt-meta",
|
|
3935
4792
|
children: "Copied or exported briefs can't be recalled."
|
|
3936
|
-
})
|
|
4793
|
+
}),
|
|
4794
|
+
/* @__PURE__ */ jsx(PreviousBriefs, { handoffs: detail.handoffs })
|
|
3937
4795
|
]
|
|
3938
4796
|
});
|
|
3939
4797
|
}
|
|
3940
4798
|
//#endregion
|
|
3941
|
-
//#region src/sessions/ui/review/
|
|
3942
|
-
function
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
4799
|
+
//#region src/sessions/ui/review/guarded-save.tsx
|
|
4800
|
+
function useGuardedSave() {
|
|
4801
|
+
const { onUnauthorized } = useSessionWorkspace();
|
|
4802
|
+
const [problem, setProblem] = useState(null);
|
|
4803
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
4804
|
+
const save = async (write) => {
|
|
4805
|
+
setIsSaving(true);
|
|
4806
|
+
setProblem(null);
|
|
4807
|
+
try {
|
|
4808
|
+
await write();
|
|
4809
|
+
} catch (error) {
|
|
4810
|
+
if (isUnauthorized(error)) onUnauthorized();
|
|
4811
|
+
else if (error instanceof SessionApiError && error.status === 409) setProblem({ kind: "stale" });
|
|
4812
|
+
else setProblem({
|
|
4813
|
+
kind: "failed",
|
|
4814
|
+
message: failureMessage(error)
|
|
4815
|
+
});
|
|
4816
|
+
}
|
|
4817
|
+
setIsSaving(false);
|
|
4818
|
+
};
|
|
4819
|
+
return {
|
|
4820
|
+
isSaving,
|
|
4821
|
+
problem,
|
|
4822
|
+
save
|
|
4823
|
+
};
|
|
3951
4824
|
}
|
|
3952
|
-
function
|
|
3953
|
-
const { refinement } = proposal;
|
|
3954
|
-
if (refinement?.state === "running") return /* @__PURE__ */ jsx("p", {
|
|
3955
|
-
className: "dt-status",
|
|
3956
|
-
children: "Refining…"
|
|
3957
|
-
});
|
|
3958
|
-
if (refinement?.state === "outdated") return /* @__PURE__ */ jsx("p", {
|
|
3959
|
-
className: "dt-meta",
|
|
3960
|
-
children: "The proposal changed while it was being refined. Add your precision again."
|
|
3961
|
-
});
|
|
3962
|
-
if (refinement?.state !== "failed") return null;
|
|
4825
|
+
function SaveProblemMessage({ onReload, problem }) {
|
|
3963
4826
|
return /* @__PURE__ */ jsxs("div", {
|
|
3964
4827
|
className: "dt-actions",
|
|
3965
|
-
|
|
4828
|
+
role: "alert",
|
|
4829
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
3966
4830
|
className: "dt-error",
|
|
3967
|
-
children:
|
|
3968
|
-
}), /* @__PURE__ */ jsx("button", {
|
|
4831
|
+
children: problem.kind === "stale" ? "Someone changed this since you opened it. Reload it, then edit again." : `Not saved: ${problem.message}`
|
|
4832
|
+
}), problem.kind === "stale" && /* @__PURE__ */ jsx("button", {
|
|
3969
4833
|
className: "dt-btn",
|
|
3970
|
-
onClick:
|
|
4834
|
+
onClick: onReload,
|
|
3971
4835
|
type: "button",
|
|
3972
|
-
children: "
|
|
4836
|
+
children: "Reload"
|
|
3973
4837
|
})]
|
|
3974
4838
|
});
|
|
3975
4839
|
}
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
3984
|
-
className: "dt-ses-form",
|
|
3985
|
-
children: [
|
|
3986
|
-
proposal.question && /* @__PURE__ */ jsx("p", {
|
|
3987
|
-
className: "dt-note-text",
|
|
3988
|
-
children: proposal.question
|
|
3989
|
-
}),
|
|
3990
|
-
/* @__PURE__ */ jsx("textarea", {
|
|
3991
|
-
"aria-label": proposal.question ? "Your answer" : "Add a precision…",
|
|
3992
|
-
className: "dt-field",
|
|
3993
|
-
disabled: isRefining,
|
|
3994
|
-
maxLength: 1e3,
|
|
3995
|
-
onChange: (event) => setText(event.target.value),
|
|
3996
|
-
placeholder: "Add a precision…",
|
|
3997
|
-
rows: 2,
|
|
3998
|
-
value: text
|
|
3999
|
-
}),
|
|
4000
|
-
/* @__PURE__ */ jsx("div", {
|
|
4001
|
-
className: "dt-actions",
|
|
4002
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
4003
|
-
className: "dt-btn",
|
|
4004
|
-
disabled: isRefining || text.trim() === "",
|
|
4005
|
-
onClick: () => send(text.trim()),
|
|
4006
|
-
type: "button",
|
|
4007
|
-
children: "Update proposal"
|
|
4008
|
-
})
|
|
4009
|
-
}),
|
|
4010
|
-
/* @__PURE__ */ jsx(RefinementState, {
|
|
4011
|
-
onRetry: () => lastClarification && send(lastClarification),
|
|
4012
|
-
proposal
|
|
4013
|
-
})
|
|
4014
|
-
]
|
|
4015
|
-
});
|
|
4016
|
-
}
|
|
4017
|
-
function Sources({ context, proposal }) {
|
|
4018
|
-
const segmentIds = new Set(proposal.segmentIds);
|
|
4019
|
-
const evidenceIds = new Set(proposal.evidenceIds);
|
|
4020
|
-
const segments = context.detail.segments.filter((segment) => segmentIds.has(segment.id));
|
|
4021
|
-
const evidence = context.detail.evidence.filter((item) => evidenceIds.has(item.id));
|
|
4022
|
-
if (segments.length + evidence.length === 0) return null;
|
|
4023
|
-
return /* @__PURE__ */ jsxs("section", { children: [
|
|
4024
|
-
/* @__PURE__ */ jsx("h4", {
|
|
4025
|
-
className: "dt-ses-label",
|
|
4026
|
-
children: "Sources"
|
|
4027
|
-
}),
|
|
4028
|
-
segments.map((segment) => /* @__PURE__ */ jsxs("button", {
|
|
4029
|
-
className: "dt-ses-quote",
|
|
4030
|
-
onClick: () => context.onShowSegment(segment.id),
|
|
4031
|
-
type: "button",
|
|
4032
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
4033
|
-
className: "dt-ses-time",
|
|
4034
|
-
children: formatClock(segment.startMs)
|
|
4035
|
-
}), ` “${segment.text}”`]
|
|
4036
|
-
}, segment.id)),
|
|
4037
|
-
/* @__PURE__ */ jsx("div", {
|
|
4038
|
-
className: "dt-ses-thumbs",
|
|
4039
|
-
children: evidence.map((item) => /* @__PURE__ */ jsx(EvidenceThumb, { evidence: item }, item.id))
|
|
4040
|
-
})
|
|
4041
|
-
] });
|
|
4042
|
-
}
|
|
4043
|
-
function ProposalDetails({ context, proposal }) {
|
|
4044
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4045
|
-
/* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
|
|
4046
|
-
className: "dt-ses-label",
|
|
4047
|
-
children: "What you said or showed — not verified"
|
|
4048
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
4049
|
-
className: "dt-note-text",
|
|
4050
|
-
children: proposal.observed
|
|
4051
|
-
})] }),
|
|
4052
|
-
proposal.response && /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
|
|
4053
|
-
className: "dt-ses-label",
|
|
4054
|
-
children: "Considered response"
|
|
4055
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
4056
|
-
className: "dt-note-text",
|
|
4057
|
-
children: proposal.response
|
|
4058
|
-
})] }),
|
|
4059
|
-
/* @__PURE__ */ jsx(ListSection, {
|
|
4060
|
-
items: proposal.setAside,
|
|
4061
|
-
title: "Set aside"
|
|
4062
|
-
}),
|
|
4063
|
-
/* @__PURE__ */ jsx(ListSection, {
|
|
4064
|
-
items: proposal.unknowns,
|
|
4065
|
-
title: "Unknowns"
|
|
4066
|
-
}),
|
|
4067
|
-
context.canEdit && /* @__PURE__ */ jsx(ClarifyBox, {
|
|
4068
|
-
context,
|
|
4069
|
-
proposal
|
|
4070
|
-
}),
|
|
4071
|
-
/* @__PURE__ */ jsx(Sources, {
|
|
4072
|
-
context,
|
|
4073
|
-
proposal
|
|
4074
|
-
})
|
|
4075
|
-
] });
|
|
4076
|
-
}
|
|
4077
|
-
//#endregion
|
|
4078
|
-
//#region src/sessions/ui/review/guarded-save.tsx
|
|
4079
|
-
function useGuardedSave() {
|
|
4080
|
-
const { onUnauthorized } = useSessionWorkspace();
|
|
4081
|
-
const [problem, setProblem] = useState(null);
|
|
4082
|
-
const [isSaving, setIsSaving] = useState(false);
|
|
4083
|
-
const save = async (write) => {
|
|
4084
|
-
setIsSaving(true);
|
|
4085
|
-
setProblem(null);
|
|
4086
|
-
try {
|
|
4087
|
-
await write();
|
|
4088
|
-
} catch (error) {
|
|
4089
|
-
if (isUnauthorized(error)) onUnauthorized();
|
|
4090
|
-
else if (error instanceof SessionApiError && error.status === 409) setProblem({ kind: "stale" });
|
|
4091
|
-
else setProblem({
|
|
4092
|
-
kind: "failed",
|
|
4093
|
-
message: failureMessage(error)
|
|
4094
|
-
});
|
|
4095
|
-
}
|
|
4096
|
-
setIsSaving(false);
|
|
4097
|
-
};
|
|
4098
|
-
return {
|
|
4099
|
-
isSaving,
|
|
4100
|
-
problem,
|
|
4101
|
-
save
|
|
4102
|
-
};
|
|
4103
|
-
}
|
|
4104
|
-
function SaveProblemMessage({ onReload, problem }) {
|
|
4105
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
4106
|
-
className: "dt-actions",
|
|
4107
|
-
role: "alert",
|
|
4108
|
-
children: [/* @__PURE__ */ jsx("p", {
|
|
4109
|
-
className: "dt-error",
|
|
4110
|
-
children: problem.kind === "stale" ? "Someone changed this since you opened it. Reload it, then edit again." : `Not saved: ${problem.message}`
|
|
4111
|
-
}), problem.kind === "stale" && /* @__PURE__ */ jsx("button", {
|
|
4112
|
-
className: "dt-btn",
|
|
4113
|
-
onClick: onReload,
|
|
4114
|
-
type: "button",
|
|
4115
|
-
children: "Reload"
|
|
4116
|
-
})]
|
|
4117
|
-
});
|
|
4118
|
-
}
|
|
4119
|
-
//#endregion
|
|
4120
|
-
//#region src/sessions/ui/review/use-session-detail.ts
|
|
4121
|
-
const POLL_INTERVAL_MS = 2e3;
|
|
4122
|
-
function nextRequest(current) {
|
|
4123
|
-
return {
|
|
4124
|
-
...current,
|
|
4125
|
-
count: current.count + 1
|
|
4840
|
+
//#endregion
|
|
4841
|
+
//#region src/sessions/ui/review/use-session-detail.ts
|
|
4842
|
+
const POLL_INTERVAL_MS = 2e3;
|
|
4843
|
+
function nextRequest(current) {
|
|
4844
|
+
return {
|
|
4845
|
+
...current,
|
|
4846
|
+
count: current.count + 1
|
|
4126
4847
|
};
|
|
4127
4848
|
}
|
|
4128
4849
|
function withProposal(detail, proposal) {
|
|
@@ -4342,147 +5063,2295 @@ function ProposalEditor({ onDone, proposal, review }) {
|
|
|
4342
5063
|
]
|
|
4343
5064
|
});
|
|
4344
5065
|
}
|
|
4345
|
-
|
|
4346
|
-
//#region src/sessions/ui/review/proposals/use-proposal-actions.ts
|
|
4347
|
-
const NOTICE_NEED_LENGTH = 60;
|
|
4348
|
-
const DECISION_NOTICE = {
|
|
4349
|
-
dismissed: "Dismissed",
|
|
4350
|
-
kept: "Kept",
|
|
4351
|
-
later: "Saved for later",
|
|
4352
|
-
pending: "Back to undecided"
|
|
4353
|
-
};
|
|
4354
|
-
function quotedNeed(need) {
|
|
4355
|
-
return `“${need.length > NOTICE_NEED_LENGTH ? `${need.slice(0, 59)}…` : need}”`;
|
|
4356
|
-
}
|
|
4357
|
-
function useProposalSave(review) {
|
|
5066
|
+
function ResponseEditor({ onDone, proposal, review }) {
|
|
4358
5067
|
const { api } = useSessionWorkspace();
|
|
4359
|
-
const
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
return proposal;
|
|
4364
|
-
};
|
|
4365
|
-
}
|
|
4366
|
-
function decideWithUndo(save) {
|
|
4367
|
-
const restore = async (proposalId, decision) => {
|
|
4368
|
-
if (await save("The decision was not undone", (client) => client.decideProposal({
|
|
4369
|
-
decision,
|
|
4370
|
-
proposalId
|
|
4371
|
-
}))) announce("Decision undone");
|
|
4372
|
-
};
|
|
4373
|
-
return async (proposal, decision) => {
|
|
4374
|
-
const previous = proposal.decision;
|
|
4375
|
-
if (await save("The decision was not saved", (client) => client.decideProposal({
|
|
4376
|
-
decision,
|
|
4377
|
-
proposalId: proposal.id
|
|
4378
|
-
}))) announce(`${DECISION_NOTICE[decision]} ${quotedNeed(proposal.need)}`, {
|
|
4379
|
-
label: "Undo",
|
|
4380
|
-
run: () => restore(proposal.id, previous)
|
|
4381
|
-
});
|
|
4382
|
-
};
|
|
4383
|
-
}
|
|
4384
|
-
function useProposalActions(review) {
|
|
4385
|
-
const save = useProposalSave(review);
|
|
4386
|
-
return {
|
|
4387
|
-
clarify: (proposal, text) => save("The precision was not sent", (client) => client.clarifyProposal({
|
|
5068
|
+
const { isSaving, problem, save } = useGuardedSave();
|
|
5069
|
+
const [response, setResponse] = useState(proposal.response ?? "");
|
|
5070
|
+
const submit = () => save(async () => {
|
|
5071
|
+
const saved = await api.editProposal({
|
|
4388
5072
|
expectedRevision: proposal.revision,
|
|
4389
5073
|
proposalId: proposal.id,
|
|
4390
|
-
|
|
4391
|
-
})
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
5074
|
+
response: response.trim() || null
|
|
5075
|
+
});
|
|
5076
|
+
review.patch((detail) => withProposal(detail, saved));
|
|
5077
|
+
onDone();
|
|
5078
|
+
});
|
|
5079
|
+
return /* @__PURE__ */ jsxs("form", {
|
|
5080
|
+
className: "dt-ses-form",
|
|
5081
|
+
onSubmit: (event) => {
|
|
5082
|
+
event.preventDefault();
|
|
5083
|
+
submit();
|
|
4398
5084
|
},
|
|
4399
|
-
save,
|
|
4400
|
-
separate: (proposalId) => save("The suggestion was not cleared", (client) => client.separateProposal(proposalId)),
|
|
4401
|
-
verify: (proposalId, { note, status }) => save("The check was not saved", (client) => client.verifyProposal({
|
|
4402
|
-
note: note || void 0,
|
|
4403
|
-
proposalId,
|
|
4404
|
-
status
|
|
4405
|
-
}))
|
|
4406
|
-
};
|
|
4407
|
-
}
|
|
4408
|
-
//#endregion
|
|
4409
|
-
//#region src/sessions/ui/review/proposals/verification.tsx
|
|
4410
|
-
const OUTCOME_LABELS = {
|
|
4411
|
-
needs_work: "Needs more work",
|
|
4412
|
-
works: "Works"
|
|
4413
|
-
};
|
|
4414
|
-
function Verification({ context, proposal }) {
|
|
4415
|
-
const [note, setNote] = useState(proposal.verification?.note ?? "");
|
|
4416
|
-
if (proposal.handedOffRevision === null) return null;
|
|
4417
|
-
const { verification } = proposal;
|
|
4418
|
-
return /* @__PURE__ */ jsxs("fieldset", {
|
|
4419
|
-
className: "dt-ses-verify",
|
|
4420
5085
|
children: [
|
|
4421
|
-
/* @__PURE__ */ jsx(
|
|
4422
|
-
|
|
4423
|
-
children: "Did the change work?"
|
|
4424
|
-
}),
|
|
4425
|
-
verification && /* @__PURE__ */ jsxs("p", {
|
|
4426
|
-
className: "dt-meta",
|
|
4427
|
-
children: [
|
|
4428
|
-
"Checked: ",
|
|
4429
|
-
OUTCOME_LABELS[verification.status],
|
|
4430
|
-
verification.note ? ` · ${verification.note}` : ""
|
|
4431
|
-
]
|
|
4432
|
-
}),
|
|
4433
|
-
/* @__PURE__ */ jsx("input", {
|
|
4434
|
-
"aria-label": "Note about the change (optional)",
|
|
4435
|
-
className: "dt-field",
|
|
5086
|
+
/* @__PURE__ */ jsx(TextField, {
|
|
5087
|
+
label: "Considered response",
|
|
4436
5088
|
maxLength: 1e3,
|
|
4437
|
-
onChange:
|
|
4438
|
-
|
|
4439
|
-
value:
|
|
5089
|
+
onChange: setResponse,
|
|
5090
|
+
rows: 3,
|
|
5091
|
+
value: response
|
|
4440
5092
|
}),
|
|
4441
|
-
/* @__PURE__ */ jsx(
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
}),
|
|
4450
|
-
type: "button",
|
|
4451
|
-
children: OUTCOME_LABELS[status]
|
|
4452
|
-
}, status))
|
|
5093
|
+
problem && /* @__PURE__ */ jsx(SaveProblemMessage, {
|
|
5094
|
+
onReload: review.reload,
|
|
5095
|
+
problem
|
|
5096
|
+
}),
|
|
5097
|
+
/* @__PURE__ */ jsx(FormActions, {
|
|
5098
|
+
isSubmitDisabled: isSaving,
|
|
5099
|
+
onCancel: onDone,
|
|
5100
|
+
submitLabel: "Save response"
|
|
4453
5101
|
})
|
|
4454
5102
|
]
|
|
4455
5103
|
});
|
|
4456
5104
|
}
|
|
4457
5105
|
//#endregion
|
|
4458
|
-
//#region src/sessions/ui/review/
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
{
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
5106
|
+
//#region src/sessions/ui/review/continuation/share-preview.tsx
|
|
5107
|
+
function ExtractList({ detail, extracts }) {
|
|
5108
|
+
const segmentIds = new Set(extracts.segmentIds);
|
|
5109
|
+
const evidenceIds = new Set(extracts.evidenceIds);
|
|
5110
|
+
const quotes = detail.segments.filter((segment) => segmentIds.has(segment.id));
|
|
5111
|
+
const screenshots = detail.evidence.filter((item) => evidenceIds.has(item.id) && item.status === "available");
|
|
5112
|
+
if (quotes.length + screenshots.length === 0) return /* @__PURE__ */ jsx("p", { children: "No quote or screenshot from this private session becomes visible to your team." });
|
|
5113
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5114
|
+
/* @__PURE__ */ jsx("p", { children: "These extracts from this private session become visible to your team:" }),
|
|
5115
|
+
/* @__PURE__ */ jsx("ul", {
|
|
5116
|
+
className: "dt-ses-list",
|
|
5117
|
+
children: quotes.map((segment) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("span", {
|
|
5118
|
+
className: "dt-ses-time",
|
|
5119
|
+
children: formatClock(segment.startMs)
|
|
5120
|
+
}), ` “${segment.text}”`] }, segment.id))
|
|
5121
|
+
}),
|
|
5122
|
+
/* @__PURE__ */ jsx("div", {
|
|
5123
|
+
className: "dt-ses-thumbs",
|
|
5124
|
+
children: screenshots.map((item) => /* @__PURE__ */ jsx(EvidenceThumb, { evidence: item }, item.id))
|
|
5125
|
+
})
|
|
5126
|
+
] });
|
|
5127
|
+
}
|
|
5128
|
+
function SharePreview({ confirmation, detail, extracts }) {
|
|
5129
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5130
|
+
className: "dt-ses-banner",
|
|
5131
|
+
children: [/* @__PURE__ */ jsx(ExtractList, {
|
|
5132
|
+
detail,
|
|
5133
|
+
extracts
|
|
5134
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
5135
|
+
className: "dt-actions",
|
|
5136
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
5137
|
+
className: "dt-btn",
|
|
5138
|
+
"data-variant": "primary",
|
|
5139
|
+
disabled: confirmation.isBusy,
|
|
5140
|
+
onClick: confirmation.onConfirm,
|
|
5141
|
+
type: "button",
|
|
5142
|
+
children: confirmation.label
|
|
5143
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
5144
|
+
className: "dt-btn",
|
|
5145
|
+
onClick: confirmation.onCancel,
|
|
5146
|
+
type: "button",
|
|
5147
|
+
children: "Cancel"
|
|
5148
|
+
})]
|
|
5149
|
+
})]
|
|
5150
|
+
});
|
|
5151
|
+
}
|
|
5152
|
+
//#endregion
|
|
5153
|
+
//#region src/sessions/ui/review/continuation/intention-change.tsx
|
|
5154
|
+
const KIND_LABELS$1 = {
|
|
5155
|
+
constraint: "New constraint",
|
|
5156
|
+
contradiction: "Contradiction",
|
|
5157
|
+
outcome: "Observed result",
|
|
5158
|
+
precision: "Precision",
|
|
5159
|
+
solution: "Different solution"
|
|
5160
|
+
};
|
|
5161
|
+
function ChangeValue({ change, intention }) {
|
|
5162
|
+
if (change.operation === "add") return /* @__PURE__ */ jsxs("p", {
|
|
5163
|
+
className: "dt-ses-diff",
|
|
5164
|
+
children: ["+ ", change.value]
|
|
5165
|
+
});
|
|
5166
|
+
if (change.operation === "remove") return /* @__PURE__ */ jsxs("p", {
|
|
5167
|
+
className: "dt-ses-diff",
|
|
5168
|
+
children: ["− ", change.value]
|
|
5169
|
+
});
|
|
5170
|
+
return /* @__PURE__ */ jsxs("dl", {
|
|
5171
|
+
className: "dt-ses-diff",
|
|
5172
|
+
children: [
|
|
5173
|
+
/* @__PURE__ */ jsx("dt", {
|
|
5174
|
+
className: "dt-ses-label",
|
|
5175
|
+
children: "Current"
|
|
5176
|
+
}),
|
|
5177
|
+
/* @__PURE__ */ jsx("dd", { children: fieldValueText(intention[change.field]) }),
|
|
5178
|
+
/* @__PURE__ */ jsx("dt", {
|
|
5179
|
+
className: "dt-ses-label",
|
|
5180
|
+
children: "Proposed"
|
|
5181
|
+
}),
|
|
5182
|
+
/* @__PURE__ */ jsx("dd", { children: change.value })
|
|
5183
|
+
]
|
|
5184
|
+
});
|
|
5185
|
+
}
|
|
5186
|
+
function ChangeSources({ change, context }) {
|
|
5187
|
+
const segmentIds = new Set(change.segmentIds);
|
|
5188
|
+
const evidenceIds = new Set(change.evidenceIds);
|
|
5189
|
+
const segments = context.detail.segments.filter((segment) => segmentIds.has(segment.id));
|
|
5190
|
+
const evidence = context.detail.evidence.filter((item) => evidenceIds.has(item.id));
|
|
5191
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [segments.map((segment) => /* @__PURE__ */ jsxs("button", {
|
|
5192
|
+
className: "dt-ses-quote",
|
|
5193
|
+
onClick: (event) => context.navigation.onShowMoment(segment.id, event.currentTarget),
|
|
5194
|
+
type: "button",
|
|
5195
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5196
|
+
className: "dt-ses-time",
|
|
5197
|
+
children: formatClock(segment.startMs)
|
|
5198
|
+
}), ` “${segment.text}”`]
|
|
5199
|
+
}, segment.id)), evidence.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
5200
|
+
className: "dt-ses-thumbs",
|
|
5201
|
+
children: evidence.map((item) => /* @__PURE__ */ jsx(EvidenceThumb, { evidence: item }, item.id))
|
|
5202
|
+
})] });
|
|
5203
|
+
}
|
|
5204
|
+
function EditedValueForm({ handlers, initialValue, isSaving, label }) {
|
|
5205
|
+
const [value, setValue] = useState(initialValue);
|
|
5206
|
+
return /* @__PURE__ */ jsxs("form", {
|
|
5207
|
+
className: "dt-ses-form",
|
|
5208
|
+
onSubmit: (event) => {
|
|
5209
|
+
event.preventDefault();
|
|
5210
|
+
handlers.onAccept(value.trim());
|
|
5211
|
+
},
|
|
5212
|
+
children: [/* @__PURE__ */ jsx(TextField, {
|
|
5213
|
+
label,
|
|
5214
|
+
maxLength: 2e3,
|
|
5215
|
+
onChange: setValue,
|
|
5216
|
+
rows: 3,
|
|
5217
|
+
value
|
|
5218
|
+
}), /* @__PURE__ */ jsx(FormActions, {
|
|
5219
|
+
isSubmitDisabled: isSaving || value.trim() === "",
|
|
5220
|
+
onCancel: handlers.onCancel,
|
|
5221
|
+
submitLabel: "Accept edited"
|
|
5222
|
+
})]
|
|
5223
|
+
});
|
|
5224
|
+
}
|
|
5225
|
+
function PendingChoices({ handlers, isSaving }) {
|
|
5226
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5227
|
+
className: "dt-actions",
|
|
5228
|
+
children: [
|
|
5229
|
+
/* @__PURE__ */ jsx("button", {
|
|
5230
|
+
className: "dt-btn",
|
|
5231
|
+
"data-variant": "primary",
|
|
5232
|
+
disabled: isSaving,
|
|
5233
|
+
onClick: handlers.onAccept,
|
|
5234
|
+
type: "button",
|
|
5235
|
+
children: "Accept"
|
|
5236
|
+
}),
|
|
5237
|
+
/* @__PURE__ */ jsx("button", {
|
|
5238
|
+
className: "dt-btn",
|
|
5239
|
+
onClick: handlers.onEdit,
|
|
5240
|
+
type: "button",
|
|
5241
|
+
children: "Edit then accept"
|
|
5242
|
+
}),
|
|
5243
|
+
/* @__PURE__ */ jsx("button", {
|
|
5244
|
+
className: "dt-btn",
|
|
5245
|
+
onClick: handlers.onReject,
|
|
5246
|
+
type: "button",
|
|
5247
|
+
children: "Reject"
|
|
5248
|
+
})
|
|
5249
|
+
]
|
|
5250
|
+
});
|
|
5251
|
+
}
|
|
5252
|
+
function useChangeAcceptance(change, scope) {
|
|
5253
|
+
const [mode, setMode] = useState({ kind: "idle" });
|
|
5254
|
+
const { isSaving, problem, save } = useGuardedSave();
|
|
5255
|
+
const accept = (value, shareWithTeam) => {
|
|
5256
|
+
if (scope.continuation.contribution.needsShareConsent && !shareWithTeam) {
|
|
5257
|
+
setMode({
|
|
5258
|
+
kind: "sharing",
|
|
5259
|
+
value
|
|
5260
|
+
});
|
|
5261
|
+
return;
|
|
5262
|
+
}
|
|
5263
|
+
save(() => scope.actions.accept({
|
|
5264
|
+
changeId: change.id,
|
|
5265
|
+
shareWithTeam,
|
|
5266
|
+
value
|
|
5267
|
+
}));
|
|
5268
|
+
};
|
|
5269
|
+
return {
|
|
5270
|
+
accept,
|
|
5271
|
+
isSaving,
|
|
5272
|
+
mode,
|
|
5273
|
+
problem,
|
|
5274
|
+
setMode
|
|
5275
|
+
};
|
|
5276
|
+
}
|
|
5277
|
+
function PendingForm({ acceptance, change, scope }) {
|
|
5278
|
+
const { accept, isSaving, mode, setMode } = acceptance;
|
|
5279
|
+
const idle = () => setMode({ kind: "idle" });
|
|
5280
|
+
if (mode.kind === "sharing") return /* @__PURE__ */ jsx(SharePreview, {
|
|
5281
|
+
confirmation: {
|
|
5282
|
+
isBusy: isSaving,
|
|
5283
|
+
label: "Share and accept",
|
|
5284
|
+
onCancel: idle,
|
|
5285
|
+
onConfirm: () => accept(mode.value, true)
|
|
5286
|
+
},
|
|
5287
|
+
detail: scope.context.detail,
|
|
5288
|
+
extracts: change
|
|
5289
|
+
});
|
|
5290
|
+
if (mode.kind === "editing") return /* @__PURE__ */ jsx(EditedValueForm, {
|
|
5291
|
+
handlers: {
|
|
5292
|
+
onAccept: (value) => accept(value, false),
|
|
5293
|
+
onCancel: idle
|
|
5294
|
+
},
|
|
5295
|
+
initialValue: change.value,
|
|
5296
|
+
isSaving,
|
|
5297
|
+
label: `${FIELD_LABELS[change.field]} (your version)`
|
|
5298
|
+
});
|
|
5299
|
+
return /* @__PURE__ */ jsx(PendingChoices, {
|
|
5300
|
+
handlers: {
|
|
5301
|
+
onAccept: () => accept(null, false),
|
|
5302
|
+
onEdit: () => setMode({ kind: "editing" }),
|
|
5303
|
+
onReject: () => scope.actions.decide(change.id, "rejected")
|
|
5304
|
+
},
|
|
5305
|
+
isSaving
|
|
5306
|
+
});
|
|
5307
|
+
}
|
|
5308
|
+
function PendingActions({ change, scope }) {
|
|
5309
|
+
const acceptance = useChangeAcceptance(change, scope);
|
|
5310
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PendingForm, {
|
|
5311
|
+
acceptance,
|
|
5312
|
+
change,
|
|
5313
|
+
scope
|
|
5314
|
+
}), acceptance.problem && /* @__PURE__ */ jsx(SaveProblemMessage, {
|
|
5315
|
+
onReload: scope.context.review.reload,
|
|
5316
|
+
problem: acceptance.problem
|
|
5317
|
+
})] });
|
|
5318
|
+
}
|
|
5319
|
+
function ChangeDecision({ change, scope }) {
|
|
5320
|
+
if (change.decision === "accepted") return /* @__PURE__ */ jsx("p", {
|
|
5321
|
+
className: "dt-meta",
|
|
5322
|
+
children: "Accepted"
|
|
5323
|
+
});
|
|
5324
|
+
if (change.decision === "rejected") return /* @__PURE__ */ jsxs("div", {
|
|
5325
|
+
className: "dt-actions",
|
|
5326
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
5327
|
+
className: "dt-meta",
|
|
5328
|
+
children: "Rejected"
|
|
5329
|
+
}), scope.context.canEdit && /* @__PURE__ */ jsx("button", {
|
|
5330
|
+
className: "dt-btn",
|
|
5331
|
+
onClick: () => scope.actions.decide(change.id, "pending"),
|
|
5332
|
+
type: "button",
|
|
5333
|
+
children: "Undo"
|
|
5334
|
+
})]
|
|
5335
|
+
});
|
|
5336
|
+
return scope.context.canEdit ? /* @__PURE__ */ jsx(PendingActions, {
|
|
5337
|
+
change,
|
|
5338
|
+
scope
|
|
5339
|
+
}) : null;
|
|
5340
|
+
}
|
|
5341
|
+
function IntentionChangeRow({ change, intention, scope }) {
|
|
5342
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
5343
|
+
className: "dt-ses-change",
|
|
5344
|
+
"data-decision": change.decision,
|
|
5345
|
+
children: [
|
|
5346
|
+
/* @__PURE__ */ jsxs("div", {
|
|
5347
|
+
className: "dt-ses-chips",
|
|
5348
|
+
children: [
|
|
5349
|
+
/* @__PURE__ */ jsx("span", {
|
|
5350
|
+
className: "dt-chip",
|
|
5351
|
+
children: KIND_LABELS$1[change.kind]
|
|
5352
|
+
}),
|
|
5353
|
+
/* @__PURE__ */ jsx("span", {
|
|
5354
|
+
className: "dt-chip",
|
|
5355
|
+
children: FIELD_LABELS[change.field]
|
|
5356
|
+
}),
|
|
5357
|
+
change.hypothesis && /* @__PURE__ */ jsx("span", {
|
|
5358
|
+
className: "dt-chip",
|
|
5359
|
+
"data-tone": "accent",
|
|
5360
|
+
children: "Hypothesis"
|
|
5361
|
+
})
|
|
5362
|
+
]
|
|
5363
|
+
}),
|
|
5364
|
+
/* @__PURE__ */ jsx(ChangeValue, {
|
|
5365
|
+
change,
|
|
5366
|
+
intention
|
|
5367
|
+
}),
|
|
5368
|
+
/* @__PURE__ */ jsx("p", {
|
|
5369
|
+
className: "dt-meta",
|
|
5370
|
+
children: change.reason
|
|
5371
|
+
}),
|
|
5372
|
+
/* @__PURE__ */ jsx(ChangeSources, {
|
|
5373
|
+
change,
|
|
5374
|
+
context: scope.context
|
|
5375
|
+
}),
|
|
5376
|
+
/* @__PURE__ */ jsx(ChangeDecision, {
|
|
5377
|
+
change,
|
|
5378
|
+
scope
|
|
5379
|
+
})
|
|
5380
|
+
]
|
|
5381
|
+
});
|
|
5382
|
+
}
|
|
5383
|
+
//#endregion
|
|
5384
|
+
//#region src/sessions/ui/review/continuation/use-update-actions.ts
|
|
5385
|
+
function withUpdate(detail, update) {
|
|
5386
|
+
return {
|
|
5387
|
+
...detail,
|
|
5388
|
+
continuation: {
|
|
5389
|
+
contribution: update.contribution,
|
|
5390
|
+
intention: update.intention
|
|
5391
|
+
}
|
|
5392
|
+
};
|
|
5393
|
+
}
|
|
5394
|
+
function useChangeDecisions({ context, continuation }) {
|
|
5395
|
+
const { api } = useSessionWorkspace();
|
|
5396
|
+
const run = useSessionAction();
|
|
5397
|
+
const { contribution, intention } = continuation;
|
|
5398
|
+
const apply = (update) => context.review.patch((detail) => withUpdate(detail, update));
|
|
5399
|
+
return {
|
|
5400
|
+
accept: async ({ changeId, shareWithTeam, value }) => {
|
|
5401
|
+
const update = await api.acceptIntentionChanges({
|
|
5402
|
+
changes: [value === null ? { changeId } : {
|
|
5403
|
+
changeId,
|
|
5404
|
+
value
|
|
5405
|
+
}],
|
|
5406
|
+
contributionId: contribution.id,
|
|
5407
|
+
expectedRevision: intention?.revision ?? contribution.baseRevision,
|
|
5408
|
+
...shareWithTeam ? { shareWithTeam } : {}
|
|
5409
|
+
});
|
|
5410
|
+
apply(update);
|
|
5411
|
+
announce("Change accepted into the intention");
|
|
5412
|
+
},
|
|
5413
|
+
decide: async (changeId, decision) => {
|
|
5414
|
+
const update = await run("The change was not updated", () => api.reopenOrRejectChange({
|
|
5415
|
+
changeId,
|
|
5416
|
+
contributionId: contribution.id,
|
|
5417
|
+
decision
|
|
5418
|
+
}));
|
|
5419
|
+
if (update) apply(update);
|
|
5420
|
+
}
|
|
5421
|
+
};
|
|
5422
|
+
}
|
|
5423
|
+
function useReloadingAction(context) {
|
|
5424
|
+
const run = useSessionAction();
|
|
5425
|
+
return async (failureLabel, action) => {
|
|
5426
|
+
if (await run(failureLabel, action) === null) return false;
|
|
5427
|
+
context.review.reload();
|
|
5428
|
+
return true;
|
|
5429
|
+
};
|
|
5430
|
+
}
|
|
5431
|
+
function useContributionActions({ context, continuation }) {
|
|
5432
|
+
const { api } = useSessionWorkspace();
|
|
5433
|
+
const runThenReload = useReloadingAction(context);
|
|
5434
|
+
const contributionId = continuation.contribution.id;
|
|
5435
|
+
const intentionId = continuation.intention?.id;
|
|
5436
|
+
const sessionId = context.detail.session.id;
|
|
5437
|
+
return {
|
|
5438
|
+
answer: (text) => runThenReload("The answer was not sent", () => api.answerIntentionQuestion({
|
|
5439
|
+
contributionId,
|
|
5440
|
+
text
|
|
5441
|
+
})),
|
|
5442
|
+
detach: async () => {
|
|
5443
|
+
if (await runThenReload("The contribution was not removed", () => api.detachContribution(contributionId))) announce("This session's contribution was removed");
|
|
5444
|
+
},
|
|
5445
|
+
retry: async () => {
|
|
5446
|
+
await runThenReload("The comparison did not restart", () => api.analyze(sessionId));
|
|
5447
|
+
},
|
|
5448
|
+
verify: async (status, note) => {
|
|
5449
|
+
if (intentionId && await runThenReload("The check was not saved", () => api.verifyProposal({
|
|
5450
|
+
note,
|
|
5451
|
+
proposalId: intentionId,
|
|
5452
|
+
sessionId,
|
|
5453
|
+
status
|
|
5454
|
+
}))) announce("Check saved on the intention");
|
|
5455
|
+
}
|
|
5456
|
+
};
|
|
5457
|
+
}
|
|
5458
|
+
function useUpdateActions(scope) {
|
|
5459
|
+
return {
|
|
5460
|
+
...useChangeDecisions(scope),
|
|
5461
|
+
...useContributionActions(scope)
|
|
5462
|
+
};
|
|
5463
|
+
}
|
|
5464
|
+
//#endregion
|
|
5465
|
+
//#region src/sessions/ui/review/continuation/verdict.tsx
|
|
5466
|
+
const VERDICT_LABELS = {
|
|
5467
|
+
insufficient: "Not enough evidence",
|
|
5468
|
+
needs_work: "Still to improve",
|
|
5469
|
+
resolved: "Looks resolved"
|
|
5470
|
+
};
|
|
5471
|
+
function QuestionBox({ scope }) {
|
|
5472
|
+
const [text, setText] = useState("");
|
|
5473
|
+
const [isSending, setIsSending] = useState(false);
|
|
5474
|
+
const { contribution } = scope.continuation;
|
|
5475
|
+
if (!contribution.question) return null;
|
|
5476
|
+
const send = async () => {
|
|
5477
|
+
setIsSending(true);
|
|
5478
|
+
try {
|
|
5479
|
+
if (await scope.actions.answer(text.trim())) setText("");
|
|
5480
|
+
} finally {
|
|
5481
|
+
setIsSending(false);
|
|
5482
|
+
}
|
|
5483
|
+
};
|
|
5484
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
5485
|
+
className: "dt-ses-form",
|
|
5486
|
+
children: [
|
|
5487
|
+
/* @__PURE__ */ jsx("h4", {
|
|
5488
|
+
className: "dt-ses-label",
|
|
5489
|
+
children: "Question"
|
|
5490
|
+
}),
|
|
5491
|
+
/* @__PURE__ */ jsx("p", {
|
|
5492
|
+
className: "dt-note-text",
|
|
5493
|
+
children: contribution.question
|
|
5494
|
+
}),
|
|
5495
|
+
/* @__PURE__ */ jsx("ul", {
|
|
5496
|
+
className: "dt-ses-list",
|
|
5497
|
+
children: contribution.answers.map((answer) => /* @__PURE__ */ jsx("li", { children: answer.text }, answer.at))
|
|
5498
|
+
}),
|
|
5499
|
+
scope.context.canEdit && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("textarea", {
|
|
5500
|
+
"aria-label": "Your answer",
|
|
5501
|
+
className: "dt-field",
|
|
5502
|
+
maxLength: 1e3,
|
|
5503
|
+
onChange: (event) => setText(event.target.value),
|
|
5504
|
+
rows: 2,
|
|
5505
|
+
value: text
|
|
5506
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
5507
|
+
className: "dt-actions",
|
|
5508
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
5509
|
+
className: "dt-btn",
|
|
5510
|
+
disabled: isSending || text.trim() === "",
|
|
5511
|
+
onClick: send,
|
|
5512
|
+
type: "button",
|
|
5513
|
+
children: "Send answer"
|
|
5514
|
+
})
|
|
5515
|
+
})] })
|
|
5516
|
+
]
|
|
5517
|
+
});
|
|
5518
|
+
}
|
|
5519
|
+
function VerdictQuotes({ context, segmentIds }) {
|
|
5520
|
+
const cited = new Set(segmentIds);
|
|
5521
|
+
return context.detail.segments.filter((segment) => cited.has(segment.id)).map((segment) => /* @__PURE__ */ jsxs("button", {
|
|
5522
|
+
className: "dt-ses-quote",
|
|
5523
|
+
onClick: (event) => context.navigation.onShowMoment(segment.id, event.currentTarget),
|
|
5524
|
+
type: "button",
|
|
5525
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5526
|
+
className: "dt-ses-time",
|
|
5527
|
+
children: formatClock(segment.startMs)
|
|
5528
|
+
}), ` “${segment.text}”`]
|
|
5529
|
+
}, segment.id));
|
|
5530
|
+
}
|
|
5531
|
+
const CHECK_LABELS = {
|
|
5532
|
+
needs_work: "needs more work",
|
|
5533
|
+
works: "works"
|
|
5534
|
+
};
|
|
5535
|
+
function VerdictBox({ scope }) {
|
|
5536
|
+
const { actions, context, continuation } = scope;
|
|
5537
|
+
const { verdict } = continuation.contribution;
|
|
5538
|
+
if (!verdict) return null;
|
|
5539
|
+
const verification = continuation.intention?.verification;
|
|
5540
|
+
const checkedHere = verification?.sessionId === context.detail.session.id ? verification : null;
|
|
5541
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
5542
|
+
className: "dt-ses-form",
|
|
5543
|
+
children: [
|
|
5544
|
+
/* @__PURE__ */ jsx("h4", {
|
|
5545
|
+
className: "dt-ses-label",
|
|
5546
|
+
children: "AI reading"
|
|
5547
|
+
}),
|
|
5548
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
5549
|
+
/* @__PURE__ */ jsx("span", {
|
|
5550
|
+
className: "dt-chip",
|
|
5551
|
+
children: VERDICT_LABELS[verdict.status]
|
|
5552
|
+
}),
|
|
5553
|
+
" ",
|
|
5554
|
+
verdict.reason
|
|
5555
|
+
] }),
|
|
5556
|
+
/* @__PURE__ */ jsx(VerdictQuotes, {
|
|
5557
|
+
context,
|
|
5558
|
+
segmentIds: verdict.segmentIds
|
|
5559
|
+
}),
|
|
5560
|
+
checkedHere && /* @__PURE__ */ jsxs("p", {
|
|
5561
|
+
className: "dt-meta",
|
|
5562
|
+
role: "status",
|
|
5563
|
+
children: [
|
|
5564
|
+
"You confirmed it ",
|
|
5565
|
+
CHECK_LABELS[checkedHere.status],
|
|
5566
|
+
" on",
|
|
5567
|
+
" ",
|
|
5568
|
+
formatDate(checkedHere.at),
|
|
5569
|
+
"."
|
|
5570
|
+
]
|
|
5571
|
+
}),
|
|
5572
|
+
context.canEdit && /* @__PURE__ */ jsxs("div", {
|
|
5573
|
+
className: "dt-actions",
|
|
5574
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
5575
|
+
className: "dt-btn",
|
|
5576
|
+
onClick: () => actions.verify("works", verdict.reason),
|
|
5577
|
+
type: "button",
|
|
5578
|
+
children: "Confirm: works"
|
|
5579
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
5580
|
+
className: "dt-btn",
|
|
5581
|
+
onClick: () => actions.verify("needs_work", verdict.reason),
|
|
5582
|
+
type: "button",
|
|
5583
|
+
children: "Confirm: needs more work"
|
|
5584
|
+
})]
|
|
5585
|
+
})
|
|
5586
|
+
]
|
|
5587
|
+
});
|
|
5588
|
+
}
|
|
5589
|
+
//#endregion
|
|
5590
|
+
//#region src/sessions/ui/review/continuation/update-card.tsx
|
|
5591
|
+
const DETACH_COPY = "Remove what this session added to the intention? Each field that still holds the value this session set goes back to its previous value. Fields changed since then are kept, and the intention is marked to re-examine.";
|
|
5592
|
+
const INTENTION_DELETED = "The intention this session continued was deleted. This session stays as it was recorded.";
|
|
5593
|
+
const INTENTION_HIDDEN = "The intention this session continued is no longer visible to you.";
|
|
5594
|
+
function ContinuationHeader({ continuation, onOpenSession }) {
|
|
5595
|
+
const { contribution, intention } = continuation;
|
|
5596
|
+
if (!intention) return /* @__PURE__ */ jsx("p", {
|
|
5597
|
+
className: "dt-meta",
|
|
5598
|
+
children: contribution.detachedAt === null ? INTENTION_HIDDEN : INTENTION_DELETED
|
|
5599
|
+
});
|
|
5600
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5601
|
+
className: "dt-ses-banner",
|
|
5602
|
+
children: [/* @__PURE__ */ jsxs("p", { children: [
|
|
5603
|
+
"Continues “",
|
|
5604
|
+
intention.need,
|
|
5605
|
+
"”"
|
|
5606
|
+
] }), /* @__PURE__ */ jsx("div", {
|
|
5607
|
+
className: "dt-actions",
|
|
5608
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
5609
|
+
className: "dt-btn",
|
|
5610
|
+
onClick: () => onOpenSession(intention.sessionId),
|
|
5611
|
+
type: "button",
|
|
5612
|
+
children: "Open the intention"
|
|
5613
|
+
})
|
|
5614
|
+
})]
|
|
5615
|
+
});
|
|
5616
|
+
}
|
|
5617
|
+
function ContributionState({ scope }) {
|
|
5618
|
+
const { actions, context, continuation } = scope;
|
|
5619
|
+
const { contribution } = continuation;
|
|
5620
|
+
if (contribution.state === "recording") return /* @__PURE__ */ jsx("p", {
|
|
5621
|
+
className: "dt-status",
|
|
5622
|
+
children: "This session is still recording. What it changes appears once it ends."
|
|
5623
|
+
});
|
|
5624
|
+
if (contribution.state === "analyzing") return /* @__PURE__ */ jsxs("p", {
|
|
5625
|
+
className: "dt-status",
|
|
5626
|
+
children: [
|
|
5627
|
+
"Comparing with revision ",
|
|
5628
|
+
contribution.baseRevision,
|
|
5629
|
+
"…"
|
|
5630
|
+
]
|
|
5631
|
+
});
|
|
5632
|
+
if (contribution.state === "no_change") return /* @__PURE__ */ jsx("p", {
|
|
5633
|
+
className: "dt-meta",
|
|
5634
|
+
children: "No change to the intention"
|
|
5635
|
+
});
|
|
5636
|
+
if (contribution.state !== "failed") return null;
|
|
5637
|
+
const reason = context.detail.session.analysis.error ?? "Unknown error.";
|
|
5638
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5639
|
+
className: "dt-actions",
|
|
5640
|
+
role: "alert",
|
|
5641
|
+
children: [/* @__PURE__ */ jsxs("p", {
|
|
5642
|
+
className: "dt-error",
|
|
5643
|
+
children: ["The comparison failed: ", reason]
|
|
5644
|
+
}), context.canEdit && /* @__PURE__ */ jsx("button", {
|
|
5645
|
+
className: "dt-btn",
|
|
5646
|
+
onClick: actions.retry,
|
|
5647
|
+
type: "button",
|
|
5648
|
+
children: "Try again"
|
|
5649
|
+
})]
|
|
5650
|
+
});
|
|
5651
|
+
}
|
|
5652
|
+
function DetachContribution({ scope }) {
|
|
5653
|
+
const [isConfirming, setIsConfirming] = useState(false);
|
|
5654
|
+
if (!isConfirming) return /* @__PURE__ */ jsx("div", {
|
|
5655
|
+
className: "dt-actions",
|
|
5656
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
5657
|
+
className: "dt-btn",
|
|
5658
|
+
"data-variant": "danger",
|
|
5659
|
+
onClick: () => setIsConfirming(true),
|
|
5660
|
+
type: "button",
|
|
5661
|
+
children: "Remove this session's contribution"
|
|
5662
|
+
})
|
|
5663
|
+
});
|
|
5664
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5665
|
+
className: "dt-ses-banner",
|
|
5666
|
+
children: [/* @__PURE__ */ jsx("p", { children: DETACH_COPY }), /* @__PURE__ */ jsxs("div", {
|
|
5667
|
+
className: "dt-actions",
|
|
5668
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
5669
|
+
className: "dt-btn",
|
|
5670
|
+
"data-variant": "danger",
|
|
5671
|
+
onClick: scope.actions.detach,
|
|
5672
|
+
type: "button",
|
|
5673
|
+
children: "Remove contribution"
|
|
5674
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
5675
|
+
className: "dt-btn",
|
|
5676
|
+
onClick: () => setIsConfirming(false),
|
|
5677
|
+
type: "button",
|
|
5678
|
+
children: "Cancel"
|
|
5679
|
+
})]
|
|
5680
|
+
})]
|
|
5681
|
+
});
|
|
5682
|
+
}
|
|
5683
|
+
function ContributionBody({ intention, scope }) {
|
|
5684
|
+
const { contribution } = scope.continuation;
|
|
5685
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5686
|
+
/* @__PURE__ */ jsx(ContributionState, { scope }),
|
|
5687
|
+
contribution.stale && /* @__PURE__ */ jsxs("p", {
|
|
5688
|
+
className: "dt-ses-banner",
|
|
5689
|
+
role: "status",
|
|
5690
|
+
children: [
|
|
5691
|
+
"The intention changed since this comparison: it is now at revision",
|
|
5692
|
+
" ",
|
|
5693
|
+
intention.revision,
|
|
5694
|
+
". Check each change against it."
|
|
5695
|
+
]
|
|
5696
|
+
}),
|
|
5697
|
+
contribution.state === "ready" && /* @__PURE__ */ jsx("ul", {
|
|
5698
|
+
className: "dt-ses-rows",
|
|
5699
|
+
children: contribution.changes.map((change) => /* @__PURE__ */ jsx(IntentionChangeRow, {
|
|
5700
|
+
change,
|
|
5701
|
+
intention,
|
|
5702
|
+
scope
|
|
5703
|
+
}, change.id))
|
|
5704
|
+
}),
|
|
5705
|
+
/* @__PURE__ */ jsx(QuestionBox, { scope }),
|
|
5706
|
+
/* @__PURE__ */ jsx(VerdictBox, { scope }),
|
|
5707
|
+
/* @__PURE__ */ jsx(AppliedChanges, { applied: contribution.applied }),
|
|
5708
|
+
scope.context.canEdit && /* @__PURE__ */ jsx(DetachContribution, { scope })
|
|
5709
|
+
] });
|
|
5710
|
+
}
|
|
5711
|
+
function UpdateCard({ context, continuation }) {
|
|
5712
|
+
const headingId = useId();
|
|
5713
|
+
const actions = useUpdateActions({
|
|
5714
|
+
context,
|
|
5715
|
+
continuation
|
|
5716
|
+
});
|
|
5717
|
+
const { contribution, intention } = continuation;
|
|
5718
|
+
if (!intention) return null;
|
|
5719
|
+
const scope = {
|
|
5720
|
+
actions,
|
|
5721
|
+
context,
|
|
5722
|
+
continuation
|
|
5723
|
+
};
|
|
5724
|
+
return /* @__PURE__ */ jsxs("article", {
|
|
5725
|
+
"aria-labelledby": headingId,
|
|
5726
|
+
className: "dt-card",
|
|
5727
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
5728
|
+
className: "dt-ses-subhead",
|
|
5729
|
+
id: headingId,
|
|
5730
|
+
children: "What changes"
|
|
5731
|
+
}), contribution.detachedAt === null ? /* @__PURE__ */ jsx(ContributionBody, {
|
|
5732
|
+
intention,
|
|
5733
|
+
scope
|
|
5734
|
+
}) : /* @__PURE__ */ jsx("p", {
|
|
5735
|
+
className: "dt-meta",
|
|
5736
|
+
children: "This session's contribution was removed from the intention."
|
|
5737
|
+
})]
|
|
5738
|
+
});
|
|
5739
|
+
}
|
|
5740
|
+
//#endregion
|
|
5741
|
+
//#region src/sessions/ui/review/transcript/remove-evidence.tsx
|
|
5742
|
+
const REMOVE_COPY = "Remove this screenshot? The image is deleted for good. Briefs you already copied or exported can't be recalled.";
|
|
5743
|
+
function useFocusWhen(shouldFocus) {
|
|
5744
|
+
const ref = useRef(null);
|
|
5745
|
+
useEffect(() => {
|
|
5746
|
+
if (shouldFocus) ref.current?.focus();
|
|
5747
|
+
}, [shouldFocus]);
|
|
5748
|
+
return ref;
|
|
5749
|
+
}
|
|
5750
|
+
function RemoveConfirmation({ evidence, onCancel, removal }) {
|
|
5751
|
+
const { api } = useSessionWorkspace();
|
|
5752
|
+
const run = useSessionAction();
|
|
5753
|
+
const cancelRef = useFocusWhen(true);
|
|
5754
|
+
const remove = async () => {
|
|
5755
|
+
const removed = await run("The screenshot was not removed", () => api.removeEvidence(evidence.id));
|
|
5756
|
+
if (removed !== null) {
|
|
5757
|
+
removal.review.patch((detail) => withEvidence(detail, removed));
|
|
5758
|
+
removal.onRemoved(removed.id);
|
|
5759
|
+
announce("Screenshot removed");
|
|
5760
|
+
}
|
|
5761
|
+
};
|
|
5762
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5763
|
+
className: "dt-ses-banner",
|
|
5764
|
+
children: [/* @__PURE__ */ jsx("p", { children: REMOVE_COPY }), /* @__PURE__ */ jsxs("div", {
|
|
5765
|
+
className: "dt-actions",
|
|
5766
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
5767
|
+
className: "dt-btn",
|
|
5768
|
+
"data-variant": "danger",
|
|
5769
|
+
onClick: remove,
|
|
5770
|
+
type: "button",
|
|
5771
|
+
children: "Remove"
|
|
5772
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
5773
|
+
className: "dt-btn",
|
|
5774
|
+
onClick: onCancel,
|
|
5775
|
+
ref: cancelRef,
|
|
5776
|
+
type: "button",
|
|
5777
|
+
children: "Cancel"
|
|
5778
|
+
})]
|
|
5779
|
+
})]
|
|
5780
|
+
});
|
|
5781
|
+
}
|
|
5782
|
+
function RemoveEvidence({ evidence, removal }) {
|
|
5783
|
+
const [isConfirming, setIsConfirming] = useState(false);
|
|
5784
|
+
const [hasCancelled, setHasCancelled] = useState(false);
|
|
5785
|
+
const triggerRef = useFocusWhen(hasCancelled);
|
|
5786
|
+
if (isConfirming) return /* @__PURE__ */ jsx(RemoveConfirmation, {
|
|
5787
|
+
evidence,
|
|
5788
|
+
onCancel: () => {
|
|
5789
|
+
setIsConfirming(false);
|
|
5790
|
+
setHasCancelled(true);
|
|
5791
|
+
},
|
|
5792
|
+
removal
|
|
5793
|
+
});
|
|
5794
|
+
return /* @__PURE__ */ jsx("button", {
|
|
5795
|
+
"aria-label": `Remove screenshot at ${formatClock(evidence.atMs)}`,
|
|
5796
|
+
className: "dt-ses-link",
|
|
5797
|
+
onClick: () => {
|
|
5798
|
+
setIsConfirming(true);
|
|
5799
|
+
setHasCancelled(false);
|
|
5800
|
+
},
|
|
5801
|
+
ref: triggerRef,
|
|
5802
|
+
type: "button",
|
|
5803
|
+
children: "Remove"
|
|
5804
|
+
});
|
|
5805
|
+
}
|
|
5806
|
+
//#endregion
|
|
5807
|
+
//#region src/sessions/ui/review/moment/moment-labels.ts
|
|
5808
|
+
const URL_PATH = /^[a-z][a-z\d+.-]*:\/\/[^/?#]*([^?#]*)/i;
|
|
5809
|
+
const KIND_LABELS = {
|
|
5810
|
+
click: "Clicked",
|
|
5811
|
+
mark: "Marked",
|
|
5812
|
+
navigation: "Opened page",
|
|
5813
|
+
screenshot: "Screenshot",
|
|
5814
|
+
select: "Pointed at explicitly"
|
|
5815
|
+
};
|
|
5816
|
+
const ORIGIN_LABELS = {
|
|
5817
|
+
auto: "Linked automatically",
|
|
5818
|
+
human: "Corrected by you",
|
|
5819
|
+
unknown: "Context unknown"
|
|
5820
|
+
};
|
|
5821
|
+
function contextKindLabel(item) {
|
|
5822
|
+
if (item.kind === "dwell") return `Hovered ${preciseSeconds(item.durationMs ?? 0)}`;
|
|
5823
|
+
return KIND_LABELS[item.kind];
|
|
5824
|
+
}
|
|
5825
|
+
function pagePath(url) {
|
|
5826
|
+
if (!url) return null;
|
|
5827
|
+
return URL_PATH.exec(url)?.[1] || "/";
|
|
5828
|
+
}
|
|
5829
|
+
function contextLine(item) {
|
|
5830
|
+
const page = pagePath(item.url ?? item.frame?.url ?? null);
|
|
5831
|
+
const element = item.element?.label ?? item.label;
|
|
5832
|
+
return page ? `${element} · ${page}` : element;
|
|
5833
|
+
}
|
|
5834
|
+
function offsetFromPassage(atMs, segment) {
|
|
5835
|
+
if (atMs < segment.startMs) return `${preciseSeconds(segment.startMs - atMs)} before`;
|
|
5836
|
+
if (atMs > segment.endMs) return `${preciseSeconds(atMs - segment.endMs)} after`;
|
|
5837
|
+
return "during the passage";
|
|
5838
|
+
}
|
|
5839
|
+
const distanceFrom = (atMs, segment) => Math.max(segment.startMs - atMs, 0, atMs - segment.endMs);
|
|
5840
|
+
function nearestSegmentId(segments, atMs) {
|
|
5841
|
+
let nearest = null;
|
|
5842
|
+
for (const segment of segments) if (!nearest || distanceFrom(atMs, segment) < distanceFrom(atMs, nearest)) nearest = segment;
|
|
5843
|
+
return nearest?.id ?? null;
|
|
5844
|
+
}
|
|
5845
|
+
function gapsDuring(gaps, segment) {
|
|
5846
|
+
return gaps.filter((gap) => gap.startMs <= segment.endMs && (gap.endMs ?? Number.POSITIVE_INFINITY) >= segment.startMs);
|
|
5847
|
+
}
|
|
5848
|
+
//#endregion
|
|
5849
|
+
//#region src/sessions/ui/review/transcript/segment-row.tsx
|
|
5850
|
+
function SaveCancelActions({ isSaveDisabled, onCancel, onSave }) {
|
|
5851
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5852
|
+
className: "dt-actions",
|
|
5853
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
5854
|
+
className: "dt-btn",
|
|
5855
|
+
"data-variant": "primary",
|
|
5856
|
+
disabled: isSaveDisabled,
|
|
5857
|
+
onClick: onSave,
|
|
5858
|
+
type: "button",
|
|
5859
|
+
children: "Save"
|
|
5860
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
5861
|
+
className: "dt-btn",
|
|
5862
|
+
onClick: onCancel,
|
|
5863
|
+
type: "button",
|
|
5864
|
+
children: "Cancel"
|
|
5865
|
+
})]
|
|
5866
|
+
});
|
|
5867
|
+
}
|
|
5868
|
+
function SegmentEditor({ onDone, review, segment }) {
|
|
5869
|
+
const { api } = useSessionWorkspace();
|
|
5870
|
+
const { isSaving, problem, save } = useGuardedSave();
|
|
5871
|
+
const [text, setText] = useState(segment.text);
|
|
5872
|
+
const submit = () => save(async () => {
|
|
5873
|
+
const saved = await api.correctSegment({
|
|
5874
|
+
expectedRevision: segment.revision,
|
|
5875
|
+
segmentId: segment.id,
|
|
5876
|
+
text: text.trim()
|
|
5877
|
+
});
|
|
5878
|
+
review.patch((detail) => withSegment(detail, saved));
|
|
5879
|
+
review.reload();
|
|
5880
|
+
onDone();
|
|
5881
|
+
});
|
|
5882
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5883
|
+
className: "dt-ses-form",
|
|
5884
|
+
children: [
|
|
5885
|
+
/* @__PURE__ */ jsx("textarea", {
|
|
5886
|
+
"aria-label": `Passage at ${formatClock(segment.startMs)}`,
|
|
5887
|
+
className: "dt-field",
|
|
5888
|
+
maxLength: 5e3,
|
|
5889
|
+
onChange: (event) => setText(event.target.value),
|
|
5890
|
+
rows: 3,
|
|
5891
|
+
value: text
|
|
5892
|
+
}),
|
|
5893
|
+
problem && /* @__PURE__ */ jsx(SaveProblemMessage, {
|
|
5894
|
+
onReload: review.reload,
|
|
5895
|
+
problem
|
|
5896
|
+
}),
|
|
5897
|
+
/* @__PURE__ */ jsx(SaveCancelActions, {
|
|
5898
|
+
isSaveDisabled: isSaving || text.trim() === "",
|
|
5899
|
+
onCancel: onDone,
|
|
5900
|
+
onSave: submit
|
|
5901
|
+
})
|
|
5902
|
+
]
|
|
5903
|
+
});
|
|
5904
|
+
}
|
|
5905
|
+
function SegmentBody({ onType, review, segment }) {
|
|
5906
|
+
const { api } = useSessionWorkspace();
|
|
5907
|
+
const run = useSessionAction();
|
|
5908
|
+
const { state } = segment.transcription;
|
|
5909
|
+
const retry = async () => {
|
|
5910
|
+
const pending = await run("The transcription did not restart", () => api.retrySegment(segment.id));
|
|
5911
|
+
if (pending) review.patch((detail) => withSegment(detail, pending));
|
|
5912
|
+
};
|
|
5913
|
+
if (state === "pending" || state === "running") return /* @__PURE__ */ jsx("p", {
|
|
5914
|
+
className: "dt-status",
|
|
5915
|
+
children: "Transcribing…"
|
|
5916
|
+
});
|
|
5917
|
+
if (state === "failed" || state === "expired") return /* @__PURE__ */ jsxs("div", {
|
|
5918
|
+
className: "dt-actions",
|
|
5919
|
+
children: [
|
|
5920
|
+
/* @__PURE__ */ jsx("p", {
|
|
5921
|
+
className: "dt-error",
|
|
5922
|
+
children: state === "failed" ? "Couldn't transcribe" : "Audio expired — type what you said"
|
|
5923
|
+
}),
|
|
5924
|
+
onType && state === "failed" && segment.hasAudio && /* @__PURE__ */ jsx("button", {
|
|
5925
|
+
className: "dt-btn",
|
|
5926
|
+
onClick: retry,
|
|
5927
|
+
type: "button",
|
|
5928
|
+
children: "Retry"
|
|
5929
|
+
}),
|
|
5930
|
+
onType && /* @__PURE__ */ jsx("button", {
|
|
5931
|
+
className: "dt-btn",
|
|
5932
|
+
onClick: onType,
|
|
5933
|
+
type: "button",
|
|
5934
|
+
children: "Type it"
|
|
5935
|
+
})
|
|
5936
|
+
]
|
|
5937
|
+
});
|
|
5938
|
+
if (state === "empty" && segment.text === "") return /* @__PURE__ */ jsx("p", {
|
|
5939
|
+
className: "dt-meta",
|
|
5940
|
+
children: "No speech was heard here."
|
|
5941
|
+
});
|
|
5942
|
+
return /* @__PURE__ */ jsx("p", {
|
|
5943
|
+
className: "dt-note-text",
|
|
5944
|
+
children: segment.text
|
|
5945
|
+
});
|
|
5946
|
+
}
|
|
5947
|
+
function ContextChips({ segment }) {
|
|
5948
|
+
if (segment.context.length === 0) return null;
|
|
5949
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5950
|
+
className: "dt-ses-chips",
|
|
5951
|
+
children: [segment.contextAmbiguous && /* @__PURE__ */ jsx("span", {
|
|
5952
|
+
className: "dt-chip",
|
|
5953
|
+
children: "Unclear which element"
|
|
5954
|
+
}), segment.context.map((item) => /* @__PURE__ */ jsx("span", {
|
|
5955
|
+
className: "dt-chip",
|
|
5956
|
+
children: item.label
|
|
5957
|
+
}, `${item.kind}:${item.atMs}:${item.label}`))]
|
|
5958
|
+
});
|
|
5959
|
+
}
|
|
5960
|
+
function SegmentLinks({ onEdit, onShowMoment, time }) {
|
|
5961
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5962
|
+
className: "dt-actions",
|
|
5963
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
5964
|
+
"aria-label": `Show context at ${time}`,
|
|
5965
|
+
className: "dt-ses-link",
|
|
5966
|
+
onClick: (event) => onShowMoment(event.currentTarget),
|
|
5967
|
+
type: "button",
|
|
5968
|
+
children: "Show context"
|
|
5969
|
+
}), onEdit && /* @__PURE__ */ jsx("button", {
|
|
5970
|
+
"aria-label": `Edit passage at ${time}`,
|
|
5971
|
+
className: "dt-ses-link",
|
|
5972
|
+
onClick: onEdit,
|
|
5973
|
+
type: "button",
|
|
5974
|
+
children: "Edit"
|
|
5975
|
+
})]
|
|
5976
|
+
});
|
|
5977
|
+
}
|
|
5978
|
+
function SegmentRow({ editing, onShowMoment, segment }) {
|
|
5979
|
+
const { canEdit, review } = editing;
|
|
5980
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
5981
|
+
const time = formatClock(segment.startMs);
|
|
5982
|
+
const isSettled = ["done", "empty"].includes(segment.transcription.state);
|
|
5983
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
5984
|
+
className: "dt-ses-seg",
|
|
5985
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5986
|
+
className: "dt-ses-time",
|
|
5987
|
+
children: time
|
|
5988
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
5989
|
+
className: "dt-ses-seg-body",
|
|
5990
|
+
children: [
|
|
5991
|
+
isEditing ? /* @__PURE__ */ jsx(SegmentEditor, {
|
|
5992
|
+
onDone: () => setIsEditing(false),
|
|
5993
|
+
review,
|
|
5994
|
+
segment
|
|
5995
|
+
}) : /* @__PURE__ */ jsx(SegmentBody, {
|
|
5996
|
+
onType: canEdit ? () => setIsEditing(true) : void 0,
|
|
5997
|
+
review,
|
|
5998
|
+
segment
|
|
5999
|
+
}),
|
|
6000
|
+
/* @__PURE__ */ jsx(ContextChips, { segment }),
|
|
6001
|
+
/* @__PURE__ */ jsx(SegmentLinks, {
|
|
6002
|
+
onEdit: canEdit && isSettled && !isEditing ? () => setIsEditing(true) : null,
|
|
6003
|
+
onShowMoment,
|
|
6004
|
+
time
|
|
6005
|
+
})
|
|
6006
|
+
]
|
|
6007
|
+
})]
|
|
6008
|
+
});
|
|
6009
|
+
}
|
|
6010
|
+
//#endregion
|
|
6011
|
+
//#region src/sessions/ui/review/transcript/transcript-tab.tsx
|
|
6012
|
+
const GAP_LABELS = {
|
|
6013
|
+
hidden: "Tab hidden",
|
|
6014
|
+
interrupted: "Interrupted",
|
|
6015
|
+
limit: "Time limit reached",
|
|
6016
|
+
paused: "Paused"
|
|
6017
|
+
};
|
|
6018
|
+
function gapLabel(gap) {
|
|
6019
|
+
const label = GAP_LABELS[gap.reason];
|
|
6020
|
+
return gap.endMs === null ? label : `${label} · ${shortDuration(gap.endMs - gap.startMs)}`;
|
|
6021
|
+
}
|
|
6022
|
+
function markLabel(mark) {
|
|
6023
|
+
const note = mark.note ? ` — ${mark.note}` : "";
|
|
6024
|
+
if (mark.kind === "navigation") return `Went to ${mark.url ?? mark.label}`;
|
|
6025
|
+
return `${mark.kind === "select" ? "Pointed at" : "Marked a moment on"} ${mark.label}${note}`;
|
|
6026
|
+
}
|
|
6027
|
+
function TimelineNote({ atMs, children, onShowMoment }) {
|
|
6028
|
+
const time = formatClock(atMs);
|
|
6029
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
6030
|
+
className: "dt-ses-seg",
|
|
6031
|
+
"data-kind": "note",
|
|
6032
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
6033
|
+
className: "dt-ses-time",
|
|
6034
|
+
children: time
|
|
6035
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
6036
|
+
className: "dt-ses-seg-body",
|
|
6037
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
6038
|
+
className: "dt-meta",
|
|
6039
|
+
children
|
|
6040
|
+
}), onShowMoment && /* @__PURE__ */ jsx("button", {
|
|
6041
|
+
"aria-label": `Show the moment at ${time}`,
|
|
6042
|
+
className: "dt-ses-link",
|
|
6043
|
+
onClick: (event) => onShowMoment(event.currentTarget),
|
|
6044
|
+
type: "button",
|
|
6045
|
+
children: "Show moment"
|
|
6046
|
+
})]
|
|
6047
|
+
})]
|
|
6048
|
+
});
|
|
6049
|
+
}
|
|
6050
|
+
function buildTimeline(detail) {
|
|
6051
|
+
return [
|
|
6052
|
+
...detail.segments.map((segment) => ({
|
|
6053
|
+
atMs: segment.startMs,
|
|
6054
|
+
key: `s:${segment.id}`,
|
|
6055
|
+
segment
|
|
6056
|
+
})),
|
|
6057
|
+
...detail.session.gaps.map((gap) => ({
|
|
6058
|
+
atMs: gap.startMs,
|
|
6059
|
+
key: `g:${gap.startMs}`,
|
|
6060
|
+
label: gapLabel(gap),
|
|
6061
|
+
segmentId: null
|
|
6062
|
+
})),
|
|
6063
|
+
...detail.marks.map((mark) => ({
|
|
6064
|
+
atMs: mark.atMs,
|
|
6065
|
+
key: `m:${mark.kind}:${mark.atMs}`,
|
|
6066
|
+
label: markLabel(mark),
|
|
6067
|
+
segmentId: nearestSegmentId(detail.segments, mark.atMs)
|
|
6068
|
+
}))
|
|
6069
|
+
].sort((earlier, later) => earlier.atMs - later.atMs);
|
|
6070
|
+
}
|
|
6071
|
+
function noteMomentOpener(segmentId, onShowMoment) {
|
|
6072
|
+
if (!segmentId) return null;
|
|
6073
|
+
return (opener) => onShowMoment(segmentId, opener);
|
|
6074
|
+
}
|
|
6075
|
+
function useTypedPassage({ detail, review }) {
|
|
6076
|
+
const { api } = useSessionWorkspace();
|
|
6077
|
+
const run = useSessionAction();
|
|
6078
|
+
const [text, setText] = useState("");
|
|
6079
|
+
const [clientKey, setClientKey] = useState(() => crypto.randomUUID());
|
|
6080
|
+
const atMs = detail.segments.at(-1)?.endMs ?? 0;
|
|
6081
|
+
const add = async () => {
|
|
6082
|
+
const segment = await run("The passage was not added", () => api.addTypedSegment({
|
|
6083
|
+
clientKey,
|
|
6084
|
+
endMs: atMs,
|
|
6085
|
+
sessionId: detail.session.id,
|
|
6086
|
+
startMs: atMs,
|
|
6087
|
+
text: text.trim()
|
|
6088
|
+
}));
|
|
6089
|
+
if (segment) {
|
|
6090
|
+
review.patch((current) => withSegment(current, segment));
|
|
6091
|
+
setText("");
|
|
6092
|
+
setClientKey(crypto.randomUUID());
|
|
6093
|
+
}
|
|
6094
|
+
};
|
|
6095
|
+
return {
|
|
6096
|
+
add,
|
|
6097
|
+
setText,
|
|
6098
|
+
text
|
|
6099
|
+
};
|
|
6100
|
+
}
|
|
6101
|
+
function AddTypedPassage({ detail, review }) {
|
|
6102
|
+
const { add, setText, text } = useTypedPassage({
|
|
6103
|
+
detail,
|
|
6104
|
+
review
|
|
6105
|
+
});
|
|
6106
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
6107
|
+
className: "dt-ses-form",
|
|
6108
|
+
children: [/* @__PURE__ */ jsx("textarea", {
|
|
6109
|
+
"aria-label": "Add a typed passage",
|
|
6110
|
+
className: "dt-field",
|
|
6111
|
+
maxLength: 5e3,
|
|
6112
|
+
onChange: (event) => setText(event.target.value),
|
|
6113
|
+
placeholder: "Add a typed passage",
|
|
6114
|
+
rows: 2,
|
|
6115
|
+
value: text
|
|
6116
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
6117
|
+
className: "dt-actions",
|
|
6118
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
6119
|
+
className: "dt-btn",
|
|
6120
|
+
disabled: text.trim() === "",
|
|
6121
|
+
onClick: add,
|
|
6122
|
+
type: "button",
|
|
6123
|
+
children: "Add passage"
|
|
6124
|
+
})
|
|
6125
|
+
})]
|
|
6126
|
+
});
|
|
6127
|
+
}
|
|
6128
|
+
function TranscriptTab({ detail, onShowMoment, review }) {
|
|
6129
|
+
const { canEdit } = detail.session;
|
|
6130
|
+
const timeline = buildTimeline(detail);
|
|
6131
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6132
|
+
/* @__PURE__ */ jsx("p", {
|
|
6133
|
+
className: "dt-hint",
|
|
6134
|
+
children: "Audio is deleted once transcribed. You can still correct the text."
|
|
6135
|
+
}),
|
|
6136
|
+
timeline.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
6137
|
+
className: "dt-empty",
|
|
6138
|
+
children: "Nothing was said in this session."
|
|
6139
|
+
}),
|
|
6140
|
+
/* @__PURE__ */ jsx("ol", {
|
|
6141
|
+
className: "dt-ses-timeline",
|
|
6142
|
+
children: timeline.map((item) => "segment" in item ? /* @__PURE__ */ jsx(SegmentRow, {
|
|
6143
|
+
editing: {
|
|
6144
|
+
canEdit,
|
|
6145
|
+
review
|
|
6146
|
+
},
|
|
6147
|
+
onShowMoment: (opener) => onShowMoment(item.segment.id, opener),
|
|
6148
|
+
segment: item.segment
|
|
6149
|
+
}, item.key) : /* @__PURE__ */ jsx(TimelineNote, {
|
|
6150
|
+
atMs: item.atMs,
|
|
6151
|
+
onShowMoment: noteMomentOpener(item.segmentId, onShowMoment),
|
|
6152
|
+
children: item.label
|
|
6153
|
+
}, item.key))
|
|
6154
|
+
}),
|
|
6155
|
+
canEdit && /* @__PURE__ */ jsx(AddTypedPassage, {
|
|
6156
|
+
detail,
|
|
6157
|
+
review
|
|
6158
|
+
})
|
|
6159
|
+
] });
|
|
6160
|
+
}
|
|
6161
|
+
//#endregion
|
|
6162
|
+
//#region src/sessions/ui/review/moment/context-correction.tsx
|
|
6163
|
+
const AUTO_CHOICE = { kind: "auto" };
|
|
6164
|
+
const UNKNOWN_CHOICE = { kind: "unknown" };
|
|
6165
|
+
function useContextChoices(segmentId) {
|
|
6166
|
+
const { api, onUnauthorized } = useSessionWorkspace();
|
|
6167
|
+
const [state, setState] = useState({ kind: "loading" });
|
|
6168
|
+
useEffect(() => {
|
|
6169
|
+
let isCurrent = true;
|
|
6170
|
+
api.contextChoices(segmentId).then((options) => isCurrent && setState({
|
|
6171
|
+
kind: "ready",
|
|
6172
|
+
options
|
|
6173
|
+
}), (error) => {
|
|
6174
|
+
if (isUnauthorized(error)) onUnauthorized();
|
|
6175
|
+
else if (isCurrent) setState({
|
|
6176
|
+
kind: "failed",
|
|
6177
|
+
message: failureMessage(error)
|
|
6178
|
+
});
|
|
6179
|
+
});
|
|
6180
|
+
return () => {
|
|
6181
|
+
isCurrent = false;
|
|
6182
|
+
};
|
|
6183
|
+
}, [
|
|
6184
|
+
api,
|
|
6185
|
+
onUnauthorized,
|
|
6186
|
+
segmentId
|
|
6187
|
+
]);
|
|
6188
|
+
return state;
|
|
6189
|
+
}
|
|
6190
|
+
function ChoiceRadio({ choice, group, label }) {
|
|
6191
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", {
|
|
6192
|
+
className: "dt-ses-check",
|
|
6193
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
6194
|
+
checked: group.chosen === choice,
|
|
6195
|
+
name: group.name,
|
|
6196
|
+
onChange: () => group.onPick(choice),
|
|
6197
|
+
type: "radio"
|
|
6198
|
+
}), /* @__PURE__ */ jsx("span", { children: label })]
|
|
6199
|
+
}) });
|
|
6200
|
+
}
|
|
6201
|
+
function optionLabel(option, segment) {
|
|
6202
|
+
const { item } = option;
|
|
6203
|
+
const element = item.element?.label ?? item.label;
|
|
6204
|
+
return `${contextKindLabel(item)} · ${element} · ${offsetFromPassage(item.atMs, segment)}`;
|
|
6205
|
+
}
|
|
6206
|
+
function ChoicesFieldset({ group, options, segment }) {
|
|
6207
|
+
return /* @__PURE__ */ jsxs("fieldset", {
|
|
6208
|
+
className: "dt-ses-choices",
|
|
6209
|
+
children: [/* @__PURE__ */ jsx("legend", {
|
|
6210
|
+
className: "dt-ses-label",
|
|
6211
|
+
children: "What this passage is about"
|
|
6212
|
+
}), /* @__PURE__ */ jsxs("ul", {
|
|
6213
|
+
className: "dt-ses-list",
|
|
6214
|
+
children: [
|
|
6215
|
+
options.map((option) => /* @__PURE__ */ jsx(ChoiceRadio, {
|
|
6216
|
+
choice: option.choice,
|
|
6217
|
+
group,
|
|
6218
|
+
label: optionLabel(option, segment)
|
|
6219
|
+
}, JSON.stringify(option.choice))),
|
|
6220
|
+
/* @__PURE__ */ jsx(ChoiceRadio, {
|
|
6221
|
+
choice: UNKNOWN_CHOICE,
|
|
6222
|
+
group,
|
|
6223
|
+
label: "Context unknown"
|
|
6224
|
+
}),
|
|
6225
|
+
segment.contextOrigin !== "auto" && /* @__PURE__ */ jsx(ChoiceRadio, {
|
|
6226
|
+
choice: AUTO_CHOICE,
|
|
6227
|
+
group,
|
|
6228
|
+
label: "Back to automatic"
|
|
6229
|
+
})
|
|
6230
|
+
]
|
|
6231
|
+
})]
|
|
6232
|
+
});
|
|
6233
|
+
}
|
|
6234
|
+
function useSaveContext(correction, chosen) {
|
|
6235
|
+
const { onDone, review, segment } = correction;
|
|
6236
|
+
const { api } = useSessionWorkspace();
|
|
6237
|
+
const { isSaving, problem, save } = useGuardedSave();
|
|
6238
|
+
const submit = () => save(async () => {
|
|
6239
|
+
if (!chosen) return;
|
|
6240
|
+
const saved = await api.setSegmentContext({
|
|
6241
|
+
choice: chosen,
|
|
6242
|
+
expectedRevision: segment.revision,
|
|
6243
|
+
segmentId: segment.id
|
|
6244
|
+
});
|
|
6245
|
+
review.patch((detail) => withSegment(detail, saved));
|
|
6246
|
+
announce("Context corrected");
|
|
6247
|
+
onDone();
|
|
6248
|
+
});
|
|
6249
|
+
return {
|
|
6250
|
+
isSaving,
|
|
6251
|
+
problem,
|
|
6252
|
+
submit
|
|
6253
|
+
};
|
|
6254
|
+
}
|
|
6255
|
+
function ChoicesForm({ correction, options }) {
|
|
6256
|
+
const { onDone, review, segment } = correction;
|
|
6257
|
+
const [picked, setPicked] = useState(null);
|
|
6258
|
+
const current = options.find((option) => option.selected)?.choice ?? (segment.contextOrigin === "unknown" ? UNKNOWN_CHOICE : null);
|
|
6259
|
+
const chosen = picked ?? current;
|
|
6260
|
+
const { isSaving, problem, submit } = useSaveContext(correction, chosen);
|
|
6261
|
+
return /* @__PURE__ */ jsxs("form", {
|
|
6262
|
+
className: "dt-ses-form",
|
|
6263
|
+
onSubmit: (event) => {
|
|
6264
|
+
event.preventDefault();
|
|
6265
|
+
submit();
|
|
6266
|
+
},
|
|
6267
|
+
children: [
|
|
6268
|
+
/* @__PURE__ */ jsx(ChoicesFieldset, {
|
|
6269
|
+
group: {
|
|
6270
|
+
chosen,
|
|
6271
|
+
name: `rf-context-${segment.id}`,
|
|
6272
|
+
onPick: setPicked
|
|
6273
|
+
},
|
|
6274
|
+
options,
|
|
6275
|
+
segment
|
|
6276
|
+
}),
|
|
6277
|
+
problem && /* @__PURE__ */ jsx(SaveProblemMessage, {
|
|
6278
|
+
onReload: review.reload,
|
|
6279
|
+
problem
|
|
6280
|
+
}),
|
|
6281
|
+
/* @__PURE__ */ jsx(FormActions, {
|
|
6282
|
+
isSubmitDisabled: isSaving || picked === null,
|
|
6283
|
+
onCancel: onDone,
|
|
6284
|
+
submitLabel: "Save context"
|
|
6285
|
+
})
|
|
6286
|
+
]
|
|
6287
|
+
});
|
|
6288
|
+
}
|
|
6289
|
+
function ContextChoices({ correction, onRetry }) {
|
|
6290
|
+
const state = useContextChoices(correction.segment.id);
|
|
6291
|
+
if (state.kind === "loading") return /* @__PURE__ */ jsx("p", {
|
|
6292
|
+
className: "dt-status",
|
|
6293
|
+
children: "Loading what was happening…"
|
|
6294
|
+
});
|
|
6295
|
+
if (state.kind === "failed") return /* @__PURE__ */ jsxs("div", {
|
|
6296
|
+
className: "dt-actions",
|
|
6297
|
+
role: "alert",
|
|
6298
|
+
children: [
|
|
6299
|
+
/* @__PURE__ */ jsxs("p", {
|
|
6300
|
+
className: "dt-error",
|
|
6301
|
+
children: ["The choices did not load: ", state.message]
|
|
6302
|
+
}),
|
|
6303
|
+
/* @__PURE__ */ jsx("button", {
|
|
6304
|
+
className: "dt-btn",
|
|
6305
|
+
onClick: onRetry,
|
|
6306
|
+
type: "button",
|
|
6307
|
+
children: "Try again"
|
|
6308
|
+
}),
|
|
6309
|
+
/* @__PURE__ */ jsx("button", {
|
|
6310
|
+
className: "dt-btn",
|
|
6311
|
+
onClick: correction.onDone,
|
|
6312
|
+
type: "button",
|
|
6313
|
+
children: "Cancel"
|
|
6314
|
+
})
|
|
6315
|
+
]
|
|
6316
|
+
});
|
|
6317
|
+
return /* @__PURE__ */ jsx(ChoicesForm, {
|
|
6318
|
+
correction,
|
|
6319
|
+
options: state.options
|
|
6320
|
+
});
|
|
6321
|
+
}
|
|
6322
|
+
function ContextCorrection({ review, segment }) {
|
|
6323
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
6324
|
+
const [hasClosed, setHasClosed] = useState(false);
|
|
6325
|
+
const [attempt, setAttempt] = useState(0);
|
|
6326
|
+
const buttonRef = useFocusWhen(hasClosed && !isOpen);
|
|
6327
|
+
if (isOpen) return /* @__PURE__ */ jsx(ContextChoices, {
|
|
6328
|
+
correction: {
|
|
6329
|
+
onDone: () => {
|
|
6330
|
+
setIsOpen(false);
|
|
6331
|
+
setHasClosed(true);
|
|
6332
|
+
},
|
|
6333
|
+
review,
|
|
6334
|
+
segment
|
|
6335
|
+
},
|
|
6336
|
+
onRetry: () => setAttempt(attempt + 1)
|
|
6337
|
+
}, attempt);
|
|
6338
|
+
return /* @__PURE__ */ jsx("button", {
|
|
6339
|
+
className: "dt-btn",
|
|
6340
|
+
onClick: () => {
|
|
6341
|
+
setIsOpen(true);
|
|
6342
|
+
setHasClosed(false);
|
|
6343
|
+
},
|
|
6344
|
+
ref: buttonRef,
|
|
6345
|
+
type: "button",
|
|
6346
|
+
children: "Change context"
|
|
6347
|
+
});
|
|
6348
|
+
}
|
|
6349
|
+
//#endregion
|
|
6350
|
+
//#region src/sessions/ui/review/moment/moment-screenshot.tsx
|
|
6351
|
+
const OTHER_STATE_CAPTION = "This screenshot was not taken at this moment: it may show another state of the page.";
|
|
6352
|
+
function approximateCaption(offsetMs) {
|
|
6353
|
+
if (offsetMs === null) return "Approximate — screenshot taken at another moment";
|
|
6354
|
+
const direction = offsetMs >= 0 ? "after" : "before";
|
|
6355
|
+
return `Approximate — screenshot taken ${preciseSeconds(offsetMs)} ${direction}`;
|
|
6356
|
+
}
|
|
6357
|
+
function overlayFor(item) {
|
|
6358
|
+
const { element, evidenceMatch, frame, pointer } = item;
|
|
6359
|
+
if (!(evidenceMatch && frame)) return null;
|
|
6360
|
+
const outline = element ? percentBox(element.rect, frame) : null;
|
|
6361
|
+
const dot = evidenceMatch === "exact" && pointer ? percentPoint(pointer, frame) : null;
|
|
6362
|
+
return outline ? {
|
|
6363
|
+
dot,
|
|
6364
|
+
match: evidenceMatch,
|
|
6365
|
+
outline
|
|
6366
|
+
} : null;
|
|
6367
|
+
}
|
|
6368
|
+
function screenshotAlt(item, evidence) {
|
|
6369
|
+
const time = formatClock(evidence.capturedAtMs ?? evidence.atMs);
|
|
6370
|
+
const element = item.element?.label;
|
|
6371
|
+
if (!(element && overlayFor(item))) return `Screenshot at ${time}`;
|
|
6372
|
+
return `Screenshot at ${time}, “${element}” outlined${item.evidenceMatch === "approximate" ? " approximately" : ""}`;
|
|
6373
|
+
}
|
|
6374
|
+
function screenshotCaption(item) {
|
|
6375
|
+
if (item.evidenceMatch === "approximate") return approximateCaption(item.evidenceOffsetMs);
|
|
6376
|
+
return item.evidenceMatch === "exact" ? null : OTHER_STATE_CAPTION;
|
|
6377
|
+
}
|
|
6378
|
+
function MissingScreenshot({ evidence }) {
|
|
6379
|
+
const reason = evidence?.missingReason;
|
|
6380
|
+
return /* @__PURE__ */ jsx("p", {
|
|
6381
|
+
className: "dt-meta",
|
|
6382
|
+
children: reason ? `Screenshot removed: ${reason}` : "Screenshot removed"
|
|
6383
|
+
});
|
|
6384
|
+
}
|
|
6385
|
+
function MomentScreenshot({ evidence, item }) {
|
|
6386
|
+
const image = evidence?.status === "available" ? evidence.image : null;
|
|
6387
|
+
if (!(evidence && image?.url)) return /* @__PURE__ */ jsx(MissingScreenshot, { evidence });
|
|
6388
|
+
const overlay = overlayFor(item);
|
|
6389
|
+
const caption = screenshotCaption(item);
|
|
6390
|
+
return /* @__PURE__ */ jsxs("figure", {
|
|
6391
|
+
className: "dt-ses-moment-shot",
|
|
6392
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
6393
|
+
className: "dt-ses-shot",
|
|
6394
|
+
"data-large": "true",
|
|
6395
|
+
children: [
|
|
6396
|
+
/* @__PURE__ */ jsx("img", {
|
|
6397
|
+
alt: screenshotAlt(item, evidence),
|
|
6398
|
+
height: image.height,
|
|
6399
|
+
src: image.url,
|
|
6400
|
+
width: image.width
|
|
6401
|
+
}),
|
|
6402
|
+
overlay && /* @__PURE__ */ jsx("span", {
|
|
6403
|
+
"aria-hidden": "true",
|
|
6404
|
+
className: "dt-ses-shot-mark",
|
|
6405
|
+
"data-match": overlay.match,
|
|
6406
|
+
style: overlay.outline
|
|
6407
|
+
}),
|
|
6408
|
+
overlay?.dot && /* @__PURE__ */ jsx("span", {
|
|
6409
|
+
"aria-hidden": "true",
|
|
6410
|
+
className: "dt-ses-shot-pointer",
|
|
6411
|
+
style: overlay.dot
|
|
6412
|
+
})
|
|
6413
|
+
]
|
|
6414
|
+
}), caption && /* @__PURE__ */ jsx("figcaption", {
|
|
6415
|
+
className: "dt-meta",
|
|
6416
|
+
children: caption
|
|
6417
|
+
})]
|
|
6418
|
+
});
|
|
6419
|
+
}
|
|
6420
|
+
//#endregion
|
|
6421
|
+
//#region src/sessions/ui/review/moment/moment-view.tsx
|
|
6422
|
+
const AMBIGUOUS_COPY = "Several things happened around this passage. Reflet linked the closest one: check it, or change it.";
|
|
6423
|
+
function ContextItem({ detail, item }) {
|
|
6424
|
+
const evidence = detail.evidence.find((candidate) => candidate.id === item.evidenceId) ?? null;
|
|
6425
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
6426
|
+
className: "dt-ses-moment-item",
|
|
6427
|
+
children: [/* @__PURE__ */ jsxs("p", {
|
|
6428
|
+
className: "dt-ses-moment-line",
|
|
6429
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
6430
|
+
className: "dt-chip",
|
|
6431
|
+
children: contextKindLabel(item)
|
|
6432
|
+
}), /* @__PURE__ */ jsx("span", { children: contextLine(item) })]
|
|
6433
|
+
}), item.evidenceId && /* @__PURE__ */ jsx(MomentScreenshot, {
|
|
6434
|
+
evidence,
|
|
6435
|
+
item
|
|
6436
|
+
})]
|
|
6437
|
+
});
|
|
6438
|
+
}
|
|
6439
|
+
function MomentContext({ detail, segment }) {
|
|
6440
|
+
const hasScreenshot = segment.context.some((item) => item.evidenceId);
|
|
6441
|
+
const isAmbiguous = segment.contextAmbiguous && segment.contextOrigin === "auto";
|
|
6442
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
6443
|
+
className: "dt-ses-moment-section",
|
|
6444
|
+
children: [
|
|
6445
|
+
/* @__PURE__ */ jsx("h4", {
|
|
6446
|
+
className: "dt-ses-label",
|
|
6447
|
+
children: "On the page"
|
|
6448
|
+
}),
|
|
6449
|
+
/* @__PURE__ */ jsx("div", {
|
|
6450
|
+
className: "dt-ses-chips",
|
|
6451
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
6452
|
+
className: "dt-chip",
|
|
6453
|
+
children: ORIGIN_LABELS[segment.contextOrigin]
|
|
6454
|
+
})
|
|
6455
|
+
}),
|
|
6456
|
+
isAmbiguous && /* @__PURE__ */ jsx("p", {
|
|
6457
|
+
className: "dt-meta",
|
|
6458
|
+
children: AMBIGUOUS_COPY
|
|
6459
|
+
}),
|
|
6460
|
+
segment.context.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
6461
|
+
className: "dt-meta",
|
|
6462
|
+
children: "Nothing on the page is linked to this passage."
|
|
6463
|
+
}),
|
|
6464
|
+
/* @__PURE__ */ jsx("ul", {
|
|
6465
|
+
className: "dt-ses-rows",
|
|
6466
|
+
children: segment.context.map((item) => /* @__PURE__ */ jsx(ContextItem, {
|
|
6467
|
+
detail,
|
|
6468
|
+
item
|
|
6469
|
+
}, `${item.kind}:${item.atMs}:${item.label}`))
|
|
6470
|
+
}),
|
|
6471
|
+
!hasScreenshot && /* @__PURE__ */ jsx("p", {
|
|
6472
|
+
className: "dt-meta",
|
|
6473
|
+
children: "No screenshot at this moment"
|
|
6474
|
+
})
|
|
6475
|
+
]
|
|
6476
|
+
});
|
|
6477
|
+
}
|
|
6478
|
+
function MomentNotices({ detail, segment }) {
|
|
6479
|
+
const gaps = gapsDuring(detail.session.gaps, segment);
|
|
6480
|
+
const hasPageChange = segment.context.some((item) => item.kind === "navigation");
|
|
6481
|
+
if (gaps.length === 0 && !hasPageChange) return null;
|
|
6482
|
+
return /* @__PURE__ */ jsxs("ul", {
|
|
6483
|
+
className: "dt-ses-list dt-ses-moment-notices",
|
|
6484
|
+
children: [gaps.map((gap) => /* @__PURE__ */ jsxs("li", { children: [gapLabel(gap), " during this passage: part of what happened was not recorded."] }, gap.startMs)), hasPageChange && /* @__PURE__ */ jsx("li", { children: "The page changed during this passage." })]
|
|
6485
|
+
});
|
|
6486
|
+
}
|
|
6487
|
+
function CitingIntentions({ detail, onOpenIntention, segmentId }) {
|
|
6488
|
+
const citing = detail.proposals.filter((proposal) => proposal.mergedIntoId === null && proposal.segmentIds.includes(segmentId));
|
|
6489
|
+
if (citing.length === 0) return null;
|
|
6490
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
6491
|
+
className: "dt-ses-moment-section",
|
|
6492
|
+
children: [/* @__PURE__ */ jsx("h4", {
|
|
6493
|
+
className: "dt-ses-label",
|
|
6494
|
+
children: "Intentions citing this passage"
|
|
6495
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
6496
|
+
className: "dt-ses-rows",
|
|
6497
|
+
children: citing.map((proposal) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
|
|
6498
|
+
className: "dt-ses-row",
|
|
6499
|
+
onClick: () => onOpenIntention(proposal.id),
|
|
6500
|
+
type: "button",
|
|
6501
|
+
children: proposal.need
|
|
6502
|
+
}) }, proposal.id))
|
|
6503
|
+
})]
|
|
6504
|
+
});
|
|
6505
|
+
}
|
|
6506
|
+
function MomentSteps({ detail, onShowMoment, segmentId }) {
|
|
6507
|
+
const ordered = [...detail.segments].sort((a, b) => a.startMs - b.startMs);
|
|
6508
|
+
const index = ordered.findIndex((segment) => segment.id === segmentId);
|
|
6509
|
+
const previous = ordered[index - 1];
|
|
6510
|
+
const next = ordered[index + 1];
|
|
6511
|
+
return /* @__PURE__ */ jsxs("nav", {
|
|
6512
|
+
"aria-label": "Moments",
|
|
6513
|
+
className: "dt-actions",
|
|
6514
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
6515
|
+
className: "dt-btn",
|
|
6516
|
+
disabled: !previous,
|
|
6517
|
+
onClick: () => previous && onShowMoment(previous.id),
|
|
6518
|
+
type: "button",
|
|
6519
|
+
children: "Previous moment"
|
|
6520
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
6521
|
+
className: "dt-btn",
|
|
6522
|
+
disabled: !next,
|
|
6523
|
+
onClick: () => next && onShowMoment(next.id),
|
|
6524
|
+
type: "button",
|
|
6525
|
+
children: "Next moment"
|
|
6526
|
+
})]
|
|
6527
|
+
});
|
|
6528
|
+
}
|
|
6529
|
+
function MomentHeading({ onClose, segment }) {
|
|
6530
|
+
const headingRef = useFocusWhen(true);
|
|
6531
|
+
const range = segment ? `${formatClock(segment.startMs)}–${formatClock(segment.endMs)}` : "";
|
|
6532
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
6533
|
+
className: "dt-card-head",
|
|
6534
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
6535
|
+
className: "dt-ses-title",
|
|
6536
|
+
ref: headingRef,
|
|
6537
|
+
tabIndex: -1,
|
|
6538
|
+
children: segment ? `Moment ${range}` : "Moment"
|
|
6539
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
6540
|
+
className: "dt-btn",
|
|
6541
|
+
onClick: onClose,
|
|
6542
|
+
type: "button",
|
|
6543
|
+
children: "Close"
|
|
6544
|
+
})]
|
|
6545
|
+
});
|
|
6546
|
+
}
|
|
6547
|
+
function useEscapeToClose(onClose) {
|
|
6548
|
+
const sectionRef = useRef(null);
|
|
6549
|
+
const onCloseRef = useRef(onClose);
|
|
6550
|
+
useLayoutEffect(() => {
|
|
6551
|
+
onCloseRef.current = onClose;
|
|
6552
|
+
});
|
|
6553
|
+
useEffect(() => {
|
|
6554
|
+
const section = sectionRef.current;
|
|
6555
|
+
const closeOnEscape = (event) => {
|
|
6556
|
+
if (event.key === "Escape") {
|
|
6557
|
+
event.stopPropagation();
|
|
6558
|
+
onCloseRef.current();
|
|
6559
|
+
}
|
|
6560
|
+
};
|
|
6561
|
+
section?.addEventListener("keydown", closeOnEscape);
|
|
6562
|
+
return () => section?.removeEventListener("keydown", closeOnEscape);
|
|
6563
|
+
}, []);
|
|
6564
|
+
return sectionRef;
|
|
6565
|
+
}
|
|
6566
|
+
function MomentView({ handlers, segmentId, session }) {
|
|
6567
|
+
const { detail, review } = session;
|
|
6568
|
+
const segment = detail.segments.find((item) => item.id === segmentId);
|
|
6569
|
+
const sectionRef = useEscapeToClose(handlers.onClose);
|
|
6570
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
6571
|
+
"aria-label": "Moment",
|
|
6572
|
+
className: "dt-ses-moment",
|
|
6573
|
+
ref: sectionRef,
|
|
6574
|
+
children: [
|
|
6575
|
+
/* @__PURE__ */ jsx(MomentHeading, {
|
|
6576
|
+
onClose: handlers.onClose,
|
|
6577
|
+
segment: segment ?? null
|
|
6578
|
+
}, segmentId),
|
|
6579
|
+
segment ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6580
|
+
/* @__PURE__ */ jsx("blockquote", {
|
|
6581
|
+
className: "dt-ses-moment-quote",
|
|
6582
|
+
children: segment.text || "No speech was heard here."
|
|
6583
|
+
}),
|
|
6584
|
+
/* @__PURE__ */ jsx(MomentContext, {
|
|
6585
|
+
detail,
|
|
6586
|
+
segment
|
|
6587
|
+
}),
|
|
6588
|
+
/* @__PURE__ */ jsx(MomentNotices, {
|
|
6589
|
+
detail,
|
|
6590
|
+
segment
|
|
6591
|
+
}),
|
|
6592
|
+
/* @__PURE__ */ jsx(CitingIntentions, {
|
|
6593
|
+
detail,
|
|
6594
|
+
onOpenIntention: handlers.onOpenIntention,
|
|
6595
|
+
segmentId: segment.id
|
|
6596
|
+
}),
|
|
6597
|
+
detail.session.canEdit && /* @__PURE__ */ jsx(ContextCorrection, {
|
|
6598
|
+
review,
|
|
6599
|
+
segment
|
|
6600
|
+
})
|
|
6601
|
+
] }) : /* @__PURE__ */ jsx("p", {
|
|
6602
|
+
className: "dt-meta",
|
|
6603
|
+
children: "This passage is no longer in the session."
|
|
6604
|
+
}),
|
|
6605
|
+
/* @__PURE__ */ jsx(MomentSteps, {
|
|
6606
|
+
detail,
|
|
6607
|
+
onShowMoment: handlers.onShowMoment,
|
|
6608
|
+
segmentId
|
|
6609
|
+
})
|
|
6610
|
+
]
|
|
6611
|
+
});
|
|
6612
|
+
}
|
|
6613
|
+
//#endregion
|
|
6614
|
+
//#region src/sessions/ui/start/session-disclosure.tsx
|
|
6615
|
+
const SEEN_KEY_PREFIX = "reflet-sessions-disclosure:";
|
|
6616
|
+
function hasSeenDisclosure(accountKey) {
|
|
6617
|
+
try {
|
|
6618
|
+
return localStorage.getItem(`${SEEN_KEY_PREFIX}${accountKey}`) !== null;
|
|
6619
|
+
} catch {
|
|
6620
|
+
return false;
|
|
6621
|
+
}
|
|
6622
|
+
}
|
|
6623
|
+
function markDisclosureSeen(accountKey) {
|
|
6624
|
+
try {
|
|
6625
|
+
localStorage.setItem(`${SEEN_KEY_PREFIX}${accountKey}`, "1");
|
|
6626
|
+
} catch {}
|
|
6627
|
+
}
|
|
6628
|
+
function DisclosureList() {
|
|
6629
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
6630
|
+
"aria-labelledby": "rf-session-disclosure",
|
|
6631
|
+
className: "dt-hint",
|
|
6632
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
6633
|
+
className: "dt-ses-subhead",
|
|
6634
|
+
id: "rf-session-disclosure",
|
|
6635
|
+
children: "What a session records"
|
|
6636
|
+
}), /* @__PURE__ */ jsxs("ul", {
|
|
6637
|
+
className: "dt-ses-list",
|
|
6638
|
+
children: [
|
|
6639
|
+
/* @__PURE__ */ jsx("li", { children: "Your voice while recording. It is sent to Reflet and transcribed by Whisper through OpenRouter with zero data retention. The audio is deleted as soon as its transcript is saved, within 24 hours at most." }),
|
|
6640
|
+
/* @__PURE__ */ jsx("li", { children: "The page: its address without sensitive parameters, its title, the scroll position and the window size." }),
|
|
6641
|
+
/* @__PURE__ */ jsx("li", { children: "What you click, point at and mark: element names and positions, never your keystrokes." }),
|
|
6642
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
6643
|
+
"Screenshots of this tab at the start, when you mark a moment and when the page changes. Form fields appear empty; password fields and elements marked ",
|
|
6644
|
+
/* @__PURE__ */ jsx("code", { children: "data-reflet-redact" }),
|
|
6645
|
+
" are hidden. Other text on screen, rich-text editors included, stays visible."
|
|
6646
|
+
] }),
|
|
6647
|
+
/* @__PURE__ */ jsx("li", { children: "Not recorded: keystrokes, other tabs or apps, network calls." }),
|
|
6648
|
+
/* @__PURE__ */ jsx("li", { children: "Pausing stops everything." }),
|
|
6649
|
+
/* @__PURE__ */ jsx("li", { children: "A session stays private to you until you share it." })
|
|
6650
|
+
]
|
|
6651
|
+
})]
|
|
6652
|
+
});
|
|
6653
|
+
}
|
|
6654
|
+
function SessionDisclosure({ accountKey }) {
|
|
6655
|
+
const [isOpen, setIsOpen] = useState(() => !hasSeenDisclosure(accountKey));
|
|
6656
|
+
if (isOpen) return /* @__PURE__ */ jsx(DisclosureList, {});
|
|
6657
|
+
return /* @__PURE__ */ jsx("button", {
|
|
6658
|
+
"aria-expanded": false,
|
|
6659
|
+
className: "dt-ses-link",
|
|
6660
|
+
onClick: () => setIsOpen(true),
|
|
6661
|
+
type: "button",
|
|
6662
|
+
children: "What a session records"
|
|
6663
|
+
});
|
|
6664
|
+
}
|
|
6665
|
+
//#endregion
|
|
6666
|
+
//#region src/sessions/ui/start/start-session.tsx
|
|
6667
|
+
const IS_MICROPHONE_FAILURE = {
|
|
6668
|
+
auth: false,
|
|
6669
|
+
insecure_context: true,
|
|
6670
|
+
limit: false,
|
|
6671
|
+
mic_blocked_by_site: true,
|
|
6672
|
+
mic_denied: true,
|
|
6673
|
+
mic_unavailable: true,
|
|
6674
|
+
network: false,
|
|
6675
|
+
storage: false
|
|
6676
|
+
};
|
|
6677
|
+
function StartProblem({ failure, onRetry, onWriteNote }) {
|
|
6678
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
6679
|
+
className: "dt-ses-problem",
|
|
6680
|
+
role: "alert",
|
|
6681
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
6682
|
+
className: "dt-error",
|
|
6683
|
+
children: failure.message
|
|
6684
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
6685
|
+
className: "dt-actions",
|
|
6686
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
6687
|
+
className: "dt-btn",
|
|
6688
|
+
onClick: onRetry,
|
|
6689
|
+
type: "button",
|
|
6690
|
+
children: "Try again"
|
|
6691
|
+
}), IS_MICROPHONE_FAILURE[failure.kind] && onWriteNote && /* @__PURE__ */ jsx("button", {
|
|
6692
|
+
className: "dt-btn",
|
|
6693
|
+
onClick: onWriteNote,
|
|
6694
|
+
type: "button",
|
|
6695
|
+
children: "Write a note instead"
|
|
6696
|
+
})]
|
|
6697
|
+
})]
|
|
6698
|
+
});
|
|
6699
|
+
}
|
|
6700
|
+
function StartActions({ isBusy, onStart, onWriteNote }) {
|
|
6701
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
6702
|
+
className: "dt-actions",
|
|
6703
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
6704
|
+
className: "dt-btn",
|
|
6705
|
+
"data-variant": "primary",
|
|
6706
|
+
disabled: isBusy,
|
|
6707
|
+
onClick: onStart,
|
|
6708
|
+
type: "button",
|
|
6709
|
+
children: "Start session"
|
|
6710
|
+
}), onWriteNote && /* @__PURE__ */ jsx("button", {
|
|
6711
|
+
className: "dt-btn",
|
|
6712
|
+
onClick: onWriteNote,
|
|
6713
|
+
type: "button",
|
|
6714
|
+
children: "Write a note instead"
|
|
6715
|
+
})]
|
|
6716
|
+
});
|
|
6717
|
+
}
|
|
6718
|
+
function useRecorderStart(onStarted) {
|
|
6719
|
+
const { account, recorder } = useSessionWorkspace();
|
|
6720
|
+
const snapshot = useRecorderSnapshot(recorder);
|
|
6721
|
+
const [hasTried, setHasTried] = useState(false);
|
|
6722
|
+
const start = async (options) => {
|
|
6723
|
+
markDisclosureSeen(account.key);
|
|
6724
|
+
setHasTried(true);
|
|
6725
|
+
await recorder.start(options);
|
|
6726
|
+
if (recorder.getSnapshot().phase === "recording") onStarted();
|
|
6727
|
+
};
|
|
6728
|
+
const isRequesting = snapshot.phase === "requesting";
|
|
6729
|
+
return {
|
|
6730
|
+
failure: hasTried && snapshot.phase === "failed" && snapshot.sessionKey === null ? snapshot.failure : null,
|
|
6731
|
+
isBusy: isRequesting || snapshot.phase === "finishing",
|
|
6732
|
+
isRequesting,
|
|
6733
|
+
start
|
|
6734
|
+
};
|
|
6735
|
+
}
|
|
6736
|
+
function MicrophonePrompt({ isRequesting }) {
|
|
6737
|
+
return /* @__PURE__ */ jsx("p", {
|
|
6738
|
+
"aria-live": "polite",
|
|
6739
|
+
className: "dt-meta",
|
|
6740
|
+
children: isRequesting ? "Allow the microphone in your browser to start." : ""
|
|
6741
|
+
});
|
|
6742
|
+
}
|
|
6743
|
+
function StartSession({ onStarted, onWriteNote }) {
|
|
6744
|
+
const { account } = useSessionWorkspace();
|
|
6745
|
+
const { failure, isBusy, isRequesting, start } = useRecorderStart(onStarted);
|
|
6746
|
+
const startBlank = () => start({});
|
|
6747
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6748
|
+
/* @__PURE__ */ jsx(SessionDisclosure, { accountKey: account.key }),
|
|
6749
|
+
/* @__PURE__ */ jsx(MicrophonePrompt, { isRequesting }),
|
|
6750
|
+
failure ? /* @__PURE__ */ jsx(StartProblem, {
|
|
6751
|
+
failure,
|
|
6752
|
+
onRetry: startBlank,
|
|
6753
|
+
onWriteNote
|
|
6754
|
+
}) : /* @__PURE__ */ jsx(StartActions, {
|
|
6755
|
+
isBusy,
|
|
6756
|
+
onStart: startBlank,
|
|
6757
|
+
onWriteNote
|
|
6758
|
+
})
|
|
6759
|
+
] });
|
|
6760
|
+
}
|
|
6761
|
+
//#endregion
|
|
6762
|
+
//#region src/sessions/ui/review/proposals/use-proposal-actions.ts
|
|
6763
|
+
const NOTICE_NEED_LENGTH = 60;
|
|
6764
|
+
const DECISION_NOTICE = {
|
|
6765
|
+
dismissed: "Dismissed",
|
|
6766
|
+
kept: "Confirmed",
|
|
6767
|
+
later: "Saved for later",
|
|
6768
|
+
pending: "Back to undecided"
|
|
6769
|
+
};
|
|
6770
|
+
function quotedNeed(need) {
|
|
6771
|
+
return `“${need.length > NOTICE_NEED_LENGTH ? `${need.slice(0, 59)}…` : need}”`;
|
|
6772
|
+
}
|
|
6773
|
+
function useProposalSave(review) {
|
|
6774
|
+
const { api } = useSessionWorkspace();
|
|
6775
|
+
const run = useSessionAction();
|
|
6776
|
+
return async (failureLabel, request) => {
|
|
6777
|
+
const proposal = await run(failureLabel, () => request(api));
|
|
6778
|
+
if (proposal) review.patch((detail) => withProposal(detail, proposal));
|
|
6779
|
+
return proposal;
|
|
6780
|
+
};
|
|
6781
|
+
}
|
|
6782
|
+
function decideWithUndo(save) {
|
|
6783
|
+
const restore = async (proposalId, decision) => {
|
|
6784
|
+
if (await save("The decision was not undone", (client) => client.decideProposal({
|
|
6785
|
+
decision,
|
|
6786
|
+
proposalId
|
|
6787
|
+
}))) announce("Decision undone");
|
|
6788
|
+
};
|
|
6789
|
+
return async (proposal, decision) => {
|
|
6790
|
+
const previous = proposal.decision;
|
|
6791
|
+
if (await save("The decision was not saved", (client) => client.decideProposal({
|
|
6792
|
+
decision,
|
|
6793
|
+
proposalId: proposal.id
|
|
6794
|
+
}))) announce(`${DECISION_NOTICE[decision]} ${quotedNeed(proposal.need)}`, {
|
|
6795
|
+
label: "Undo",
|
|
6796
|
+
run: () => restore(proposal.id, previous)
|
|
6797
|
+
});
|
|
6798
|
+
};
|
|
6799
|
+
}
|
|
6800
|
+
function useProposalActions(review) {
|
|
6801
|
+
const save = useProposalSave(review);
|
|
6802
|
+
return {
|
|
6803
|
+
clarify: (proposal, text) => save("The precision was not sent", (client) => client.clarifyProposal({
|
|
6804
|
+
expectedRevision: proposal.revision,
|
|
6805
|
+
proposalId: proposal.id,
|
|
6806
|
+
text
|
|
6807
|
+
})),
|
|
6808
|
+
decide: decideWithUndo(save),
|
|
6809
|
+
merge: async (targetId, sourceIds) => {
|
|
6810
|
+
if (await save("The intentions were not merged", (client) => client.mergeProposals({
|
|
6811
|
+
sourceIds,
|
|
6812
|
+
targetId
|
|
6813
|
+
}))) review.reload();
|
|
6814
|
+
},
|
|
6815
|
+
save,
|
|
6816
|
+
separate: (proposalId) => save("The suggestion was not cleared", (client) => client.separateProposal(proposalId)),
|
|
6817
|
+
verify: (proposalId, { note, status }) => save("The check was not saved", (client) => client.verifyProposal({
|
|
6818
|
+
note: note || void 0,
|
|
6819
|
+
proposalId,
|
|
6820
|
+
status
|
|
6821
|
+
}))
|
|
6822
|
+
};
|
|
6823
|
+
}
|
|
6824
|
+
//#endregion
|
|
6825
|
+
//#region src/sessions/ui/review/proposals/delete-intention.tsx
|
|
6826
|
+
const DELETE_INTENTION_COPY = "Delete this intention? What later sessions added to it is deleted too, along with Reflet's copies of the briefs that included it. Briefs already copied or exported can't be recalled.";
|
|
6827
|
+
function DeleteConfirmation$1({ context, onCancel, proposal }) {
|
|
6828
|
+
const { api } = useSessionWorkspace();
|
|
6829
|
+
const run = useSessionAction();
|
|
6830
|
+
const remove = async () => {
|
|
6831
|
+
if (await run("The intention was not deleted", () => api.deleteProposal(proposal.id)) !== null) {
|
|
6832
|
+
context.review.patch((detail) => ({
|
|
6833
|
+
...detail,
|
|
6834
|
+
proposals: detail.proposals.filter((item) => item.id !== proposal.id)
|
|
6835
|
+
}));
|
|
6836
|
+
announce(`Deleted ${quotedNeed(proposal.need)}`);
|
|
6837
|
+
}
|
|
6838
|
+
};
|
|
6839
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
6840
|
+
className: "dt-ses-banner",
|
|
6841
|
+
children: [/* @__PURE__ */ jsx("p", { children: DELETE_INTENTION_COPY }), /* @__PURE__ */ jsxs("div", {
|
|
6842
|
+
className: "dt-actions",
|
|
6843
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
6844
|
+
className: "dt-btn",
|
|
6845
|
+
"data-variant": "danger",
|
|
6846
|
+
onClick: remove,
|
|
6847
|
+
type: "button",
|
|
6848
|
+
children: "Delete"
|
|
6849
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
6850
|
+
className: "dt-btn",
|
|
6851
|
+
onClick: onCancel,
|
|
6852
|
+
type: "button",
|
|
6853
|
+
children: "Cancel"
|
|
6854
|
+
})]
|
|
6855
|
+
})]
|
|
6856
|
+
});
|
|
6857
|
+
}
|
|
6858
|
+
function DeleteIntention({ context, proposal }) {
|
|
6859
|
+
const [isConfirming, setIsConfirming] = useState(false);
|
|
6860
|
+
const [hasCancelled, setHasCancelled] = useState(false);
|
|
6861
|
+
const buttonRef = useFocusWhen(hasCancelled && !isConfirming);
|
|
6862
|
+
if (isConfirming) return /* @__PURE__ */ jsx(DeleteConfirmation$1, {
|
|
6863
|
+
context,
|
|
6864
|
+
onCancel: () => {
|
|
6865
|
+
setIsConfirming(false);
|
|
6866
|
+
setHasCancelled(true);
|
|
6867
|
+
},
|
|
6868
|
+
proposal
|
|
6869
|
+
});
|
|
6870
|
+
return /* @__PURE__ */ jsx("div", {
|
|
6871
|
+
className: "dt-actions",
|
|
6872
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
6873
|
+
className: "dt-btn",
|
|
6874
|
+
"data-variant": "danger",
|
|
6875
|
+
onClick: () => {
|
|
6876
|
+
setIsConfirming(true);
|
|
6877
|
+
setHasCancelled(false);
|
|
6878
|
+
},
|
|
6879
|
+
ref: buttonRef,
|
|
6880
|
+
type: "button",
|
|
6881
|
+
children: "Delete intention"
|
|
6882
|
+
})
|
|
6883
|
+
});
|
|
6884
|
+
}
|
|
6885
|
+
//#endregion
|
|
6886
|
+
//#region src/sessions/ui/review/proposals/proposal-details.tsx
|
|
6887
|
+
function ListSection({ items, title }) {
|
|
6888
|
+
if (items.length === 0) return null;
|
|
6889
|
+
return /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
|
|
6890
|
+
className: "dt-ses-label",
|
|
6891
|
+
children: title
|
|
6892
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
6893
|
+
className: "dt-ses-list",
|
|
6894
|
+
children: items.map((item) => /* @__PURE__ */ jsx("li", { children: item }, item))
|
|
6895
|
+
})] });
|
|
6896
|
+
}
|
|
6897
|
+
function RefinementState({ onRetry, proposal }) {
|
|
6898
|
+
const { refinement } = proposal;
|
|
6899
|
+
if (refinement?.state === "running") return /* @__PURE__ */ jsx("p", {
|
|
6900
|
+
className: "dt-status",
|
|
6901
|
+
children: "Refining…"
|
|
6902
|
+
});
|
|
6903
|
+
if (refinement?.state === "outdated") return /* @__PURE__ */ jsx("p", {
|
|
6904
|
+
className: "dt-meta",
|
|
6905
|
+
children: "The intention changed while it was being refined. Add your precision again."
|
|
6906
|
+
});
|
|
6907
|
+
if (refinement?.state !== "failed") return null;
|
|
6908
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
6909
|
+
className: "dt-actions",
|
|
6910
|
+
children: [/* @__PURE__ */ jsxs("p", {
|
|
6911
|
+
className: "dt-error",
|
|
6912
|
+
children: ["Refining failed: ", refinement.error ?? "Unknown error."]
|
|
6913
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
6914
|
+
className: "dt-btn",
|
|
6915
|
+
onClick: onRetry,
|
|
6916
|
+
type: "button",
|
|
6917
|
+
children: "Retry"
|
|
6918
|
+
})]
|
|
6919
|
+
});
|
|
6920
|
+
}
|
|
6921
|
+
function ClarifyBox({ context, proposal }) {
|
|
6922
|
+
const [text, setText] = useState("");
|
|
6923
|
+
const isRefining = proposal.refinement?.state === "running";
|
|
6924
|
+
const lastClarification = proposal.clarifications.at(-1)?.text;
|
|
6925
|
+
const send = async (value) => {
|
|
6926
|
+
if (await context.actions.clarify(proposal, value)) setText("");
|
|
6927
|
+
};
|
|
6928
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
6929
|
+
className: "dt-ses-form",
|
|
6930
|
+
children: [
|
|
6931
|
+
proposal.question && /* @__PURE__ */ jsx("p", {
|
|
6932
|
+
className: "dt-note-text",
|
|
6933
|
+
children: proposal.question
|
|
6934
|
+
}),
|
|
6935
|
+
/* @__PURE__ */ jsx("textarea", {
|
|
6936
|
+
"aria-label": proposal.question ? "Your answer" : "Add a precision…",
|
|
6937
|
+
className: "dt-field",
|
|
6938
|
+
disabled: isRefining,
|
|
6939
|
+
maxLength: 1e3,
|
|
6940
|
+
onChange: (event) => setText(event.target.value),
|
|
6941
|
+
placeholder: "Add a precision…",
|
|
6942
|
+
rows: 2,
|
|
6943
|
+
value: text
|
|
6944
|
+
}),
|
|
6945
|
+
/* @__PURE__ */ jsx("div", {
|
|
6946
|
+
className: "dt-actions",
|
|
6947
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
6948
|
+
className: "dt-btn",
|
|
6949
|
+
disabled: isRefining || text.trim() === "",
|
|
6950
|
+
onClick: () => send(text.trim()),
|
|
6951
|
+
type: "button",
|
|
6952
|
+
children: "Update intention"
|
|
6953
|
+
})
|
|
6954
|
+
}),
|
|
6955
|
+
/* @__PURE__ */ jsx(RefinementState, {
|
|
6956
|
+
onRetry: () => lastClarification && send(lastClarification),
|
|
6957
|
+
proposal
|
|
6958
|
+
})
|
|
6959
|
+
]
|
|
6960
|
+
});
|
|
6961
|
+
}
|
|
6962
|
+
function Sources({ context, proposal }) {
|
|
6963
|
+
const segmentIds = new Set(proposal.segmentIds);
|
|
6964
|
+
const evidenceIds = new Set(proposal.evidenceIds);
|
|
6965
|
+
const segments = context.detail.segments.filter((segment) => segmentIds.has(segment.id));
|
|
6966
|
+
const evidence = context.detail.evidence.filter((item) => evidenceIds.has(item.id));
|
|
6967
|
+
if (segments.length + evidence.length === 0) return null;
|
|
6968
|
+
return /* @__PURE__ */ jsxs("section", { children: [
|
|
6969
|
+
/* @__PURE__ */ jsx("h4", {
|
|
6970
|
+
className: "dt-ses-label",
|
|
6971
|
+
children: "Sources"
|
|
6972
|
+
}),
|
|
6973
|
+
segments.map((segment) => /* @__PURE__ */ jsxs("button", {
|
|
6974
|
+
className: "dt-ses-quote",
|
|
6975
|
+
onClick: (event) => context.navigation.onShowMoment(segment.id, event.currentTarget),
|
|
6976
|
+
type: "button",
|
|
6977
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
6978
|
+
className: "dt-ses-time",
|
|
6979
|
+
children: formatClock(segment.startMs)
|
|
6980
|
+
}), ` “${segment.text}”`]
|
|
6981
|
+
}, segment.id)),
|
|
6982
|
+
/* @__PURE__ */ jsx("div", {
|
|
6983
|
+
className: "dt-ses-thumbs",
|
|
6984
|
+
children: evidence.map((item) => /* @__PURE__ */ jsx(EvidenceThumb, { evidence: item }, item.id))
|
|
6985
|
+
})
|
|
6986
|
+
] });
|
|
6987
|
+
}
|
|
6988
|
+
function ProposalDetails({ context, proposal }) {
|
|
6989
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6990
|
+
/* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
|
|
6991
|
+
className: "dt-ses-label",
|
|
6992
|
+
children: "What you said or showed — not verified"
|
|
6993
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
6994
|
+
className: "dt-note-text",
|
|
6995
|
+
children: proposal.observed
|
|
6996
|
+
})] }),
|
|
6997
|
+
/* @__PURE__ */ jsx(ListSection, {
|
|
6998
|
+
items: proposal.setAside,
|
|
6999
|
+
title: "Set aside"
|
|
7000
|
+
}),
|
|
7001
|
+
context.canEdit && /* @__PURE__ */ jsx(ClarifyBox, {
|
|
7002
|
+
context,
|
|
7003
|
+
proposal
|
|
7004
|
+
}),
|
|
7005
|
+
/* @__PURE__ */ jsx(Sources, {
|
|
7006
|
+
context,
|
|
7007
|
+
proposal
|
|
7008
|
+
}),
|
|
7009
|
+
/* @__PURE__ */ jsx(IntentionHistory, {
|
|
7010
|
+
context,
|
|
7011
|
+
intentionId: proposal.id
|
|
7012
|
+
}),
|
|
7013
|
+
context.canEdit && /* @__PURE__ */ jsx(DeleteIntention, {
|
|
7014
|
+
context,
|
|
7015
|
+
proposal
|
|
7016
|
+
})
|
|
7017
|
+
] });
|
|
7018
|
+
}
|
|
7019
|
+
//#endregion
|
|
7020
|
+
//#region src/sessions/ui/review/continuation/continue-panel.tsx
|
|
7021
|
+
const MAX_TITLE_NEED_CHARS = 80;
|
|
7022
|
+
function continuationOf(proposal) {
|
|
7023
|
+
return {
|
|
7024
|
+
continues: {
|
|
7025
|
+
intentionId: proposal.id,
|
|
7026
|
+
revision: proposal.revision
|
|
7027
|
+
},
|
|
7028
|
+
title: continueTitle(proposal.need)
|
|
7029
|
+
};
|
|
7030
|
+
}
|
|
7031
|
+
function continueTitle(need) {
|
|
7032
|
+
return `Continue: ${need.length > MAX_TITLE_NEED_CHARS ? `${need.slice(0, 79)}…` : need}`;
|
|
7033
|
+
}
|
|
7034
|
+
function IntentionRecap({ proposal }) {
|
|
7035
|
+
const stillOpen = proposal.question ? [...proposal.unknowns, proposal.question] : proposal.unknowns;
|
|
7036
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
7037
|
+
/* @__PURE__ */ jsx("p", {
|
|
7038
|
+
className: "dt-note-text",
|
|
7039
|
+
children: proposal.need
|
|
7040
|
+
}),
|
|
7041
|
+
proposal.response && /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h5", {
|
|
7042
|
+
className: "dt-ses-label",
|
|
7043
|
+
children: "Considered response"
|
|
7044
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
7045
|
+
className: "dt-note-text",
|
|
7046
|
+
children: proposal.response
|
|
7047
|
+
})] }),
|
|
7048
|
+
/* @__PURE__ */ jsx(ListSection, {
|
|
7049
|
+
items: stillOpen,
|
|
7050
|
+
title: "Still open"
|
|
7051
|
+
}),
|
|
7052
|
+
/* @__PURE__ */ jsx("p", {
|
|
7053
|
+
className: "dt-meta",
|
|
7054
|
+
children: "Speak to refine the need or check the result. Nothing is sent to an agent."
|
|
7055
|
+
})
|
|
7056
|
+
] });
|
|
7057
|
+
}
|
|
7058
|
+
function ContinuePanel({ onCancel, onStarted, proposal }) {
|
|
7059
|
+
const headingId = useId();
|
|
7060
|
+
const headingRef = useFocusWhen(true);
|
|
7061
|
+
const { failure, isBusy, isRequesting, start } = useRecorderStart(onStarted);
|
|
7062
|
+
const startContinuation = () => start(continuationOf(proposal));
|
|
7063
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
7064
|
+
"aria-labelledby": headingId,
|
|
7065
|
+
className: "dt-ses-banner",
|
|
7066
|
+
children: [
|
|
7067
|
+
/* @__PURE__ */ jsx("h4", {
|
|
7068
|
+
className: "dt-ses-subhead",
|
|
7069
|
+
id: headingId,
|
|
7070
|
+
ref: headingRef,
|
|
7071
|
+
tabIndex: -1,
|
|
7072
|
+
children: "Continue this intention"
|
|
7073
|
+
}),
|
|
7074
|
+
/* @__PURE__ */ jsx(IntentionRecap, { proposal }),
|
|
7075
|
+
/* @__PURE__ */ jsx(MicrophonePrompt, { isRequesting }),
|
|
7076
|
+
failure && /* @__PURE__ */ jsx(StartProblem, {
|
|
7077
|
+
failure,
|
|
7078
|
+
onRetry: startContinuation
|
|
7079
|
+
}),
|
|
7080
|
+
/* @__PURE__ */ jsxs("div", {
|
|
7081
|
+
className: "dt-actions",
|
|
7082
|
+
children: [!failure && /* @__PURE__ */ jsx("button", {
|
|
7083
|
+
className: "dt-btn",
|
|
7084
|
+
"data-variant": "primary",
|
|
7085
|
+
disabled: isBusy,
|
|
7086
|
+
onClick: startContinuation,
|
|
7087
|
+
type: "button",
|
|
7088
|
+
children: "Start session"
|
|
7089
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
7090
|
+
className: "dt-btn",
|
|
7091
|
+
onClick: onCancel,
|
|
7092
|
+
type: "button",
|
|
7093
|
+
children: "Cancel"
|
|
7094
|
+
})]
|
|
7095
|
+
})
|
|
7096
|
+
]
|
|
7097
|
+
});
|
|
7098
|
+
}
|
|
7099
|
+
function ContinueIntention({ context, proposal }) {
|
|
7100
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
7101
|
+
const [hasClosed, setHasClosed] = useState(false);
|
|
7102
|
+
const buttonRef = useFocusWhen(hasClosed && !isOpen);
|
|
7103
|
+
if (!(context.canEdit && proposal.decision !== "dismissed" && proposal.mergedIntoId === null && proposal.attachment === null)) return null;
|
|
7104
|
+
if (isOpen) return /* @__PURE__ */ jsx(ContinuePanel, {
|
|
7105
|
+
onCancel: () => {
|
|
7106
|
+
setIsOpen(false);
|
|
7107
|
+
setHasClosed(true);
|
|
7108
|
+
},
|
|
7109
|
+
onStarted: context.navigation.onStarted,
|
|
7110
|
+
proposal
|
|
7111
|
+
});
|
|
7112
|
+
return /* @__PURE__ */ jsx("div", {
|
|
7113
|
+
className: "dt-actions",
|
|
7114
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
7115
|
+
className: "dt-btn",
|
|
7116
|
+
onClick: () => {
|
|
7117
|
+
setIsOpen(true);
|
|
7118
|
+
setHasClosed(false);
|
|
7119
|
+
},
|
|
7120
|
+
ref: buttonRef,
|
|
7121
|
+
type: "button",
|
|
7122
|
+
children: "Continue this intention"
|
|
7123
|
+
})
|
|
7124
|
+
});
|
|
7125
|
+
}
|
|
7126
|
+
//#endregion
|
|
7127
|
+
//#region src/sessions/ui/review/moment/intention-source.tsx
|
|
7128
|
+
function IntentionSource({ context, proposal }) {
|
|
7129
|
+
const { segments } = context.detail;
|
|
7130
|
+
const firstSegmentId = proposal.segmentIds.find((segmentId) => segments.some((segment) => segment.id === segmentId));
|
|
7131
|
+
const segment = segments.find((item) => item.id === firstSegmentId);
|
|
7132
|
+
if (!segment) return null;
|
|
7133
|
+
const [firstContext] = segment.context;
|
|
7134
|
+
const range = `${formatClock(segment.startMs)}–${formatClock(segment.endMs)}`;
|
|
7135
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
7136
|
+
className: "dt-ses-block",
|
|
7137
|
+
children: [
|
|
7138
|
+
/* @__PURE__ */ jsxs("p", {
|
|
7139
|
+
className: "dt-ses-source",
|
|
7140
|
+
children: [
|
|
7141
|
+
/* @__PURE__ */ jsx("span", {
|
|
7142
|
+
className: "dt-ses-time",
|
|
7143
|
+
children: range
|
|
7144
|
+
}),
|
|
7145
|
+
" “",
|
|
7146
|
+
segment.text,
|
|
7147
|
+
"”"
|
|
7148
|
+
]
|
|
7149
|
+
}),
|
|
7150
|
+
firstContext && /* @__PURE__ */ jsx("p", {
|
|
7151
|
+
className: "dt-meta",
|
|
7152
|
+
children: contextLine(firstContext)
|
|
7153
|
+
}),
|
|
7154
|
+
/* @__PURE__ */ jsx("button", {
|
|
7155
|
+
className: "dt-ses-link",
|
|
7156
|
+
onClick: (event) => context.navigation.onShowMoment(segment.id, event.currentTarget),
|
|
7157
|
+
type: "button",
|
|
7158
|
+
children: "Show context"
|
|
7159
|
+
})
|
|
7160
|
+
]
|
|
7161
|
+
});
|
|
7162
|
+
}
|
|
7163
|
+
//#endregion
|
|
7164
|
+
//#region src/sessions/ui/review/proposals/intention-suggestion.tsx
|
|
7165
|
+
function useAttach(context, proposal) {
|
|
7166
|
+
const [isBusy, setIsBusy] = useState(false);
|
|
7167
|
+
const attach = async (suggestion, shareWithTeam) => {
|
|
7168
|
+
setIsBusy(true);
|
|
7169
|
+
try {
|
|
7170
|
+
if (await context.actions.save("The session was not added to the intention", (api) => api.attachToIntention({
|
|
7171
|
+
intentionId: suggestion.intentionId,
|
|
7172
|
+
proposalId: proposal.id,
|
|
7173
|
+
...shareWithTeam ? { shareWithTeam } : {}
|
|
7174
|
+
}))) announce(`Added to ${quotedNeed(suggestion.need)}`);
|
|
7175
|
+
} finally {
|
|
7176
|
+
setIsBusy(false);
|
|
7177
|
+
}
|
|
7178
|
+
};
|
|
7179
|
+
const keepSeparate = () => context.actions.save("The suggestion was not cleared", (api) => api.keepSeparate(proposal.id));
|
|
7180
|
+
return {
|
|
7181
|
+
attach,
|
|
7182
|
+
isBusy,
|
|
7183
|
+
keepSeparate
|
|
7184
|
+
};
|
|
7185
|
+
}
|
|
7186
|
+
function SuggestionChoices({ canEdit, handlers, suggestion }) {
|
|
7187
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
7188
|
+
className: "dt-ses-banner",
|
|
7189
|
+
children: [/* @__PURE__ */ jsxs("p", { children: [
|
|
7190
|
+
"Might belong to “",
|
|
7191
|
+
suggestion.need,
|
|
7192
|
+
"” (from “",
|
|
7193
|
+
suggestion.sessionTitle,
|
|
7194
|
+
"”):",
|
|
7195
|
+
" ",
|
|
7196
|
+
suggestion.reason
|
|
7197
|
+
] }), canEdit && /* @__PURE__ */ jsxs("div", {
|
|
7198
|
+
className: "dt-actions",
|
|
7199
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
7200
|
+
className: "dt-btn",
|
|
7201
|
+
disabled: handlers.isBusy,
|
|
7202
|
+
onClick: handlers.onAdd,
|
|
7203
|
+
type: "button",
|
|
7204
|
+
children: "Add to this intention"
|
|
7205
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
7206
|
+
className: "dt-btn",
|
|
7207
|
+
onClick: handlers.onKeepSeparate,
|
|
7208
|
+
type: "button",
|
|
7209
|
+
children: "Keep separate"
|
|
7210
|
+
})]
|
|
7211
|
+
})]
|
|
7212
|
+
});
|
|
7213
|
+
}
|
|
7214
|
+
function IntentionSuggestionBanner({ context, proposal }) {
|
|
7215
|
+
const [isPreviewing, setIsPreviewing] = useState(false);
|
|
7216
|
+
const { attach, isBusy, keepSeparate } = useAttach(context, proposal);
|
|
7217
|
+
const suggestion = proposal.intentionSuggestion;
|
|
7218
|
+
if (!suggestion) return null;
|
|
7219
|
+
if (isPreviewing) return /* @__PURE__ */ jsx(SharePreview, {
|
|
7220
|
+
confirmation: {
|
|
7221
|
+
isBusy,
|
|
7222
|
+
label: "Share and add",
|
|
7223
|
+
onCancel: () => setIsPreviewing(false),
|
|
7224
|
+
onConfirm: () => attach(suggestion, true)
|
|
7225
|
+
},
|
|
7226
|
+
detail: context.detail,
|
|
7227
|
+
extracts: proposal
|
|
7228
|
+
});
|
|
7229
|
+
return /* @__PURE__ */ jsx(SuggestionChoices, {
|
|
7230
|
+
canEdit: context.canEdit,
|
|
7231
|
+
handlers: {
|
|
7232
|
+
isBusy,
|
|
7233
|
+
onAdd: () => suggestion.needsShareConsent ? setIsPreviewing(true) : attach(suggestion, false),
|
|
7234
|
+
onKeepSeparate: keepSeparate
|
|
7235
|
+
},
|
|
7236
|
+
suggestion
|
|
7237
|
+
});
|
|
7238
|
+
}
|
|
7239
|
+
function AttachedIntentionRow({ attachment, context }) {
|
|
7240
|
+
const { api } = useSessionWorkspace();
|
|
7241
|
+
const run = useSessionAction();
|
|
7242
|
+
const undo = async () => {
|
|
7243
|
+
if (await run("The session was not separated", () => api.detachContribution(attachment.contributionId))) {
|
|
7244
|
+
announce(`Separated from ${quotedNeed(attachment.need)}`);
|
|
7245
|
+
context.review.reload();
|
|
7246
|
+
}
|
|
7247
|
+
};
|
|
7248
|
+
return /* @__PURE__ */ jsxs("article", {
|
|
7249
|
+
className: "dt-card dt-ses-attached",
|
|
7250
|
+
children: [/* @__PURE__ */ jsxs("p", {
|
|
7251
|
+
className: "dt-meta",
|
|
7252
|
+
children: [
|
|
7253
|
+
"Added to intention “",
|
|
7254
|
+
attachment.need,
|
|
7255
|
+
"”"
|
|
7256
|
+
]
|
|
7257
|
+
}), context.canEdit && /* @__PURE__ */ jsx("button", {
|
|
7258
|
+
className: "dt-btn",
|
|
7259
|
+
onClick: undo,
|
|
7260
|
+
type: "button",
|
|
7261
|
+
children: "Undo"
|
|
7262
|
+
})]
|
|
7263
|
+
});
|
|
7264
|
+
}
|
|
7265
|
+
//#endregion
|
|
7266
|
+
//#region src/sessions/ui/review/proposals/verification.tsx
|
|
7267
|
+
const OUTCOME_LABELS = {
|
|
7268
|
+
needs_work: "Needs more work",
|
|
7269
|
+
works: "Works"
|
|
7270
|
+
};
|
|
7271
|
+
function Verification({ context, proposal }) {
|
|
7272
|
+
const [note, setNote] = useState(proposal.verification?.note ?? "");
|
|
7273
|
+
if (proposal.handedOffRevision === null) return null;
|
|
7274
|
+
const { verification } = proposal;
|
|
7275
|
+
return /* @__PURE__ */ jsxs("fieldset", {
|
|
7276
|
+
className: "dt-ses-verify",
|
|
7277
|
+
children: [
|
|
7278
|
+
/* @__PURE__ */ jsx("legend", {
|
|
7279
|
+
className: "dt-ses-label",
|
|
7280
|
+
children: "Did the change work?"
|
|
7281
|
+
}),
|
|
7282
|
+
verification && /* @__PURE__ */ jsxs("p", {
|
|
7283
|
+
className: "dt-meta",
|
|
7284
|
+
children: [
|
|
7285
|
+
"Checked: ",
|
|
7286
|
+
OUTCOME_LABELS[verification.status],
|
|
7287
|
+
verification.note ? ` · ${verification.note}` : ""
|
|
7288
|
+
]
|
|
7289
|
+
}),
|
|
7290
|
+
/* @__PURE__ */ jsx("input", {
|
|
7291
|
+
"aria-label": "Note about the change (optional)",
|
|
7292
|
+
className: "dt-field",
|
|
7293
|
+
maxLength: 1e3,
|
|
7294
|
+
onChange: (event) => setNote(event.target.value),
|
|
7295
|
+
placeholder: "Note (optional)",
|
|
7296
|
+
value: note
|
|
7297
|
+
}),
|
|
7298
|
+
/* @__PURE__ */ jsx("div", {
|
|
7299
|
+
className: "dt-actions",
|
|
7300
|
+
children: ["works", "needs_work"].map((status) => /* @__PURE__ */ jsx("button", {
|
|
7301
|
+
"aria-pressed": verification?.status === status,
|
|
7302
|
+
className: "dt-btn",
|
|
7303
|
+
onClick: () => context.actions.verify(proposal.id, {
|
|
7304
|
+
note: note.trim(),
|
|
7305
|
+
status
|
|
7306
|
+
}),
|
|
7307
|
+
type: "button",
|
|
7308
|
+
children: OUTCOME_LABELS[status]
|
|
7309
|
+
}, status))
|
|
7310
|
+
})
|
|
7311
|
+
]
|
|
7312
|
+
});
|
|
7313
|
+
}
|
|
7314
|
+
//#endregion
|
|
7315
|
+
//#region src/sessions/ui/review/proposals/proposal-card.tsx
|
|
7316
|
+
const DECISION_CHIPS = {
|
|
7317
|
+
dismissed: "Dismissed",
|
|
7318
|
+
kept: "Confirmed",
|
|
7319
|
+
later: "Later"
|
|
7320
|
+
};
|
|
7321
|
+
const FORMULATION_CHIPS = {
|
|
7322
|
+
ai: "AI wording",
|
|
7323
|
+
human: "Your wording"
|
|
7324
|
+
};
|
|
7325
|
+
const DECISION_BUTTONS = [
|
|
7326
|
+
{
|
|
7327
|
+
decision: "kept",
|
|
7328
|
+
label: "Confirm"
|
|
7329
|
+
},
|
|
7330
|
+
{
|
|
7331
|
+
decision: "later",
|
|
7332
|
+
label: "Later"
|
|
7333
|
+
},
|
|
7334
|
+
{
|
|
7335
|
+
decision: "dismissed",
|
|
7336
|
+
label: "Dismiss"
|
|
7337
|
+
}
|
|
7338
|
+
];
|
|
7339
|
+
function ProposalChips({ proposal }) {
|
|
7340
|
+
const decisionChip = DECISION_CHIPS[proposal.decision];
|
|
7341
|
+
const { handedOffRevision } = proposal;
|
|
7342
|
+
let handoffChip = null;
|
|
7343
|
+
if (handedOffRevision !== null) handoffChip = proposal.revision > handedOffRevision ? "Changed since handoff" : `Handed off rev ${handedOffRevision}`;
|
|
4483
7344
|
return /* @__PURE__ */ jsxs("div", {
|
|
4484
7345
|
className: "dt-ses-chips",
|
|
4485
7346
|
children: [
|
|
7347
|
+
decisionChip && /* @__PURE__ */ jsx("span", {
|
|
7348
|
+
className: "dt-chip",
|
|
7349
|
+
children: decisionChip
|
|
7350
|
+
}),
|
|
7351
|
+
/* @__PURE__ */ jsx("span", {
|
|
7352
|
+
className: "dt-chip",
|
|
7353
|
+
children: FORMULATION_CHIPS[proposal.formulation]
|
|
7354
|
+
}),
|
|
4486
7355
|
proposal.certainty === "uncertain" && /* @__PURE__ */ jsx("span", {
|
|
4487
7356
|
className: "dt-chip",
|
|
4488
7357
|
children: "Unclear"
|
|
@@ -4490,11 +7359,7 @@ function ProposalChips({ proposal }) {
|
|
|
4490
7359
|
proposal.needsReview && /* @__PURE__ */ jsx("span", {
|
|
4491
7360
|
className: "dt-chip",
|
|
4492
7361
|
"data-tone": "accent",
|
|
4493
|
-
children: "
|
|
4494
|
-
}),
|
|
4495
|
-
decisionChip && /* @__PURE__ */ jsx("span", {
|
|
4496
|
-
className: "dt-chip",
|
|
4497
|
-
children: decisionChip
|
|
7362
|
+
children: "To re-examine"
|
|
4498
7363
|
}),
|
|
4499
7364
|
handoffChip && /* @__PURE__ */ jsx("span", {
|
|
4500
7365
|
className: "dt-chip",
|
|
@@ -4585,34 +7450,94 @@ function DetailsDisclosure({ context, disclosure, proposal }) {
|
|
|
4585
7450
|
})
|
|
4586
7451
|
})] });
|
|
4587
7452
|
}
|
|
4588
|
-
function
|
|
4589
|
-
const
|
|
7453
|
+
function ToCheck({ proposal }) {
|
|
7454
|
+
const items = proposal.question ? [...proposal.unknowns, proposal.question] : proposal.unknowns;
|
|
7455
|
+
return /* @__PURE__ */ jsx(ListSection, {
|
|
7456
|
+
items,
|
|
7457
|
+
title: "To check"
|
|
7458
|
+
});
|
|
7459
|
+
}
|
|
7460
|
+
function ResponseBlock({ context, proposal }) {
|
|
4590
7461
|
const [isEditing, setIsEditing] = useState(false);
|
|
4591
|
-
if (isEditing) return /* @__PURE__ */ jsx(
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
7462
|
+
if (isEditing) return /* @__PURE__ */ jsx(ResponseEditor, {
|
|
7463
|
+
onDone: () => setIsEditing(false),
|
|
7464
|
+
proposal,
|
|
7465
|
+
review: context.review
|
|
7466
|
+
});
|
|
7467
|
+
if (!(proposal.response || context.canEdit)) return null;
|
|
7468
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
7469
|
+
className: "dt-ses-block",
|
|
7470
|
+
children: [
|
|
7471
|
+
/* @__PURE__ */ jsx("h4", {
|
|
7472
|
+
className: "dt-ses-label",
|
|
7473
|
+
children: "Considered response"
|
|
7474
|
+
}),
|
|
7475
|
+
/* @__PURE__ */ jsx("p", {
|
|
7476
|
+
className: proposal.response ? "dt-note-text" : "dt-meta",
|
|
7477
|
+
children: proposal.response ?? "No considered response yet."
|
|
7478
|
+
}),
|
|
7479
|
+
context.canEdit && /* @__PURE__ */ jsx("button", {
|
|
7480
|
+
className: "dt-ses-link",
|
|
7481
|
+
onClick: () => setIsEditing(true),
|
|
7482
|
+
type: "button",
|
|
7483
|
+
children: "Edit response"
|
|
7484
|
+
})
|
|
7485
|
+
]
|
|
4598
7486
|
});
|
|
7487
|
+
}
|
|
7488
|
+
function IntentionBody({ context, disclosure, proposal }) {
|
|
7489
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
7490
|
+
/* @__PURE__ */ jsx("p", {
|
|
7491
|
+
className: "dt-ses-label",
|
|
7492
|
+
children: "Intention"
|
|
7493
|
+
}),
|
|
7494
|
+
/* @__PURE__ */ jsx("p", {
|
|
7495
|
+
className: "dt-ses-need",
|
|
7496
|
+
"data-expanded": disclosure.isExpanded,
|
|
7497
|
+
children: proposal.need
|
|
7498
|
+
}),
|
|
7499
|
+
/* @__PURE__ */ jsx(ProposalChips, { proposal }),
|
|
7500
|
+
/* @__PURE__ */ jsx(IntentionSource, {
|
|
7501
|
+
context,
|
|
7502
|
+
proposal
|
|
7503
|
+
}),
|
|
7504
|
+
/* @__PURE__ */ jsx(ResponseBlock, {
|
|
7505
|
+
context,
|
|
7506
|
+
proposal
|
|
7507
|
+
}),
|
|
7508
|
+
/* @__PURE__ */ jsx(ToCheck, { proposal })
|
|
7509
|
+
] });
|
|
7510
|
+
}
|
|
7511
|
+
function IntentionCard({ context, onEdit, proposal }) {
|
|
7512
|
+
const disclosure = useDisclosure();
|
|
7513
|
+
const cardRef = useFocusWhen(context.focusIntentionId === proposal.id);
|
|
4599
7514
|
return /* @__PURE__ */ jsxs("article", {
|
|
7515
|
+
"aria-label": `Intention: ${proposal.need}`,
|
|
4600
7516
|
className: "dt-card",
|
|
4601
7517
|
"data-decision": proposal.decision,
|
|
7518
|
+
ref: cardRef,
|
|
7519
|
+
tabIndex: -1,
|
|
4602
7520
|
children: [
|
|
4603
|
-
/* @__PURE__ */ jsx(
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
7521
|
+
/* @__PURE__ */ jsx(IntentionBody, {
|
|
7522
|
+
context,
|
|
7523
|
+
disclosure,
|
|
7524
|
+
proposal
|
|
4607
7525
|
}),
|
|
4608
|
-
/* @__PURE__ */ jsx(ProposalChips, { proposal }),
|
|
4609
7526
|
/* @__PURE__ */ jsx(MergeSuggestion, {
|
|
4610
7527
|
context,
|
|
4611
7528
|
proposal
|
|
4612
7529
|
}),
|
|
7530
|
+
/* @__PURE__ */ jsx(IntentionSuggestionBanner, {
|
|
7531
|
+
context,
|
|
7532
|
+
proposal
|
|
7533
|
+
}),
|
|
4613
7534
|
context.canEdit && /* @__PURE__ */ jsx(DecisionButtons, {
|
|
4614
7535
|
context,
|
|
4615
|
-
onEdit
|
|
7536
|
+
onEdit,
|
|
7537
|
+
proposal
|
|
7538
|
+
}),
|
|
7539
|
+
/* @__PURE__ */ jsx(ContinueIntention, {
|
|
7540
|
+
context,
|
|
4616
7541
|
proposal
|
|
4617
7542
|
}),
|
|
4618
7543
|
/* @__PURE__ */ jsx(DetailsDisclosure, {
|
|
@@ -4627,16 +7552,36 @@ function ProposalCard({ context, proposal }) {
|
|
|
4627
7552
|
]
|
|
4628
7553
|
});
|
|
4629
7554
|
}
|
|
7555
|
+
function ProposalCard({ context, proposal }) {
|
|
7556
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
7557
|
+
if (proposal.attachment) return /* @__PURE__ */ jsx(AttachedIntentionRow, {
|
|
7558
|
+
attachment: proposal.attachment,
|
|
7559
|
+
context
|
|
7560
|
+
});
|
|
7561
|
+
if (isEditing) return /* @__PURE__ */ jsx("article", {
|
|
7562
|
+
className: "dt-card",
|
|
7563
|
+
children: /* @__PURE__ */ jsx(ProposalEditor, {
|
|
7564
|
+
onDone: () => setIsEditing(false),
|
|
7565
|
+
proposal,
|
|
7566
|
+
review: context.review
|
|
7567
|
+
}, proposal.revision)
|
|
7568
|
+
});
|
|
7569
|
+
return /* @__PURE__ */ jsx(IntentionCard, {
|
|
7570
|
+
context,
|
|
7571
|
+
onEdit: () => setIsEditing(true),
|
|
7572
|
+
proposal
|
|
7573
|
+
});
|
|
7574
|
+
}
|
|
4630
7575
|
//#endregion
|
|
4631
7576
|
//#region src/sessions/ui/review/proposals/proposals-tab.tsx
|
|
4632
7577
|
function emptyCopy(detail) {
|
|
4633
7578
|
const { session } = detail;
|
|
4634
|
-
if (session.collection !== "ended") return "
|
|
7579
|
+
if (session.collection !== "ended") return "Intentions appear here once the recording has uploaded and Reflet has read it.";
|
|
4635
7580
|
if (detail.segments.length === 0 && detail.marks.length === 0) return "Nothing was said or marked in this session.";
|
|
4636
|
-
if (isAnalysisBusy(session)) return "
|
|
4637
|
-
if (session.analysis.state === "failed") return "The transcript is kept. Read it, or write the
|
|
4638
|
-
if (session.analysis.state === "succeeded") return "No request stood out in this session. Read the transcript or write the
|
|
4639
|
-
return "
|
|
7581
|
+
if (isAnalysisBusy(session)) return "Intentions appear here once Reflet has read the session.";
|
|
7582
|
+
if (session.analysis.state === "failed") return "The transcript is kept. Read it, or write the intention yourself.";
|
|
7583
|
+
if (session.analysis.state === "succeeded") return "No request stood out in this session. Read the transcript or write the intention yourself.";
|
|
7584
|
+
return "Intentions appear here once the session is analyzed.";
|
|
4640
7585
|
}
|
|
4641
7586
|
function useAddProposal(context, onDone) {
|
|
4642
7587
|
const [need, setNeed] = useState("");
|
|
@@ -4647,7 +7592,7 @@ function useAddProposal(context, onDone) {
|
|
|
4647
7592
|
const add = async () => {
|
|
4648
7593
|
setIsSaving(true);
|
|
4649
7594
|
try {
|
|
4650
|
-
if (await context.actions.save("The
|
|
7595
|
+
if (await context.actions.save("The intention was not added", (api) => api.createProposal({
|
|
4651
7596
|
clientKey,
|
|
4652
7597
|
need: need.trim(),
|
|
4653
7598
|
observed: observed.trim() || void 0,
|
|
@@ -4692,7 +7637,7 @@ function AddProposalForm({ context, onDone }) {
|
|
|
4692
7637
|
/* @__PURE__ */ jsx(FormActions, {
|
|
4693
7638
|
isSubmitDisabled: isSaving || need.trim() === "",
|
|
4694
7639
|
onCancel: onDone,
|
|
4695
|
-
submitLabel: "Add
|
|
7640
|
+
submitLabel: "Add intention"
|
|
4696
7641
|
})
|
|
4697
7642
|
]
|
|
4698
7643
|
});
|
|
@@ -4700,8 +7645,13 @@ function AddProposalForm({ context, onDone }) {
|
|
|
4700
7645
|
function ProposalsTab({ context }) {
|
|
4701
7646
|
const [isAdding, setIsAdding] = useState(false);
|
|
4702
7647
|
const proposals = context.detail.proposals.filter((proposal) => proposal.mergedIntoId === null);
|
|
7648
|
+
const { continuation } = context.detail;
|
|
4703
7649
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4704
|
-
|
|
7650
|
+
continuation && /* @__PURE__ */ jsx(UpdateCard, {
|
|
7651
|
+
context,
|
|
7652
|
+
continuation
|
|
7653
|
+
}),
|
|
7654
|
+
proposals.length === 0 && !continuation && /* @__PURE__ */ jsx("p", {
|
|
4705
7655
|
className: "dt-empty",
|
|
4706
7656
|
children: emptyCopy(context.detail)
|
|
4707
7657
|
}),
|
|
@@ -4718,14 +7668,14 @@ function ProposalsTab({ context }) {
|
|
|
4718
7668
|
className: "dt-btn",
|
|
4719
7669
|
onClick: () => setIsAdding(true),
|
|
4720
7670
|
type: "button",
|
|
4721
|
-
children: "Add
|
|
7671
|
+
children: "Add an intention"
|
|
4722
7672
|
})
|
|
4723
7673
|
}))
|
|
4724
7674
|
] });
|
|
4725
7675
|
}
|
|
4726
7676
|
//#endregion
|
|
4727
7677
|
//#region src/sessions/ui/review/review-header.tsx
|
|
4728
|
-
const DELETE_COPY = "Delete this session? Its recordings, transcript and
|
|
7678
|
+
const DELETE_COPY = "Delete this session? Its recordings, transcript and intentions are removed, including what later sessions added to those intentions. Briefs you already copied or exported can't be recalled.";
|
|
4729
7679
|
function useSessionUpdate(review, sessionId) {
|
|
4730
7680
|
const { api } = useSessionWorkspace();
|
|
4731
7681
|
const run = useSessionAction();
|
|
@@ -5084,60 +8034,23 @@ function AttachLink({ detail, onDone, review }) {
|
|
|
5084
8034
|
});
|
|
5085
8035
|
}
|
|
5086
8036
|
//#endregion
|
|
5087
|
-
//#region src/sessions/ui/review/transcript/
|
|
5088
|
-
const
|
|
5089
|
-
function
|
|
5090
|
-
const
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
}
|
|
5102
|
-
};
|
|
5103
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
5104
|
-
className: "dt-ses-banner",
|
|
5105
|
-
children: [/* @__PURE__ */ jsx("p", { children: REMOVE_COPY }), /* @__PURE__ */ jsxs("div", {
|
|
5106
|
-
className: "dt-actions",
|
|
5107
|
-
children: [/* @__PURE__ */ jsx("button", {
|
|
5108
|
-
className: "dt-btn",
|
|
5109
|
-
"data-variant": "danger",
|
|
5110
|
-
onClick: remove,
|
|
5111
|
-
type: "button",
|
|
5112
|
-
children: "Remove"
|
|
5113
|
-
}), /* @__PURE__ */ jsx("button", {
|
|
5114
|
-
className: "dt-btn",
|
|
5115
|
-
onClick: onCancel,
|
|
5116
|
-
ref: cancelRef,
|
|
5117
|
-
type: "button",
|
|
5118
|
-
children: "Cancel"
|
|
5119
|
-
})]
|
|
8037
|
+
//#region src/sessions/ui/review/transcript/evidence-tab.tsx
|
|
8038
|
+
const isViewable = (evidence) => evidence.status === "available" && Boolean(evidence.image?.url);
|
|
8039
|
+
function MissingEvidenceItem({ caption, evidence, isJustRemoved }) {
|
|
8040
|
+
const cardRef = useFocusWhen(isJustRemoved);
|
|
8041
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
8042
|
+
className: "dt-card",
|
|
8043
|
+
ref: cardRef,
|
|
8044
|
+
tabIndex: isJustRemoved ? -1 : void 0,
|
|
8045
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
8046
|
+
className: "dt-meta",
|
|
8047
|
+
children: caption
|
|
8048
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
8049
|
+
className: "dt-meta",
|
|
8050
|
+
children: ["Screenshot missing: ", evidence.missingReason ?? "not captured."]
|
|
5120
8051
|
})]
|
|
5121
8052
|
});
|
|
5122
8053
|
}
|
|
5123
|
-
function RemoveEvidence({ evidence, review }) {
|
|
5124
|
-
const [isConfirming, setIsConfirming] = useState(false);
|
|
5125
|
-
if (isConfirming) return /* @__PURE__ */ jsx(RemoveConfirmation, {
|
|
5126
|
-
evidence,
|
|
5127
|
-
onCancel: () => setIsConfirming(false),
|
|
5128
|
-
review
|
|
5129
|
-
});
|
|
5130
|
-
return /* @__PURE__ */ jsx("button", {
|
|
5131
|
-
"aria-label": `Remove screenshot at ${formatClock(evidence.atMs)}`,
|
|
5132
|
-
className: "dt-ses-link",
|
|
5133
|
-
onClick: () => setIsConfirming(true),
|
|
5134
|
-
type: "button",
|
|
5135
|
-
children: "Remove"
|
|
5136
|
-
});
|
|
5137
|
-
}
|
|
5138
|
-
//#endregion
|
|
5139
|
-
//#region src/sessions/ui/review/transcript/evidence-tab.tsx
|
|
5140
|
-
const isViewable = (evidence) => evidence.status === "available" && Boolean(evidence.image?.url);
|
|
5141
8054
|
function LinkEvidenceItem({ link }) {
|
|
5142
8055
|
return /* @__PURE__ */ jsxs("li", {
|
|
5143
8056
|
className: "dt-card",
|
|
@@ -5160,18 +8073,23 @@ function LinkEvidenceItem({ link }) {
|
|
|
5160
8073
|
]
|
|
5161
8074
|
});
|
|
5162
8075
|
}
|
|
5163
|
-
function
|
|
8076
|
+
function MomentLink({ atMs, onShowMoment }) {
|
|
8077
|
+
if (!onShowMoment) return null;
|
|
8078
|
+
return /* @__PURE__ */ jsx("button", {
|
|
8079
|
+
"aria-label": `Show the moment at ${formatClock(atMs)}`,
|
|
8080
|
+
className: "dt-ses-link",
|
|
8081
|
+
onClick: (event) => onShowMoment(event.currentTarget),
|
|
8082
|
+
type: "button",
|
|
8083
|
+
children: "Show moment"
|
|
8084
|
+
});
|
|
8085
|
+
}
|
|
8086
|
+
function EvidenceItem({ evidence, openers, removal }) {
|
|
5164
8087
|
const caption = `${formatClock(evidence.atMs)} · ${evidenceCaption(evidence)}`;
|
|
5165
8088
|
if (evidence.link) return /* @__PURE__ */ jsx(LinkEvidenceItem, { link: evidence.link });
|
|
5166
|
-
if (!isViewable(evidence)) return /* @__PURE__ */
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
children: caption
|
|
5171
|
-
}), /* @__PURE__ */ jsxs("p", {
|
|
5172
|
-
className: "dt-meta",
|
|
5173
|
-
children: ["Screenshot missing: ", evidence.missingReason ?? "not captured."]
|
|
5174
|
-
})]
|
|
8089
|
+
if (!isViewable(evidence)) return /* @__PURE__ */ jsx(MissingEvidenceItem, {
|
|
8090
|
+
caption,
|
|
8091
|
+
evidence,
|
|
8092
|
+
isJustRemoved: evidence.id === removal.removedId
|
|
5175
8093
|
});
|
|
5176
8094
|
return /* @__PURE__ */ jsxs("li", {
|
|
5177
8095
|
className: "dt-card",
|
|
@@ -5179,7 +8097,7 @@ function EvidenceItem({ evidence, onEnlarge, removal }) {
|
|
|
5179
8097
|
/* @__PURE__ */ jsx("button", {
|
|
5180
8098
|
"aria-label": `Enlarge: ${caption}`,
|
|
5181
8099
|
className: "dt-ses-shot-btn",
|
|
5182
|
-
onClick: onEnlarge,
|
|
8100
|
+
onClick: openers.onEnlarge,
|
|
5183
8101
|
type: "button",
|
|
5184
8102
|
children: /* @__PURE__ */ jsx(EvidenceThumb, { evidence })
|
|
5185
8103
|
}),
|
|
@@ -5191,9 +8109,13 @@ function EvidenceItem({ evidence, onEnlarge, removal }) {
|
|
|
5191
8109
|
className: "dt-note-text",
|
|
5192
8110
|
children: evidence.note
|
|
5193
8111
|
}),
|
|
8112
|
+
/* @__PURE__ */ jsx(MomentLink, {
|
|
8113
|
+
atMs: evidence.atMs,
|
|
8114
|
+
onShowMoment: openers.onShowMoment
|
|
8115
|
+
}),
|
|
5194
8116
|
removal.canRemove && /* @__PURE__ */ jsx(RemoveEvidence, {
|
|
5195
8117
|
evidence,
|
|
5196
|
-
|
|
8118
|
+
removal
|
|
5197
8119
|
})
|
|
5198
8120
|
]
|
|
5199
8121
|
});
|
|
@@ -5220,11 +8142,17 @@ function EnlargedEvidence({ evidence, onBack, removal }) {
|
|
|
5220
8142
|
}),
|
|
5221
8143
|
removal.canRemove && /* @__PURE__ */ jsx(RemoveEvidence, {
|
|
5222
8144
|
evidence,
|
|
5223
|
-
|
|
8145
|
+
removal
|
|
5224
8146
|
})
|
|
5225
8147
|
] });
|
|
5226
8148
|
}
|
|
5227
|
-
function EvidenceList({
|
|
8149
|
+
function EvidenceList({ detail, openers, removal }) {
|
|
8150
|
+
const { onEnlarge, onShowMoment } = openers;
|
|
8151
|
+
const { evidence } = detail;
|
|
8152
|
+
const momentOpener = (atMs) => {
|
|
8153
|
+
const segmentId = nearestSegmentId(detail.segments, atMs);
|
|
8154
|
+
return segmentId ? (opener) => onShowMoment(segmentId, opener) : null;
|
|
8155
|
+
};
|
|
5228
8156
|
return /* @__PURE__ */ jsxs(Fragment, { children: [evidence.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
5229
8157
|
className: "dt-empty",
|
|
5230
8158
|
children: "No screenshots or links in this session."
|
|
@@ -5232,352 +8160,86 @@ function EvidenceList({ evidence, onEnlarge, removal }) {
|
|
|
5232
8160
|
className: "dt-ses-rows",
|
|
5233
8161
|
children: evidence.map((item) => /* @__PURE__ */ jsx(EvidenceItem, {
|
|
5234
8162
|
evidence: item,
|
|
5235
|
-
|
|
8163
|
+
openers: {
|
|
8164
|
+
onEnlarge: () => onEnlarge(item.id),
|
|
8165
|
+
onShowMoment: momentOpener(item.atMs)
|
|
8166
|
+
},
|
|
5236
8167
|
removal
|
|
5237
8168
|
}, item.id))
|
|
5238
|
-
})] });
|
|
5239
|
-
}
|
|
5240
|
-
function EvidenceTab({ detail, review }) {
|
|
5241
|
-
const [enlargedId, setEnlargedId] = useState(null);
|
|
5242
|
-
const [isAttaching, setIsAttaching] = useState(false);
|
|
5243
|
-
const enlarged = detail.evidence.find((item) => item.id === enlargedId && isViewable(item));
|
|
5244
|
-
const removal = {
|
|
5245
|
-
canRemove: detail.session.canEdit,
|
|
5246
|
-
review
|
|
5247
|
-
};
|
|
5248
|
-
if (enlarged) return /* @__PURE__ */ jsx(EnlargedEvidence, {
|
|
5249
|
-
evidence: enlarged,
|
|
5250
|
-
onBack: () => setEnlargedId(null),
|
|
5251
|
-
removal
|
|
5252
|
-
});
|
|
5253
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(EvidenceList, {
|
|
5254
|
-
evidence: detail.evidence,
|
|
5255
|
-
onEnlarge: setEnlargedId,
|
|
5256
|
-
removal
|
|
5257
|
-
}), detail.session.canEdit && (isAttaching ? /* @__PURE__ */ jsx(AttachLink, {
|
|
5258
|
-
detail,
|
|
5259
|
-
onDone: () => setIsAttaching(false),
|
|
5260
|
-
review
|
|
5261
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
5262
|
-
className: "dt-actions",
|
|
5263
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
5264
|
-
className: "dt-btn",
|
|
5265
|
-
onClick: () => setIsAttaching(true),
|
|
5266
|
-
type: "button",
|
|
5267
|
-
children: "Attach a link"
|
|
5268
|
-
})
|
|
5269
|
-
}))] });
|
|
5270
|
-
}
|
|
5271
|
-
//#endregion
|
|
5272
|
-
//#region src/sessions/ui/review/transcript/segment-row.tsx
|
|
5273
|
-
function SaveCancelActions({ isSaveDisabled, onCancel, onSave }) {
|
|
5274
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
5275
|
-
className: "dt-actions",
|
|
5276
|
-
children: [/* @__PURE__ */ jsx("button", {
|
|
5277
|
-
className: "dt-btn",
|
|
5278
|
-
"data-variant": "primary",
|
|
5279
|
-
disabled: isSaveDisabled,
|
|
5280
|
-
onClick: onSave,
|
|
5281
|
-
type: "button",
|
|
5282
|
-
children: "Save"
|
|
5283
|
-
}), /* @__PURE__ */ jsx("button", {
|
|
5284
|
-
className: "dt-btn",
|
|
5285
|
-
onClick: onCancel,
|
|
5286
|
-
type: "button",
|
|
5287
|
-
children: "Cancel"
|
|
5288
|
-
})]
|
|
5289
|
-
});
|
|
5290
|
-
}
|
|
5291
|
-
function SegmentEditor({ onDone, review, segment }) {
|
|
5292
|
-
const { api } = useSessionWorkspace();
|
|
5293
|
-
const { isSaving, problem, save } = useGuardedSave();
|
|
5294
|
-
const [text, setText] = useState(segment.text);
|
|
5295
|
-
const submit = () => save(async () => {
|
|
5296
|
-
const saved = await api.correctSegment({
|
|
5297
|
-
expectedRevision: segment.revision,
|
|
5298
|
-
segmentId: segment.id,
|
|
5299
|
-
text: text.trim()
|
|
5300
|
-
});
|
|
5301
|
-
review.patch((detail) => withSegment(detail, saved));
|
|
5302
|
-
review.reload();
|
|
5303
|
-
onDone();
|
|
5304
|
-
});
|
|
5305
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
5306
|
-
className: "dt-ses-form",
|
|
5307
|
-
children: [
|
|
5308
|
-
/* @__PURE__ */ jsx("textarea", {
|
|
5309
|
-
"aria-label": `Passage at ${formatClock(segment.startMs)}`,
|
|
5310
|
-
className: "dt-field",
|
|
5311
|
-
maxLength: 5e3,
|
|
5312
|
-
onChange: (event) => setText(event.target.value),
|
|
5313
|
-
rows: 3,
|
|
5314
|
-
value: text
|
|
5315
|
-
}),
|
|
5316
|
-
problem && /* @__PURE__ */ jsx(SaveProblemMessage, {
|
|
5317
|
-
onReload: review.reload,
|
|
5318
|
-
problem
|
|
5319
|
-
}),
|
|
5320
|
-
/* @__PURE__ */ jsx(SaveCancelActions, {
|
|
5321
|
-
isSaveDisabled: isSaving || text.trim() === "",
|
|
5322
|
-
onCancel: onDone,
|
|
5323
|
-
onSave: submit
|
|
5324
|
-
})
|
|
5325
|
-
]
|
|
5326
|
-
});
|
|
5327
|
-
}
|
|
5328
|
-
function SegmentBody({ onType, review, segment }) {
|
|
5329
|
-
const { api } = useSessionWorkspace();
|
|
5330
|
-
const run = useSessionAction();
|
|
5331
|
-
const { state } = segment.transcription;
|
|
5332
|
-
const retry = async () => {
|
|
5333
|
-
const pending = await run("The transcription did not restart", () => api.retrySegment(segment.id));
|
|
5334
|
-
if (pending) review.patch((detail) => withSegment(detail, pending));
|
|
5335
|
-
};
|
|
5336
|
-
if (state === "pending" || state === "running") return /* @__PURE__ */ jsx("p", {
|
|
5337
|
-
className: "dt-status",
|
|
5338
|
-
children: "Transcribing…"
|
|
5339
|
-
});
|
|
5340
|
-
if (state === "failed" || state === "expired") return /* @__PURE__ */ jsxs("div", {
|
|
5341
|
-
className: "dt-actions",
|
|
5342
|
-
children: [
|
|
5343
|
-
/* @__PURE__ */ jsx("p", {
|
|
5344
|
-
className: "dt-error",
|
|
5345
|
-
children: state === "failed" ? "Couldn't transcribe" : "Audio expired — type what you said"
|
|
5346
|
-
}),
|
|
5347
|
-
onType && state === "failed" && segment.hasAudio && /* @__PURE__ */ jsx("button", {
|
|
5348
|
-
className: "dt-btn",
|
|
5349
|
-
onClick: retry,
|
|
5350
|
-
type: "button",
|
|
5351
|
-
children: "Retry"
|
|
5352
|
-
}),
|
|
5353
|
-
onType && /* @__PURE__ */ jsx("button", {
|
|
5354
|
-
className: "dt-btn",
|
|
5355
|
-
onClick: onType,
|
|
5356
|
-
type: "button",
|
|
5357
|
-
children: "Type it"
|
|
5358
|
-
})
|
|
5359
|
-
]
|
|
5360
|
-
});
|
|
5361
|
-
if (state === "empty" && segment.text === "") return /* @__PURE__ */ jsx("p", {
|
|
5362
|
-
className: "dt-meta",
|
|
5363
|
-
children: "No speech was heard here."
|
|
5364
|
-
});
|
|
5365
|
-
return /* @__PURE__ */ jsx("p", {
|
|
5366
|
-
className: "dt-note-text",
|
|
5367
|
-
children: segment.text
|
|
5368
|
-
});
|
|
5369
|
-
}
|
|
5370
|
-
function ContextChips({ segment }) {
|
|
5371
|
-
if (segment.context.length === 0) return null;
|
|
5372
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
5373
|
-
className: "dt-ses-chips",
|
|
5374
|
-
children: [segment.contextAmbiguous && /* @__PURE__ */ jsx("span", {
|
|
5375
|
-
className: "dt-chip",
|
|
5376
|
-
children: "Unclear which element"
|
|
5377
|
-
}), segment.context.map((item) => /* @__PURE__ */ jsx("span", {
|
|
5378
|
-
className: "dt-chip",
|
|
5379
|
-
children: item.label
|
|
5380
|
-
}, `${item.kind}:${item.atMs}:${item.label}`))]
|
|
5381
|
-
});
|
|
5382
|
-
}
|
|
5383
|
-
function EditPassageLink({ onEdit, time }) {
|
|
5384
|
-
return /* @__PURE__ */ jsx("button", {
|
|
5385
|
-
"aria-label": `Edit passage at ${time}`,
|
|
5386
|
-
className: "dt-ses-link",
|
|
5387
|
-
onClick: onEdit,
|
|
5388
|
-
type: "button",
|
|
5389
|
-
children: "Edit"
|
|
5390
|
-
});
|
|
5391
|
-
}
|
|
5392
|
-
function SegmentRow({ canEdit, isFocused, review, segment }) {
|
|
5393
|
-
const [isEditing, setIsEditing] = useState(false);
|
|
5394
|
-
const time = formatClock(segment.startMs);
|
|
5395
|
-
const isSettled = ["done", "empty"].includes(segment.transcription.state);
|
|
5396
|
-
return /* @__PURE__ */ jsxs("li", {
|
|
5397
|
-
className: "dt-ses-seg",
|
|
5398
|
-
ref: isFocused ? (node) => node?.focus({ preventScroll: false }) : void 0,
|
|
5399
|
-
tabIndex: -1,
|
|
5400
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
5401
|
-
className: "dt-ses-time",
|
|
5402
|
-
children: time
|
|
5403
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
5404
|
-
className: "dt-ses-seg-body",
|
|
5405
|
-
children: [
|
|
5406
|
-
isEditing ? /* @__PURE__ */ jsx(SegmentEditor, {
|
|
5407
|
-
onDone: () => setIsEditing(false),
|
|
5408
|
-
review,
|
|
5409
|
-
segment
|
|
5410
|
-
}) : /* @__PURE__ */ jsx(SegmentBody, {
|
|
5411
|
-
onType: canEdit ? () => setIsEditing(true) : void 0,
|
|
5412
|
-
review,
|
|
5413
|
-
segment
|
|
5414
|
-
}),
|
|
5415
|
-
/* @__PURE__ */ jsx(ContextChips, { segment }),
|
|
5416
|
-
canEdit && isSettled && !isEditing && /* @__PURE__ */ jsx(EditPassageLink, {
|
|
5417
|
-
onEdit: () => setIsEditing(true),
|
|
5418
|
-
time
|
|
5419
|
-
})
|
|
5420
|
-
]
|
|
5421
|
-
})]
|
|
5422
|
-
});
|
|
5423
|
-
}
|
|
5424
|
-
//#endregion
|
|
5425
|
-
//#region src/sessions/ui/review/transcript/transcript-tab.tsx
|
|
5426
|
-
const GAP_LABELS = {
|
|
5427
|
-
hidden: "Tab hidden",
|
|
5428
|
-
interrupted: "Interrupted",
|
|
5429
|
-
limit: "Time limit reached",
|
|
5430
|
-
paused: "Paused"
|
|
5431
|
-
};
|
|
5432
|
-
function gapLabel(gap) {
|
|
5433
|
-
const label = GAP_LABELS[gap.reason];
|
|
5434
|
-
return gap.endMs === null ? label : `${label} · ${shortDuration(gap.endMs - gap.startMs)}`;
|
|
5435
|
-
}
|
|
5436
|
-
function markLabel(mark) {
|
|
5437
|
-
const note = mark.note ? ` — ${mark.note}` : "";
|
|
5438
|
-
if (mark.kind === "navigation") return `Went to ${mark.url ?? mark.label}`;
|
|
5439
|
-
return `${mark.kind === "select" ? "Pointed at" : "Marked a moment on"} ${mark.label}${note}`;
|
|
5440
|
-
}
|
|
5441
|
-
function TimelineNote({ atMs, children }) {
|
|
5442
|
-
return /* @__PURE__ */ jsxs("li", {
|
|
5443
|
-
className: "dt-ses-seg",
|
|
5444
|
-
"data-kind": "note",
|
|
5445
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
5446
|
-
className: "dt-ses-time",
|
|
5447
|
-
children: formatClock(atMs)
|
|
5448
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
5449
|
-
className: "dt-meta",
|
|
5450
|
-
children
|
|
5451
|
-
})]
|
|
5452
|
-
});
|
|
5453
|
-
}
|
|
5454
|
-
function buildTimeline(detail) {
|
|
5455
|
-
return [
|
|
5456
|
-
...detail.segments.map((segment) => ({
|
|
5457
|
-
atMs: segment.startMs,
|
|
5458
|
-
key: `s:${segment.id}`,
|
|
5459
|
-
segment
|
|
5460
|
-
})),
|
|
5461
|
-
...detail.session.gaps.map((gap) => ({
|
|
5462
|
-
atMs: gap.startMs,
|
|
5463
|
-
key: `g:${gap.startMs}`,
|
|
5464
|
-
label: gapLabel(gap)
|
|
5465
|
-
})),
|
|
5466
|
-
...detail.marks.map((mark) => ({
|
|
5467
|
-
atMs: mark.atMs,
|
|
5468
|
-
key: `m:${mark.kind}:${mark.atMs}`,
|
|
5469
|
-
label: markLabel(mark)
|
|
5470
|
-
}))
|
|
5471
|
-
].sort((earlier, later) => earlier.atMs - later.atMs);
|
|
5472
|
-
}
|
|
5473
|
-
function useTypedPassage({ detail, review }) {
|
|
5474
|
-
const { api } = useSessionWorkspace();
|
|
5475
|
-
const run = useSessionAction();
|
|
5476
|
-
const [text, setText] = useState("");
|
|
5477
|
-
const [clientKey, setClientKey] = useState(() => crypto.randomUUID());
|
|
5478
|
-
const atMs = detail.segments.at(-1)?.endMs ?? 0;
|
|
5479
|
-
const add = async () => {
|
|
5480
|
-
const segment = await run("The passage was not added", () => api.addTypedSegment({
|
|
5481
|
-
clientKey,
|
|
5482
|
-
endMs: atMs,
|
|
5483
|
-
sessionId: detail.session.id,
|
|
5484
|
-
startMs: atMs,
|
|
5485
|
-
text: text.trim()
|
|
5486
|
-
}));
|
|
5487
|
-
if (segment) {
|
|
5488
|
-
review.patch((current) => withSegment(current, segment));
|
|
5489
|
-
setText("");
|
|
5490
|
-
setClientKey(crypto.randomUUID());
|
|
5491
|
-
}
|
|
5492
|
-
};
|
|
5493
|
-
return {
|
|
5494
|
-
add,
|
|
5495
|
-
setText,
|
|
5496
|
-
text
|
|
5497
|
-
};
|
|
8169
|
+
})] });
|
|
5498
8170
|
}
|
|
5499
|
-
function
|
|
5500
|
-
const
|
|
8171
|
+
function AttachLinkAction({ detail, review }) {
|
|
8172
|
+
const [isAttaching, setIsAttaching] = useState(false);
|
|
8173
|
+
if (isAttaching) return /* @__PURE__ */ jsx(AttachLink, {
|
|
5501
8174
|
detail,
|
|
8175
|
+
onDone: () => setIsAttaching(false),
|
|
5502
8176
|
review
|
|
5503
8177
|
});
|
|
5504
|
-
return /* @__PURE__ */
|
|
5505
|
-
className: "dt-
|
|
5506
|
-
children:
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
rows: 2,
|
|
5513
|
-
value: text
|
|
5514
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
5515
|
-
className: "dt-actions",
|
|
5516
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
5517
|
-
className: "dt-btn",
|
|
5518
|
-
disabled: text.trim() === "",
|
|
5519
|
-
onClick: add,
|
|
5520
|
-
type: "button",
|
|
5521
|
-
children: "Add passage"
|
|
5522
|
-
})
|
|
5523
|
-
})]
|
|
8178
|
+
return /* @__PURE__ */ jsx("div", {
|
|
8179
|
+
className: "dt-actions",
|
|
8180
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
8181
|
+
className: "dt-btn",
|
|
8182
|
+
onClick: () => setIsAttaching(true),
|
|
8183
|
+
type: "button",
|
|
8184
|
+
children: "Attach a link"
|
|
8185
|
+
})
|
|
5524
8186
|
});
|
|
5525
8187
|
}
|
|
5526
|
-
function
|
|
5527
|
-
const
|
|
5528
|
-
const
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
}
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
] });
|
|
8188
|
+
function EvidenceTab({ detail, onShowMoment, review }) {
|
|
8189
|
+
const [enlargedId, setEnlargedId] = useState(null);
|
|
8190
|
+
const [removedId, setRemovedId] = useState(null);
|
|
8191
|
+
const enlarged = detail.evidence.find((item) => item.id === enlargedId && isViewable(item));
|
|
8192
|
+
const removal = {
|
|
8193
|
+
canRemove: detail.session.canEdit,
|
|
8194
|
+
onRemoved: (evidenceId) => {
|
|
8195
|
+
setEnlargedId(null);
|
|
8196
|
+
setRemovedId(evidenceId);
|
|
8197
|
+
},
|
|
8198
|
+
removedId,
|
|
8199
|
+
review
|
|
8200
|
+
};
|
|
8201
|
+
if (enlarged) return /* @__PURE__ */ jsx(EnlargedEvidence, {
|
|
8202
|
+
evidence: enlarged,
|
|
8203
|
+
onBack: () => setEnlargedId(null),
|
|
8204
|
+
removal
|
|
8205
|
+
});
|
|
8206
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(EvidenceList, {
|
|
8207
|
+
detail,
|
|
8208
|
+
openers: {
|
|
8209
|
+
onEnlarge: setEnlargedId,
|
|
8210
|
+
onShowMoment
|
|
8211
|
+
},
|
|
8212
|
+
removal
|
|
8213
|
+
}), detail.session.canEdit && /* @__PURE__ */ jsx(AttachLinkAction, {
|
|
8214
|
+
detail,
|
|
8215
|
+
review
|
|
8216
|
+
})] });
|
|
5555
8217
|
}
|
|
5556
8218
|
//#endregion
|
|
5557
8219
|
//#region src/sessions/ui/review/session-review.tsx
|
|
5558
8220
|
function HandoffFooter({ detail, onHandOff }) {
|
|
5559
|
-
const
|
|
8221
|
+
const confirmed = detail.proposals.filter((proposal) => proposal.decision === "kept" && proposal.mergedIntoId === null).length;
|
|
5560
8222
|
return /* @__PURE__ */ jsxs("div", {
|
|
5561
8223
|
className: "dt-sheet-foot",
|
|
5562
8224
|
children: [/* @__PURE__ */ jsx("button", {
|
|
5563
|
-
"aria-describedby":
|
|
8225
|
+
"aria-describedby": confirmed === 0 ? "rf-handoff-hint" : void 0,
|
|
5564
8226
|
className: "dt-btn",
|
|
5565
8227
|
"data-variant": "primary",
|
|
5566
|
-
disabled:
|
|
8228
|
+
disabled: confirmed === 0,
|
|
5567
8229
|
onClick: onHandOff,
|
|
5568
8230
|
type: "button",
|
|
5569
|
-
children:
|
|
5570
|
-
}),
|
|
8231
|
+
children: confirmed === 0 ? "Hand off" : `Hand off ${confirmed} confirmed`
|
|
8232
|
+
}), confirmed === 0 && /* @__PURE__ */ jsx("span", {
|
|
5571
8233
|
className: "dt-status",
|
|
5572
8234
|
id: "rf-handoff-hint",
|
|
5573
|
-
children: "
|
|
8235
|
+
children: "Confirm an intention to hand it off"
|
|
5574
8236
|
})]
|
|
5575
8237
|
});
|
|
5576
8238
|
}
|
|
5577
8239
|
function ReviewTabs({ detail, onSelect, tab }) {
|
|
5578
8240
|
const options = [
|
|
5579
8241
|
{
|
|
5580
|
-
label: `
|
|
8242
|
+
label: `Intentions (${detail.proposals.filter((proposal) => proposal.mergedIntoId === null && proposal.attachment === null).length})`,
|
|
5581
8243
|
value: "proposals"
|
|
5582
8244
|
},
|
|
5583
8245
|
{
|
|
@@ -5600,43 +8262,114 @@ function ReviewTabs({ detail, onSelect, tab }) {
|
|
|
5600
8262
|
}, option.value))
|
|
5601
8263
|
});
|
|
5602
8264
|
}
|
|
5603
|
-
function ReviewTabPanel({ context,
|
|
5604
|
-
const { detail, review } = context;
|
|
8265
|
+
function ReviewTabPanel({ context, tab }) {
|
|
8266
|
+
const { detail, navigation, review } = context;
|
|
5605
8267
|
if (tab === "proposals") return /* @__PURE__ */ jsx(ProposalsTab, { context });
|
|
5606
8268
|
if (tab === "transcript") return /* @__PURE__ */ jsx(TranscriptTab, {
|
|
5607
8269
|
detail,
|
|
5608
|
-
|
|
8270
|
+
onShowMoment: navigation.onShowMoment,
|
|
5609
8271
|
review
|
|
5610
8272
|
});
|
|
5611
8273
|
return /* @__PURE__ */ jsx(EvidenceTab, {
|
|
5612
8274
|
detail,
|
|
8275
|
+
onShowMoment: navigation.onShowMoment,
|
|
5613
8276
|
review
|
|
5614
8277
|
});
|
|
5615
8278
|
}
|
|
5616
|
-
function
|
|
8279
|
+
function useReviewPlace() {
|
|
5617
8280
|
const [tab, setTab] = useState("proposals");
|
|
5618
|
-
const [
|
|
5619
|
-
const
|
|
5620
|
-
|
|
5621
|
-
|
|
8281
|
+
const [focusIntentionId, setFocusIntentionId] = useState(null);
|
|
8282
|
+
const [moment, setMoment] = useState(null);
|
|
8283
|
+
const closeMoment = (shouldReturnFocus) => {
|
|
8284
|
+
const opener = moment?.opener;
|
|
8285
|
+
flushSync(() => setMoment(null));
|
|
8286
|
+
if (shouldReturnFocus && opener?.isConnected) opener.focus();
|
|
8287
|
+
};
|
|
8288
|
+
const showMoment = (segmentId, opener) => {
|
|
8289
|
+
setFocusIntentionId(null);
|
|
8290
|
+
setMoment((current) => ({
|
|
8291
|
+
opener: opener ?? current?.opener ?? null,
|
|
8292
|
+
segmentId
|
|
8293
|
+
}));
|
|
5622
8294
|
};
|
|
5623
8295
|
return {
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
8296
|
+
closeMoment,
|
|
8297
|
+
focusIntentionId,
|
|
8298
|
+
moment,
|
|
8299
|
+
openIntention: (proposalId) => {
|
|
8300
|
+
closeMoment(false);
|
|
8301
|
+
setTab("proposals");
|
|
8302
|
+
setFocusIntentionId(proposalId);
|
|
8303
|
+
},
|
|
8304
|
+
selectTab: (next) => {
|
|
8305
|
+
closeMoment(false);
|
|
8306
|
+
setTab(next);
|
|
8307
|
+
},
|
|
8308
|
+
showMoment,
|
|
5627
8309
|
tab
|
|
5628
8310
|
};
|
|
5629
8311
|
}
|
|
5630
|
-
function
|
|
5631
|
-
const {
|
|
8312
|
+
function ReviewPanels({ context, place }) {
|
|
8313
|
+
const { detail, review } = context;
|
|
8314
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
8315
|
+
/* @__PURE__ */ jsx(ReviewTabs, {
|
|
8316
|
+
detail,
|
|
8317
|
+
onSelect: place.selectTab,
|
|
8318
|
+
tab: place.tab
|
|
8319
|
+
}),
|
|
8320
|
+
/* @__PURE__ */ jsx("div", {
|
|
8321
|
+
className: "dt-ses-panel",
|
|
8322
|
+
hidden: place.moment !== null,
|
|
8323
|
+
children: /* @__PURE__ */ jsx(ReviewTabPanel, {
|
|
8324
|
+
context,
|
|
8325
|
+
tab: place.tab
|
|
8326
|
+
})
|
|
8327
|
+
}),
|
|
8328
|
+
place.moment && /* @__PURE__ */ jsx(MomentView, {
|
|
8329
|
+
handlers: {
|
|
8330
|
+
onClose: () => place.closeMoment(true),
|
|
8331
|
+
onOpenIntention: place.openIntention,
|
|
8332
|
+
onShowMoment: (segmentId) => place.showMoment(segmentId)
|
|
8333
|
+
},
|
|
8334
|
+
segmentId: place.moment.segmentId,
|
|
8335
|
+
session: {
|
|
8336
|
+
detail,
|
|
8337
|
+
review
|
|
8338
|
+
}
|
|
8339
|
+
})
|
|
8340
|
+
] });
|
|
8341
|
+
}
|
|
8342
|
+
function ReviewTop({ context, links }) {
|
|
8343
|
+
const { detail, review } = context;
|
|
8344
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
8345
|
+
/* @__PURE__ */ jsx(ReviewHeader, {
|
|
8346
|
+
onBack: links.onBack,
|
|
8347
|
+
review,
|
|
8348
|
+
session: detail.session
|
|
8349
|
+
}),
|
|
8350
|
+
/* @__PURE__ */ jsx(StatusStrip, {
|
|
8351
|
+
detail,
|
|
8352
|
+
review
|
|
8353
|
+
}),
|
|
8354
|
+
detail.continuation && /* @__PURE__ */ jsx(ContinuationHeader, {
|
|
8355
|
+
continuation: detail.continuation,
|
|
8356
|
+
onOpenSession: links.onOpenSession
|
|
8357
|
+
})
|
|
8358
|
+
] });
|
|
8359
|
+
}
|
|
8360
|
+
function ReviewScreen({ detail, links, review }) {
|
|
8361
|
+
const place = useReviewPlace();
|
|
5632
8362
|
const [isHandingOff, setIsHandingOff] = useState(false);
|
|
5633
|
-
const actions = useProposalActions(review);
|
|
5634
|
-
const { canEdit } = detail.session;
|
|
5635
8363
|
const context = {
|
|
5636
|
-
actions,
|
|
5637
|
-
canEdit,
|
|
8364
|
+
actions: useProposalActions(review),
|
|
8365
|
+
canEdit: detail.session.canEdit,
|
|
5638
8366
|
detail,
|
|
5639
|
-
|
|
8367
|
+
focusIntentionId: place.focusIntentionId,
|
|
8368
|
+
navigation: {
|
|
8369
|
+
onOpenSession: links.onOpenSession,
|
|
8370
|
+
onShowMoment: place.showMoment,
|
|
8371
|
+
onStarted: links.onStarted
|
|
8372
|
+
},
|
|
5640
8373
|
review
|
|
5641
8374
|
};
|
|
5642
8375
|
if (isHandingOff) return /* @__PURE__ */ jsx(HandoffView, {
|
|
@@ -5645,32 +8378,21 @@ function ReviewScreen({ detail, onBack, review }) {
|
|
|
5645
8378
|
review
|
|
5646
8379
|
});
|
|
5647
8380
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5648
|
-
/* @__PURE__ */ jsx(
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
session: detail.session
|
|
5652
|
-
}),
|
|
5653
|
-
/* @__PURE__ */ jsx(StatusStrip, {
|
|
5654
|
-
detail,
|
|
5655
|
-
review
|
|
5656
|
-
}),
|
|
5657
|
-
/* @__PURE__ */ jsx(ReviewTabs, {
|
|
5658
|
-
detail,
|
|
5659
|
-
onSelect: setTab,
|
|
5660
|
-
tab
|
|
8381
|
+
/* @__PURE__ */ jsx(ReviewTop, {
|
|
8382
|
+
context,
|
|
8383
|
+
links
|
|
5661
8384
|
}),
|
|
5662
|
-
/* @__PURE__ */ jsx(
|
|
8385
|
+
/* @__PURE__ */ jsx(ReviewPanels, {
|
|
5663
8386
|
context,
|
|
5664
|
-
|
|
5665
|
-
tab
|
|
8387
|
+
place
|
|
5666
8388
|
}),
|
|
5667
|
-
canEdit && /* @__PURE__ */ jsx(HandoffFooter, {
|
|
8389
|
+
context.canEdit && /* @__PURE__ */ jsx(HandoffFooter, {
|
|
5668
8390
|
detail,
|
|
5669
8391
|
onHandOff: () => setIsHandingOff(true)
|
|
5670
8392
|
})
|
|
5671
8393
|
] });
|
|
5672
8394
|
}
|
|
5673
|
-
function SessionReview({
|
|
8395
|
+
function SessionReview({ links, sessionId }) {
|
|
5674
8396
|
const handle = useSessionDetail(sessionId);
|
|
5675
8397
|
const { state } = handle;
|
|
5676
8398
|
if (state.kind === "loading") return /* @__PURE__ */ jsx("p", {
|
|
@@ -5679,7 +8401,7 @@ function SessionReview({ onBack, sessionId }) {
|
|
|
5679
8401
|
});
|
|
5680
8402
|
if (state.kind === "ready") return /* @__PURE__ */ jsx(ReviewScreen, {
|
|
5681
8403
|
detail: state.detail,
|
|
5682
|
-
|
|
8404
|
+
links,
|
|
5683
8405
|
review: handle
|
|
5684
8406
|
});
|
|
5685
8407
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -5693,7 +8415,7 @@ function SessionReview({ onBack, sessionId }) {
|
|
|
5693
8415
|
children: "Try again"
|
|
5694
8416
|
}), /* @__PURE__ */ jsx("button", {
|
|
5695
8417
|
className: "dt-btn",
|
|
5696
|
-
onClick: onBack,
|
|
8418
|
+
onClick: links.onBack,
|
|
5697
8419
|
type: "button",
|
|
5698
8420
|
children: "All sessions"
|
|
5699
8421
|
})]
|
|
@@ -5834,141 +8556,6 @@ function SessionList({ drafts, onOpen }) {
|
|
|
5834
8556
|
] });
|
|
5835
8557
|
}
|
|
5836
8558
|
//#endregion
|
|
5837
|
-
//#region src/sessions/ui/start/session-disclosure.tsx
|
|
5838
|
-
const SEEN_KEY_PREFIX = "reflet-sessions-disclosure:";
|
|
5839
|
-
function hasSeenDisclosure(accountKey) {
|
|
5840
|
-
try {
|
|
5841
|
-
return localStorage.getItem(`${SEEN_KEY_PREFIX}${accountKey}`) !== null;
|
|
5842
|
-
} catch {
|
|
5843
|
-
return false;
|
|
5844
|
-
}
|
|
5845
|
-
}
|
|
5846
|
-
function markDisclosureSeen(accountKey) {
|
|
5847
|
-
try {
|
|
5848
|
-
localStorage.setItem(`${SEEN_KEY_PREFIX}${accountKey}`, "1");
|
|
5849
|
-
} catch {}
|
|
5850
|
-
}
|
|
5851
|
-
function DisclosureList() {
|
|
5852
|
-
return /* @__PURE__ */ jsxs("section", {
|
|
5853
|
-
"aria-labelledby": "rf-session-disclosure",
|
|
5854
|
-
className: "dt-hint",
|
|
5855
|
-
children: [/* @__PURE__ */ jsx("h3", {
|
|
5856
|
-
className: "dt-ses-subhead",
|
|
5857
|
-
id: "rf-session-disclosure",
|
|
5858
|
-
children: "What a session records"
|
|
5859
|
-
}), /* @__PURE__ */ jsxs("ul", {
|
|
5860
|
-
className: "dt-ses-list",
|
|
5861
|
-
children: [
|
|
5862
|
-
/* @__PURE__ */ jsx("li", { children: "Your voice while recording. It is sent to Reflet and transcribed by Whisper through OpenRouter with zero data retention. The audio is deleted as soon as its transcript is saved, within 24 hours at most." }),
|
|
5863
|
-
/* @__PURE__ */ jsx("li", { children: "The page: its address without sensitive parameters, its title, the scroll position and the window size." }),
|
|
5864
|
-
/* @__PURE__ */ jsx("li", { children: "What you click, point at and mark: element names and positions, never your keystrokes." }),
|
|
5865
|
-
/* @__PURE__ */ jsxs("li", { children: [
|
|
5866
|
-
"Screenshots of this tab at the start, when you mark a moment and when the page changes. Form fields appear empty; password fields and elements marked ",
|
|
5867
|
-
/* @__PURE__ */ jsx("code", { children: "data-reflet-redact" }),
|
|
5868
|
-
" are hidden. Other text on screen, rich-text editors included, stays visible."
|
|
5869
|
-
] }),
|
|
5870
|
-
/* @__PURE__ */ jsx("li", { children: "Not recorded: keystrokes, other tabs or apps, network calls." }),
|
|
5871
|
-
/* @__PURE__ */ jsx("li", { children: "Pausing stops everything." }),
|
|
5872
|
-
/* @__PURE__ */ jsx("li", { children: "A session stays private to you until you share it." })
|
|
5873
|
-
]
|
|
5874
|
-
})]
|
|
5875
|
-
});
|
|
5876
|
-
}
|
|
5877
|
-
function SessionDisclosure({ accountKey }) {
|
|
5878
|
-
const [isOpen, setIsOpen] = useState(() => !hasSeenDisclosure(accountKey));
|
|
5879
|
-
if (isOpen) return /* @__PURE__ */ jsx(DisclosureList, {});
|
|
5880
|
-
return /* @__PURE__ */ jsx("button", {
|
|
5881
|
-
"aria-expanded": false,
|
|
5882
|
-
className: "dt-ses-link",
|
|
5883
|
-
onClick: () => setIsOpen(true),
|
|
5884
|
-
type: "button",
|
|
5885
|
-
children: "What a session records"
|
|
5886
|
-
});
|
|
5887
|
-
}
|
|
5888
|
-
//#endregion
|
|
5889
|
-
//#region src/sessions/ui/start/start-session.tsx
|
|
5890
|
-
const IS_MICROPHONE_FAILURE = {
|
|
5891
|
-
auth: false,
|
|
5892
|
-
insecure_context: true,
|
|
5893
|
-
limit: false,
|
|
5894
|
-
mic_blocked_by_site: true,
|
|
5895
|
-
mic_denied: true,
|
|
5896
|
-
mic_unavailable: true,
|
|
5897
|
-
network: false,
|
|
5898
|
-
storage: false
|
|
5899
|
-
};
|
|
5900
|
-
function StartProblem({ failure, onRetry, onWriteNote }) {
|
|
5901
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
5902
|
-
className: "dt-ses-problem",
|
|
5903
|
-
role: "alert",
|
|
5904
|
-
children: [/* @__PURE__ */ jsx("p", {
|
|
5905
|
-
className: "dt-error",
|
|
5906
|
-
children: failure.message
|
|
5907
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
5908
|
-
className: "dt-actions",
|
|
5909
|
-
children: [/* @__PURE__ */ jsx("button", {
|
|
5910
|
-
className: "dt-btn",
|
|
5911
|
-
onClick: onRetry,
|
|
5912
|
-
type: "button",
|
|
5913
|
-
children: "Try again"
|
|
5914
|
-
}), IS_MICROPHONE_FAILURE[failure.kind] && onWriteNote && /* @__PURE__ */ jsx("button", {
|
|
5915
|
-
className: "dt-btn",
|
|
5916
|
-
onClick: onWriteNote,
|
|
5917
|
-
type: "button",
|
|
5918
|
-
children: "Write a note instead"
|
|
5919
|
-
})]
|
|
5920
|
-
})]
|
|
5921
|
-
});
|
|
5922
|
-
}
|
|
5923
|
-
function StartActions({ isBusy, onStart, onWriteNote }) {
|
|
5924
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
5925
|
-
className: "dt-actions",
|
|
5926
|
-
children: [/* @__PURE__ */ jsx("button", {
|
|
5927
|
-
className: "dt-btn",
|
|
5928
|
-
"data-variant": "primary",
|
|
5929
|
-
disabled: isBusy,
|
|
5930
|
-
onClick: onStart,
|
|
5931
|
-
type: "button",
|
|
5932
|
-
children: "Start session"
|
|
5933
|
-
}), onWriteNote && /* @__PURE__ */ jsx("button", {
|
|
5934
|
-
className: "dt-btn",
|
|
5935
|
-
onClick: onWriteNote,
|
|
5936
|
-
type: "button",
|
|
5937
|
-
children: "Write a note instead"
|
|
5938
|
-
})]
|
|
5939
|
-
});
|
|
5940
|
-
}
|
|
5941
|
-
function StartSession({ onStarted, onWriteNote }) {
|
|
5942
|
-
const { account, recorder } = useSessionWorkspace();
|
|
5943
|
-
const snapshot = useRecorderSnapshot(recorder);
|
|
5944
|
-
const [hasTried, setHasTried] = useState(false);
|
|
5945
|
-
const start = async () => {
|
|
5946
|
-
markDisclosureSeen(account.key);
|
|
5947
|
-
setHasTried(true);
|
|
5948
|
-
await recorder.start({});
|
|
5949
|
-
if (recorder.getSnapshot().phase === "recording") onStarted();
|
|
5950
|
-
};
|
|
5951
|
-
const isRequesting = snapshot.phase === "requesting";
|
|
5952
|
-
const startFailure = hasTried && snapshot.phase === "failed" && snapshot.sessionKey === null ? snapshot.failure : null;
|
|
5953
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5954
|
-
/* @__PURE__ */ jsx(SessionDisclosure, { accountKey: account.key }),
|
|
5955
|
-
/* @__PURE__ */ jsx("p", {
|
|
5956
|
-
"aria-live": "polite",
|
|
5957
|
-
className: "dt-meta",
|
|
5958
|
-
children: isRequesting ? "Allow the microphone in your browser to start." : ""
|
|
5959
|
-
}),
|
|
5960
|
-
startFailure ? /* @__PURE__ */ jsx(StartProblem, {
|
|
5961
|
-
failure: startFailure,
|
|
5962
|
-
onRetry: start,
|
|
5963
|
-
onWriteNote
|
|
5964
|
-
}) : /* @__PURE__ */ jsx(StartActions, {
|
|
5965
|
-
isBusy: isRequesting || snapshot.phase === "finishing",
|
|
5966
|
-
onStart: start,
|
|
5967
|
-
onWriteNote
|
|
5968
|
-
})
|
|
5969
|
-
] });
|
|
5970
|
-
}
|
|
5971
|
-
//#endregion
|
|
5972
8559
|
//#region src/sessions/ui/start/sessions-home.tsx
|
|
5973
8560
|
function useDraftRecovery({ draft, onOpenSession, onStarted }) {
|
|
5974
8561
|
const { account, api, recorder } = useSessionWorkspace();
|
|
@@ -6060,7 +8647,14 @@ function SessionsPanel({ navigation, onStarted, onWriteNote }) {
|
|
|
6060
8647
|
const sessionId = useResolvedSessionId(navigation.open);
|
|
6061
8648
|
const back = () => navigation.onOpen(null);
|
|
6062
8649
|
if (sessionId) return /* @__PURE__ */ jsx(SessionReview, {
|
|
6063
|
-
|
|
8650
|
+
links: {
|
|
8651
|
+
onBack: back,
|
|
8652
|
+
onOpenSession: (openedId) => navigation.onOpen({
|
|
8653
|
+
sessionId: openedId,
|
|
8654
|
+
sessionKey: null
|
|
8655
|
+
}),
|
|
8656
|
+
onStarted
|
|
8657
|
+
},
|
|
6064
8658
|
sessionId
|
|
6065
8659
|
}, sessionId);
|
|
6066
8660
|
if (navigation.open) return /* @__PURE__ */ jsxs("div", {
|
|
@@ -6080,6 +8674,6 @@ function SessionsPanel({ navigation, onStarted, onWriteNote }) {
|
|
|
6080
8674
|
});
|
|
6081
8675
|
}
|
|
6082
8676
|
//#endregion
|
|
6083
|
-
export {
|
|
8677
|
+
export { Notices as A, searchCode as C, openDatabase as D, describeStorageFailure as E, reportFailure as M, settle as O, probeDevRoute as S, committed as T, teamTransport as _, DevtoolsBar as a, fetchBoardFeedback as b, copyToClipboard as c, PersonIcon as d, SessionWorkspaceProvider as f, devtoolsTransport as g, purgeAccount as h, BarButton as i, announce as j, sessionApiErrorSchema as k, SESSIONS_STYLES as l, useWorkspaceLoader as m, DEVTOOLS_STYLES as n, DevtoolsBarButtons as o, failureMessage as p, DevtoolsSheet as r, SessionsBarButton as s, SessionsPanel as t, RecordingLayer as u, callReflet as v, startBoardConnect as w, fetchSourceFile as x, disconnectBoard as y };
|
|
6084
8678
|
|
|
6085
|
-
//# sourceMappingURL=sessions-panel-
|
|
8679
|
+
//# sourceMappingURL=sessions-panel-DPhFYhbk.js.map
|