@opengeni/react 0.28.2 → 0.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/dist/approvals.d.ts +14 -0
  2. package/dist/{chunk-M7X4JZOD.js → chunk-4QOQ2DSR.js} +8 -1
  3. package/dist/chunk-4QOQ2DSR.js.map +1 -0
  4. package/dist/{chunk-HQFPO7VX.js → chunk-7CJOAW3V.js} +24 -3
  5. package/dist/{chunk-HQFPO7VX.js.map → chunk-7CJOAW3V.js.map} +1 -1
  6. package/dist/{chunk-PMSGE233.js → chunk-A2WITZTI.js} +377 -113
  7. package/dist/chunk-A2WITZTI.js.map +1 -0
  8. package/dist/{chunk-BC6RKIHS.js → chunk-GZ4KR67U.js} +11 -11
  9. package/dist/{chunk-4F2YLLHP.js → chunk-KZRUYCL4.js} +14 -17
  10. package/dist/chunk-KZRUYCL4.js.map +1 -0
  11. package/dist/{chunk-OCHBZ2X7.js → chunk-TCCE5ZU5.js} +192 -28
  12. package/dist/chunk-TCCE5ZU5.js.map +1 -0
  13. package/dist/chunk-U255M5EZ.js +178 -0
  14. package/dist/chunk-U255M5EZ.js.map +1 -0
  15. package/dist/{chunk-XVVQP7A4.js → chunk-WY2QN2CX.js} +2539 -2174
  16. package/dist/chunk-WY2QN2CX.js.map +1 -0
  17. package/dist/chunk-ZNNE2VES.js +6413 -0
  18. package/dist/chunk-ZNNE2VES.js.map +1 -0
  19. package/dist/client.d.ts +33 -0
  20. package/dist/commands/index.d.ts +3 -0
  21. package/dist/commands/registry.d.ts +41 -0
  22. package/dist/commands/types.d.ts +85 -0
  23. package/dist/components/approval-surface.d.ts +28 -0
  24. package/dist/components/chat-composer.d.ts +51 -0
  25. package/dist/components/code-editor.d.ts +43 -0
  26. package/dist/components/command-palette.d.ts +25 -0
  27. package/dist/components/composer-transcription-control.d.ts +36 -0
  28. package/dist/components/composer.d.ts +277 -0
  29. package/dist/components/copy-button.d.ts +26 -0
  30. package/dist/components/desktop-viewer.d.ts +85 -0
  31. package/dist/components/diff-view.d.ts +38 -0
  32. package/dist/components/enrollment-consent.d.ts +33 -0
  33. package/dist/components/enrollment-device-flow.d.ts +27 -0
  34. package/dist/components/file-browser.d.ts +47 -0
  35. package/dist/components/fleet-tile.d.ts +18 -0
  36. package/dist/components/human-input-form.d.ts +47 -0
  37. package/dist/components/machine-card.d.ts +21 -0
  38. package/dist/components/machine-dock-bar.d.ts +31 -0
  39. package/dist/components/machine-health-pill.d.ts +9 -0
  40. package/dist/components/machine-metrics.d.ts +15 -0
  41. package/dist/components/machine-status-pill.d.ts +54 -0
  42. package/dist/components/machines/health.d.ts +25 -0
  43. package/dist/components/machines/machine-detail.d.ts +14 -0
  44. package/dist/components/machines/metric-history-chart.d.ts +24 -0
  45. package/dist/components/machines/metric-sparkline.d.ts +9 -0
  46. package/dist/components/machines/series.d.ts +24 -0
  47. package/dist/components/machines-dashboard.d.ts +30 -0
  48. package/dist/components/markdown.d.ts +31 -0
  49. package/dist/components/message-timeline.d.ts +98 -0
  50. package/dist/components/model-picker.d.ts +19 -0
  51. package/dist/components/pierre-diff.d.ts +34 -0
  52. package/dist/components/pierre-file.d.ts +27 -0
  53. package/dist/components/queue-draft-policy.d.ts +7 -0
  54. package/dist/components/queue-surface-implementation.d.ts +20 -0
  55. package/dist/components/queue-surface-state.d.ts +12 -0
  56. package/dist/components/queue-surface.d.ts +12 -0
  57. package/dist/components/sandbox-files.d.ts +49 -0
  58. package/dist/components/sandbox-terminal.d.ts +123 -0
  59. package/dist/components/sandbox-workspace.d.ts +128 -0
  60. package/dist/components/session-chrome.d.ts +70 -0
  61. package/dist/components/session-status.d.ts +25 -0
  62. package/dist/components/soften-streaming-markdown.d.ts +13 -0
  63. package/dist/components/stream-reveal.d.ts +37 -0
  64. package/dist/components/tip-follow.d.ts +139 -0
  65. package/dist/components/tooltip.d.ts +16 -0
  66. package/dist/components/workbench-changes.d.ts +34 -0
  67. package/dist/components/workspace-dock.d.ts +35 -0
  68. package/dist/composer.d.ts +11 -7
  69. package/dist/composer.js +9 -6
  70. package/dist/hooks/internal.d.ts +46 -0
  71. package/dist/hooks/use-available-models.d.ts +37 -0
  72. package/dist/hooks/use-billing-usage.d.ts +26 -0
  73. package/dist/hooks/use-codex-accounts.d.ts +69 -0
  74. package/dist/{use-composer-ClHy1rkL.d.ts → hooks/use-composer.d.ts} +16 -24
  75. package/dist/hooks/use-desktop-stream.d.ts +43 -0
  76. package/dist/hooks/use-environments.d.ts +35 -0
  77. package/dist/{use-file-attachments-BmT8WtqO.d.ts → hooks/use-file-attachments.d.ts} +6 -9
  78. package/dist/hooks/use-goal.d.ts +46 -0
  79. package/dist/hooks/use-human-input.d.ts +26 -0
  80. package/dist/hooks/use-last-started-turn-policy.d.ts +27 -0
  81. package/dist/hooks/use-machine-chip.d.ts +54 -0
  82. package/dist/hooks/use-machines.d.ts +81 -0
  83. package/dist/hooks/use-packs.d.ts +27 -0
  84. package/dist/hooks/use-relay-frame-stream.d.ts +53 -0
  85. package/dist/hooks/use-rigs.d.ts +87 -0
  86. package/dist/hooks/use-sandbox-files.d.ts +108 -0
  87. package/dist/hooks/use-sandbox-git.d.ts +58 -0
  88. package/dist/hooks/use-sandbox-terminal.d.ts +62 -0
  89. package/dist/hooks/use-scheduled-tasks.d.ts +15 -0
  90. package/dist/hooks/use-session-capabilities.d.ts +83 -0
  91. package/dist/hooks/use-session-control.d.ts +22 -0
  92. package/dist/hooks/use-session-events.d.ts +92 -0
  93. package/dist/hooks/use-session-lineage.d.ts +17 -0
  94. package/dist/hooks/use-session-mcp-approval-policy.d.ts +23 -0
  95. package/dist/hooks/use-session.d.ts +23 -0
  96. package/dist/hooks/use-slash-commands.d.ts +66 -0
  97. package/dist/hooks/use-terminal-stream.d.ts +46 -0
  98. package/dist/hooks/use-transcription.d.ts +73 -0
  99. package/dist/hooks/use-turn-queue.d.ts +44 -0
  100. package/dist/hooks/use-voice-input.d.ts +25 -0
  101. package/dist/hooks/use-windowed-sections.d.ts +42 -0
  102. package/dist/hooks/use-workspace-capture.d.ts +49 -0
  103. package/dist/hooks/use-workspace-edit.d.ts +73 -0
  104. package/dist/hooks/use-workspace-sessions.d.ts +30 -0
  105. package/dist/hooks/use-workspaces.d.ts +22 -0
  106. package/dist/human-input.d.ts +18 -0
  107. package/dist/index.d.ts +144 -2119
  108. package/dist/index.js +421 -324
  109. package/dist/index.js.map +1 -1
  110. package/dist/lib/clipboard.d.ts +7 -0
  111. package/dist/lib/cn.d.ts +3 -0
  112. package/dist/lib/format.d.ts +48 -0
  113. package/dist/lib/git-patch.d.ts +8 -0
  114. package/dist/lib/motion-inspect.d.ts +5 -0
  115. package/dist/lib/motion.d.ts +14 -0
  116. package/dist/lib/noto-sans-arabic-loader.d.ts +1 -0
  117. package/dist/lib/noto-sans-jp-loader.generated.d.ts +1 -0
  118. package/dist/lib/relay-wire.d.ts +30 -0
  119. package/dist/lib/use-portal-token-style.d.ts +9 -0
  120. package/dist/lib/use-theme-type.d.ts +5 -0
  121. package/dist/lib/use-unicode-fonts.d.ts +12 -0
  122. package/dist/lib/xterm-renderer.d.ts +19 -0
  123. package/dist/lib/xterm-theme.d.ts +75 -0
  124. package/dist/machines.d.ts +30 -413
  125. package/dist/machines.js +3 -3
  126. package/dist/model-policy.d.ts +32 -0
  127. package/dist/model-policy.js +33 -0
  128. package/dist/model-policy.js.map +1 -0
  129. package/dist/provider.d.ts +16 -0
  130. package/dist/{queue-surface-implementation-UQ535OKQ.js → queue-surface-implementation-THPAKGMD.js} +2 -2
  131. package/dist/{queue-surface-implementation-UQ535OKQ.js.map → queue-surface-implementation-THPAKGMD.js.map} +1 -1
  132. package/dist/session-context.d.ts +82 -0
  133. package/dist/session-ui.d.ts +10 -7
  134. package/dist/session-ui.js +9 -5
  135. package/dist/session.d.ts +28 -6
  136. package/dist/session.js +11 -11
  137. package/dist/timeline/activity-rail.d.ts +19 -0
  138. package/dist/timeline/disclosure-context.d.ts +14 -0
  139. package/dist/timeline/entrance.d.ts +14 -0
  140. package/dist/timeline/fleet-decision-projection.d.ts +9 -0
  141. package/dist/timeline/fleet-decision-row.d.ts +11 -0
  142. package/dist/timeline/fold-memory.d.ts +15 -0
  143. package/dist/timeline/index.d.ts +18 -0
  144. package/dist/timeline/parsers.d.ts +96 -0
  145. package/dist/timeline/projection.d.ts +38 -0
  146. package/dist/timeline/registry.d.ts +40 -0
  147. package/dist/timeline/screenshot-lightbox.d.ts +26 -0
  148. package/dist/timeline/seen-activity-ids.d.ts +2 -0
  149. package/dist/timeline/shared.d.ts +125 -0
  150. package/dist/timeline/tool-diff.d.ts +8 -0
  151. package/dist/timeline/tool-display-name.d.ts +10 -0
  152. package/dist/timeline/tool-renderers.d.ts +5 -0
  153. package/dist/timeline/turn-summary.d.ts +116 -0
  154. package/dist/{use-turn-queue-Drm_ypue.d.ts → timeline/types.d.ts} +57 -67
  155. package/dist/types/machines.d.ts +11 -0
  156. package/package.json +8 -4
  157. package/src/client.ts +1 -0
  158. package/src/components/composer-transcription-control.tsx +278 -130
  159. package/src/components/composer.tsx +20 -15
  160. package/src/components/copy-button.tsx +127 -0
  161. package/src/components/markdown.tsx +231 -32
  162. package/src/components/message-timeline.tsx +1382 -378
  163. package/src/components/model-picker.tsx +83 -43
  164. package/src/components/queue-surface-implementation.tsx +1 -1
  165. package/src/components/session-chrome.tsx +942 -0
  166. package/src/components/soften-streaming-markdown.ts +205 -0
  167. package/src/components/stream-reveal.ts +195 -0
  168. package/src/components/tip-follow.ts +459 -0
  169. package/src/components/tooltip.tsx +103 -0
  170. package/src/composer.ts +6 -0
  171. package/src/hooks/internal.ts +30 -4
  172. package/src/hooks/use-available-models.ts +51 -4
  173. package/src/hooks/use-composer.ts +24 -18
  174. package/src/hooks/use-last-started-turn-policy.ts +95 -0
  175. package/src/hooks/use-session-events.ts +359 -5
  176. package/src/hooks/use-voice-input.ts +232 -0
  177. package/src/index.ts +49 -1
  178. package/src/lib/clipboard.ts +58 -0
  179. package/src/lib/format.ts +18 -0
  180. package/src/lib/motion-inspect.ts +5 -0
  181. package/src/lib/motion.ts +80 -0
  182. package/src/model-policy.ts +210 -0
  183. package/src/session-ui.ts +7 -0
  184. package/src/session.ts +2 -0
  185. package/src/timeline/activity-rail.tsx +46 -7
  186. package/src/timeline/entrance.tsx +10 -0
  187. package/src/timeline/fold-memory.ts +52 -0
  188. package/src/timeline/index.ts +11 -1
  189. package/src/timeline/projection.ts +252 -47
  190. package/src/timeline/seen-activity-ids.ts +19 -0
  191. package/src/timeline/shared.tsx +1 -1
  192. package/src/timeline/tool-renderers.tsx +75 -6
  193. package/src/timeline/turn-summary.tsx +387 -97
  194. package/src/timeline/types.ts +47 -0
  195. package/styles/index.css +99 -7
  196. package/styles/tokens.css +44 -3
  197. package/dist/chunk-4F2YLLHP.js.map +0 -1
  198. package/dist/chunk-M7X4JZOD.js.map +0 -1
  199. package/dist/chunk-OCHBZ2X7.js.map +0 -1
  200. package/dist/chunk-PMSGE233.js.map +0 -1
  201. package/dist/chunk-WAA247AB.js +0 -3858
  202. package/dist/chunk-WAA247AB.js.map +0 -1
  203. package/dist/chunk-XVVQP7A4.js.map +0 -1
  204. package/dist/composer-BMzimGTO.d.ts +0 -528
  205. package/dist/session-C3tIXLim.d.ts +0 -264
  206. package/dist/session-context-CAjlaeZb.d.ts +0 -84
  207. package/dist/session-ui-B_7cfN5u.d.ts +0 -259
  208. /package/dist/{chunk-BC6RKIHS.js.map → chunk-GZ4KR67U.js.map} +0 -0
@@ -0,0 +1,459 @@
1
+ /* ----------------------------------------------------------------------------
2
+ Tip-follow camera (pure)
3
+
4
+ DOM truth is immediate; the camera eases down toward the layout tip.
5
+ No React, no DOM — the shell reads metrics, calls step, writes scrollTop.
6
+
7
+ One law while pinned — second-order camera (no hard first bite):
8
+
9
+ desiredVel = debt / τ
10
+ scrollVel → exponential approach to desiredVel (accel τ = k·τ)
11
+ scrollTop += scrollVel · dt (clamp: no overshoot past tip)
12
+
13
+ - Debt jumps with layout; velocity cannot — so motion eases in, then as
14
+ debt falls desiredVel→0 and velocity eases out (soft settle).
15
+ - τ still adapts (calm / catch-up / line / idle settle).
16
+ - maxStep is a speed ceiling only, never a one-frame glue.
17
+ - Shrink is separate: compensate Δh so collapse doesn't fight the ease
18
+ - Snap only for first paint / reduced motion / cold oversized jumps
19
+ -------------------------------------------------------------------------- */
20
+
21
+ import { MOTION_INSPECT_SCALE } from "../lib/motion-inspect";
22
+
23
+ const _t = (ms: number) => ms * MOTION_INSPECT_SCALE;
24
+ const _s = (pxPerSec: number) => pxPerSec / MOTION_INSPECT_SCALE;
25
+
26
+ /** Calm ease while live: remaining debt shrinks ~63% every τ_max ms. */
27
+ export const TIP_FOLLOW_TAU_MAX_MS = _t(720);
28
+ /** Catch-up ease under large tip-debt (reader far behind / big paste). */
29
+ export const TIP_FOLLOW_TAU_MIN_MS = _t(200);
30
+ /**
31
+ * Idle settle τ — stream quiet, soft landing into the tip. Longer than live
32
+ * calm so the last pixels decelerate instead of linear-crawling then stopping.
33
+ */
34
+ export const TIP_FOLLOW_SETTLE_TAU_MS = _t(960);
35
+ /** Debt that saturates urgency toward τ_min. */
36
+ export const TIP_FOLLOW_CATCHUP_DEBT_PX = 240;
37
+ /** Keep the rAF alive this long after the last height growth. */
38
+ export const TIP_FOLLOW_HOT_IDLE_MS = _t(320);
39
+ /** Cold oversized debt snaps (session switch / huge fold), not eases. */
40
+ export const TIP_FOLLOW_SNAP_PX = 480;
41
+ /** Soft speed floor while live with tiny debt (px/s). */
42
+ export const TIP_FOLLOW_CALM_MAX_PX_S = _s(42);
43
+ /** Soft speed ceiling while catching a fast stream / modest tip-debt (px/s). */
44
+ export const TIP_FOLLOW_BURST_MAX_PX_S = _s(420);
45
+ /** Ceiling for huge tip-debt / large single-frame walls (px/s). */
46
+ export const TIP_FOLLOW_SURGE_MAX_PX_S = _s(1800);
47
+ /**
48
+ * @deprecated Position growth-track removed — camera is velocity-smoothed.
49
+ * Kept so older tests/imports do not break.
50
+ */
51
+ export const TIP_FOLLOW_GROWTH_TRACK = 0;
52
+ /** @deprecated */
53
+ export const TIP_FOLLOW_LINE_TRACK = 0;
54
+ /** @deprecated Sub-line special-case removed with growth-track. */
55
+ export const TIP_FOLLOW_TOKEN_GROWTH_PX = 10;
56
+ /** Hot line-sized tip-debt uses the short {@link TIP_FOLLOW_LINE_TAU_MS}. */
57
+ export const TIP_FOLLOW_LINE_GROWTH_PX = 48;
58
+ /** Hot τ ceiling while closing a line-sized tip-debt (ms). */
59
+ export const TIP_FOLLOW_LINE_TAU_MS = _t(140);
60
+ /**
61
+ * Velocity ease-in/out: scrollVel approaches desiredVel with this fraction of τ.
62
+ * Lower → snappier accel; higher → softer start (and slower reaction).
63
+ */
64
+ export const TIP_FOLLOW_ACCEL_TAU_FRAC = 0.45;
65
+ /** @deprecated Soft-rise FLIP removed — line glue was the bug. */
66
+ export const TIP_FOLLOW_SOFT_RISE_MS = 0;
67
+ /** @deprecated */
68
+ export const TIP_FOLLOW_SOFT_RISE_MAX_PX = 96;
69
+ /** @deprecated */
70
+ export const TIP_FOLLOW_SOFT_RISE_EASING = "cubic-bezier(0.22, 1, 0.36, 1)";
71
+ /** Ignore sub-pixel / reflow height noise for shrink compensate. */
72
+ export const TIP_FOLLOW_SHRINK_EPS_PX = 4;
73
+ /** Growth speed (px/s) that saturates velocity urgency. */
74
+ export const TIP_FOLLOW_VELOCITY_REF_PX_S = _s(420);
75
+ /**
76
+ * Tip-debt below which growth velocity must not tighten τ.
77
+ * Sparse tokens are high-velocity by nature; only arm once actually behind.
78
+ */
79
+ export const TIP_FOLLOW_VELOCITY_ARM_DEBT_PX = 72;
80
+ /** EMA decay τ for growthVelocity when height is flat (ms). */
81
+ export const TIP_FOLLOW_VELOCITY_DECAY_MS = _t(180);
82
+ /** Reader-up pixels above clamp budget that count as leaving the tip. */
83
+ export const TIP_FOLLOW_READER_UP_EPS_PX = 2;
84
+ /** @deprecated Shrink lock removed; kept so old imports do not break. */
85
+ export const TIP_FOLLOW_SHRINK_LOCK_MS = 0;
86
+ /** @deprecated Absorb thresholds removed — growth track is continuous. */
87
+ export const TIP_FOLLOW_GROWTH_ABSORB_PX = 96;
88
+
89
+ /**
90
+ * ScrollTop decrease not explained by a maxScroll clamp (fold / composer
91
+ * shrink). Fold clamp: top falls by ≈ maxScroll fall → 0. Real scroll-up:
92
+ * top falls while maxScroll holds → positive. No timers.
93
+ */
94
+ export function readerScrollUpPx(
95
+ prevTop: number,
96
+ nextTop: number,
97
+ prevMaxScroll: number,
98
+ nextMaxScroll: number,
99
+ ): number {
100
+ const topDelta = prevTop - nextTop;
101
+ const clampBudget = Math.max(0, prevMaxScroll - nextMaxScroll);
102
+ return topDelta - clampBudget;
103
+ }
104
+
105
+ export type TipFollowState = {
106
+ running: boolean;
107
+ hotUntil: number;
108
+ lastTs: number;
109
+ lastHeight: number;
110
+ lastGrowthAt: number;
111
+ /** EMA of recent height growth (px/s); nudges τ / ceiling under bursts. */
112
+ growthVelocity: number;
113
+ /** Camera velocity toward tip (px/s). Smoothed — never jumps with debt. */
114
+ scrollVelocity: number;
115
+ };
116
+
117
+ export type TipFollowStepInput = {
118
+ scrollTop: number;
119
+ scrollHeight: number;
120
+ clientHeight: number;
121
+ now: number;
122
+ pinned: boolean;
123
+ reducedMotion: boolean;
124
+ revealed: boolean;
125
+ };
126
+
127
+ export type TipFollowStepResult = {
128
+ scrollTop: number;
129
+ state: TipFollowState;
130
+ };
131
+
132
+ export function createTipFollowState(): TipFollowState {
133
+ return {
134
+ running: false,
135
+ hotUntil: 0,
136
+ lastTs: 0,
137
+ lastHeight: 0,
138
+ lastGrowthAt: 0,
139
+ growthVelocity: 0,
140
+ scrollVelocity: 0,
141
+ };
142
+ }
143
+
144
+ export function tipFollowCancel(state: TipFollowState): TipFollowState {
145
+ return {
146
+ ...state,
147
+ running: false,
148
+ lastTs: 0,
149
+ scrollVelocity: 0,
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Record content height. Positive growth extends the hot window so sparse
155
+ * lines do not tear down the follow loop between appends.
156
+ */
157
+ export function tipFollowNoteGrowth(
158
+ state: TipFollowState,
159
+ height: number,
160
+ now: number,
161
+ ): TipFollowState {
162
+ const previous = state.lastHeight;
163
+ if (previous <= 0) {
164
+ return { ...state, lastHeight: height };
165
+ }
166
+ if (height <= previous) {
167
+ return { ...state, lastHeight: height };
168
+ }
169
+ const dt = state.lastGrowthAt > 0 ? now - state.lastGrowthAt : 0;
170
+ let growthVelocity = state.growthVelocity;
171
+ if (dt > 0) {
172
+ const instant = ((height - previous) / dt) * 1000;
173
+ growthVelocity = growthVelocity * 0.65 + instant * 0.35;
174
+ }
175
+ return {
176
+ ...state,
177
+ lastHeight: height,
178
+ lastGrowthAt: now,
179
+ growthVelocity,
180
+ hotUntil: now + TIP_FOLLOW_HOT_IDLE_MS,
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Adaptive τ: small debt → calm; large debt / armed velocity → catch-up.
186
+ * While hot, floor urgency so live streams are not stuck on calm τ.
187
+ * Idle settle uses the longer settle τ.
188
+ */
189
+ export function tipFollowTauMs(
190
+ debtPx: number,
191
+ growthVelocityPxPerSec = 0,
192
+ settling = false,
193
+ hot = false,
194
+ ): number {
195
+ if (settling) {
196
+ return TIP_FOLLOW_SETTLE_TAU_MS;
197
+ }
198
+ const absDebt = Math.abs(debtPx);
199
+ const debtT = Math.min(1, absDebt / TIP_FOLLOW_CATCHUP_DEBT_PX);
200
+ let velUrgency = 0;
201
+ if (absDebt >= TIP_FOLLOW_VELOCITY_ARM_DEBT_PX) {
202
+ const velT = Math.min(1, Math.max(0, growthVelocityPxPerSec) / TIP_FOLLOW_VELOCITY_REF_PX_S);
203
+ velUrgency = velT * 0.55;
204
+ }
205
+ const hotUrgency = hot ? 0.65 : 0;
206
+ const urgency = Math.min(1, Math.max(debtT, velUrgency, hotUrgency));
207
+ let tau = TIP_FOLLOW_TAU_MAX_MS + (TIP_FOLLOW_TAU_MIN_MS - TIP_FOLLOW_TAU_MAX_MS) * urgency;
208
+ // Line-sized live debt: close over ~LINE_TAU so the glide finishes before
209
+ // the next newline without needing 1:1 tip-glue.
210
+ if (hot && absDebt > 0 && absDebt <= TIP_FOLLOW_LINE_GROWTH_PX) {
211
+ tau = Math.min(tau, TIP_FOLLOW_LINE_TAU_MS);
212
+ }
213
+ return tau;
214
+ }
215
+
216
+ /**
217
+ * Soft speed ceiling from continuous signals (debt, velocity EMA, this-frame
218
+ * growth). No mode switches — larger inputs raise the ceiling smoothly.
219
+ */
220
+ export function tipFollowMaxStepPx(
221
+ debtPx: number,
222
+ growthVelocityPxPerSec: number,
223
+ dtMs: number,
224
+ frameGrowthPx = 0,
225
+ hot = false,
226
+ ): number {
227
+ const absDebt = Math.abs(debtPx);
228
+ const debtT = Math.min(1, absDebt / TIP_FOLLOW_CATCHUP_DEBT_PX);
229
+ const velT = Math.min(1, Math.max(0, growthVelocityPxPerSec) / TIP_FOLLOW_VELOCITY_REF_PX_S);
230
+ const armed = absDebt >= TIP_FOLLOW_VELOCITY_ARM_DEBT_PX;
231
+ const blend = Math.max(debtT, armed ? velT : 0, hot ? 0.35 : 0);
232
+ let maxPxS =
233
+ TIP_FOLLOW_CALM_MAX_PX_S +
234
+ (TIP_FOLLOW_BURST_MAX_PX_S - TIP_FOLLOW_CALM_MAX_PX_S) * blend * blend;
235
+ if (hot && frameGrowthPx > 0 && dtMs > 0) {
236
+ // Allow tracking this frame's growth rate (growthTrack uses a fraction).
237
+ maxPxS = Math.max(maxPxS, (frameGrowthPx / dtMs) * 1000);
238
+ }
239
+ if (hot && growthVelocityPxPerSec > TIP_FOLLOW_CALM_MAX_PX_S) {
240
+ // Live stream: slightly outrun the EMA so tip-debt cannot ratchet forever.
241
+ maxPxS = Math.max(maxPxS, Math.min(TIP_FOLLOW_BURST_MAX_PX_S, growthVelocityPxPerSec * 1.25));
242
+ }
243
+ if (hot && absDebt > 8) {
244
+ // Continuous debt horizon (no catch-up cliff at CATCHUP_DEBT).
245
+ maxPxS = Math.max(maxPxS, Math.min(TIP_FOLLOW_SURGE_MAX_PX_S, absDebt / 0.2));
246
+ } else if (absDebt >= TIP_FOLLOW_CATCHUP_DEBT_PX) {
247
+ maxPxS = Math.max(maxPxS, Math.min(TIP_FOLLOW_SURGE_MAX_PX_S, absDebt / 0.12));
248
+ }
249
+ return (maxPxS / 1000) * dtMs;
250
+ }
251
+
252
+ function targetScrollTop(scrollHeight: number, clientHeight: number): number {
253
+ return Math.max(0, scrollHeight - clientHeight);
254
+ }
255
+
256
+ /**
257
+ * Counter-offset for soft-rise after tip-glue: start from the mid-flight
258
+ * translateY (if any) plus this frame's glued scroll delta, capped.
259
+ * Shell applies translateY(from) → 0 over {@link TIP_FOLLOW_SOFT_RISE_MS}.
260
+ */
261
+ export function tipFollowSoftRiseFrom(
262
+ currentTranslateY: number,
263
+ gluedScrollDelta: number,
264
+ maxPx: number = TIP_FOLLOW_SOFT_RISE_MAX_PX,
265
+ ): number {
266
+ const stacked = Math.max(0, currentTranslateY) + Math.max(0, gluedScrollDelta);
267
+ return Math.min(maxPx, stacked);
268
+ }
269
+
270
+ /** Read live translateY from a transformed content column (mid soft-rise). */
271
+ export function readTranslateY(el: HTMLElement): number {
272
+ const raw = getComputedStyle(el).transform;
273
+ if (!raw || raw === "none") {
274
+ return 0;
275
+ }
276
+ try {
277
+ return new DOMMatrixReadOnly(raw).m42;
278
+ } catch {
279
+ return 0;
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Cancel the tip-glue pop: start at translateY(+δ) and ease to 0.
285
+ * Uses WAAPI — a same-frame CSS transition never paints the counter-offset,
286
+ * so the scroll jump stays an instant one-line pop.
287
+ */
288
+ export function tipFollowPlaySoftRise(
289
+ el: HTMLElement,
290
+ gluedScrollDelta: number,
291
+ opts?: {
292
+ durationMs?: number;
293
+ maxPx?: number;
294
+ easing?: string;
295
+ },
296
+ ): void {
297
+ if (gluedScrollDelta <= 0.5) {
298
+ return;
299
+ }
300
+ const durationMs = opts?.durationMs ?? TIP_FOLLOW_SOFT_RISE_MS;
301
+ const maxPx = opts?.maxPx ?? TIP_FOLLOW_SOFT_RISE_MAX_PX;
302
+ const easing = opts?.easing ?? TIP_FOLLOW_SOFT_RISE_EASING;
303
+ const from = tipFollowSoftRiseFrom(readTranslateY(el), gluedScrollDelta, maxPx);
304
+ if (from <= 0.5) {
305
+ return;
306
+ }
307
+ // Drop any in-flight rise / leftover CSS transition before starting clean.
308
+ if (typeof el.getAnimations === "function") {
309
+ for (const animation of el.getAnimations()) {
310
+ animation.cancel();
311
+ }
312
+ }
313
+ el.style.transition = "";
314
+ // Hold the counter-offset in style until WAAPI’s first keyframe applies.
315
+ // Clearing to "" here painted one frame of hard tip-glue (the pop).
316
+ el.style.transform = `translateY(${from}px)`;
317
+ if (typeof el.animate !== "function") {
318
+ return;
319
+ }
320
+ const animation = el.animate(
321
+ [{ transform: `translateY(${from}px)` }, { transform: "translateY(0px)" }],
322
+ // `backwards`: first keyframe applies immediately (no gap after cancel).
323
+ { duration: durationMs, easing, fill: "backwards" },
324
+ );
325
+ const clearHold = () => {
326
+ if (el.style.transform === `translateY(${from}px)`) {
327
+ el.style.transform = "";
328
+ }
329
+ };
330
+ animation.addEventListener("finish", clearHold, { once: true });
331
+ animation.addEventListener("cancel", clearHold, { once: true });
332
+ }
333
+
334
+ /** Stop soft-rise and clear any CSS transform residue. */
335
+ export function tipFollowClearSoftRise(el: HTMLElement): void {
336
+ if (typeof el.getAnimations === "function") {
337
+ for (const animation of el.getAnimations()) {
338
+ animation.cancel();
339
+ }
340
+ }
341
+ el.style.transition = "";
342
+ el.style.transform = "";
343
+ }
344
+
345
+ /**
346
+ * Height left the document (settle collapse / composer). Keep the same pixels
347
+ * on screen: scrollTop -= Δh, clamped to the new max.
348
+ */
349
+ export function tipFollowCompensateShrink(
350
+ scrollTop: number,
351
+ previousHeight: number,
352
+ nextHeight: number,
353
+ clientHeight: number,
354
+ epsPx: number = TIP_FOLLOW_SHRINK_EPS_PX,
355
+ ): number {
356
+ const delta = previousHeight - nextHeight;
357
+ if (delta <= epsPx) {
358
+ return scrollTop;
359
+ }
360
+ const maxScroll = Math.max(0, nextHeight - clientHeight);
361
+ return Math.max(0, Math.min(maxScroll, scrollTop - delta));
362
+ }
363
+
364
+ /**
365
+ * One camera step. Shell schedules the next rAF while `result.state.running`.
366
+ */
367
+ export function tipFollowStep(
368
+ state: TipFollowState,
369
+ input: TipFollowStepInput,
370
+ ): TipFollowStepResult {
371
+ const { scrollTop, scrollHeight, clientHeight, now, pinned, reducedMotion, revealed } = input;
372
+ const previousHeight = state.lastHeight;
373
+ const frameGrowth =
374
+ previousHeight > 0 && scrollHeight > previousHeight ? scrollHeight - previousHeight : 0;
375
+ const grew = frameGrowth > 0;
376
+ let noted = tipFollowNoteGrowth(state, scrollHeight, now);
377
+ const target = targetScrollTop(scrollHeight, clientHeight);
378
+ const debt = target - scrollTop;
379
+ const hot = now < noted.hotUntil;
380
+ const settling = !hot && Math.abs(debt) < TIP_FOLLOW_CATCHUP_DEBT_PX;
381
+
382
+ if (!pinned) {
383
+ return {
384
+ scrollTop,
385
+ state: tipFollowCancel(noted),
386
+ };
387
+ }
388
+
389
+ if (!revealed || reducedMotion) {
390
+ return {
391
+ scrollTop: target,
392
+ state: tipFollowCancel(noted),
393
+ };
394
+ }
395
+
396
+ if (Math.abs(debt) > TIP_FOLLOW_SNAP_PX && !hot) {
397
+ return {
398
+ scrollTop: target,
399
+ state: tipFollowCancel(noted),
400
+ };
401
+ }
402
+
403
+ const dt = noted.lastTs === 0 ? 16 : Math.min(64, Math.max(8, now - noted.lastTs));
404
+ if (!grew && noted.growthVelocity > 0) {
405
+ const decayed = noted.growthVelocity * Math.exp(-dt / TIP_FOLLOW_VELOCITY_DECAY_MS);
406
+ noted = {
407
+ ...noted,
408
+ growthVelocity: decayed < 1 ? 0 : decayed,
409
+ };
410
+ }
411
+
412
+ if (Math.abs(debt) <= 0.75) {
413
+ return {
414
+ scrollTop: Math.abs(debt) > 0 ? target : scrollTop,
415
+ state: {
416
+ ...tipFollowCancel(noted),
417
+ growthVelocity: hot ? noted.growthVelocity : 0,
418
+ },
419
+ };
420
+ }
421
+
422
+ const tau = tipFollowTauMs(debt, noted.growthVelocity, settling, hot);
423
+ const dtSec = dt / 1000;
424
+ const tauSec = Math.max(tau / 1000, 1e-3);
425
+ // P-gain: held desiredVel would close debt in ~τ. Velocity cannot jump —
426
+ // it eases toward desiredVel, so a new line accelerates in then settles out.
427
+ const desiredVel = debt / tauSec;
428
+ const accelTauSec = Math.max(tauSec * TIP_FOLLOW_ACCEL_TAU_FRAC, 0.02);
429
+ const velAlpha = 1 - Math.exp(-dtSec / accelTauSec);
430
+ let scrollVelocity =
431
+ (noted.scrollVelocity ?? 0) + (desiredVel - (noted.scrollVelocity ?? 0)) * velAlpha;
432
+
433
+ const maxStep = tipFollowMaxStepPx(debt, noted.growthVelocity, dt, frameGrowth, hot);
434
+ const maxVel = maxStep / dtSec;
435
+ if (Math.abs(scrollVelocity) > maxVel) {
436
+ scrollVelocity = Math.sign(scrollVelocity) * maxVel;
437
+ }
438
+
439
+ let next = scrollTop + scrollVelocity * dtSec;
440
+ // Never overshoot the tip — stop and kill velocity on arrival.
441
+ if (debt > 0 && next >= target - 0.35) {
442
+ next = target;
443
+ scrollVelocity = 0;
444
+ } else if (debt < 0 && next <= target + 0.35) {
445
+ next = target;
446
+ scrollVelocity = 0;
447
+ }
448
+ next = Math.max(0, next);
449
+
450
+ return {
451
+ scrollTop: next,
452
+ state: {
453
+ ...noted,
454
+ scrollVelocity,
455
+ running: Math.abs(target - next) > 0.35 || Math.abs(scrollVelocity) > 1,
456
+ lastTs: now,
457
+ },
458
+ };
459
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Token-styled Radix tooltip — SDK counterpart to apps/web `components/ui/tooltip`.
3
+ * Portals copy `--og-*` tokens so embeds keep the host theme outside `.og-root`.
4
+ *
5
+ * Background/foreground are applied as inline styles (not only Tailwind
6
+ * utilities): `.og-root` sets `color: var(--og-color-fg)` after utilities in the
7
+ * consumer CSS cascade, which would otherwise paint light-on-light inverted
8
+ * tips and look like a blank white bubble.
9
+ */
10
+ import { Tooltip as TooltipPrimitive } from "radix-ui";
11
+ import {
12
+ createContext,
13
+ useContext,
14
+ useRef,
15
+ type ComponentProps,
16
+ type CSSProperties,
17
+ type RefObject,
18
+ } from "react";
19
+
20
+ import { cn } from "../lib/cn";
21
+ import { usePortalTokenStyle } from "../lib/use-portal-token-style";
22
+
23
+ const TooltipSourceContext = createContext<RefObject<HTMLElement | null> | null>(null);
24
+
25
+ function TooltipProvider({
26
+ delayDuration = 300,
27
+ ...props
28
+ }: ComponentProps<typeof TooltipPrimitive.Provider>) {
29
+ return <TooltipPrimitive.Provider delayDuration={delayDuration} {...props} />;
30
+ }
31
+
32
+ function Tooltip({ children, ...props }: ComponentProps<typeof TooltipPrimitive.Root>) {
33
+ const sourceRef = useRef<HTMLElement | null>(null);
34
+ return (
35
+ <TooltipSourceContext.Provider value={sourceRef}>
36
+ <TooltipPrimitive.Root data-slot="tooltip" {...props}>
37
+ {children}
38
+ </TooltipPrimitive.Root>
39
+ </TooltipSourceContext.Provider>
40
+ );
41
+ }
42
+
43
+ function TooltipTrigger({
44
+ asChild = true,
45
+ ...props
46
+ }: ComponentProps<typeof TooltipPrimitive.Trigger>) {
47
+ const sourceRef = useContext(TooltipSourceContext);
48
+ return (
49
+ <TooltipPrimitive.Trigger
50
+ data-slot="tooltip-trigger"
51
+ asChild={asChild}
52
+ ref={(node) => {
53
+ if (sourceRef) sourceRef.current = node;
54
+ }}
55
+ {...props}
56
+ />
57
+ );
58
+ }
59
+
60
+ const tooltipSurfaceStyle: CSSProperties = {
61
+ backgroundColor: "var(--og-color-fg)",
62
+ color: "var(--og-color-bg)",
63
+ };
64
+
65
+ function TooltipContent({
66
+ className,
67
+ sideOffset = 6,
68
+ children,
69
+ style,
70
+ ...props
71
+ }: ComponentProps<typeof TooltipPrimitive.Content>) {
72
+ const sourceRef = useContext(TooltipSourceContext);
73
+ const fallbackRef = useRef<HTMLElement | null>(null);
74
+ const portalStyle = usePortalTokenStyle(sourceRef ?? fallbackRef);
75
+
76
+ return (
77
+ <TooltipPrimitive.Portal>
78
+ <TooltipPrimitive.Content
79
+ data-slot="tooltip-content"
80
+ sideOffset={sideOffset}
81
+ className={cn(
82
+ "og-root z-50 w-fit max-w-xs origin-[var(--radix-tooltip-content-transform-origin)]",
83
+ "rounded-og-md px-2.5 py-1.5 text-og-xs leading-4 text-balance shadow-og-md",
84
+ "animate-og-enter data-[state=closed]:opacity-0 data-[state=closed]:transition-opacity",
85
+ className,
86
+ )}
87
+ style={{ ...portalStyle, ...tooltipSurfaceStyle, ...style }}
88
+ {...props}
89
+ >
90
+ {children}
91
+ <TooltipPrimitive.Arrow
92
+ className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]"
93
+ style={{
94
+ backgroundColor: "var(--og-color-fg)",
95
+ fill: "var(--og-color-fg)",
96
+ }}
97
+ />
98
+ </TooltipPrimitive.Content>
99
+ </TooltipPrimitive.Portal>
100
+ );
101
+ }
102
+
103
+ export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
package/src/composer.ts CHANGED
@@ -58,3 +58,9 @@ export type {
58
58
  ComposerTranscriptionControlProps,
59
59
  ComposerTranscriptionMessages,
60
60
  } from "./components/composer-transcription-control";
61
+ export { useVoiceInput } from "./hooks/use-voice-input";
62
+ export type {
63
+ UseVoiceInputOptions,
64
+ UseVoiceInputResult,
65
+ VoiceInputStatus,
66
+ } from "./hooks/use-voice-input";
@@ -236,6 +236,7 @@ export function useSessionEventTrigger(
236
236
  }, [match, onEvent, reconcileBeforeLive]);
237
237
  const consumedRef = useRef(0);
238
238
  const feedKeyRef = useRef<string | null>(null);
239
+ const sharedFeedHasEventsRef = useRef(false);
239
240
 
240
241
  // Shared-log mode: scan only the unseen tail on every append.
241
242
  useEffect(() => {
@@ -244,12 +245,37 @@ export function useSessionEventTrigger(
244
245
  }
245
246
  const feedKey = `${workspaceId}\u0000${sessionId}`;
246
247
  const firstSequence = events[0]?.sequence ?? 0;
247
- // A new session target or a log reset (sequence restarted below the
248
- // cursor) restarts consumption from the top of the shared log.
249
- if (feedKeyRef.current !== feedKey || firstSequence > consumedRef.current + 1) {
248
+ if (feedKeyRef.current !== feedKey) {
250
249
  feedKeyRef.current = feedKey;
251
- consumedRef.current = 0;
250
+ sharedFeedHasEventsRef.current = events.length > 0;
251
+ // The caller has already loaded its authoritative initial projection.
252
+ // Seed from the shared log's current tail so mounting a large historical
253
+ // session cannot replay every old event as a new live trigger.
254
+ consumedRef.current = events.at(-1)?.sequence ?? 0;
255
+ return;
256
+ }
257
+ const firstNonEmptyBatch = !sharedFeedHasEventsRef.current && events.length > 0;
258
+ if (firstNonEmptyBatch || firstSequence > consumedRef.current + 1) {
259
+ // The usual shared feed mounts empty, then receives a historical tail.
260
+ // A later discontinuity can likewise replace the retained browser
261
+ // window. In either case, reconcile once from the latest relevant event
262
+ // instead of replaying the whole retained window as live traffic.
263
+ sharedFeedHasEventsRef.current = events.length > 0;
264
+ consumedRef.current = events.at(-1)?.sequence ?? consumedRef.current;
265
+ let latestMatch: SessionEvent | undefined;
266
+ for (let index = events.length - 1; index >= 0; index -= 1) {
267
+ const event = events[index];
268
+ if (event && matchRef.current(event)) {
269
+ latestMatch = event;
270
+ break;
271
+ }
272
+ }
273
+ if (latestMatch) {
274
+ onEventRef.current(latestMatch);
275
+ }
276
+ return;
252
277
  }
278
+ sharedFeedHasEventsRef.current ||= events.length > 0;
253
279
  for (const event of events) {
254
280
  if (event.sequence <= consumedRef.current) {
255
281
  continue;
@@ -1,5 +1,6 @@
1
- import type { ClientModel } from "@opengeni/sdk";
1
+ import type { WorkspaceModelCatalogModel } from "@opengeni/sdk";
2
2
  import { useCallback } from "react";
3
+ import { projectPickerRows, sortPickerRows, type PickerModelRow } from "../model-policy";
3
4
  import { useOpenGeniClient, type ClientOverride } from "../provider";
4
5
  import { usePolledValue } from "./internal";
5
6
 
@@ -10,9 +11,22 @@ export type UseAvailableModelsOptions = Pick<ClientOverride, "client"> & {
10
11
  };
11
12
 
12
13
  export type UseAvailableModelsResult = {
13
- /** The provider-grouped models the host exposes (empty until loaded). */
14
- models: ClientModel[];
15
- /** The deployment's default model id, null until loaded. */
14
+ models: import("@opengeni/sdk").ClientModel[];
15
+ defaultModel: string | null;
16
+ loading: boolean;
17
+ error: Error | null;
18
+ refresh: () => Promise<void>;
19
+ };
20
+
21
+ export type UseWorkspaceModelCatalogOptions = Pick<ClientOverride, "client"> & {
22
+ workspaceId: string | null;
23
+ pollIntervalMs?: number | undefined;
24
+ enabled?: boolean | undefined;
25
+ };
26
+
27
+ export type UseWorkspaceModelCatalogResult = {
28
+ models: WorkspaceModelCatalogModel[];
29
+ rows: PickerModelRow[];
16
30
  defaultModel: string | null;
17
31
  loading: boolean;
18
32
  error: Error | null;
@@ -42,3 +56,36 @@ export function useAvailableModels(
42
56
  refresh: state.refresh,
43
57
  };
44
58
  }
59
+
60
+ /** Workspace-scoped catalog with selectability and billing-class picker rows. */
61
+ export function useWorkspaceModelCatalog(
62
+ options: UseWorkspaceModelCatalogOptions,
63
+ ): UseWorkspaceModelCatalogResult {
64
+ const client = useOpenGeniClient(options);
65
+ const load = useCallback(async () => {
66
+ if (!options.workspaceId) {
67
+ return { models: [], defaultModel: null };
68
+ }
69
+ const [catalog, config] = await Promise.all([
70
+ client.getWorkspaceModelCatalog(options.workspaceId),
71
+ client.getClientConfig(),
72
+ ]);
73
+ return {
74
+ models: catalog.models,
75
+ defaultModel: config.defaultModel,
76
+ };
77
+ }, [client, options.workspaceId]);
78
+ const state = usePolledValue(load, {
79
+ pollIntervalMs: options.pollIntervalMs,
80
+ enabled: options.enabled !== false && Boolean(options.workspaceId),
81
+ });
82
+ const rows = sortPickerRows(projectPickerRows(state.data?.models ?? []));
83
+ return {
84
+ models: state.data?.models ?? [],
85
+ rows,
86
+ defaultModel: state.data?.defaultModel ?? null,
87
+ loading: state.loading,
88
+ error: state.error,
89
+ refresh: state.refresh,
90
+ };
91
+ }