@arbidocs/blocks 0.5.125 → 0.5.127

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 (200) hide show
  1. package/README.md +19 -0
  2. package/dist/{ClippingMediaViewer-ZFEX5A3U.cjs → ClippingMediaViewer-47BAXNLO.cjs} +10 -11
  3. package/dist/ClippingMediaViewer-47BAXNLO.cjs.map +1 -0
  4. package/dist/{ClippingPdfViewer-3LRTEPHV.cjs → ClippingPdfViewer-D5BYHEAH.cjs} +16 -14
  5. package/dist/ClippingPdfViewer-D5BYHEAH.cjs.map +1 -0
  6. package/dist/ConnectedCallDock-XJOYVVAH.cjs +6 -6
  7. package/dist/ConnectedCallRoom-CUDYAPQK.cjs +6 -6
  8. package/dist/{ConnectedNativeDocxComparison-7QQ55JIO.cjs → ConnectedNativeDocxComparison-JUF2TI2P.cjs} +12 -12
  9. package/dist/{ConnectedNativeDocxComparison-7QQ55JIO.cjs.map → ConnectedNativeDocxComparison-JUF2TI2P.cjs.map} +1 -1
  10. package/dist/{ConnectedPresentation-L32JPRFI.cjs → ConnectedPresentation-SI3L77J4.cjs} +35 -36
  11. package/dist/ConnectedPresentation-SI3L77J4.cjs.map +1 -0
  12. package/dist/{ConnectedProductionSetDialog-4EAY6KAZ.cjs → ConnectedProductionSetDialog-LHB4VQWC.cjs} +3 -3
  13. package/dist/{ConnectedProductionSetDialog-4EAY6KAZ.cjs.map → ConnectedProductionSetDialog-LHB4VQWC.cjs.map} +1 -1
  14. package/dist/{ConnectedSlidesTab-P7GGNUTO.cjs → ConnectedSlidesTab-E76OVY34.cjs} +38 -44
  15. package/dist/ConnectedSlidesTab-E76OVY34.cjs.map +1 -0
  16. package/dist/{ConnectedStreamedWordArtifact-7NJ3N52V.cjs → ConnectedStreamedWordArtifact-Q64DRRQD.cjs} +12 -12
  17. package/dist/{ConnectedStreamedWordArtifact-7NJ3N52V.cjs.map → ConnectedStreamedWordArtifact-Q64DRRQD.cjs.map} +1 -1
  18. package/dist/{DocumentReviewCard-BFPOFQXI.cjs → DocumentReviewCard-M53XFLI2.cjs} +16 -16
  19. package/dist/{DocumentReviewCard-BFPOFQXI.cjs.map → DocumentReviewCard-M53XFLI2.cjs.map} +1 -1
  20. package/dist/{FilesViewerDialog-MHUVEZ7M.cjs → FilesViewerDialog-V6FSRQTP.cjs} +33 -34
  21. package/dist/FilesViewerDialog-V6FSRQTP.cjs.map +1 -0
  22. package/dist/{NativeDocxEditor-SIRYPMBN.cjs → NativeDocxEditor-2PTEK6ND.cjs} +6 -6
  23. package/dist/{NativeDocxEditor-SIRYPMBN.cjs.map → NativeDocxEditor-2PTEK6ND.cjs.map} +1 -1
  24. package/dist/{SpotlightStrip-ED22UYTM.cjs → SpotlightStrip-QQ2WUI32.cjs} +28 -29
  25. package/dist/SpotlightStrip-QQ2WUI32.cjs.map +1 -0
  26. package/dist/{WorkspaceTableView-MIFYXV3G.cjs → WorkspaceTableView-3XYAV4BV.cjs} +8 -8
  27. package/dist/{WorkspaceTableView-MIFYXV3G.cjs.map → WorkspaceTableView-3XYAV4BV.cjs.map} +1 -1
  28. package/dist/agents/index.cjs +215 -215
  29. package/dist/ai-IEIR7SC2.cjs +3 -3
  30. package/dist/auth/index.cjs +1 -1
  31. package/dist/calls/index.cjs +2 -2
  32. package/dist/chat/index.cjs +6 -6
  33. package/dist/{chunk-GAMRVB3I.cjs → chunk-23I5EUZA.cjs} +65 -10
  34. package/dist/chunk-23I5EUZA.cjs.map +1 -0
  35. package/dist/{chunk-ZZZITHGK.cjs → chunk-26SFI2BH.cjs} +16 -3
  36. package/dist/chunk-26SFI2BH.cjs.map +1 -0
  37. package/dist/{chunk-OGK377NL.cjs → chunk-2SU37RZA.cjs} +409 -42
  38. package/dist/chunk-2SU37RZA.cjs.map +1 -0
  39. package/dist/{chunk-CQ2RJYE2.cjs → chunk-3IH55CDE.cjs} +3 -3
  40. package/dist/{chunk-CQ2RJYE2.cjs.map → chunk-3IH55CDE.cjs.map} +1 -1
  41. package/dist/{chunk-AS6QZETU.cjs → chunk-5QERK4PK.cjs} +35 -35
  42. package/dist/{chunk-AS6QZETU.cjs.map → chunk-5QERK4PK.cjs.map} +1 -1
  43. package/dist/{chunk-AISYXX4M.cjs → chunk-7NLO7ASW.cjs} +4 -4
  44. package/dist/{chunk-AISYXX4M.cjs.map → chunk-7NLO7ASW.cjs.map} +1 -1
  45. package/dist/{chunk-QIK7E6VF.cjs → chunk-7PN3YENO.cjs} +4 -4
  46. package/dist/{chunk-QIK7E6VF.cjs.map → chunk-7PN3YENO.cjs.map} +1 -1
  47. package/dist/{chunk-JY7UUZQG.cjs → chunk-7Q46YAX4.cjs} +74 -71
  48. package/dist/chunk-7Q46YAX4.cjs.map +1 -0
  49. package/dist/{chunk-J3FFSEKO.cjs → chunk-ADDQKZ5I.cjs} +4 -4
  50. package/dist/{chunk-J3FFSEKO.cjs.map → chunk-ADDQKZ5I.cjs.map} +1 -1
  51. package/dist/{chunk-L6HTZIBD.cjs → chunk-ALK3PUUB.cjs} +3 -3
  52. package/dist/{chunk-L6HTZIBD.cjs.map → chunk-ALK3PUUB.cjs.map} +1 -1
  53. package/dist/{chunk-3ZZZO4F7.cjs → chunk-BY3QA3S5.cjs} +9 -9
  54. package/dist/{chunk-3ZZZO4F7.cjs.map → chunk-BY3QA3S5.cjs.map} +1 -1
  55. package/dist/{chunk-3UP32FM3.cjs → chunk-CQQQIZU4.cjs} +5 -5
  56. package/dist/{chunk-3UP32FM3.cjs.map → chunk-CQQQIZU4.cjs.map} +1 -1
  57. package/dist/{chunk-YOEP3I4V.cjs → chunk-E3L5DB5F.cjs} +5 -5
  58. package/dist/{chunk-YOEP3I4V.cjs.map → chunk-E3L5DB5F.cjs.map} +1 -1
  59. package/dist/{chunk-UA7HOLAZ.cjs → chunk-ESVRVZ53.cjs} +5 -5
  60. package/dist/{chunk-UA7HOLAZ.cjs.map → chunk-ESVRVZ53.cjs.map} +1 -1
  61. package/dist/{chunk-2K6IMIVL.cjs → chunk-GNZAH6UI.cjs} +3 -3
  62. package/dist/{chunk-2K6IMIVL.cjs.map → chunk-GNZAH6UI.cjs.map} +1 -1
  63. package/dist/{chunk-KA3F6TIH.cjs → chunk-IWHP7FAB.cjs} +12 -5
  64. package/dist/chunk-IWHP7FAB.cjs.map +1 -0
  65. package/dist/{chunk-ORNLCBNE.cjs → chunk-JVEDKKNO.cjs} +43 -4
  66. package/dist/chunk-JVEDKKNO.cjs.map +1 -0
  67. package/dist/{chunk-6JMW3IJ2.cjs → chunk-JVLOIIA7.cjs} +103 -100
  68. package/dist/chunk-JVLOIIA7.cjs.map +1 -0
  69. package/dist/{chunk-UUENH4BJ.cjs → chunk-KXIRVKCJ.cjs} +3 -3
  70. package/dist/{chunk-UUENH4BJ.cjs.map → chunk-KXIRVKCJ.cjs.map} +1 -1
  71. package/dist/{chunk-YVO6Z5BS.cjs → chunk-M6WVIGYP.cjs} +10 -10
  72. package/dist/{chunk-YVO6Z5BS.cjs.map → chunk-M6WVIGYP.cjs.map} +1 -1
  73. package/dist/{chunk-PSB64T43.cjs → chunk-MLA2BJ2Y.cjs} +28 -28
  74. package/dist/{chunk-PSB64T43.cjs.map → chunk-MLA2BJ2Y.cjs.map} +1 -1
  75. package/dist/{chunk-B3R62764.cjs → chunk-MMSHIIKG.cjs} +6 -6
  76. package/dist/{chunk-B3R62764.cjs.map → chunk-MMSHIIKG.cjs.map} +1 -1
  77. package/dist/{chunk-ENOPKX7E.cjs → chunk-MXBXZXXQ.cjs} +3 -3
  78. package/dist/{chunk-ENOPKX7E.cjs.map → chunk-MXBXZXXQ.cjs.map} +1 -1
  79. package/dist/chunk-NTDQQLDD.cjs +18 -0
  80. package/dist/{chunk-IRGRMJPP.cjs.map → chunk-NTDQQLDD.cjs.map} +1 -1
  81. package/dist/{chunk-PVJ5ODDB.cjs → chunk-Q6IKXWPG.cjs} +4 -4
  82. package/dist/{chunk-PVJ5ODDB.cjs.map → chunk-Q6IKXWPG.cjs.map} +1 -1
  83. package/dist/{chunk-4LP5MJGQ.cjs → chunk-QG2U2JGX.cjs} +4 -4
  84. package/dist/{chunk-4LP5MJGQ.cjs.map → chunk-QG2U2JGX.cjs.map} +1 -1
  85. package/dist/{chunk-MVXHTAJ4.cjs → chunk-QWVUBTW3.cjs} +9 -9
  86. package/dist/{chunk-MVXHTAJ4.cjs.map → chunk-QWVUBTW3.cjs.map} +1 -1
  87. package/dist/{chunk-OHWHYVKG.cjs → chunk-T73BHNQX.cjs} +3 -3
  88. package/dist/{chunk-OHWHYVKG.cjs.map → chunk-T73BHNQX.cjs.map} +1 -1
  89. package/dist/{chunk-PWBRE7JA.cjs → chunk-VLQCLQAS.cjs} +23 -10
  90. package/dist/chunk-VLQCLQAS.cjs.map +1 -0
  91. package/dist/{chunk-ADPRIBSH.cjs → chunk-X7ALF4MP.cjs} +7 -7
  92. package/dist/{chunk-ADPRIBSH.cjs.map → chunk-X7ALF4MP.cjs.map} +1 -1
  93. package/dist/{chunk-7RJZIPLE.cjs → chunk-YP5EBGLU.cjs} +33 -33
  94. package/dist/{chunk-7RJZIPLE.cjs.map → chunk-YP5EBGLU.cjs.map} +1 -1
  95. package/dist/{chunk-TSFNLY3K.cjs → chunk-ZCU72Q76.cjs} +192 -166
  96. package/dist/chunk-ZCU72Q76.cjs.map +1 -0
  97. package/dist/{chunk-BLIO3B7Z.cjs → chunk-ZYGAXD3N.cjs} +3 -3
  98. package/dist/{chunk-BLIO3B7Z.cjs.map → chunk-ZYGAXD3N.cjs.map} +1 -1
  99. package/dist/connect/docx.cjs +16 -16
  100. package/dist/connect/index.cjs +1095 -1114
  101. package/dist/connect/index.cjs.map +1 -1
  102. package/dist/connect/viewer.cjs +26 -27
  103. package/dist/connection/ai.d.ts +1 -0
  104. package/dist/connection/docx/ConnectedNativeDocxEditor.js +4 -5
  105. package/dist/connection/docx/ConnectedNativeDocxEditor.js.map +2 -2
  106. package/dist/connection/docx/save-native-version.d.ts +3 -0
  107. package/dist/connection/docx/save-native-version.js +19 -0
  108. package/dist/connection/docx/save-native-version.js.map +7 -0
  109. package/dist/connection/presentation/ClippingPdfViewer.js +8 -5
  110. package/dist/connection/presentation/ClippingPdfViewer.js.map +2 -2
  111. package/dist/connection/presentation/ConnectedSlidesTab.js +8 -13
  112. package/dist/connection/presentation/ConnectedSlidesTab.js.map +2 -2
  113. package/dist/connection/presentation/usePresentationController.js +24 -15
  114. package/dist/connection/presentation/usePresentationController.js.map +2 -2
  115. package/dist/connection/presentation/usePresentationFiles.d.ts +2 -1
  116. package/dist/connection/presentation/usePresentationFiles.js +15 -6
  117. package/dist/connection/presentation/usePresentationFiles.js.map +2 -2
  118. package/dist/connection/remote-control/ConnectedRemoteControlConsent.js +6 -3
  119. package/dist/connection/remote-control/ConnectedRemoteControlConsent.js.map +2 -2
  120. package/dist/connection/remote-control/ConnectedRemoteControlIndicator.js +12 -2
  121. package/dist/connection/remote-control/ConnectedRemoteControlIndicator.js.map +2 -2
  122. package/dist/connection/remote-control/ConnectedRemoteControlSwitch.js +1 -1
  123. package/dist/connection/remote-control/ConnectedRemoteControlSwitch.js.map +1 -1
  124. package/dist/connection/remote-control/browser-diagnostics.d.ts +7 -0
  125. package/dist/connection/remote-control/browser-diagnostics.js +109 -0
  126. package/dist/connection/remote-control/browser-diagnostics.js.map +7 -0
  127. package/dist/connection/remote-control/remoteControl.d.ts +9 -1
  128. package/dist/connection/remote-control/remoteControl.js +130 -10
  129. package/dist/connection/remote-control/remoteControl.js.map +2 -2
  130. package/dist/connection/remote-control/screen-sharing.d.ts +11 -0
  131. package/dist/connection/remote-control/screen-sharing.js +78 -0
  132. package/dist/connection/remote-control/screen-sharing.js.map +7 -0
  133. package/dist/connection/report-problem/screen-capture.d.ts +1 -0
  134. package/dist/connection/report-problem/screen-capture.js +2 -1
  135. package/dist/connection/report-problem/screen-capture.js.map +2 -2
  136. package/dist/connection/search/ConnectedSearchPage.d.ts +0 -11
  137. package/dist/connection/search/ConnectedSearchPage.js +3 -0
  138. package/dist/connection/search/ConnectedSearchPage.js.map +2 -2
  139. package/dist/connection/search/SearchResultRows.js +75 -43
  140. package/dist/connection/search/SearchResultRows.js.map +2 -2
  141. package/dist/connection/viewer/CleanViewControl.d.ts +3 -1
  142. package/dist/connection/viewer/CleanViewControl.js +5 -1
  143. package/dist/connection/viewer/CleanViewControl.js.map +2 -2
  144. package/dist/connection/viewer/ConnectedDocumentViewer.js +1 -1
  145. package/dist/connection/viewer/ConnectedDocumentViewer.js.map +2 -2
  146. package/dist/connection/viewer/useCleanView.d.ts +1 -1
  147. package/dist/connection/viewer/useCleanView.js +2 -2
  148. package/dist/connection/viewer/useCleanView.js.map +2 -2
  149. package/dist/connection/viewer/useViewedDocument.d.ts +1 -0
  150. package/dist/document-tools/index.cjs +20 -20
  151. package/dist/docx/NativeDocxEditor.d.ts +2 -1
  152. package/dist/docx/NativeDocxEditor.js +34 -7
  153. package/dist/docx/NativeDocxEditor.js.map +2 -2
  154. package/dist/docx/index.cjs +38 -38
  155. package/dist/docx/useVersionAutosave.d.ts +10 -0
  156. package/dist/docx/useVersionAutosave.js +36 -0
  157. package/dist/docx/useVersionAutosave.js.map +7 -0
  158. package/dist/embed/index.cjs +19 -19
  159. package/dist/facts/index.cjs +64 -64
  160. package/dist/files/index.cjs +223 -223
  161. package/dist/index.cjs +542 -542
  162. package/dist/presentation/ClipCaptureOverlay.js +14 -1
  163. package/dist/presentation/ClipCaptureOverlay.js.map +2 -2
  164. package/dist/presentation/ClipImage.js +7 -4
  165. package/dist/presentation/ClipImage.js.map +2 -2
  166. package/dist/presentation/ClipTray.js +11 -7
  167. package/dist/presentation/ClipTray.js.map +2 -2
  168. package/dist/presentation/SlidesEditor.js +6 -2
  169. package/dist/presentation/SlidesEditor.js.map +2 -2
  170. package/dist/presentation/store.d.ts +2 -0
  171. package/dist/presentation/store.js +15 -0
  172. package/dist/presentation/store.js.map +3 -3
  173. package/dist/settings/index.cjs +146 -146
  174. package/dist/shell/RemoteControlPill.d.ts +2 -1
  175. package/dist/shell/RemoteControlPill.js +10 -3
  176. package/dist/shell/RemoteControlPill.js.map +2 -2
  177. package/dist/shell/index.cjs +21 -21
  178. package/dist/site/index.cjs +6 -6
  179. package/dist/studio/index.cjs +9 -9
  180. package/dist/workflow-canvas/index.cjs +34 -34
  181. package/node_modules/@arbidocs/coedit/package.json +1 -1
  182. package/package.json +5 -5
  183. package/dist/ClippingMediaViewer-ZFEX5A3U.cjs.map +0 -1
  184. package/dist/ClippingPdfViewer-3LRTEPHV.cjs.map +0 -1
  185. package/dist/ConnectedPresentation-L32JPRFI.cjs.map +0 -1
  186. package/dist/ConnectedSlidesTab-P7GGNUTO.cjs.map +0 -1
  187. package/dist/FilesViewerDialog-MHUVEZ7M.cjs.map +0 -1
  188. package/dist/SpotlightStrip-ED22UYTM.cjs.map +0 -1
  189. package/dist/chunk-6JMW3IJ2.cjs.map +0 -1
  190. package/dist/chunk-GAMRVB3I.cjs.map +0 -1
  191. package/dist/chunk-GFUOCGY6.cjs +0 -32
  192. package/dist/chunk-GFUOCGY6.cjs.map +0 -1
  193. package/dist/chunk-IRGRMJPP.cjs +0 -18
  194. package/dist/chunk-JY7UUZQG.cjs.map +0 -1
  195. package/dist/chunk-KA3F6TIH.cjs.map +0 -1
  196. package/dist/chunk-OGK377NL.cjs.map +0 -1
  197. package/dist/chunk-ORNLCBNE.cjs.map +0 -1
  198. package/dist/chunk-PWBRE7JA.cjs.map +0 -1
  199. package/dist/chunk-TSFNLY3K.cjs.map +0 -1
  200. package/dist/chunk-ZZZITHGK.cjs.map +0 -1
@@ -7,25 +7,115 @@ import {
7
7
  toRemoteControlRequest,
8
8
  useArbiHost,
9
9
  useArbiSession,
10
- useArbiSessionStore
10
+ useArbiSessionStore,
11
+ useArbiSessionState
11
12
  } from "@arbidocs/react";
12
13
  import { cancelGuidedRun } from "../../guided/runner.js";
13
14
  import { useGuidedRuns } from "../../guided/useGuidedRuns.js";
14
15
  import { isSurfaceMounted } from "../../guided/surfaces.js";
15
- function createRemoteControl(session, guided) {
16
+ import { createScreenSharing } from "./screen-sharing.js";
17
+ const screenRuntimes = /* @__PURE__ */ new WeakMap();
18
+ function screenRuntime(session) {
19
+ let runtime = screenRuntimes.get(session);
20
+ if (!runtime) {
21
+ runtime = { sharing: createScreenSharing(), generation: 0, setup: "" };
22
+ screenRuntimes.set(session, runtime);
23
+ }
24
+ return runtime;
25
+ }
26
+ function stopScreen(session) {
27
+ const runtime = screenRuntimes.get(session);
28
+ if (!runtime) return;
29
+ runtime.generation++;
30
+ runtime.sharing.stop();
31
+ runtime.setup = "";
32
+ const actions = remoteControlActions(session);
33
+ if (remoteControlStateOf(session.getState()).sharing) actions.setSharing(false);
34
+ }
35
+ function createRemoteControl(session, guided, screenTransport) {
16
36
  const actions = remoteControlActions(session);
17
37
  const state = () => remoteControlStateOf(session.getState());
38
+ const runtime = screenRuntime(session);
39
+ const reply = async (req, status) => {
40
+ if ((req.screenRequestId || req.actionRequestId) && screenTransport) {
41
+ await screenTransport.reply(req.screenRequestId || req.actionRequestId, { status }).catch(() => {
42
+ });
43
+ }
44
+ };
45
+ const disable = () => {
46
+ const pending = state().pending;
47
+ if (pending) void reply(pending, "stopped");
48
+ stopScreen(session);
49
+ actions.setEnabled(false);
50
+ cancelGuidedRun();
51
+ };
52
+ const inspect = async (req, keepSharing) => {
53
+ if (!req.screenRequestId || !screenTransport) return;
54
+ const workspace = session.getState().workspaceId;
55
+ if (workspace !== req.workspaceExtId) {
56
+ await reply(req, "unavailable");
57
+ return;
58
+ }
59
+ const token = runtime.generation;
60
+ const current = () => token === runtime.generation && session.getState().workspaceId === workspace;
61
+ actions.setControllingAgent({ name: req.agentName, extId: req.agentExtId });
62
+ try {
63
+ if (!runtime.sharing.active) {
64
+ await runtime.sharing.start(disable);
65
+ if (!current()) {
66
+ runtime.sharing.stop();
67
+ await reply(req, "stopped");
68
+ return;
69
+ }
70
+ actions.setSharing(true);
71
+ if (screenTransport.diagnose)
72
+ runtime.setup = await screenTransport.diagnose().catch(() => "Setup checks unavailable.");
73
+ }
74
+ if (!current()) {
75
+ await reply(req, "stopped");
76
+ return;
77
+ }
78
+ const frame = await runtime.sharing.frame();
79
+ if (!current()) {
80
+ await reply(req, "stopped");
81
+ return;
82
+ }
83
+ await screenTransport.reply(req.screenRequestId, {
84
+ status: "captured",
85
+ ...frame,
86
+ diagnostics: `${frame.diagnostics}
87
+ ${runtime.setup}`.slice(0, 24e3)
88
+ });
89
+ } catch {
90
+ await reply(req, current() ? "unavailable" : "stopped");
91
+ if (current()) disable();
92
+ } finally {
93
+ if (current()) {
94
+ actions.setControllingAgent(null);
95
+ if (!keepSharing) disable();
96
+ }
97
+ }
98
+ };
18
99
  const resolveRun = (req) => {
19
100
  if (req.steps && req.steps.length > 0) return req.steps;
20
101
  if (req.sequenceId && guided.isSequenceId(req.sequenceId)) return req.sequenceId;
21
102
  return null;
22
103
  };
23
- const runRemoteRequest = async (req) => {
104
+ const runRemoteRequest = async (req, keepSharing = state().enabled) => {
105
+ if (req.screenRequestId) {
106
+ await inspect(req, keepSharing);
107
+ return;
108
+ }
24
109
  const run = resolveRun(req);
25
110
  if (!run) return;
111
+ const token = runtime.generation;
26
112
  actions.setControllingAgent({ name: req.agentName, extId: req.agentExtId });
27
113
  try {
28
114
  await guided.run(run, { remote: true });
115
+ if (req.actionRequestId)
116
+ await reply(req, token === runtime.generation ? "action_finished" : "stopped");
117
+ } catch {
118
+ await reply(req, "unavailable");
29
119
  } finally {
30
120
  const current = state().controllingAgent;
31
121
  if (current && current.name === req.agentName && current.extId === req.agentExtId) {
@@ -45,31 +135,49 @@ function createRemoteControl(session, guided) {
45
135
  }
46
136
  const req = toRemoteControlRequest(msg, guided.isSequenceId);
47
137
  if (!req) return;
48
- if (state().enabled) {
138
+ if ((req.screenRequestId || req.actionRequestId) && (!screenTransport || session.getState().workspaceId !== req.workspaceExtId)) {
139
+ void reply(req, "unavailable");
140
+ return;
141
+ }
142
+ if (state().enabled && (!req.screenRequestId || runtime.sharing.active)) {
49
143
  logger.info("[RemoteControl] Enabled \u2014 running requested action from", req.agentName);
50
144
  void runRemoteRequest(req);
51
145
  return;
52
146
  }
53
147
  logger.info("[RemoteControl] Consent required \u2014 prompting for", req.agentName);
148
+ if (state().pending) void reply(state().pending, "denied");
54
149
  actions.request(req);
55
150
  },
56
151
  grantAndRun(opts) {
57
152
  const pending = state().pending;
153
+ const keepSharing = opts.forSession || state().enabled;
58
154
  actions.grant(opts);
59
- if (pending) void runRemoteRequest(pending);
155
+ if (pending) void runRemoteRequest(pending, keepSharing);
60
156
  },
61
- deny: () => actions.deny(),
62
- disable() {
63
- actions.setEnabled(false);
64
- cancelGuidedRun();
157
+ deny: () => {
158
+ const pending = state().pending;
159
+ actions.deny();
160
+ if (pending) void reply(pending, "denied");
65
161
  },
162
+ disable,
66
163
  enable: () => actions.setEnabled(true)
67
164
  };
68
165
  }
69
166
  function useRemoteControlActions() {
70
167
  const session = useArbiSessionStore();
71
168
  const guided = useGuidedRuns();
72
- return useMemo(() => createRemoteControl(session, guided), [session, guided]);
169
+ const { arbi } = useArbiHost();
170
+ return useMemo(
171
+ () => createRemoteControl(
172
+ session,
173
+ guided,
174
+ arbi ? {
175
+ reply: (id, result) => arbi.remoteControl.replyScreen(id, result),
176
+ diagnose: async () => JSON.stringify(await arbi.diagnose())
177
+ } : void 0
178
+ ),
179
+ [session, guided, arbi]
180
+ );
73
181
  }
74
182
  function useRemoteControlSessionReset() {
75
183
  const session = useArbiSessionStore();
@@ -77,6 +185,7 @@ function useRemoteControlSessionReset() {
77
185
  const userId = useArbiSession().user?.external_id ?? null;
78
186
  useEffect(() => {
79
187
  const reset = () => {
188
+ stopScreen(session);
80
189
  remoteControlActions(session).reset();
81
190
  cancelGuidedRun();
82
191
  };
@@ -92,6 +201,7 @@ function useRemoteControlSessionReset() {
92
201
  useEffect(() => {
93
202
  if (lastUser.current === userId) return;
94
203
  lastUser.current = userId;
204
+ stopScreen(session);
95
205
  remoteControlActions(session).reset();
96
206
  cancelGuidedRun();
97
207
  }, [userId, session]);
@@ -100,6 +210,16 @@ function useRemoteControlHandlers() {
100
210
  useRemoteControlSessionReset();
101
211
  const remote = useRemoteControlActions();
102
212
  const { remoteControl } = useGuidedRuns();
213
+ const session = useArbiSessionStore();
214
+ const workspaceId = useArbiSessionState((s) => s.workspaceId);
215
+ useEffect(
216
+ () => () => {
217
+ stopScreen(session);
218
+ remoteControlActions(session).setEnabled(false);
219
+ cancelGuidedRun();
220
+ },
221
+ [session, workspaceId]
222
+ );
103
223
  return useMemo(
104
224
  () => remoteControl ? {
105
225
  remote_control: (message) => remote.handleMessage(message)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/connection/remote-control/remoteControl.ts"],
4
- "sourcesContent": ["/**\n * Remote-control orchestration \u2014 the frontend side of \"a backend agent drives the UI over the\n * websocket\" (the message and the session state: `@arbidocs/react`'s remote-control module).\n *\n * This is the SEPARATE, gated entry point for websocket-arriving runs. It is deliberately distinct\n * from a site's `GuidedRuns.run` so that local \"Show me\" tips (which run directly) are NEVER gated\n * by consent \u2014 GUARANTEE #4. Only runs that arrive here, from the socket, pass through the gate.\n *\n * Gate logic (`handleMessage`):\n * - the site has not turned remote control on (`guided.remoteControl`), or the consent prompt or\n * the guided cursor is not on screen \u2192 refuse: nothing runs, nothing is asked.\n * - remote control `enabled` this session \u2192 run immediately.\n * - otherwise \u2192 stash the request in `pending` (the consent prompt opens); run NOTHING until the\n * user grants (GUARANTEE #2).\n *\n * What a remote command can run: a `sequence_id` the site registered (`GuidedRuns.isSequenceId`)\n * or inline `steps` whose every step has a target, through the one guided runner \u2014 nothing else\n * (no action dispatch). In a remote run the runner acts only on what the user can see, caps waits\n * and pauses at 5 s, and never acts inside an off-limits surface (remote control's own controls,\n * so a one-off grant cannot grant itself the session; credential and endpoint forms) nor on a\n * label whose control is inside one; no guided run types into a secret field.\n *\n * Every sign-in, sign-out, session expiry and change of user drops the grant, any pending request\n * and the run in flight (`useRemoteControlSessionReset`, mounted with the handler and the cursor),\n * so nothing carries over to the next person in the tab.\n *\n * While a run executes, `controllingAgent` is set (labels the pill) and cleared afterwards.\n * Turning remote control off (`disable`) cancels any in-flight run immediately (GUARANTEE #5).\n */\nimport { useEffect, useMemo, useRef } from 'react'\nimport { logger } from '@arbidocs/client'\nimport {\n remoteControlActions,\n remoteControlStateOf,\n toRemoteControlRequest,\n useArbiHost,\n useArbiSession,\n useArbiSessionStore,\n type ArbiWsHandlers,\n type GuidedStep,\n type RemoteControlMessage,\n type RemoteControlRequest,\n type RemoteControlSession,\n} from '@arbidocs/react'\nimport { cancelGuidedRun } from '../../guided/runner'\nimport type { GuidedRuns } from '../../guided/sequences'\nimport { useGuidedRuns } from '../../guided/useGuidedRuns'\nimport { isSurfaceMounted } from '../../guided/surfaces'\n\nexport interface RemoteControl {\n /** The websocket entry point, for a `remote_control` message. Gated (see the file header). */\n handleMessage(msg: RemoteControlMessage): void\n /**\n * Grant the pending consent and run it. `forSession: true` keeps remote control enabled so\n * later websocket runs go without re-prompting; `false` runs the one pending run and leaves it\n * disabled (\"just this once\").\n */\n grantAndRun(opts: { forSession: boolean }): void\n /** Deny the pending consent \u2014 clears it and runs nothing. */\n deny(): void\n /**\n * Turn remote control OFF and revoke immediately (GUARANTEE #5): clears the enabled flag, the\n * pending prompt and the pill, and cancels any in-flight run so the guided cursor stops at once.\n */\n disable(): void\n /** Turn remote control ON directly \u2014 the user's own explicit choice. Runs nothing by itself. */\n enable(): void\n}\n\n/** Remote control on a session's state, running the site's guided runs. */\nexport function createRemoteControl(\n session: RemoteControlSession,\n guided: Pick<GuidedRuns, 'isSequenceId' | 'run' | 'remoteControl'>\n): RemoteControl {\n const actions = remoteControlActions(session)\n const state = () => remoteControlStateOf(session.getState())\n\n /** Resolve a request to the concrete run input for the runner. */\n const resolveRun = (req: RemoteControlRequest): string | GuidedStep[] | null => {\n if (req.steps && req.steps.length > 0) return req.steps\n if (req.sequenceId && guided.isSequenceId(req.sequenceId)) return req.sequenceId\n return null\n }\n\n /** Execute a request, labelling the pill for its duration. The ONLY place a websocket-arriving\n * run reaches the runner. */\n const runRemoteRequest = async (req: RemoteControlRequest): Promise<void> => {\n const run = resolveRun(req)\n if (!run) return\n actions.setControllingAgent({ name: req.agentName, extId: req.agentExtId })\n try {\n await guided.run(run, { remote: true })\n } finally {\n // Only clear the pill if THIS run is still the active one \u2014 a newer run or a disable will\n // have replaced/cleared it already.\n const current = state().controllingAgent\n if (current && current.name === req.agentName && current.extId === req.agentExtId) {\n actions.setControllingAgent(null)\n }\n }\n }\n\n return {\n handleMessage(msg) {\n if (!guided.remoteControl) {\n logger.warn('[RemoteControl] Off on this site \u2014 ignoring a request')\n return\n }\n if (!isSurfaceMounted('consent') || !isSurfaceMounted('cursor')) {\n logger.warn('[RemoteControl] No consent prompt or cursor on screen \u2014 ignoring a request')\n return\n }\n const req = toRemoteControlRequest(msg, guided.isSequenceId)\n if (!req) return\n if (state().enabled) {\n logger.info('[RemoteControl] Enabled \u2014 running requested action from', req.agentName)\n void runRemoteRequest(req)\n return\n }\n logger.info('[RemoteControl] Consent required \u2014 prompting for', req.agentName)\n actions.request(req)\n },\n grantAndRun(opts) {\n const pending = state().pending\n actions.grant(opts)\n if (pending) void runRemoteRequest(pending)\n },\n deny: () => actions.deny(),\n disable() {\n actions.setEnabled(false)\n cancelGuidedRun()\n },\n enable: () => actions.setEnabled(true),\n }\n}\n\n/** Remote control on the host's session and guided runs. */\nexport function useRemoteControlActions(): RemoteControl {\n const session = useArbiSessionStore()\n const guided = useGuidedRuns()\n return useMemo(() => createRemoteControl(session, guided), [session, guided])\n}\n\n/**\n * Drop remote control's grant, its pending request and the guided run in flight on every sign-in,\n * sign-out and session expiry of the host's `Arbi`, and whenever the signed-in user changes (a\n * host that signs in its own way), so nothing carries over to the next person in the tab.\n */\nexport function useRemoteControlSessionReset(): void {\n const session = useArbiSessionStore()\n const { arbi } = useArbiHost()\n const userId = useArbiSession().user?.external_id ?? null\n useEffect(() => {\n const reset = () => {\n remoteControlActions(session).reset()\n cancelGuidedRun()\n }\n if (!arbi) return\n // A host's stand-in `Arbi` (a demo, a test) may lack some of them.\n const off = [\n arbi.onSigningOut?.(reset),\n arbi.onSessionExpired?.(reset),\n arbi.onSignedIn?.(reset),\n ]\n return () => off.forEach((unsubscribe) => unsubscribe?.())\n }, [arbi, session])\n const lastUser = useRef(userId)\n useEffect(() => {\n if (lastUser.current === userId) return\n lastUser.current = userId\n remoteControlActions(session).reset()\n cancelGuidedRun()\n }, [userId, session])\n}\n\n/**\n * The live connection's handler for remote control (`ArbiWebSocketProvider` `handlers`): an\n * `agt-\u2026` agent asking to drive the UI goes to the consent-gated entry point. None while the\n * site has not turned remote control on (`guided.remoteControl`). It also drops the grant and\n * the run in flight at every sign-in and sign-out (`useRemoteControlSessionReset`).\n */\nexport function useRemoteControlHandlers(): ArbiWsHandlers {\n useRemoteControlSessionReset()\n const remote = useRemoteControlActions()\n const { remoteControl } = useGuidedRuns()\n return useMemo(\n () =>\n remoteControl\n ? {\n remote_control: (message) =>\n remote.handleMessage(message as unknown as RemoteControlMessage),\n }\n : {},\n [remote, remoteControl]\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Remote-control orchestration \u2014 the frontend side of \"a backend agent drives the UI over the\n * websocket\" (the message and the session state: `@arbidocs/react`'s remote-control module).\n *\n * This is the SEPARATE, gated entry point for websocket-arriving runs. It is deliberately distinct\n * from a site's `GuidedRuns.run` so that local \"Show me\" tips (which run directly) are NEVER gated\n * by consent \u2014 GUARANTEE #4. Only runs that arrive here, from the socket, pass through the gate.\n *\n * Gate logic (`handleMessage`):\n * - the site has not turned remote control on (`guided.remoteControl`), or the consent prompt or\n * the guided cursor is not on screen \u2192 refuse: nothing runs, nothing is asked.\n * - remote control `enabled` this session \u2192 run immediately.\n * - otherwise \u2192 stash the request in `pending` (the consent prompt opens); run NOTHING until the\n * user grants (GUARANTEE #2).\n *\n * What a remote command can run: a `sequence_id` the site registered (`GuidedRuns.isSequenceId`)\n * or inline `steps` whose every step has a target, through the one guided runner \u2014 nothing else\n * (no action dispatch). In a remote run the runner acts only on what the user can see, caps waits\n * and pauses at 5 s, and never acts inside an off-limits surface (remote control's own controls,\n * so a one-off grant cannot grant itself the session; credential and endpoint forms) nor on a\n * label whose control is inside one; no guided run types into a secret field.\n *\n * Every sign-in, sign-out, session expiry and change of user drops the grant, any pending request\n * and the run in flight (`useRemoteControlSessionReset`, mounted with the handler and the cursor),\n * so nothing carries over to the next person in the tab.\n *\n * While a run executes, `controllingAgent` is set (labels the pill) and cleared afterwards.\n * Turning remote control off (`disable`) cancels any in-flight run immediately (GUARANTEE #5).\n */\nimport { useEffect, useMemo, useRef } from 'react'\nimport { logger } from '@arbidocs/client'\nimport {\n remoteControlActions,\n remoteControlStateOf,\n toRemoteControlRequest,\n useArbiHost,\n useArbiSession,\n useArbiSessionStore,\n useArbiSessionState,\n type ArbiWsHandlers,\n type GuidedStep,\n type RemoteControlMessage,\n type RemoteControlRequest,\n type RemoteControlSession,\n} from '@arbidocs/react'\nimport { cancelGuidedRun } from '../../guided/runner'\nimport type { GuidedRuns } from '../../guided/sequences'\nimport { useGuidedRuns } from '../../guided/useGuidedRuns'\nimport { isSurfaceMounted } from '../../guided/surfaces'\nimport { createScreenSharing, type ScreenSharing } from './screen-sharing'\n\nexport interface RemoteControl {\n /** The websocket entry point, for a `remote_control` message. Gated (see the file header). */\n handleMessage(msg: RemoteControlMessage): void\n /**\n * Grant the pending consent and run it. `forSession: true` keeps remote control enabled so\n * later websocket runs go without re-prompting; `false` runs the one pending run and leaves it\n * disabled (\"just this once\").\n */\n grantAndRun(opts: { forSession: boolean }): void\n /** Deny the pending consent \u2014 clears it and runs nothing. */\n deny(): void\n /**\n * Turn remote control OFF and revoke immediately (GUARANTEE #5): clears the enabled flag, the\n * pending prompt and the pill, and cancels any in-flight run so the guided cursor stops at once.\n */\n disable(): void\n /** Turn remote control ON directly \u2014 the user's own explicit choice. Runs nothing by itself. */\n enable(): void\n}\n\nexport interface ScreenTransport {\n reply(\n requestId: string,\n result: {\n status: 'captured' | 'denied' | 'stopped' | 'unavailable' | 'action_finished'\n screenshot_png?: string\n diagnostics?: string\n }\n ): Promise<void>\n diagnose?(): Promise<string>\n}\nconst screenRuntimes = new WeakMap<\n RemoteControlSession,\n { sharing: ScreenSharing; generation: number; setup: string }\n>()\nfunction screenRuntime(session: RemoteControlSession) {\n let runtime = screenRuntimes.get(session)\n if (!runtime) {\n runtime = { sharing: createScreenSharing(), generation: 0, setup: '' }\n screenRuntimes.set(session, runtime)\n }\n return runtime\n}\nfunction stopScreen(session: RemoteControlSession) {\n const runtime = screenRuntimes.get(session)\n if (!runtime) return\n runtime.generation++\n runtime.sharing.stop()\n runtime.setup = ''\n const actions = remoteControlActions(session)\n if (remoteControlStateOf(session.getState()).sharing) actions.setSharing(false)\n}\n\n/** Remote control on a session's state, running the site's guided runs. */\nexport function createRemoteControl(\n session: RemoteControlSession,\n guided: Pick<GuidedRuns, 'isSequenceId' | 'run' | 'remoteControl'>,\n screenTransport?: ScreenTransport\n): RemoteControl {\n const actions = remoteControlActions(session)\n const state = () => remoteControlStateOf(session.getState())\n\n const runtime = screenRuntime(session)\n const reply = async (\n req: RemoteControlRequest,\n status: 'denied' | 'stopped' | 'unavailable' | 'action_finished'\n ) => {\n if ((req.screenRequestId || req.actionRequestId) && screenTransport) {\n await screenTransport\n .reply((req.screenRequestId || req.actionRequestId)!, { status })\n .catch(() => {})\n }\n }\n const disable = () => {\n const pending = state().pending\n if (pending) void reply(pending, 'stopped')\n stopScreen(session)\n actions.setEnabled(false)\n cancelGuidedRun()\n }\n const inspect = async (req: RemoteControlRequest, keepSharing: boolean) => {\n if (!req.screenRequestId || !screenTransport) return\n const workspace = session.getState().workspaceId\n if (workspace !== req.workspaceExtId) {\n await reply(req, 'unavailable')\n return\n }\n const token = runtime.generation\n const current = () =>\n token === runtime.generation && session.getState().workspaceId === workspace\n actions.setControllingAgent({ name: req.agentName, extId: req.agentExtId })\n try {\n if (!runtime.sharing.active) {\n // Called directly from the consent button: getDisplayMedia needs this user gesture.\n await runtime.sharing.start(disable)\n if (!current()) {\n runtime.sharing.stop()\n await reply(req, 'stopped')\n return\n }\n actions.setSharing(true)\n if (screenTransport.diagnose)\n runtime.setup = await screenTransport.diagnose().catch(() => 'Setup checks unavailable.')\n }\n if (!current()) {\n await reply(req, 'stopped')\n return\n }\n const frame = await runtime.sharing.frame()\n if (!current()) {\n await reply(req, 'stopped')\n return\n }\n await screenTransport.reply(req.screenRequestId, {\n status: 'captured',\n ...frame,\n diagnostics: `${frame.diagnostics}\\n${runtime.setup}`.slice(0, 24000),\n })\n } catch {\n await reply(req, current() ? 'unavailable' : 'stopped')\n if (current()) disable()\n } finally {\n if (current()) {\n actions.setControllingAgent(null)\n if (!keepSharing) disable()\n }\n }\n }\n\n /** Resolve a request to the concrete run input for the runner. */\n const resolveRun = (req: RemoteControlRequest): string | GuidedStep[] | null => {\n if (req.steps && req.steps.length > 0) return req.steps\n if (req.sequenceId && guided.isSequenceId(req.sequenceId)) return req.sequenceId\n return null\n }\n\n /** Execute a request, labelling the pill for its duration. The ONLY place a websocket-arriving\n * run reaches the runner. */\n const runRemoteRequest = async (\n req: RemoteControlRequest,\n keepSharing = state().enabled\n ): Promise<void> => {\n if (req.screenRequestId) {\n await inspect(req, keepSharing)\n return\n }\n const run = resolveRun(req)\n if (!run) return\n const token = runtime.generation\n actions.setControllingAgent({ name: req.agentName, extId: req.agentExtId })\n try {\n await guided.run(run, { remote: true })\n if (req.actionRequestId)\n await reply(req, token === runtime.generation ? 'action_finished' : 'stopped')\n } catch {\n await reply(req, 'unavailable')\n } finally {\n // Only clear the pill if THIS run is still the active one \u2014 a newer run or a disable will\n // have replaced/cleared it already.\n const current = state().controllingAgent\n if (current && current.name === req.agentName && current.extId === req.agentExtId) {\n actions.setControllingAgent(null)\n }\n }\n }\n\n return {\n handleMessage(msg) {\n if (!guided.remoteControl) {\n logger.warn('[RemoteControl] Off on this site \u2014 ignoring a request')\n return\n }\n if (!isSurfaceMounted('consent') || !isSurfaceMounted('cursor')) {\n logger.warn('[RemoteControl] No consent prompt or cursor on screen \u2014 ignoring a request')\n return\n }\n const req = toRemoteControlRequest(msg, guided.isSequenceId)\n if (!req) return\n if (\n (req.screenRequestId || req.actionRequestId) &&\n (!screenTransport || session.getState().workspaceId !== req.workspaceExtId)\n ) {\n void reply(req, 'unavailable')\n return\n }\n if (state().enabled && (!req.screenRequestId || runtime.sharing.active)) {\n logger.info('[RemoteControl] Enabled \u2014 running requested action from', req.agentName)\n void runRemoteRequest(req)\n return\n }\n logger.info('[RemoteControl] Consent required \u2014 prompting for', req.agentName)\n if (state().pending) void reply(state().pending!, 'denied')\n actions.request(req)\n },\n grantAndRun(opts) {\n const pending = state().pending\n const keepSharing = opts.forSession || state().enabled\n actions.grant(opts)\n if (pending) void runRemoteRequest(pending, keepSharing)\n },\n deny: () => {\n const pending = state().pending\n actions.deny()\n if (pending) void reply(pending, 'denied')\n },\n disable,\n enable: () => actions.setEnabled(true),\n }\n}\n\n/** Remote control on the host's session and guided runs. */\nexport function useRemoteControlActions(): RemoteControl {\n const session = useArbiSessionStore()\n const guided = useGuidedRuns()\n const { arbi } = useArbiHost()\n return useMemo(\n () =>\n createRemoteControl(\n session,\n guided,\n arbi\n ? {\n reply: (id, result) => arbi.remoteControl.replyScreen(id, result),\n diagnose: async () => JSON.stringify(await arbi.diagnose()),\n }\n : undefined\n ),\n [session, guided, arbi]\n )\n}\n\n/**\n * Drop remote control's grant, its pending request and the guided run in flight on every sign-in,\n * sign-out and session expiry of the host's `Arbi`, and whenever the signed-in user changes (a\n * host that signs in its own way), so nothing carries over to the next person in the tab.\n */\nexport function useRemoteControlSessionReset(): void {\n const session = useArbiSessionStore()\n const { arbi } = useArbiHost()\n const userId = useArbiSession().user?.external_id ?? null\n useEffect(() => {\n const reset = () => {\n stopScreen(session)\n remoteControlActions(session).reset()\n cancelGuidedRun()\n }\n if (!arbi) return\n // A host's stand-in `Arbi` (a demo, a test) may lack some of them.\n const off = [\n arbi.onSigningOut?.(reset),\n arbi.onSessionExpired?.(reset),\n arbi.onSignedIn?.(reset),\n ]\n return () => off.forEach((unsubscribe) => unsubscribe?.())\n }, [arbi, session])\n const lastUser = useRef(userId)\n useEffect(() => {\n if (lastUser.current === userId) return\n lastUser.current = userId\n stopScreen(session)\n remoteControlActions(session).reset()\n cancelGuidedRun()\n }, [userId, session])\n}\n\n/**\n * The live connection's handler for remote control (`ArbiWebSocketProvider` `handlers`): an\n * `agt-\u2026` agent asking to drive the UI goes to the consent-gated entry point. None while the\n * site has not turned remote control on (`guided.remoteControl`). It also drops the grant and\n * the run in flight at every sign-in and sign-out (`useRemoteControlSessionReset`).\n */\nexport function useRemoteControlHandlers(): ArbiWsHandlers {\n useRemoteControlSessionReset()\n const remote = useRemoteControlActions()\n const { remoteControl } = useGuidedRuns()\n const session = useArbiSessionStore()\n const workspaceId = useArbiSessionState((s) => s.workspaceId)\n // The handler owns the stream lifecycle. A route/auth host teardown always stops sharing.\n useEffect(\n () => () => {\n stopScreen(session)\n remoteControlActions(session).setEnabled(false)\n cancelGuidedRun()\n },\n [session, workspaceId]\n )\n return useMemo(\n () =>\n remoteControl\n ? {\n remote_control: (message) =>\n remote.handleMessage(message as unknown as RemoteControlMessage),\n }\n : {},\n [remote, remoteControl]\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,11 @@
1
+ /** A retained browser-authorized surface. Fresh frames are taken only when the agent requests one. */
2
+ export interface ScreenSharing {
3
+ readonly active: boolean;
4
+ start(onEnded: () => void): Promise<void>;
5
+ frame(): Promise<{
6
+ screenshot_png: string;
7
+ diagnostics: string;
8
+ }>;
9
+ stop(): void;
10
+ }
11
+ export declare function createScreenSharing(): ScreenSharing;
@@ -0,0 +1,78 @@
1
+ "use client";
2
+ import { grabFrame, ScreenCaptureError } from "../report-problem/screen-capture.js";
3
+ import { collectBrowserDiagnostics } from "./browser-diagnostics.js";
4
+ function createScreenSharing() {
5
+ let stream = null;
6
+ let diagnostics = null;
7
+ let generation = 0;
8
+ const stop = () => {
9
+ generation++;
10
+ const previous = stream;
11
+ stream = null;
12
+ previous?.getTracks().forEach((track) => track.stop());
13
+ diagnostics?.stop();
14
+ diagnostics = null;
15
+ };
16
+ return {
17
+ get active() {
18
+ return !!stream?.getVideoTracks().some((track) => track.readyState === "live");
19
+ },
20
+ async start(onEnded) {
21
+ const token = ++generation;
22
+ if (!navigator.mediaDevices?.getDisplayMedia)
23
+ throw new ScreenCaptureError(
24
+ "unsupported",
25
+ "Screen sharing is unavailable in this browser."
26
+ );
27
+ const next = await navigator.mediaDevices.getDisplayMedia({
28
+ video: { displaySurface: "browser" },
29
+ audio: false,
30
+ preferCurrentTab: true,
31
+ selfBrowserSurface: "include",
32
+ surfaceSwitching: "exclude"
33
+ });
34
+ if (token !== generation) {
35
+ next.getTracks().forEach((track) => track.stop());
36
+ throw new ScreenCaptureError("cancelled", "Sharing stopped.");
37
+ }
38
+ stream = next;
39
+ diagnostics = collectBrowserDiagnostics();
40
+ next.getVideoTracks().forEach(
41
+ (track) => track.addEventListener(
42
+ "ended",
43
+ () => {
44
+ if (stream === next) {
45
+ stop();
46
+ onEnded();
47
+ }
48
+ },
49
+ { once: true }
50
+ )
51
+ );
52
+ },
53
+ async frame() {
54
+ if (!stream) throw new ScreenCaptureError("cancelled", "Sharing stopped.");
55
+ const token = generation;
56
+ const blob = await grabFrame(stream);
57
+ if (token !== generation) throw new ScreenCaptureError("cancelled", "Sharing stopped.");
58
+ if (blob.size > 8 * 1024 * 1024)
59
+ throw new ScreenCaptureError(
60
+ "failed",
61
+ "The shared screen image is too large. Share a smaller window."
62
+ );
63
+ const screenshot_png = await new Promise((resolve, reject) => {
64
+ const reader = new FileReader();
65
+ reader.onload = () => resolve(String(reader.result).split(",")[1]);
66
+ reader.onerror = () => reject(new Error("Could not read the screen frame."));
67
+ reader.readAsDataURL(blob);
68
+ });
69
+ if (token !== generation) throw new ScreenCaptureError("cancelled", "Sharing stopped.");
70
+ return { screenshot_png, diagnostics: diagnostics?.snapshot() ?? "" };
71
+ },
72
+ stop
73
+ };
74
+ }
75
+ export {
76
+ createScreenSharing
77
+ };
78
+ //# sourceMappingURL=screen-sharing.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/connection/remote-control/screen-sharing.ts"],
4
+ "sourcesContent": ["import { grabFrame, ScreenCaptureError } from '../report-problem/screen-capture'\nimport { collectBrowserDiagnostics } from './browser-diagnostics'\n\n/** A retained browser-authorized surface. Fresh frames are taken only when the agent requests one. */\nexport interface ScreenSharing {\n readonly active: boolean\n start(onEnded: () => void): Promise<void>\n frame(): Promise<{ screenshot_png: string; diagnostics: string }>\n stop(): void\n}\n\nexport function createScreenSharing(): ScreenSharing {\n let stream: MediaStream | null = null\n let diagnostics: ReturnType<typeof collectBrowserDiagnostics> | null = null\n let generation = 0\n const stop = () => {\n generation++\n const previous = stream\n stream = null\n previous?.getTracks().forEach((track) => track.stop())\n diagnostics?.stop()\n diagnostics = null\n }\n return {\n get active() {\n return !!stream?.getVideoTracks().some((track) => track.readyState === 'live')\n },\n async start(onEnded) {\n const token = ++generation\n if (!navigator.mediaDevices?.getDisplayMedia)\n throw new ScreenCaptureError(\n 'unsupported',\n 'Screen sharing is unavailable in this browser.'\n )\n const next = await navigator.mediaDevices.getDisplayMedia({\n video: { displaySurface: 'browser' },\n audio: false,\n preferCurrentTab: true,\n selfBrowserSurface: 'include',\n surfaceSwitching: 'exclude',\n } as DisplayMediaStreamOptions)\n if (token !== generation) {\n next.getTracks().forEach((track) => track.stop())\n throw new ScreenCaptureError('cancelled', 'Sharing stopped.')\n }\n stream = next\n diagnostics = collectBrowserDiagnostics()\n next.getVideoTracks().forEach((track) =>\n track.addEventListener(\n 'ended',\n () => {\n if (stream === next) {\n stop()\n onEnded()\n }\n },\n { once: true }\n )\n )\n },\n async frame() {\n if (!stream) throw new ScreenCaptureError('cancelled', 'Sharing stopped.')\n const token = generation\n const blob = await grabFrame(stream)\n if (token !== generation) throw new ScreenCaptureError('cancelled', 'Sharing stopped.')\n if (blob.size > 8 * 1024 * 1024)\n throw new ScreenCaptureError(\n 'failed',\n 'The shared screen image is too large. Share a smaller window.'\n )\n const screenshot_png = await new Promise<string>((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = () => resolve(String(reader.result).split(',')[1])\n reader.onerror = () => reject(new Error('Could not read the screen frame.'))\n reader.readAsDataURL(blob)\n })\n if (token !== generation) throw new ScreenCaptureError('cancelled', 'Sharing stopped.')\n return { screenshot_png, diagnostics: diagnostics?.snapshot() ?? '' }\n },\n stop,\n }\n}\n"],
5
+ "mappings": ";AAAA,SAAS,WAAW,0BAA0B;AAC9C,SAAS,iCAAiC;AAUnC,SAAS,sBAAqC;AACnD,MAAI,SAA6B;AACjC,MAAI,cAAmE;AACvE,MAAI,aAAa;AACjB,QAAM,OAAO,MAAM;AACjB;AACA,UAAM,WAAW;AACjB,aAAS;AACT,cAAU,UAAU,EAAE,QAAQ,CAAC,UAAU,MAAM,KAAK,CAAC;AACrD,iBAAa,KAAK;AAClB,kBAAc;AAAA,EAChB;AACA,SAAO;AAAA,IACL,IAAI,SAAS;AACX,aAAO,CAAC,CAAC,QAAQ,eAAe,EAAE,KAAK,CAAC,UAAU,MAAM,eAAe,MAAM;AAAA,IAC/E;AAAA,IACA,MAAM,MAAM,SAAS;AACnB,YAAM,QAAQ,EAAE;AAChB,UAAI,CAAC,UAAU,cAAc;AAC3B,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QACF;AACF,YAAM,OAAO,MAAM,UAAU,aAAa,gBAAgB;AAAA,QACxD,OAAO,EAAE,gBAAgB,UAAU;AAAA,QACnC,OAAO;AAAA,QACP,kBAAkB;AAAA,QAClB,oBAAoB;AAAA,QACpB,kBAAkB;AAAA,MACpB,CAA8B;AAC9B,UAAI,UAAU,YAAY;AACxB,aAAK,UAAU,EAAE,QAAQ,CAAC,UAAU,MAAM,KAAK,CAAC;AAChD,cAAM,IAAI,mBAAmB,aAAa,kBAAkB;AAAA,MAC9D;AACA,eAAS;AACT,oBAAc,0BAA0B;AACxC,WAAK,eAAe,EAAE;AAAA,QAAQ,CAAC,UAC7B,MAAM;AAAA,UACJ;AAAA,UACA,MAAM;AACJ,gBAAI,WAAW,MAAM;AACnB,mBAAK;AACL,sBAAQ;AAAA,YACV;AAAA,UACF;AAAA,UACA,EAAE,MAAM,KAAK;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAAA,IACA,MAAM,QAAQ;AACZ,UAAI,CAAC,OAAQ,OAAM,IAAI,mBAAmB,aAAa,kBAAkB;AACzE,YAAM,QAAQ;AACd,YAAM,OAAO,MAAM,UAAU,MAAM;AACnC,UAAI,UAAU,WAAY,OAAM,IAAI,mBAAmB,aAAa,kBAAkB;AACtF,UAAI,KAAK,OAAO,IAAI,OAAO;AACzB,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QACF;AACF,YAAM,iBAAiB,MAAM,IAAI,QAAgB,CAAC,SAAS,WAAW;AACpE,cAAM,SAAS,IAAI,WAAW;AAC9B,eAAO,SAAS,MAAM,QAAQ,OAAO,OAAO,MAAM,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC;AACjE,eAAO,UAAU,MAAM,OAAO,IAAI,MAAM,kCAAkC,CAAC;AAC3E,eAAO,cAAc,IAAI;AAAA,MAC3B,CAAC;AACD,UAAI,UAAU,WAAY,OAAM,IAAI,mBAAmB,aAAa,kBAAkB;AACtF,aAAO,EAAE,gBAAgB,aAAa,aAAa,SAAS,KAAK,GAAG;AAAA,IACtE;AAAA,IACA;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -11,4 +11,5 @@ export declare class ScreenCaptureError extends Error {
11
11
  export declare function canCaptureScreen(): boolean;
12
12
  /** Capture the current tab as a PNG blob. Throws ScreenCaptureError. */
13
13
  export declare function captureCurrentTab(): Promise<Blob>;
14
+ export declare function grabFrame(stream: MediaStream): Promise<Blob>;
14
15
  export declare function canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob>;
@@ -68,6 +68,7 @@ export {
68
68
  ScreenCaptureError,
69
69
  canCaptureScreen,
70
70
  canvasToBlob,
71
- captureCurrentTab
71
+ captureCurrentTab,
72
+ grabFrame
72
73
  };
73
74
  //# sourceMappingURL=screen-capture.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/connection/report-problem/screen-capture.ts"],
4
- "sourcesContent": ["/**\n * Capture one still frame of the current tab with the browser's screen-capture API. It sees\n * exactly what is on screen, including PDFs, canvases and embedded viewers, at the cost of one\n * browser prompt (\"share this tab\").\n */\n\nexport type ScreenCaptureErrorCode = 'unsupported' | 'cancelled' | 'failed'\n\nexport class ScreenCaptureError extends Error {\n constructor(\n readonly code: ScreenCaptureErrorCode,\n message: string\n ) {\n super(message)\n this.name = 'ScreenCaptureError'\n }\n}\n\nexport function canCaptureScreen(): boolean {\n return typeof navigator !== 'undefined' && !!navigator.mediaDevices?.getDisplayMedia\n}\n\n/** Capture the current tab as a PNG blob. Throws ScreenCaptureError. */\nexport async function captureCurrentTab(): Promise<Blob> {\n if (!canCaptureScreen()) {\n throw new ScreenCaptureError('unsupported', \"This browser can't capture the screen.\")\n }\n let stream: MediaStream\n try {\n stream = await navigator.mediaDevices.getDisplayMedia({\n video: { displaySurface: 'browser' },\n audio: false,\n // Chromium hints: offer this tab first, and don't let the person switch surfaces mid-capture.\n preferCurrentTab: true,\n selfBrowserSurface: 'include',\n surfaceSwitching: 'exclude',\n } as DisplayMediaStreamOptions)\n } catch (error) {\n const name = error instanceof DOMException ? error.name : ''\n if (name === 'NotAllowedError' || name === 'AbortError') {\n throw new ScreenCaptureError('cancelled', 'Screen capture was cancelled.')\n }\n throw new ScreenCaptureError('failed', \"Couldn't start screen capture.\")\n }\n try {\n return await grabFrame(stream)\n } finally {\n stream.getTracks().forEach((track) => track.stop())\n }\n}\n\nasync function grabFrame(stream: MediaStream): Promise<Blob> {\n const video = document.createElement('video')\n video.muted = true\n video.playsInline = true\n video.srcObject = stream\n await video.play()\n // The first frames can be blank while the capture warms up: wait for real dimensions and one\n // painted frame.\n for (let i = 0; i < 50 && (!video.videoWidth || !video.videoHeight); i++) {\n await new Promise((resolve) => setTimeout(resolve, 40))\n }\n await new Promise((resolve) => requestAnimationFrame(() => resolve(null)))\n const canvas = document.createElement('canvas')\n canvas.width = video.videoWidth\n canvas.height = video.videoHeight\n const ctx = canvas.getContext('2d')\n if (!ctx || !canvas.width) throw new ScreenCaptureError('failed', \"Couldn't read the screen.\")\n ctx.drawImage(video, 0, 0)\n video.srcObject = null\n return canvasToBlob(canvas)\n}\n\nexport function canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {\n return new Promise((resolve, reject) =>\n canvas.toBlob(\n (blob) =>\n blob ? resolve(blob) : reject(new ScreenCaptureError('failed', \"Couldn't save the image.\")),\n 'image/png'\n )\n )\n}\n"],
5
- "mappings": ";AAQO,MAAM,2BAA2B,MAAM;AAAA,EAC5C,YACW,MACT,SACA;AACA,UAAM,OAAO;AAHJ;AAIT,SAAK,OAAO;AAAA,EACd;AAAA,EALW;AAMb;AAEO,SAAS,mBAA4B;AAC1C,SAAO,OAAO,cAAc,eAAe,CAAC,CAAC,UAAU,cAAc;AACvE;AAGA,eAAsB,oBAAmC;AACvD,MAAI,CAAC,iBAAiB,GAAG;AACvB,UAAM,IAAI,mBAAmB,eAAe,wCAAwC;AAAA,EACtF;AACA,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,UAAU,aAAa,gBAAgB;AAAA,MACpD,OAAO,EAAE,gBAAgB,UAAU;AAAA,MACnC,OAAO;AAAA;AAAA,MAEP,kBAAkB;AAAA,MAClB,oBAAoB;AAAA,MACpB,kBAAkB;AAAA,IACpB,CAA8B;AAAA,EAChC,SAAS,OAAO;AACd,UAAM,OAAO,iBAAiB,eAAe,MAAM,OAAO;AAC1D,QAAI,SAAS,qBAAqB,SAAS,cAAc;AACvD,YAAM,IAAI,mBAAmB,aAAa,+BAA+B;AAAA,IAC3E;AACA,UAAM,IAAI,mBAAmB,UAAU,gCAAgC;AAAA,EACzE;AACA,MAAI;AACF,WAAO,MAAM,UAAU,MAAM;AAAA,EAC/B,UAAE;AACA,WAAO,UAAU,EAAE,QAAQ,CAAC,UAAU,MAAM,KAAK,CAAC;AAAA,EACpD;AACF;AAEA,eAAe,UAAU,QAAoC;AAC3D,QAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,QAAM,QAAQ;AACd,QAAM,cAAc;AACpB,QAAM,YAAY;AAClB,QAAM,MAAM,KAAK;AAGjB,WAAS,IAAI,GAAG,IAAI,OAAO,CAAC,MAAM,cAAc,CAAC,MAAM,cAAc,KAAK;AACxE,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,EACxD;AACA,QAAM,IAAI,QAAQ,CAAC,YAAY,sBAAsB,MAAM,QAAQ,IAAI,CAAC,CAAC;AACzE,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,QAAQ,MAAM;AACrB,SAAO,SAAS,MAAM;AACtB,QAAM,MAAM,OAAO,WAAW,IAAI;AAClC,MAAI,CAAC,OAAO,CAAC,OAAO,MAAO,OAAM,IAAI,mBAAmB,UAAU,2BAA2B;AAC7F,MAAI,UAAU,OAAO,GAAG,CAAC;AACzB,QAAM,YAAY;AAClB,SAAO,aAAa,MAAM;AAC5B;AAEO,SAAS,aAAa,QAA0C;AACrE,SAAO,IAAI;AAAA,IAAQ,CAAC,SAAS,WAC3B,OAAO;AAAA,MACL,CAAC,SACC,OAAO,QAAQ,IAAI,IAAI,OAAO,IAAI,mBAAmB,UAAU,0BAA0B,CAAC;AAAA,MAC5F;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["/**\n * Capture one still frame of the current tab with the browser's screen-capture API. It sees\n * exactly what is on screen, including PDFs, canvases and embedded viewers, at the cost of one\n * browser prompt (\"share this tab\").\n */\n\nexport type ScreenCaptureErrorCode = 'unsupported' | 'cancelled' | 'failed'\n\nexport class ScreenCaptureError extends Error {\n constructor(\n readonly code: ScreenCaptureErrorCode,\n message: string\n ) {\n super(message)\n this.name = 'ScreenCaptureError'\n }\n}\n\nexport function canCaptureScreen(): boolean {\n return typeof navigator !== 'undefined' && !!navigator.mediaDevices?.getDisplayMedia\n}\n\n/** Capture the current tab as a PNG blob. Throws ScreenCaptureError. */\nexport async function captureCurrentTab(): Promise<Blob> {\n if (!canCaptureScreen()) {\n throw new ScreenCaptureError('unsupported', \"This browser can't capture the screen.\")\n }\n let stream: MediaStream\n try {\n stream = await navigator.mediaDevices.getDisplayMedia({\n video: { displaySurface: 'browser' },\n audio: false,\n // Chromium hints: offer this tab first, and don't let the person switch surfaces mid-capture.\n preferCurrentTab: true,\n selfBrowserSurface: 'include',\n surfaceSwitching: 'exclude',\n } as DisplayMediaStreamOptions)\n } catch (error) {\n const name = error instanceof DOMException ? error.name : ''\n if (name === 'NotAllowedError' || name === 'AbortError') {\n throw new ScreenCaptureError('cancelled', 'Screen capture was cancelled.')\n }\n throw new ScreenCaptureError('failed', \"Couldn't start screen capture.\")\n }\n try {\n return await grabFrame(stream)\n } finally {\n stream.getTracks().forEach((track) => track.stop())\n }\n}\n\nexport async function grabFrame(stream: MediaStream): Promise<Blob> {\n const video = document.createElement('video')\n video.muted = true\n video.playsInline = true\n video.srcObject = stream\n await video.play()\n // The first frames can be blank while the capture warms up: wait for real dimensions and one\n // painted frame.\n for (let i = 0; i < 50 && (!video.videoWidth || !video.videoHeight); i++) {\n await new Promise((resolve) => setTimeout(resolve, 40))\n }\n await new Promise((resolve) => requestAnimationFrame(() => resolve(null)))\n const canvas = document.createElement('canvas')\n canvas.width = video.videoWidth\n canvas.height = video.videoHeight\n const ctx = canvas.getContext('2d')\n if (!ctx || !canvas.width) throw new ScreenCaptureError('failed', \"Couldn't read the screen.\")\n ctx.drawImage(video, 0, 0)\n video.srcObject = null\n return canvasToBlob(canvas)\n}\n\nexport function canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {\n return new Promise((resolve, reject) =>\n canvas.toBlob(\n (blob) =>\n blob ? resolve(blob) : reject(new ScreenCaptureError('failed', \"Couldn't save the image.\")),\n 'image/png'\n )\n )\n}\n"],
5
+ "mappings": ";AAQO,MAAM,2BAA2B,MAAM;AAAA,EAC5C,YACW,MACT,SACA;AACA,UAAM,OAAO;AAHJ;AAIT,SAAK,OAAO;AAAA,EACd;AAAA,EALW;AAMb;AAEO,SAAS,mBAA4B;AAC1C,SAAO,OAAO,cAAc,eAAe,CAAC,CAAC,UAAU,cAAc;AACvE;AAGA,eAAsB,oBAAmC;AACvD,MAAI,CAAC,iBAAiB,GAAG;AACvB,UAAM,IAAI,mBAAmB,eAAe,wCAAwC;AAAA,EACtF;AACA,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,UAAU,aAAa,gBAAgB;AAAA,MACpD,OAAO,EAAE,gBAAgB,UAAU;AAAA,MACnC,OAAO;AAAA;AAAA,MAEP,kBAAkB;AAAA,MAClB,oBAAoB;AAAA,MACpB,kBAAkB;AAAA,IACpB,CAA8B;AAAA,EAChC,SAAS,OAAO;AACd,UAAM,OAAO,iBAAiB,eAAe,MAAM,OAAO;AAC1D,QAAI,SAAS,qBAAqB,SAAS,cAAc;AACvD,YAAM,IAAI,mBAAmB,aAAa,+BAA+B;AAAA,IAC3E;AACA,UAAM,IAAI,mBAAmB,UAAU,gCAAgC;AAAA,EACzE;AACA,MAAI;AACF,WAAO,MAAM,UAAU,MAAM;AAAA,EAC/B,UAAE;AACA,WAAO,UAAU,EAAE,QAAQ,CAAC,UAAU,MAAM,KAAK,CAAC;AAAA,EACpD;AACF;AAEA,eAAsB,UAAU,QAAoC;AAClE,QAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,QAAM,QAAQ;AACd,QAAM,cAAc;AACpB,QAAM,YAAY;AAClB,QAAM,MAAM,KAAK;AAGjB,WAAS,IAAI,GAAG,IAAI,OAAO,CAAC,MAAM,cAAc,CAAC,MAAM,cAAc,KAAK;AACxE,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,EACxD;AACA,QAAM,IAAI,QAAQ,CAAC,YAAY,sBAAsB,MAAM,QAAQ,IAAI,CAAC,CAAC;AACzE,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,QAAQ,MAAM;AACrB,SAAO,SAAS,MAAM;AACtB,QAAM,MAAM,OAAO,WAAW,IAAI;AAClC,MAAI,CAAC,OAAO,CAAC,OAAO,MAAO,OAAM,IAAI,mBAAmB,UAAU,2BAA2B;AAC7F,MAAI,UAAU,OAAO,GAAG,CAAC;AACzB,QAAM,YAAY;AAClB,SAAO,aAAa,MAAM;AAC5B;AAEO,SAAS,aAAa,QAA0C;AACrE,SAAO,IAAI;AAAA,IAAQ,CAAC,SAAS,WAC3B,OAAO;AAAA,MACL,CAAC,SACC,OAAO,QAAQ,IAAI,IAAI,OAAO,IAAI,mBAAmB,UAAU,0BAA0B,CAAC;AAAA,MAC5F;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,14 +1,3 @@
1
- /**
2
- * The Smart Search page, connected: search the open workspace's documents for passages
3
- * (`useSemanticSearch` over the shared search session, so results outlive the page), group them
4
- * by document or list them by relevance, cut them by a relevance threshold, open a passage in the
5
- * host's viewer (`BlocksEnvironment.openDocument`), and label every listed document.
6
- *
7
- * Runs under any host (`useConnectedWorkspace`). It searches what the Files filter lets through
8
- * (`useSearchScope`: the same filter state as the Files page; none, the whole workspace). A group's
9
- * document is its `ConnectedDocumentPill`. An empty workspace offers the upload surface of the
10
- * Files views, through the host's `openUpload`.
11
- */
12
1
  import { type SearchRowLabels } from './SearchResultRows';
13
2
  export interface ConnectedSearchPageLabels extends SearchRowLabels {
14
3
  tab: string;
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useSpotlight } from "../../presentation/spotlight.js";
3
4
  import {
4
5
  useCallback,
5
6
  useEffect,
@@ -163,6 +164,8 @@ function ConnectedSearchPage({ embedded = false, labels }) {
163
164
  (e, hit) => {
164
165
  e.preventDefault();
165
166
  e.stopPropagation();
167
+ if (e.currentTarget.dataset.clip === "true")
168
+ useSpotlight.getState().setClipping(true);
166
169
  openDocument(hit.docId, { page: hit.page, passageId: hit.chunkExtId });
167
170
  },
168
171
  [openDocument]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/connection/search/ConnectedSearchPage.tsx"],
4
- "sourcesContent": ["/**\n * The Smart Search page, connected: search the open workspace's documents for passages\n * (`useSemanticSearch` over the shared search session, so results outlive the page), group them\n * by document or list them by relevance, cut them by a relevance threshold, open a passage in the\n * host's viewer (`BlocksEnvironment.openDocument`), and label every listed document.\n *\n * Runs under any host (`useConnectedWorkspace`). It searches what the Files filter lets through\n * (`useSearchScope`: the same filter state as the Files page; none, the whole workspace). A group's\n * document is its `ConnectedDocumentPill`. An empty workspace offers the upload surface of the\n * Files views, through the host's `openUpload`.\n */\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type MouseEvent,\n} from 'react'\nimport { FileText, Search, Tag as List } from 'lucide-react'\nimport {\n MAX_RETRIEVE_DOC_IDS,\n useAssignDocTags,\n useCreateTag,\n useSemanticSearch,\n useUserSettings,\n type SearchChunk,\n} from '@arbidocs/react'\nimport { sharedSearchSession, takePendingSearch } from '@arbidocs/react/shell'\nimport { useIsOnline } from '@arbidocs/react/document-tools'\nimport { useBlocksEnvironment } from '../../provider/environment'\nimport { TabHeader, type Tab } from '../../shell/TabHeader'\nimport { SearchEmptyState } from '../../retrieval/SearchEmptyState'\nimport { SearchPagination } from '../../retrieval/SearchPagination'\nimport {\n ApplyLabelButton,\n ApplyLabelDialog,\n RelevanceThreshold,\n SearchBar,\n SearchControlsBar,\n SearchErrorBanner,\n SearchGroupCard,\n SearchNoResults,\n SearchPageBody,\n SearchPageFrame,\n SearchPageHeader,\n SearchPillToggle,\n SearchResultList,\n SearchResultsInner,\n SearchResultsMain,\n SearchToolbarRow,\n SearchTotalsHeadline,\n} from '../../retrieval/SearchPage'\nimport {\n extractHighlightTerms,\n getSearchMatchHint,\n groupSearchResults,\n isMeaningMatch,\n passesRelevanceCutoff,\n type SearchDocumentGroup,\n} from '../../retrieval/search-results'\nimport { useConnectedWorkspace } from '../ConnectionProvider'\nimport { ConnectedDocumentPill } from '../entities/ConnectedDocumentPill'\nimport { EmptyWorkspaceUpload } from '../files/upload/EmptyWorkspaceUpload'\nimport { useAddFromUrls } from '../files/upload/useAddFromUrls'\nimport { useConnectedDocuments } from '../workspaceData'\nimport { useSearchScope } from './useSearchScope'\nimport {\n DEFAULT_SEARCH_ROW_LABELS,\n FlatResultRow,\n GroupedResultRow,\n SearchGroupRowHeader,\n type SearchRowLabels,\n} from './SearchResultRows'\n\n/**\n * Hits on one page. The user always sees the true totals (\"X hits across Y documents\"); only\n * this many rows mount at a time.\n */\nconst PAGE_SIZE_FLAT = 50\nconst PAGE_SIZE_GROUPED = 20\n\ntype ViewMode = 'grouped' | 'flat'\n\nexport interface ConnectedSearchPageLabels extends SearchRowLabels {\n tab: string\n /** An example query rather than an instruction: quoted phrase plus free words. */\n placeholder: string\n byDoc: string\n byDocTitle: string\n byPassage: string\n byPassageTitle: string\n grouping: string\n offline: string\n tooManyDocuments: (count: number, max: number) => string\n /** The default name of the label made from a search. */\n labelName: (query: string) => string\n labelled: (count: number, name: string) => string\n labelFailed: string\n}\n\nexport const DEFAULT_CONNECTED_SEARCH_PAGE_LABELS: ConnectedSearchPageLabels = {\n ...DEFAULT_SEARCH_ROW_LABELS,\n tab: 'Smart Search',\n placeholder: 'e.g. \"payment terms\" invoice',\n byDoc: 'By Doc',\n byDocTitle: 'Group by document',\n byPassage: 'By Passage',\n byPassageTitle: 'All passages by relevance',\n grouping: 'Result grouping',\n offline: \"You're offline \u2014 reconnect to search.\",\n tooManyDocuments: (count, max) =>\n `You selected ${count.toLocaleString()} documents. Maximum for individual selection is ${max.toLocaleString()}. Narrow your filter, or clear it to search the whole workspace.`,\n // `Search(\u2026)` tells a saved search apart from a hand-made label at a glance.\n labelName: (query) => `Search(${query})`,\n labelled: (count, name) =>\n `Labeled ${count} ${count === 1 ? 'document' : 'documents'} as \"${name}\"`,\n labelFailed: 'Failed to save search',\n}\n\nexport interface ConnectedSearchPageProps {\n /**\n * Shown inside a host's Files view: no tab header of its own (the host has it and the filter),\n * and the grouping toggle on a slim row above the search box.\n */\n embedded?: boolean\n labels?: Partial<ConnectedSearchPageLabels>\n}\n\nconst readPending = () => sharedSearchSession.getState().pending\n\nexport function ConnectedSearchPage({ embedded = false, labels }: ConnectedSearchPageProps) {\n const l = useMemo(() => ({ ...DEFAULT_CONNECTED_SEARCH_PAGE_LABELS, ...labels }), [labels])\n const { notify, openDocument, openUpload } = useBlocksEnvironment()\n const { workspaceId, live } = useConnectedWorkspace()\n const { data: documents } = useConnectedDocuments()\n const { data: settings } = useUserSettings({ enabled: live })\n const isOnline = useIsOnline()\n\n const scope = useSearchScope(documents)\n const scopeDocIds = scope.docIds\n const scopeCount = scope.count\n const tooMany = !!scopeDocIds && scopeDocIds.length > MAX_RETRIEVE_DOC_IDS\n const tooManyMessage = tooMany ? l.tooManyDocuments(scopeCount, MAX_RETRIEVE_DOC_IDS) : undefined\n\n // An empty workspace's links: a zip is unpacked by the upload, the rest added here.\n const uploadUrls = useAddFromUrls({\n onZipFiles: (files) => openUpload?.(files, { source: 'computer' }),\n })\n\n const search = useSemanticSearch(scopeDocIds ?? [], {\n session: sharedSearchSession,\n workspaceId,\n model: settings?.last_config || 'default',\n })\n const { results, searched, status, error, query: submittedQuery } = search\n const isSearching = status === 'running'\n\n // The box opens on the last query, so a search is not retyped after a reload or a page switch.\n const [draft, setDraft] = useState(submittedQuery)\n const inputRef = useRef<HTMLTextAreaElement>(null)\n const [expanded, setExpanded] = useState<Set<string>>(new Set())\n const [cutoff, setCutoff] = useState(0)\n const [viewMode, setViewMode] = useState<ViewMode>('grouped')\n const [page, setPage] = useState(1)\n const [labelOpen, setLabelOpen] = useState(false)\n const [labelName, setLabelName] = useState('')\n const createTag = useCreateTag()\n const assignDocTags = useAssignDocTags()\n const [isLabelling, setIsLabelling] = useState(false)\n\n // Highlight quoted phrases and the query's significant words.\n const terms = useMemo(() => extractHighlightTerms(submittedQuery), [submittedQuery])\n\n // The threshold cuts what is shown; the headline's totals stay those of the whole result set.\n const shown = useMemo(\n () => results.filter((hit) => passesRelevanceCutoff(hit, cutoff, terms)),\n [results, cutoff, terms]\n )\n const meaningHits = useMemo(\n () => shown.filter((hit) => isMeaningMatch(hit.content, terms)).length,\n [shown, terms]\n )\n const { groups, docIds: listedDocIds } = useMemo(\n () => groupSearchResults(shown, terms),\n [shown, terms]\n )\n const totalDocuments = useMemo(() => new Set(results.map((hit) => hit.docId)).size, [results])\n const docsById = useMemo(\n () => new Map(documents.map((doc) => [doc.external_id, doc])),\n [documents]\n )\n\n // A new result set opens every group; the threshold, the view and paging keep the user's\n // collapsed groups.\n const lastResults = useRef<SearchChunk[] | null>(null)\n useEffect(() => {\n if (results === lastResults.current) return\n lastResults.current = results\n setExpanded(new Set(results.map((hit) => hit.docId)))\n }, [results])\n\n useEffect(() => setPage(1), [results, cutoff, viewMode])\n\n const pageData = useMemo(() => {\n const size = viewMode === 'flat' ? PAGE_SIZE_FLAT : PAGE_SIZE_GROUPED\n const total = viewMode === 'flat' ? shown.length : groups.length\n const totalPages = Math.max(1, Math.ceil(total / size))\n const start = (Math.min(page, totalPages) - 1) * size\n const end = Math.min(start + size, total)\n return {\n totalPages,\n start,\n flat: viewMode === 'flat' ? shown.slice(start, end) : [],\n grouped: viewMode === 'grouped' ? groups.slice(start, end) : ([] as SearchDocumentGroup[]),\n range: { start: total === 0 ? 0 : start + 1, end, total },\n }\n }, [viewMode, page, shown, groups])\n\n const toggleGroup = useCallback((docId: string) => {\n setExpanded((prev) => {\n const next = new Set(prev)\n if (next.has(docId)) next.delete(docId)\n else next.add(docId)\n return next\n })\n }, [])\n\n const openHit = useCallback(\n (e: MouseEvent, hit: SearchChunk) => {\n e.preventDefault()\n e.stopPropagation()\n openDocument(hit.docId, { page: hit.page, passageId: hit.chunkExtId })\n },\n [openDocument]\n )\n\n const runSearch = (text: string) => {\n const query = text.trim()\n if (!query || !workspaceId || tooMany) return\n // Search runs on the server: nothing to do offline.\n if (!isOnline) {\n notify(l.offline, 'warning')\n return\n }\n setDraft(text)\n // Every new search starts at Low, a step the threshold buttons show as selected.\n setCutoff(0)\n void search.search(query)\n }\n\n // A query handed over by another surface (the workspace home's ask bar) runs on arrival: the\n // user already pressed Search once. Taken once, so a failed search never retries itself.\n const pending = useSyncExternalStore(sharedSearchSession.subscribe, readPending, readPending)\n useEffect(() => {\n if (pending === null || !workspaceId) return\n const query = takePendingSearch(sharedSearchSession)\n if (query?.trim()) runSearch(query)\n // runSearch is redefined per render; the pending query is the one-shot trigger.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pending, workspaceId])\n\n // The box's clear empties everything and returns to the intro.\n const clear = () => {\n setDraft('')\n search.clear()\n setExpanded(new Set())\n setCutoff(0)\n setPage(1)\n if (inputRef.current) inputRef.current.style.height = 'auto'\n inputRef.current?.focus()\n }\n\n const openLabelDialog = () => {\n setLabelName(l.labelName(submittedQuery.trim()))\n setLabelOpen(true)\n }\n\n const applyLabel = async (name: string) => {\n const trimmed = name.trim()\n if (!workspaceId || listedDocIds.length === 0 || !trimmed) return\n setIsLabelling(true)\n try {\n // A private label; one request then tags every listed document in a single transaction.\n const tag = await createTag.mutateAsync({\n name: trimmed,\n tagType: { type: 'checkbox', options: [] },\n shared: false,\n })\n await assignDocTags.mutateAsync({ tagId: tag.external_id, docIds: listedDocIds })\n notify(l.labelled(listedDocIds.length, trimmed), 'success')\n setLabelOpen(false)\n } catch (err) {\n // The server's reason, so a failed save is something the user can act on.\n notify(err instanceof Error ? err.message : l.labelFailed, 'error')\n } finally {\n setIsLabelling(false)\n }\n }\n\n const viewOptions = useMemo(\n () => [\n {\n value: 'grouped',\n content: (\n <span className=\"flex items-center gap-1 px-0.5 text-xs\">\n <FileText className=\"size-3.5\" />\n {l.byDoc}\n </span>\n ),\n title: l.byDocTitle,\n },\n {\n value: 'flat',\n content: (\n <span className=\"flex items-center gap-1 px-0.5 text-xs\">\n <List className=\"size-3.5\" />\n {l.byPassage}\n </span>\n ),\n title: l.byPassageTitle,\n },\n ],\n [l]\n )\n const viewToggle = (\n <SearchPillToggle\n options={viewOptions}\n value={viewMode}\n onChange={(value) => setViewMode(value as ViewMode)}\n ariaLabel={l.grouping}\n />\n )\n\n const tabs: Tab[] = [\n {\n id: 'search',\n label: l.tab,\n icon: <Search className=\"size-4\" />,\n count: scopeCount,\n hasActiveFilters: !!scopeDocIds,\n actions: scope.filter,\n actionsHoverHandlers: scope.filterHoverHandlers,\n },\n ]\n\n const pagination = (\n <SearchPagination\n currentPage={Math.min(page, pageData.totalPages)}\n totalPages={pageData.totalPages}\n onPageChange={setPage}\n />\n )\n const hasResults = searched && results.length > 0\n\n return (\n <SearchPageFrame isEmbedded={embedded}>\n {!embedded && (\n <SearchPageHeader>\n <TabHeader tabs={tabs} activeTabId=\"search\" onTabChange={() => {}} variant=\"underline\" />\n </SearchPageHeader>\n )}\n\n <ApplyLabelDialog\n open={labelOpen}\n onOpenChange={setLabelOpen}\n name={labelName}\n onNameChange={setLabelName}\n onApply={applyLabel}\n isApplying={isLabelling}\n documentCount={listedDocIds.length}\n passageCount={shown.length}\n />\n\n <SearchPageBody>\n {/* Embedded: the grouping toggle sits on the folder line, visible before any search. */}\n {embedded && <SearchToolbarRow>{viewToggle}</SearchToolbarRow>}\n <SearchBar\n query={draft}\n onQueryChange={setDraft}\n onSubmit={() => runSearch(draft)}\n onClear={clear}\n canSubmit={!!draft.trim() && !!workspaceId && !isSearching && !tooMany}\n isSearching={isSearching}\n placeholder={l.placeholder}\n submitTitle={tooManyMessage}\n notice={tooManyMessage}\n inputRef={inputRef}\n isEmbedded={embedded}\n />\n\n {/* Pinned between the box and the scrolling results. */}\n {hasResults && (\n <SearchControlsBar\n start={\n <SearchTotalsHeadline\n shownHits={shown.length}\n totalHits={results.length}\n shownDocuments={groups.length}\n totalDocuments={totalDocuments}\n meaningHits={meaningHits}\n range={\n pageData.totalPages > 1\n ? { ...pageData.range, unit: viewMode === 'flat' ? 'passages' : 'documents' }\n : null\n }\n action={\n listedDocIds.length > 0 ? (\n <ApplyLabelButton onClick={openLabelDialog} />\n ) : undefined\n }\n />\n }\n end={\n <>\n <RelevanceThreshold value={cutoff} onChange={setCutoff} />\n {!embedded && viewToggle}\n </>\n }\n />\n )}\n\n {hasResults && (\n <p\n className=\"pb-3 pl-6 pr-4 text-xs text-muted-foreground\"\n data-testid=\"smart-search-match-hint\"\n >\n {getSearchMatchHint(submittedQuery)}\n </p>\n )}\n\n <SearchResultsMain>\n {searched || error ? (\n <SearchResultsInner>\n {error ? (\n <SearchErrorBanner message={error} />\n ) : shown.length > 0 ? (\n <>\n {/* The same pager above and below a long page. */}\n {pagination}\n {viewMode === 'flat' ? (\n <SearchResultList testId=\"smart-search-results-flat\">\n {pageData.flat.map((hit, i) => (\n <FlatResultRow\n key={hit.id}\n hit={hit}\n position={pageData.start + i}\n terms={terms}\n onOpen={openHit}\n labels={l}\n />\n ))}\n </SearchResultList>\n ) : (\n <SearchResultList testId=\"smart-search-results\">\n {pageData.grouped.map((group) => (\n <SearchGroupCard\n key={group.docId}\n testId={`search-result-group-${group.docId}`}\n isExpanded={expanded.has(group.docId)}\n header={\n <SearchGroupRowHeader\n group={group}\n source={\n <ConnectedDocumentPill\n docId={group.docId}\n doc={docsById.get(group.docId)}\n labelMaxWidth=\"max-w-none\"\n />\n }\n isExpanded={expanded.has(group.docId)}\n onToggle={toggleGroup}\n />\n }\n >\n {group.results.map((hit, i) => (\n <GroupedResultRow\n key={hit.id}\n hit={hit}\n testId={`search-result-${group.docId}-${i}`}\n terms={terms}\n onOpen={openHit}\n labels={l}\n />\n ))}\n </SearchGroupCard>\n ))}\n </SearchResultList>\n )}\n {pagination}\n </>\n ) : (\n <SearchNoResults\n hiddenHits={results.length}\n onLowerThreshold={() => setCutoff(0)}\n />\n )}\n </SearchResultsInner>\n ) : documents.length === 0 && openUpload ? (\n <EmptyWorkspaceUpload\n onFilesSelected={(files) => openUpload(files, { source: 'computer' })}\n onUrlsSubmit={uploadUrls}\n onImportFromEmail={() => openUpload(undefined, { source: 'email' })}\n />\n ) : (\n <SearchEmptyState count={scopeCount} />\n )}\n </SearchResultsMain>\n </SearchPageBody>\n </SearchPageFrame>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { useSpotlight } from '../../presentation/spotlight'\n/**\n * The Smart Search page, connected: search the open workspace's documents for passages\n * (`useSemanticSearch` over the shared search session, so results outlive the page), group them\n * by document or list them by relevance, cut them by a relevance threshold, open a passage in the\n * host's viewer (`BlocksEnvironment.openDocument`), and label every listed document.\n *\n * Runs under any host (`useConnectedWorkspace`). It searches what the Files filter lets through\n * (`useSearchScope`: the same filter state as the Files page; none, the whole workspace). A group's\n * document is its `ConnectedDocumentPill`. An empty workspace offers the upload surface of the\n * Files views, through the host's `openUpload`.\n */\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type MouseEvent,\n} from 'react'\nimport { FileText, Search, Tag as List } from 'lucide-react'\nimport {\n MAX_RETRIEVE_DOC_IDS,\n useAssignDocTags,\n useCreateTag,\n useSemanticSearch,\n useUserSettings,\n type SearchChunk,\n} from '@arbidocs/react'\nimport { sharedSearchSession, takePendingSearch } from '@arbidocs/react/shell'\nimport { useIsOnline } from '@arbidocs/react/document-tools'\nimport { useBlocksEnvironment } from '../../provider/environment'\nimport { TabHeader, type Tab } from '../../shell/TabHeader'\nimport { SearchEmptyState } from '../../retrieval/SearchEmptyState'\nimport { SearchPagination } from '../../retrieval/SearchPagination'\nimport {\n ApplyLabelButton,\n ApplyLabelDialog,\n RelevanceThreshold,\n SearchBar,\n SearchControlsBar,\n SearchErrorBanner,\n SearchGroupCard,\n SearchNoResults,\n SearchPageBody,\n SearchPageFrame,\n SearchPageHeader,\n SearchPillToggle,\n SearchResultList,\n SearchResultsInner,\n SearchResultsMain,\n SearchToolbarRow,\n SearchTotalsHeadline,\n} from '../../retrieval/SearchPage'\nimport {\n extractHighlightTerms,\n getSearchMatchHint,\n groupSearchResults,\n isMeaningMatch,\n passesRelevanceCutoff,\n type SearchDocumentGroup,\n} from '../../retrieval/search-results'\nimport { useConnectedWorkspace } from '../ConnectionProvider'\nimport { ConnectedDocumentPill } from '../entities/ConnectedDocumentPill'\nimport { EmptyWorkspaceUpload } from '../files/upload/EmptyWorkspaceUpload'\nimport { useAddFromUrls } from '../files/upload/useAddFromUrls'\nimport { useConnectedDocuments } from '../workspaceData'\nimport { useSearchScope } from './useSearchScope'\nimport {\n DEFAULT_SEARCH_ROW_LABELS,\n FlatResultRow,\n GroupedResultRow,\n SearchGroupRowHeader,\n type SearchRowLabels,\n} from './SearchResultRows'\n\n/**\n * Hits on one page. The user always sees the true totals (\"X hits across Y documents\"); only\n * this many rows mount at a time.\n */\nconst PAGE_SIZE_FLAT = 50\nconst PAGE_SIZE_GROUPED = 20\n\ntype ViewMode = 'grouped' | 'flat'\n\nexport interface ConnectedSearchPageLabels extends SearchRowLabels {\n tab: string\n /** An example query rather than an instruction: quoted phrase plus free words. */\n placeholder: string\n byDoc: string\n byDocTitle: string\n byPassage: string\n byPassageTitle: string\n grouping: string\n offline: string\n tooManyDocuments: (count: number, max: number) => string\n /** The default name of the label made from a search. */\n labelName: (query: string) => string\n labelled: (count: number, name: string) => string\n labelFailed: string\n}\n\nexport const DEFAULT_CONNECTED_SEARCH_PAGE_LABELS: ConnectedSearchPageLabels = {\n ...DEFAULT_SEARCH_ROW_LABELS,\n tab: 'Smart Search',\n placeholder: 'e.g. \"payment terms\" invoice',\n byDoc: 'By Doc',\n byDocTitle: 'Group by document',\n byPassage: 'By Passage',\n byPassageTitle: 'All passages by relevance',\n grouping: 'Result grouping',\n offline: \"You're offline \u2014 reconnect to search.\",\n tooManyDocuments: (count, max) =>\n `You selected ${count.toLocaleString()} documents. Maximum for individual selection is ${max.toLocaleString()}. Narrow your filter, or clear it to search the whole workspace.`,\n // `Search(\u2026)` tells a saved search apart from a hand-made label at a glance.\n labelName: (query) => `Search(${query})`,\n labelled: (count, name) =>\n `Labeled ${count} ${count === 1 ? 'document' : 'documents'} as \"${name}\"`,\n labelFailed: 'Failed to save search',\n}\n\nexport interface ConnectedSearchPageProps {\n /**\n * Shown inside a host's Files view: no tab header of its own (the host has it and the filter),\n * and the grouping toggle on a slim row above the search box.\n */\n embedded?: boolean\n labels?: Partial<ConnectedSearchPageLabels>\n}\n\nconst readPending = () => sharedSearchSession.getState().pending\n\nexport function ConnectedSearchPage({ embedded = false, labels }: ConnectedSearchPageProps) {\n const l = useMemo(() => ({ ...DEFAULT_CONNECTED_SEARCH_PAGE_LABELS, ...labels }), [labels])\n const { notify, openDocument, openUpload } = useBlocksEnvironment()\n const { workspaceId, live } = useConnectedWorkspace()\n const { data: documents } = useConnectedDocuments()\n const { data: settings } = useUserSettings({ enabled: live })\n const isOnline = useIsOnline()\n\n const scope = useSearchScope(documents)\n const scopeDocIds = scope.docIds\n const scopeCount = scope.count\n const tooMany = !!scopeDocIds && scopeDocIds.length > MAX_RETRIEVE_DOC_IDS\n const tooManyMessage = tooMany ? l.tooManyDocuments(scopeCount, MAX_RETRIEVE_DOC_IDS) : undefined\n\n // An empty workspace's links: a zip is unpacked by the upload, the rest added here.\n const uploadUrls = useAddFromUrls({\n onZipFiles: (files) => openUpload?.(files, { source: 'computer' }),\n })\n\n const search = useSemanticSearch(scopeDocIds ?? [], {\n session: sharedSearchSession,\n workspaceId,\n model: settings?.last_config || 'default',\n })\n const { results, searched, status, error, query: submittedQuery } = search\n const isSearching = status === 'running'\n\n // The box opens on the last query, so a search is not retyped after a reload or a page switch.\n const [draft, setDraft] = useState(submittedQuery)\n const inputRef = useRef<HTMLTextAreaElement>(null)\n const [expanded, setExpanded] = useState<Set<string>>(new Set())\n const [cutoff, setCutoff] = useState(0)\n const [viewMode, setViewMode] = useState<ViewMode>('grouped')\n const [page, setPage] = useState(1)\n const [labelOpen, setLabelOpen] = useState(false)\n const [labelName, setLabelName] = useState('')\n const createTag = useCreateTag()\n const assignDocTags = useAssignDocTags()\n const [isLabelling, setIsLabelling] = useState(false)\n\n // Highlight quoted phrases and the query's significant words.\n const terms = useMemo(() => extractHighlightTerms(submittedQuery), [submittedQuery])\n\n // The threshold cuts what is shown; the headline's totals stay those of the whole result set.\n const shown = useMemo(\n () => results.filter((hit) => passesRelevanceCutoff(hit, cutoff, terms)),\n [results, cutoff, terms]\n )\n const meaningHits = useMemo(\n () => shown.filter((hit) => isMeaningMatch(hit.content, terms)).length,\n [shown, terms]\n )\n const { groups, docIds: listedDocIds } = useMemo(\n () => groupSearchResults(shown, terms),\n [shown, terms]\n )\n const totalDocuments = useMemo(() => new Set(results.map((hit) => hit.docId)).size, [results])\n const docsById = useMemo(\n () => new Map(documents.map((doc) => [doc.external_id, doc])),\n [documents]\n )\n\n // A new result set opens every group; the threshold, the view and paging keep the user's\n // collapsed groups.\n const lastResults = useRef<SearchChunk[] | null>(null)\n useEffect(() => {\n if (results === lastResults.current) return\n lastResults.current = results\n setExpanded(new Set(results.map((hit) => hit.docId)))\n }, [results])\n\n useEffect(() => setPage(1), [results, cutoff, viewMode])\n\n const pageData = useMemo(() => {\n const size = viewMode === 'flat' ? PAGE_SIZE_FLAT : PAGE_SIZE_GROUPED\n const total = viewMode === 'flat' ? shown.length : groups.length\n const totalPages = Math.max(1, Math.ceil(total / size))\n const start = (Math.min(page, totalPages) - 1) * size\n const end = Math.min(start + size, total)\n return {\n totalPages,\n start,\n flat: viewMode === 'flat' ? shown.slice(start, end) : [],\n grouped: viewMode === 'grouped' ? groups.slice(start, end) : ([] as SearchDocumentGroup[]),\n range: { start: total === 0 ? 0 : start + 1, end, total },\n }\n }, [viewMode, page, shown, groups])\n\n const toggleGroup = useCallback((docId: string) => {\n setExpanded((prev) => {\n const next = new Set(prev)\n if (next.has(docId)) next.delete(docId)\n else next.add(docId)\n return next\n })\n }, [])\n\n const openHit = useCallback(\n (e: MouseEvent, hit: SearchChunk) => {\n e.preventDefault()\n e.stopPropagation()\n if ((e.currentTarget as HTMLElement).dataset.clip === 'true')\n useSpotlight.getState().setClipping(true)\n openDocument(hit.docId, { page: hit.page, passageId: hit.chunkExtId })\n },\n [openDocument]\n )\n\n const runSearch = (text: string) => {\n const query = text.trim()\n if (!query || !workspaceId || tooMany) return\n // Search runs on the server: nothing to do offline.\n if (!isOnline) {\n notify(l.offline, 'warning')\n return\n }\n setDraft(text)\n // Every new search starts at Low, a step the threshold buttons show as selected.\n setCutoff(0)\n void search.search(query)\n }\n\n // A query handed over by another surface (the workspace home's ask bar) runs on arrival: the\n // user already pressed Search once. Taken once, so a failed search never retries itself.\n const pending = useSyncExternalStore(sharedSearchSession.subscribe, readPending, readPending)\n useEffect(() => {\n if (pending === null || !workspaceId) return\n const query = takePendingSearch(sharedSearchSession)\n if (query?.trim()) runSearch(query)\n // runSearch is redefined per render; the pending query is the one-shot trigger.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pending, workspaceId])\n\n // The box's clear empties everything and returns to the intro.\n const clear = () => {\n setDraft('')\n search.clear()\n setExpanded(new Set())\n setCutoff(0)\n setPage(1)\n if (inputRef.current) inputRef.current.style.height = 'auto'\n inputRef.current?.focus()\n }\n\n const openLabelDialog = () => {\n setLabelName(l.labelName(submittedQuery.trim()))\n setLabelOpen(true)\n }\n\n const applyLabel = async (name: string) => {\n const trimmed = name.trim()\n if (!workspaceId || listedDocIds.length === 0 || !trimmed) return\n setIsLabelling(true)\n try {\n // A private label; one request then tags every listed document in a single transaction.\n const tag = await createTag.mutateAsync({\n name: trimmed,\n tagType: { type: 'checkbox', options: [] },\n shared: false,\n })\n await assignDocTags.mutateAsync({ tagId: tag.external_id, docIds: listedDocIds })\n notify(l.labelled(listedDocIds.length, trimmed), 'success')\n setLabelOpen(false)\n } catch (err) {\n // The server's reason, so a failed save is something the user can act on.\n notify(err instanceof Error ? err.message : l.labelFailed, 'error')\n } finally {\n setIsLabelling(false)\n }\n }\n\n const viewOptions = useMemo(\n () => [\n {\n value: 'grouped',\n content: (\n <span className=\"flex items-center gap-1 px-0.5 text-xs\">\n <FileText className=\"size-3.5\" />\n {l.byDoc}\n </span>\n ),\n title: l.byDocTitle,\n },\n {\n value: 'flat',\n content: (\n <span className=\"flex items-center gap-1 px-0.5 text-xs\">\n <List className=\"size-3.5\" />\n {l.byPassage}\n </span>\n ),\n title: l.byPassageTitle,\n },\n ],\n [l]\n )\n const viewToggle = (\n <SearchPillToggle\n options={viewOptions}\n value={viewMode}\n onChange={(value) => setViewMode(value as ViewMode)}\n ariaLabel={l.grouping}\n />\n )\n\n const tabs: Tab[] = [\n {\n id: 'search',\n label: l.tab,\n icon: <Search className=\"size-4\" />,\n count: scopeCount,\n hasActiveFilters: !!scopeDocIds,\n actions: scope.filter,\n actionsHoverHandlers: scope.filterHoverHandlers,\n },\n ]\n\n const pagination = (\n <SearchPagination\n currentPage={Math.min(page, pageData.totalPages)}\n totalPages={pageData.totalPages}\n onPageChange={setPage}\n />\n )\n const hasResults = searched && results.length > 0\n\n return (\n <SearchPageFrame isEmbedded={embedded}>\n {!embedded && (\n <SearchPageHeader>\n <TabHeader tabs={tabs} activeTabId=\"search\" onTabChange={() => {}} variant=\"underline\" />\n </SearchPageHeader>\n )}\n\n <ApplyLabelDialog\n open={labelOpen}\n onOpenChange={setLabelOpen}\n name={labelName}\n onNameChange={setLabelName}\n onApply={applyLabel}\n isApplying={isLabelling}\n documentCount={listedDocIds.length}\n passageCount={shown.length}\n />\n\n <SearchPageBody>\n {/* Embedded: the grouping toggle sits on the folder line, visible before any search. */}\n {embedded && <SearchToolbarRow>{viewToggle}</SearchToolbarRow>}\n <SearchBar\n query={draft}\n onQueryChange={setDraft}\n onSubmit={() => runSearch(draft)}\n onClear={clear}\n canSubmit={!!draft.trim() && !!workspaceId && !isSearching && !tooMany}\n isSearching={isSearching}\n placeholder={l.placeholder}\n submitTitle={tooManyMessage}\n notice={tooManyMessage}\n inputRef={inputRef}\n isEmbedded={embedded}\n />\n\n {/* Pinned between the box and the scrolling results. */}\n {hasResults && (\n <SearchControlsBar\n start={\n <SearchTotalsHeadline\n shownHits={shown.length}\n totalHits={results.length}\n shownDocuments={groups.length}\n totalDocuments={totalDocuments}\n meaningHits={meaningHits}\n range={\n pageData.totalPages > 1\n ? { ...pageData.range, unit: viewMode === 'flat' ? 'passages' : 'documents' }\n : null\n }\n action={\n listedDocIds.length > 0 ? (\n <ApplyLabelButton onClick={openLabelDialog} />\n ) : undefined\n }\n />\n }\n end={\n <>\n <RelevanceThreshold value={cutoff} onChange={setCutoff} />\n {!embedded && viewToggle}\n </>\n }\n />\n )}\n\n {hasResults && (\n <p\n className=\"pb-3 pl-6 pr-4 text-xs text-muted-foreground\"\n data-testid=\"smart-search-match-hint\"\n >\n {getSearchMatchHint(submittedQuery)}\n </p>\n )}\n\n <SearchResultsMain>\n {searched || error ? (\n <SearchResultsInner>\n {error ? (\n <SearchErrorBanner message={error} />\n ) : shown.length > 0 ? (\n <>\n {/* The same pager above and below a long page. */}\n {pagination}\n {viewMode === 'flat' ? (\n <SearchResultList testId=\"smart-search-results-flat\">\n {pageData.flat.map((hit, i) => (\n <FlatResultRow\n key={hit.id}\n hit={hit}\n position={pageData.start + i}\n terms={terms}\n onOpen={openHit}\n labels={l}\n />\n ))}\n </SearchResultList>\n ) : (\n <SearchResultList testId=\"smart-search-results\">\n {pageData.grouped.map((group) => (\n <SearchGroupCard\n key={group.docId}\n testId={`search-result-group-${group.docId}`}\n isExpanded={expanded.has(group.docId)}\n header={\n <SearchGroupRowHeader\n group={group}\n source={\n <ConnectedDocumentPill\n docId={group.docId}\n doc={docsById.get(group.docId)}\n labelMaxWidth=\"max-w-none\"\n />\n }\n isExpanded={expanded.has(group.docId)}\n onToggle={toggleGroup}\n />\n }\n >\n {group.results.map((hit, i) => (\n <GroupedResultRow\n key={hit.id}\n hit={hit}\n testId={`search-result-${group.docId}-${i}`}\n terms={terms}\n onOpen={openHit}\n labels={l}\n />\n ))}\n </SearchGroupCard>\n ))}\n </SearchResultList>\n )}\n {pagination}\n </>\n ) : (\n <SearchNoResults\n hiddenHits={results.length}\n onLowerThreshold={() => setCutoff(0)}\n />\n )}\n </SearchResultsInner>\n ) : documents.length === 0 && openUpload ? (\n <EmptyWorkspaceUpload\n onFilesSelected={(files) => openUpload(files, { source: 'computer' })}\n onUrlsSubmit={uploadUrls}\n onImportFromEmail={() => openUpload(undefined, { source: 'email' })}\n />\n ) : (\n <SearchEmptyState count={scopeCount} />\n )}\n </SearchResultsMain>\n </SearchPageBody>\n </SearchPageFrame>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }