@opengeni/react 3.8.0-canary.0 → 4.0.1-canary.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.
Files changed (111) hide show
  1. package/README.md +11 -24
  2. package/dist/artifacts.js +7 -7
  3. package/dist/{browser-viewer-SS46LUAC.js → browser-viewer-NTD6UIPR.js} +3 -3
  4. package/dist/{chunk-VEPNNDNG.js → chunk-24M4YBCL.js} +4 -4
  5. package/dist/chunk-5M6VTFJ5.js +171 -0
  6. package/dist/chunk-5M6VTFJ5.js.map +1 -0
  7. package/dist/{chunk-IOLVN74D.js → chunk-6J56T3MG.js} +15 -1
  8. package/dist/chunk-6J56T3MG.js.map +1 -0
  9. package/dist/{chunk-ELIYWBZR.js → chunk-BV5A4OTE.js} +4 -4
  10. package/dist/{chunk-3HIF5IFC.js → chunk-HO6X4FHG.js} +2597 -1030
  11. package/dist/chunk-HO6X4FHG.js.map +1 -0
  12. package/dist/{chunk-BXJOQFCD.js → chunk-HUMIRRYI.js} +84 -221
  13. package/dist/chunk-HUMIRRYI.js.map +1 -0
  14. package/dist/{chunk-ZFOFY5ST.js → chunk-NLTS7JHI.js} +16 -31
  15. package/dist/chunk-NLTS7JHI.js.map +1 -0
  16. package/dist/chunk-NOSYBS2D.js +208 -0
  17. package/dist/chunk-NOSYBS2D.js.map +1 -0
  18. package/dist/chunk-YBM6CQRU.js +292 -0
  19. package/dist/chunk-YBM6CQRU.js.map +1 -0
  20. package/dist/{chunk-BQGIXRKV.js → chunk-Z7WBDB4E.js} +416 -290
  21. package/dist/chunk-Z7WBDB4E.js.map +1 -0
  22. package/dist/chunk-ZYRGEKWS.js +216 -0
  23. package/dist/chunk-ZYRGEKWS.js.map +1 -0
  24. package/dist/components/composer.d.ts +4 -2
  25. package/dist/components/human-input-form.d.ts +3 -1
  26. package/dist/components/human-input-surface.d.ts +3 -2
  27. package/dist/components/message-timeline.d.ts +9 -2
  28. package/dist/components/session-conversation.d.ts +2 -0
  29. package/dist/components/timeline-annotation-chrome.d.ts +25 -0
  30. package/dist/components/timeline-annotation-layout.d.ts +42 -0
  31. package/dist/components/timeline-annotation-markers.d.ts +8 -0
  32. package/dist/components/timeline-annotation-reveal-context.d.ts +4 -0
  33. package/dist/components/timeline-annotation-shared.d.ts +31 -0
  34. package/dist/components/timeline-annotations-dialog.d.ts +1 -1
  35. package/dist/components/timeline-annotations.d.ts +8 -2
  36. package/dist/composer.js +3 -4
  37. package/dist/{computer-viewer-FWEZISW4.js → computer-viewer-7TEZC6Y2.js} +3 -3
  38. package/dist/hooks/use-codex-accounts.d.ts +11 -1
  39. package/dist/hooks/use-composer.d.ts +2 -0
  40. package/dist/hooks/use-session-events.d.ts +2 -2
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.js +50 -37
  43. package/dist/index.js.map +1 -1
  44. package/dist/interaction.js +3 -3
  45. package/dist/machines.js +1 -1
  46. package/dist/older-history.d.ts +3 -2
  47. package/dist/{platform-activity-row-BJHUHXAT.js → platform-activity-row-27LKAWZD.js} +2 -2
  48. package/dist/platform-activity-row-27LKAWZD.js.map +1 -0
  49. package/dist/{queue-surface-implementation-XL2A6PBM.js → queue-surface-implementation-LK3S4BIW.js} +5 -2
  50. package/dist/{queue-surface-implementation-XL2A6PBM.js.map → queue-surface-implementation-LK3S4BIW.js.map} +1 -1
  51. package/dist/realtime.js +3 -3
  52. package/dist/session-ui.js +11 -11
  53. package/dist/session.js +2 -2
  54. package/dist/timeline/types.d.ts +3 -0
  55. package/dist/timeline-annotation-markers-DRSTLEM6.js +165 -0
  56. package/dist/timeline-annotation-markers-DRSTLEM6.js.map +1 -0
  57. package/dist/timeline-annotation-selection-IHLUGVND.js +236 -0
  58. package/dist/timeline-annotation-selection-IHLUGVND.js.map +1 -0
  59. package/dist/timeline-annotations-dialog-6Q53N5FG.js +262 -0
  60. package/dist/timeline-annotations-dialog-6Q53N5FG.js.map +1 -0
  61. package/package.json +2 -6
  62. package/src/components/chat-composer.tsx +5 -2
  63. package/src/components/composer.tsx +40 -5
  64. package/src/components/copy-button.tsx +1 -0
  65. package/src/components/human-input-form.tsx +185 -53
  66. package/src/components/human-input-surface.tsx +3 -0
  67. package/src/components/machines-dashboard.tsx +1 -16
  68. package/src/components/message-timeline.tsx +397 -302
  69. package/src/components/session-commands-panel.tsx +7 -1
  70. package/src/components/session-conversation.tsx +4 -1
  71. package/src/components/timeline-annotation-chrome.tsx +183 -0
  72. package/src/components/timeline-annotation-layout.ts +268 -0
  73. package/src/components/timeline-annotation-markers.tsx +185 -0
  74. package/src/components/timeline-annotation-reveal-context.ts +9 -0
  75. package/src/components/timeline-annotation-selection.tsx +99 -54
  76. package/src/components/timeline-annotation-shared.ts +245 -0
  77. package/src/components/timeline-annotations-dialog.tsx +172 -128
  78. package/src/components/timeline-annotations.tsx +178 -45
  79. package/src/hooks/use-codex-accounts.ts +32 -5
  80. package/src/hooks/use-composer.ts +17 -0
  81. package/src/hooks/use-session-events.ts +16 -277
  82. package/src/index.ts +0 -1
  83. package/src/older-history.ts +22 -4
  84. package/src/timeline/platform-activity-row.tsx +4 -1
  85. package/src/timeline/projection.ts +99 -9
  86. package/src/timeline/types.ts +3 -0
  87. package/styles/compiled.css +86 -22
  88. package/dist/chat.d.ts +0 -37
  89. package/dist/chat.js +0 -315
  90. package/dist/chat.js.map +0 -1
  91. package/dist/chunk-3HIF5IFC.js.map +0 -1
  92. package/dist/chunk-BQGIXRKV.js.map +0 -1
  93. package/dist/chunk-BXJOQFCD.js.map +0 -1
  94. package/dist/chunk-F2CFHN3Y.js +0 -1415
  95. package/dist/chunk-F2CFHN3Y.js.map +0 -1
  96. package/dist/chunk-HQV2I5HV.js +0 -124
  97. package/dist/chunk-HQV2I5HV.js.map +0 -1
  98. package/dist/chunk-IOLVN74D.js.map +0 -1
  99. package/dist/chunk-W3OUAH6M.js +0 -170
  100. package/dist/chunk-W3OUAH6M.js.map +0 -1
  101. package/dist/chunk-ZFOFY5ST.js.map +0 -1
  102. package/dist/platform-activity-row-BJHUHXAT.js.map +0 -1
  103. package/dist/timeline-annotation-selection-3L7LHGG2.js +0 -187
  104. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +0 -1
  105. package/dist/timeline-annotations-dialog-U7EVLR75.js +0 -196
  106. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +0 -1
  107. package/src/chat.tsx +0 -393
  108. /package/dist/{browser-viewer-SS46LUAC.js.map → browser-viewer-NTD6UIPR.js.map} +0 -0
  109. /package/dist/{chunk-VEPNNDNG.js.map → chunk-24M4YBCL.js.map} +0 -0
  110. /package/dist/{chunk-ELIYWBZR.js.map → chunk-BV5A4OTE.js.map} +0 -0
  111. /package/dist/{computer-viewer-FWEZISW4.js.map → computer-viewer-7TEZC6Y2.js.map} +0 -0
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  HumanInputAnswer,
3
3
  HumanInputQuestion,
4
+ SkillRecord,
4
5
  SessionHumanInputRequest,
5
6
  SubmitHumanInputResponseRequest,
6
7
  } from "@opengeni/sdk";
@@ -64,6 +65,10 @@ export const defaultHumanInputFormMessages: HumanInputFormMessages = {
64
65
  };
65
66
 
66
67
  export type HumanInputFormProps = {
68
+ /** Scoped immutable revision reader. Saving a Skill fails closed without its full preview. */
69
+ loadSkillReview?:
70
+ | ((reference: NonNullable<HumanInputQuestion["skillReview"]>) => Promise<SkillRecord>)
71
+ | undefined;
67
72
  request: Pick<SessionHumanInputRequest, "id" | "questions" | "allowSkip" | "expiresAt">;
68
73
  onSubmit: (response: SubmitHumanInputResponseRequest) => void | Promise<void>;
69
74
  submitting?: boolean | undefined;
@@ -98,6 +103,7 @@ export function HumanInputForm(props: HumanInputFormProps) {
98
103
  function HumanInputRequestForm({
99
104
  request,
100
105
  onSubmit,
106
+ loadSkillReview,
101
107
  submitting = false,
102
108
  error,
103
109
  title,
@@ -142,6 +148,116 @@ function HumanInputRequestForm({
142
148
  const submissionInFlight = useRef(false);
143
149
  const submissionGeneration = useRef(0);
144
150
  const busy = submitting || submittingInternally;
151
+ const reviewIdentity = JSON.stringify(
152
+ request.questions
153
+ .filter((question) => question.skillReview)
154
+ .map((question) => ({
155
+ id: question.id,
156
+ reference: {
157
+ sourceOperationId: question.skillReview!.sourceOperationId,
158
+ skillId: question.skillReview!.skillId,
159
+ revisionId: question.skillReview!.revisionId,
160
+ expectedRevisionId: question.skillReview!.expectedRevisionId,
161
+ expectedScopeVersion: question.skillReview!.expectedScopeVersion,
162
+ },
163
+ })),
164
+ );
165
+ const [reviewReload, setReviewReload] = useState(0);
166
+ const [reviews, setReviews] = useState<{
167
+ loader: typeof loadSkillReview;
168
+ identity: string;
169
+ records: Record<string, SkillRecord>;
170
+ error: string | null;
171
+ } | null>(null);
172
+ useEffect(() => {
173
+ let current = true;
174
+ const questions = JSON.parse(reviewIdentity) as Array<{
175
+ id: string;
176
+ reference: NonNullable<HumanInputQuestion["skillReview"]>;
177
+ }>;
178
+ if (!questions.length) return;
179
+ setReviews(null);
180
+ void Promise.all(
181
+ questions.map(async (question) => {
182
+ if (!loadSkillReview)
183
+ throw new Error(
184
+ "This client cannot preview Skill files. Open this request in OpenGeni to review it.",
185
+ );
186
+ const reference = question.reference;
187
+ const record = await loadSkillReview(reference);
188
+ if (
189
+ record.id !== reference.skillId ||
190
+ record.revisionId !== reference.revisionId ||
191
+ !record.files.some((file) => file.path === "SKILL.md")
192
+ ) {
193
+ throw new Error("The requested Skill revision could not be verified.");
194
+ }
195
+ return [question.id, record] as const;
196
+ }),
197
+ )
198
+ .then((records) => {
199
+ if (current)
200
+ setReviews({
201
+ loader: loadSkillReview,
202
+ identity: reviewIdentity,
203
+ records: Object.fromEntries(records),
204
+ error: null,
205
+ });
206
+ })
207
+ .catch((cause) => {
208
+ if (current)
209
+ setReviews({
210
+ loader: loadSkillReview,
211
+ identity: reviewIdentity,
212
+ records: {},
213
+ error: cause instanceof Error ? cause.message : "Could not load the Skill files.",
214
+ });
215
+ });
216
+ return () => {
217
+ current = false;
218
+ };
219
+ }, [loadSkillReview, reviewIdentity, reviewReload]);
220
+ const visibleReviews =
221
+ reviews?.loader === loadSkillReview && reviews?.identity === reviewIdentity ? reviews : null;
222
+ const preview = (question: HumanInputQuestion) => {
223
+ if (!question.skillReview) return null;
224
+ const record = visibleReviews?.records[question.id];
225
+ return (
226
+ <div className="mb-3 min-w-0 space-y-2" data-skill-review="">
227
+ {record ? (
228
+ <>
229
+ <p className="text-og-sm">
230
+ {record.title ?? "Skill"} · {record.scope} · {record.files.length} files
231
+ </p>
232
+ <p className="text-og-xs text-og-fg-muted">
233
+ Saving activates these exact files. No additional review is required.
234
+ </p>
235
+ {record.files.map((file) => (
236
+ <details key={file.path} open={file.path === "SKILL.md"}>
237
+ <summary className="cursor-pointer break-all text-og-sm">{file.path}</summary>
238
+ <pre className="max-h-64 overflow-auto whitespace-pre-wrap break-words rounded-og-md bg-og-surface-1 p-2 text-og-xs">
239
+ {file.content}
240
+ </pre>
241
+ </details>
242
+ ))}
243
+ </>
244
+ ) : visibleReviews?.error ? (
245
+ <>
246
+ <p role="alert" className="text-og-sm">
247
+ {visibleReviews.error}
248
+ </p>
249
+ <button type="button" onClick={() => setReviewReload((value) => value + 1)}>
250
+ Retry preview
251
+ </button>
252
+ </>
253
+ ) : (
254
+ <p role="status" className="text-og-sm">
255
+ Loading the exact Skill files…
256
+ </p>
257
+ )}
258
+ </div>
259
+ );
260
+ };
145
261
 
146
262
  useEffect(() => {
147
263
  if (collapsed) {
@@ -232,6 +348,18 @@ function HumanInputRequestForm({
232
348
  }
233
349
  return;
234
350
  }
351
+ const unreviewedSave = request.questions.find(
352
+ (question) =>
353
+ question.skillReview &&
354
+ !visibleReviews?.records[question.id] &&
355
+ result.answers.some(
356
+ (answer) => answer.questionId === question.id && answer.values.includes("save"),
357
+ ),
358
+ );
359
+ if (unreviewedSave) {
360
+ setValidationErrors({ [unreviewedSave.id]: "Load the exact Skill files before saving." });
361
+ return;
362
+ }
235
363
  await submitResponse({ outcome: "answered", answers: result.answers });
236
364
  };
237
365
 
@@ -365,6 +493,7 @@ function HumanInputRequestForm({
365
493
  // Title already carries the question; only render the control + help extras.
366
494
  return (
367
495
  <div key={question.id} data-human-input-question={question.id}>
496
+ {preview(question)}
368
497
  <QuestionControls
369
498
  question={question}
370
499
  questionNumber={null}
@@ -389,6 +518,7 @@ function HumanInputRequestForm({
389
518
  data-human-input-question={question.id}
390
519
  className="flex flex-col gap-1.5"
391
520
  >
521
+ {preview(question)}
392
522
  <QuestionControls
393
523
  question={question}
394
524
  questionNumber={index + 1}
@@ -643,62 +773,64 @@ function QuestionControls({
643
773
  </label>
644
774
  );
645
775
  })}
646
- <div
647
- className={cn(
648
- "flex items-start gap-2.5 rounded-og-md px-2.5 py-2 transition-colors",
649
- draft.otherSelected
650
- ? "bg-og-status-waiting/12 text-og-fg"
651
- : "text-og-fg hover:bg-og-surface-1/80",
652
- )}
653
- >
654
- <input
655
- id={otherChoiceId}
656
- type={question.kind === "single_select" ? "radio" : "checkbox"}
657
- name={question.kind === "single_select" ? fieldId : undefined}
658
- aria-labelledby={otherLabelId}
659
- checked={draft.otherSelected}
660
- autoFocus={autoFocus && firstOption && question.options.length === 0}
661
- onChange={(event) =>
662
- onUpdate((current) => ({
663
- ...current,
664
- otherSelected: event.target.checked,
665
- ...(question.kind === "single_select" && event.target.checked
666
- ? { values: [] }
667
- : {}),
668
- }))
669
- }
670
- className="mt-2 accent-og-accent"
671
- />
672
- <span className="min-w-0 flex-1">
673
- <label
674
- id={otherLabelId}
675
- htmlFor={otherChoiceId}
676
- className="block text-og-sm font-medium"
677
- >
678
- {messages.other}
679
- </label>
680
- <label htmlFor={otherTextId} className="sr-only">
681
- {messages.other} answer for {visibleLabel}
682
- </label>
776
+ {!question.skillReview ? (
777
+ <div
778
+ className={cn(
779
+ "flex items-start gap-2.5 rounded-og-md px-2.5 py-2 transition-colors",
780
+ draft.otherSelected
781
+ ? "bg-og-status-waiting/12 text-og-fg"
782
+ : "text-og-fg hover:bg-og-surface-1/80",
783
+ )}
784
+ >
683
785
  <input
684
- id={otherTextId}
685
- type="text"
686
- value={draft.other}
687
- disabled={busy}
688
- placeholder="Type a value…"
689
- onClick={() => onUpdate(selectOtherDraft)}
690
- onFocus={() => onUpdate(selectOtherDraft)}
691
- onChange={(event) => {
692
- const other = event.target.value;
786
+ id={otherChoiceId}
787
+ type={question.kind === "single_select" ? "radio" : "checkbox"}
788
+ name={question.kind === "single_select" ? fieldId : undefined}
789
+ aria-labelledby={otherLabelId}
790
+ checked={draft.otherSelected}
791
+ autoFocus={autoFocus && firstOption && question.options.length === 0}
792
+ onChange={(event) =>
693
793
  onUpdate((current) => ({
694
- ...selectOtherDraft(current),
695
- other,
696
- }));
697
- }}
698
- className="mt-1.5 w-full rounded-og-sm border border-og-border bg-og-surface-1 px-2 py-1.5 text-og-sm text-og-fg outline-hidden focus:border-og-accent disabled:opacity-50"
794
+ ...current,
795
+ otherSelected: event.target.checked,
796
+ ...(question.kind === "single_select" && event.target.checked
797
+ ? { values: [] }
798
+ : {}),
799
+ }))
800
+ }
801
+ className="mt-2 accent-og-accent"
699
802
  />
700
- </span>
701
- </div>
803
+ <span className="min-w-0 flex-1">
804
+ <label
805
+ id={otherLabelId}
806
+ htmlFor={otherChoiceId}
807
+ className="block text-og-sm font-medium"
808
+ >
809
+ {messages.other}
810
+ </label>
811
+ <label htmlFor={otherTextId} className="sr-only">
812
+ {messages.other} answer for {visibleLabel}
813
+ </label>
814
+ <input
815
+ id={otherTextId}
816
+ type="text"
817
+ value={draft.other}
818
+ disabled={busy}
819
+ placeholder="Type a value…"
820
+ onClick={() => onUpdate(selectOtherDraft)}
821
+ onFocus={() => onUpdate(selectOtherDraft)}
822
+ onChange={(event) => {
823
+ const other = event.target.value;
824
+ onUpdate((current) => ({
825
+ ...selectOtherDraft(current),
826
+ other,
827
+ }));
828
+ }}
829
+ className="mt-1.5 w-full rounded-og-sm border border-og-border bg-og-surface-1 px-2 py-1.5 text-og-sm text-og-fg outline-hidden focus:border-og-accent disabled:opacity-50"
830
+ />
831
+ </span>
832
+ </div>
833
+ ) : null}
702
834
  </div>
703
835
  )}
704
836
  {error ? (
@@ -9,6 +9,7 @@ import {
9
9
  } from "./human-input-form";
10
10
 
11
11
  export type HumanInputSurfaceProps = {
12
+ loadSkillReview?: HumanInputFormProps["loadSkillReview"];
12
13
  requests: SessionHumanInputRequest[];
13
14
  onSubmit: (requestId: string, response: SubmitHumanInputResponseRequest) => void | Promise<void>;
14
15
  respondingRequestId?: string | null | undefined;
@@ -28,6 +29,7 @@ export type HumanInputSurfaceProps = {
28
29
  export function HumanInputSurface({
29
30
  requests,
30
31
  onSubmit,
32
+ loadSkillReview,
31
33
  respondingRequestId = null,
32
34
  error,
33
35
  messages,
@@ -68,6 +70,7 @@ export function HumanInputSurface({
68
70
 
69
71
  const formProps: HumanInputFormProps = {
70
72
  request: active,
73
+ loadSkillReview,
71
74
  submitting: respondingRequestId !== null,
72
75
  error: error ?? null,
73
76
  progressLabel,
@@ -62,13 +62,9 @@ function EmptyState({ onEnroll }: { onEnroll?: (() => void) | undefined }) {
62
62
  function Header({
63
63
  count,
64
64
  onEnroll,
65
- onRefresh,
66
- loading,
67
65
  }: {
68
66
  count: number;
69
67
  onEnroll?: (() => void) | undefined;
70
- onRefresh?: (() => void) | undefined;
71
- loading?: boolean | undefined;
72
68
  }): ReactNode {
73
69
  return (
74
70
  <div className="flex items-center justify-between gap-3">
@@ -77,17 +73,6 @@ function Header({
77
73
  <span className="font-og-mono text-og-xs text-og-fg-subtle">{count}</span>
78
74
  </div>
79
75
  <div className="flex items-center gap-1.5">
80
- {onRefresh ? (
81
- <button
82
- type="button"
83
- data-refresh
84
- onClick={onRefresh}
85
- title="Refresh"
86
- className="rounded-og-sm p-1.5 text-og-fg-subtle transition-colors hover:bg-og-surface-2 hover:text-og-fg"
87
- >
88
- <RefreshCwIcon className={cn("size-3.5", loading && "animate-og-spin")} aria-hidden />
89
- </button>
90
- ) : null}
91
76
  {onEnroll ? (
92
77
  <button
93
78
  type="button"
@@ -131,7 +116,7 @@ export function MachinesDashboard({
131
116
 
132
117
  return (
133
118
  <section data-machines-dashboard className={cn("og-root flex flex-col gap-3", className)}>
134
- <Header count={machines.length} onEnroll={onEnroll} onRefresh={onRefresh} loading={loading} />
119
+ <Header count={machines.length} onEnroll={onEnroll} />
135
120
 
136
121
  {error ? (
137
122
  <div