@opengeni/react 0.15.0 → 0.20.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 (101) hide show
  1. package/README.md +110 -10
  2. package/dist/{chunk-TOJR776I.js → chunk-6BXBGJ3B.js} +45 -369
  3. package/dist/chunk-6BXBGJ3B.js.map +1 -0
  4. package/dist/chunk-6UT5OC3P.js +2133 -0
  5. package/dist/chunk-6UT5OC3P.js.map +1 -0
  6. package/dist/chunk-EBTT3MYN.js +92 -0
  7. package/dist/chunk-EBTT3MYN.js.map +1 -0
  8. package/dist/chunk-HHFA4AFX.js +425 -0
  9. package/dist/chunk-HHFA4AFX.js.map +1 -0
  10. package/dist/chunk-LAZ2A743.js +2393 -0
  11. package/dist/chunk-LAZ2A743.js.map +1 -0
  12. package/dist/chunk-MRSECXRP.js +563 -0
  13. package/dist/chunk-MRSECXRP.js.map +1 -0
  14. package/dist/chunk-TK7G6XLT.js +18 -0
  15. package/dist/chunk-TK7G6XLT.js.map +1 -0
  16. package/dist/composer-DvUaa5ki.d.ts +585 -0
  17. package/dist/composer.d.ts +6 -0
  18. package/dist/composer.js +56 -0
  19. package/dist/composer.js.map +1 -0
  20. package/dist/index.d.ts +1288 -1785
  21. package/dist/index.js +7015 -8806
  22. package/dist/index.js.map +1 -1
  23. package/dist/machines.d.ts +412 -3
  24. package/dist/machines.js +3 -1
  25. package/dist/noto-sans-arabic-loader-IA2T4X2A.js +21 -0
  26. package/dist/noto-sans-arabic-loader-IA2T4X2A.js.map +1 -0
  27. package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js +292 -0
  28. package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js.map +1 -0
  29. package/dist/queue-surface-implementation-NQMV2ML3.js +890 -0
  30. package/dist/queue-surface-implementation-NQMV2ML3.js.map +1 -0
  31. package/dist/session-Gd4iVPYw.d.ts +425 -0
  32. package/dist/session-context-DIFFlXKc.d.ts +52 -0
  33. package/dist/session.d.ts +4 -0
  34. package/dist/session.js +50 -0
  35. package/dist/session.js.map +1 -0
  36. package/dist/use-composer-BCfm4mzX.d.ts +112 -0
  37. package/package.json +20 -4
  38. package/src/approvals.ts +5 -3
  39. package/src/client.ts +40 -0
  40. package/src/components/approval-surface.tsx +198 -0
  41. package/src/components/chat-composer.tsx +94 -993
  42. package/src/components/code-editor.tsx +104 -26
  43. package/src/components/command-palette.tsx +8 -2
  44. package/src/components/composer-transcription-control.tsx +211 -0
  45. package/src/components/composer.tsx +1536 -0
  46. package/src/components/desktop-viewer.tsx +3 -3
  47. package/src/components/enrollment-consent.tsx +2 -2
  48. package/src/components/file-browser.tsx +367 -29
  49. package/src/components/human-input-form.tsx +417 -0
  50. package/src/components/machine-card.tsx +1 -1
  51. package/src/components/machines-dashboard.tsx +3 -3
  52. package/src/components/message-timeline.tsx +15 -7
  53. package/src/components/model-picker.tsx +12 -3
  54. package/src/components/pierre-diff.tsx +32 -6
  55. package/src/components/queue-surface-implementation.tsx +1041 -0
  56. package/src/components/queue-surface-state.tsx +94 -0
  57. package/src/components/queue-surface.tsx +41 -559
  58. package/src/components/sandbox-files.tsx +138 -14
  59. package/src/components/sandbox-terminal.tsx +8 -2
  60. package/src/components/sandbox-workspace.tsx +339 -119
  61. package/src/components/workbench-changes.tsx +84 -33
  62. package/src/components/workspace-dock.tsx +188 -18
  63. package/src/composer.ts +60 -0
  64. package/src/hooks/internal.ts +115 -34
  65. package/src/hooks/use-composer.ts +335 -104
  66. package/src/hooks/use-human-input.ts +131 -0
  67. package/src/hooks/use-machine-chip.ts +2 -2
  68. package/src/hooks/use-machines.ts +27 -11
  69. package/src/hooks/use-sandbox-files.ts +310 -62
  70. package/src/hooks/use-sandbox-git.ts +154 -40
  71. package/src/hooks/use-sandbox-terminal.ts +28 -6
  72. package/src/hooks/use-session-capabilities.ts +38 -9
  73. package/src/hooks/use-session-control.ts +37 -8
  74. package/src/hooks/use-session-events.ts +522 -48
  75. package/src/hooks/use-slash-commands.ts +38 -38
  76. package/src/hooks/use-transcription.ts +757 -0
  77. package/src/hooks/use-turn-queue.ts +134 -66
  78. package/src/hooks/use-windowed-sections.ts +2 -2
  79. package/src/hooks/use-workspace-capture.ts +146 -40
  80. package/src/hooks/use-workspace-edit.ts +50 -14
  81. package/src/hooks/use-workspace-sessions.ts +3 -0
  82. package/src/human-input.ts +72 -0
  83. package/src/index.ts +76 -6
  84. package/src/lib/noto-sans-arabic-loader.ts +21 -0
  85. package/src/lib/noto-sans-jp-loader.generated.ts +295 -0
  86. package/src/lib/use-portal-token-style.ts +51 -0
  87. package/src/lib/use-unicode-fonts.ts +57 -0
  88. package/src/provider.tsx +4 -69
  89. package/src/session-context.ts +135 -0
  90. package/src/session.ts +79 -0
  91. package/src/timeline/parsers.ts +35 -0
  92. package/src/timeline/projection.ts +12 -4
  93. package/src/timeline/tool-renderers.tsx +41 -0
  94. package/src/timeline/turn-summary.tsx +2 -2
  95. package/src/timeline/types.ts +16 -8
  96. package/styles/index.css +56 -0
  97. package/styles/index.d.ts +2 -0
  98. package/styles/tokens.css +11 -6
  99. package/styles/tokens.d.ts +2 -0
  100. package/dist/chunk-TOJR776I.js.map +0 -1
  101. package/dist/machines-BpdwuQcD.d.ts +0 -449
@@ -1,6 +1,6 @@
1
1
  import type { SessionEvent } from "@opengeni/sdk";
2
- import { useCallback, useEffect, useRef, useState } from "react";
3
- import type { SessionClientLike } from "../client";
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
3
+ import type { EmbeddedSessionClientLike } from "../client";
4
4
 
5
5
  export type AsyncListState<T> = {
6
6
  data: T | null;
@@ -14,7 +14,7 @@ export type AsyncListState<T> = {
14
14
  * responses (superseded by a newer load or an unmount) are dropped.
15
15
  */
16
16
  export function usePolledValue<T>(
17
- load: () => Promise<T>,
17
+ load: (signal?: AbortSignal) => Promise<T>,
18
18
  options: { pollIntervalMs?: number | undefined; enabled?: boolean | undefined } = {},
19
19
  ): AsyncListState<T> {
20
20
  const enabled = options.enabled ?? true;
@@ -23,32 +23,55 @@ export function usePolledValue<T>(
23
23
  const [loading, setLoading] = useState(enabled);
24
24
  const [error, setError] = useState<Error | null>(null);
25
25
  const generation = useRef(0);
26
- const loadRef = useRef(load);
26
+ const activeLoadRef = useRef(load);
27
+ useLayoutEffect(() => {
28
+ activeLoadRef.current = load;
29
+ }, [load]);
30
+ const requestAbortRef = useRef<AbortController | null>(null);
31
+ const [stateIdentity, setStateIdentity] = useState<{ load: typeof load }>(() => ({ load }));
27
32
 
28
33
  // A new loader identity means a new query (different session/workspace/...):
29
34
  // drop the previous result instead of showing it as the new query's data.
30
35
  useEffect(() => {
31
- if (loadRef.current !== load) {
32
- loadRef.current = load;
36
+ if (stateIdentity.load !== load) {
37
+ setStateIdentity({ load });
33
38
  setData(null);
34
39
  setError(null);
35
40
  }
36
- }, [load]);
41
+ }, [load, stateIdentity.load]);
37
42
 
38
43
  const run = useCallback(async () => {
44
+ // A callback retained by a completed mutation from the previous query must
45
+ // not supersede or settle the current query's request.
46
+ if (activeLoadRef.current !== load) return;
39
47
  const ticket = ++generation.current;
48
+ requestAbortRef.current?.abort();
49
+ const requestAbort = new AbortController();
50
+ requestAbortRef.current = requestAbort;
40
51
  try {
41
- const result = await load();
42
- if (ticket === generation.current) {
52
+ const result = await load(requestAbort.signal);
53
+ if (
54
+ ticket === generation.current &&
55
+ activeLoadRef.current === load &&
56
+ !requestAbort.signal.aborted
57
+ ) {
43
58
  setData(result);
44
59
  setError(null);
45
60
  setLoading(false);
46
61
  }
47
62
  } catch (cause) {
48
- if (ticket === generation.current) {
63
+ if (
64
+ ticket === generation.current &&
65
+ activeLoadRef.current === load &&
66
+ !requestAbort.signal.aborted
67
+ ) {
49
68
  setError(cause instanceof Error ? cause : new Error(String(cause)));
50
69
  setLoading(false);
51
70
  }
71
+ } finally {
72
+ if (requestAbortRef.current === requestAbort) {
73
+ requestAbortRef.current = null;
74
+ }
52
75
  }
53
76
  }, [load]);
54
77
 
@@ -62,16 +85,24 @@ export function usePolledValue<T>(
62
85
  if (pollIntervalMs === undefined || pollIntervalMs <= 0) {
63
86
  return () => {
64
87
  generation.current += 1;
88
+ requestAbortRef.current?.abort();
65
89
  };
66
90
  }
67
91
  const timer = setInterval(() => void run(), pollIntervalMs);
68
92
  return () => {
69
93
  clearInterval(timer);
70
94
  generation.current += 1;
95
+ requestAbortRef.current?.abort();
71
96
  };
72
97
  }, [run, enabled, pollIntervalMs]);
73
98
 
74
- return { data, loading, error, refresh: run };
99
+ const identityMatches = stateIdentity.load === load;
100
+ return {
101
+ data: identityMatches ? data : null,
102
+ loading: identityMatches ? loading : enabled,
103
+ error: identityMatches ? error : null,
104
+ refresh: run,
105
+ };
75
106
  }
76
107
 
77
108
  export type MutationState = {
@@ -85,12 +116,21 @@ export type MutationState = {
85
116
  * operation's value, or `null` after capturing the error in `mutationError`
86
117
  * (callers then roll back optimistic state).
87
118
  */
88
- export function useMutationRunner(): MutationState & {
119
+ export function useMutationRunner(identity: unknown = undefined): MutationState & {
89
120
  run: <T>(operation: () => Promise<T>) => Promise<T | null>;
90
121
  } {
91
122
  const [mutating, setMutating] = useState(false);
92
123
  const [mutationError, setMutationError] = useState<Error | null>(null);
124
+ const [stateIdentity, setStateIdentity] = useState<unknown>(() => identity);
93
125
  const inFlight = useRef(0);
126
+ const generation = useRef(0);
127
+ const identityRef = useRef(identity);
128
+ useLayoutEffect(() => {
129
+ if (Object.is(identityRef.current, identity)) return;
130
+ identityRef.current = identity;
131
+ generation.current += 1;
132
+ inFlight.current = 0;
133
+ }, [identity]);
94
134
  const mounted = useRef(true);
95
135
  useEffect(() => {
96
136
  mounted.current = true;
@@ -98,30 +138,63 @@ export function useMutationRunner(): MutationState & {
98
138
  mounted.current = false;
99
139
  };
100
140
  }, []);
101
- const run = useCallback(async <T>(operation: () => Promise<T>): Promise<T | null> => {
102
- inFlight.current += 1;
103
- if (mounted.current) {
104
- setMutating(true);
141
+ useEffect(() => {
142
+ if (!Object.is(stateIdentity, identity)) {
143
+ setStateIdentity(() => identity);
144
+ setMutating(false);
105
145
  setMutationError(null);
106
146
  }
107
- try {
108
- return await operation();
109
- } catch (cause) {
147
+ }, [identity, stateIdentity]);
148
+ const run = useCallback(
149
+ async <T>(operation: () => Promise<T>): Promise<T | null> => {
150
+ const ownedIdentity = identity;
151
+ const ownedGeneration = generation.current;
152
+ if (!Object.is(identityRef.current, ownedIdentity)) return null;
153
+ inFlight.current += 1;
110
154
  if (mounted.current) {
111
- setMutationError(cause instanceof Error ? cause : new Error(String(cause)));
155
+ setMutating(true);
156
+ setMutationError(null);
112
157
  }
113
- return null;
114
- } finally {
115
- inFlight.current -= 1;
116
- if (mounted.current && inFlight.current === 0) {
117
- setMutating(false);
158
+ try {
159
+ const result = await operation();
160
+ if (
161
+ !mounted.current ||
162
+ generation.current !== ownedGeneration ||
163
+ !Object.is(identityRef.current, ownedIdentity)
164
+ ) {
165
+ return null;
166
+ }
167
+ return result;
168
+ } catch (cause) {
169
+ if (
170
+ mounted.current &&
171
+ generation.current === ownedGeneration &&
172
+ Object.is(identityRef.current, ownedIdentity)
173
+ ) {
174
+ setMutationError(cause instanceof Error ? cause : new Error(String(cause)));
175
+ }
176
+ return null;
177
+ } finally {
178
+ if (
179
+ generation.current === ownedGeneration &&
180
+ Object.is(identityRef.current, ownedIdentity)
181
+ ) {
182
+ inFlight.current -= 1;
183
+ if (mounted.current && inFlight.current === 0) {
184
+ setMutating(false);
185
+ }
186
+ }
118
187
  }
119
- }
120
- }, []);
188
+ },
189
+ [identity],
190
+ );
191
+ const identityMatches = Object.is(stateIdentity, identity);
121
192
  return {
122
- mutating,
123
- mutationError,
124
- clearMutationError: useCallback(() => setMutationError(null), []),
193
+ mutating: identityMatches && mutating,
194
+ mutationError: identityMatches ? mutationError : null,
195
+ clearMutationError: useCallback(() => {
196
+ if (Object.is(identityRef.current, identity)) setMutationError(null);
197
+ }, [identity]),
125
198
  run,
126
199
  };
127
200
  }
@@ -142,20 +215,25 @@ export type SessionEventFeedOptions = {
142
215
  * `events` log or tails the stream directly (reconnect handled by the SDK).
143
216
  */
144
217
  export function useSessionEventTrigger(
145
- client: SessionClientLike,
218
+ client: EmbeddedSessionClientLike,
146
219
  workspaceId: string,
147
220
  sessionId: string | null | undefined,
148
221
  match: (event: SessionEvent) => boolean,
149
222
  onEvent: (event: SessionEvent) => void,
150
223
  options: SessionEventFeedOptions = {},
224
+ reconcileBeforeLive?: (() => void | Promise<void>) | undefined,
151
225
  ): void {
152
226
  const enabled = options.enabled ?? true;
153
227
  const events = options.events;
154
228
  const sharedFeed = events !== undefined;
155
229
  const matchRef = useRef(match);
156
- matchRef.current = match;
157
230
  const onEventRef = useRef(onEvent);
158
- onEventRef.current = onEvent;
231
+ const reconcileBeforeLiveRef = useRef(reconcileBeforeLive);
232
+ useLayoutEffect(() => {
233
+ matchRef.current = match;
234
+ onEventRef.current = onEvent;
235
+ reconcileBeforeLiveRef.current = reconcileBeforeLive;
236
+ }, [match, onEvent, reconcileBeforeLive]);
159
237
  const consumedRef = useRef(0);
160
238
  const feedKeyRef = useRef<string | null>(null);
161
239
 
@@ -198,6 +276,7 @@ export function useSessionEventTrigger(
198
276
  const stream = client.streamEvents(workspaceId, sessionId, {
199
277
  after: session.lastSequence,
200
278
  signal: controller.signal,
279
+ beforeLive: async () => await reconcileBeforeLiveRef.current?.(),
201
280
  });
202
281
  for await (const event of stream) {
203
282
  if (matchRef.current(event)) {
@@ -218,7 +297,9 @@ export function useSessionEventTrigger(
218
297
  /** Debounce rapid event bursts into one trailing call (default 150ms). */
219
298
  export function useDebouncedCallback(callback: () => void, delayMs = 150): () => void {
220
299
  const callbackRef = useRef(callback);
221
- callbackRef.current = callback;
300
+ useLayoutEffect(() => {
301
+ callbackRef.current = callback;
302
+ }, [callback]);
222
303
  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
223
304
  useEffect(() => {
224
305
  return () => {