@xopcai/xopc 0.0.274 → 0.0.276

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 (137) hide show
  1. package/dist/browser-ext/_locales/en/messages.json +215 -0
  2. package/dist/browser-ext/_locales/zh_CN/messages.json +215 -0
  3. package/dist/browser-ext/dist/assets/{auth-O8RiOF3l.js → auth-ClQTK565.js} +97 -38
  4. package/dist/browser-ext/dist/assets/{sidepanel-C86O7q5Y.css → sidepanel-CeGqObOa.css} +41 -20
  5. package/dist/browser-ext/dist/background.js +36 -4
  6. package/dist/browser-ext/dist/sidepanel.html +2 -2
  7. package/dist/browser-ext/dist/sidepanel.js +416 -299
  8. package/dist/browser-ext/manifest.json +6 -4
  9. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  10. package/dist/extensions/telegram/xopc.extension.json +1 -1
  11. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BckaANxj.js → agent-avatar-dicebear-sj_1IC5H.js} +1 -1
  12. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-B8eyC6K1.js → agent-defaults-settings-panel-B5MRLEy8.js} +1 -1
  13. package/dist/gateway/static/root/assets/{agents-CSWj_yo1.js → agents-CIzev0Qj.js} +1 -1
  14. package/dist/gateway/static/root/assets/{app-management-settings-panel-DY-Zbgmt.js → app-management-settings-panel-DY3Vwxjq.js} +1 -1
  15. package/dist/gateway/static/root/assets/{appearance-settings-CoJR7ZRl.js → appearance-settings-BeXvF-dZ.js} +1 -1
  16. package/dist/gateway/static/root/assets/apps-page-Ch_9Ffgk.js +1 -0
  17. package/dist/gateway/static/root/assets/{archive-plugin-BwAlRT53.js → archive-plugin-BSgaIIGE.js} +1 -1
  18. package/dist/gateway/static/root/assets/{attachment-load-R835uxgL.js → attachment-load-CGNt02hP.js} +1 -1
  19. package/dist/gateway/static/root/assets/{automations-page-BEZy0P_u.js → automations-page-DoafJiVA.js} +1 -1
  20. package/dist/gateway/static/root/assets/automations-workspace-MACr99IO.js +6 -0
  21. package/dist/gateway/static/root/assets/{binary-plugins-CESFx3yb.js → binary-plugins-BsAL26Aq.js} +1 -1
  22. package/dist/gateway/static/root/assets/{block-editor-DDJcf2wR.js → block-editor-BxB7m8aI.js} +3 -3
  23. package/dist/gateway/static/root/assets/{browser-automations-page-BH4lSfr5.js → browser-automations-page-BNsO-Ip2.js} +2 -2
  24. package/dist/gateway/static/root/assets/browser-settings-page-Nvdoi0B1.js +8 -0
  25. package/dist/gateway/static/root/assets/capabilities-settings-panel-BluLJq5I.js +8 -0
  26. package/dist/gateway/static/root/assets/channels-settings-DYxxhcjb.js +1 -0
  27. package/dist/gateway/static/root/assets/{chat-terminal-dock-D8U97O95.js → chat-terminal-dock-ZCFwco1W.js} +2 -2
  28. package/dist/gateway/static/root/assets/connectors-page-Bpcp8UyE.js +2 -0
  29. package/dist/gateway/static/root/assets/{date-picker-7sDSMswL.js → date-picker-CpxFfgpg.js} +1 -1
  30. package/dist/gateway/static/root/assets/{dependency-picker-DMIC86ZW.js → dependency-picker-eCIGAfHa.js} +1 -1
  31. package/dist/gateway/static/root/assets/{desktop-pet-BtdiY5jK.js → desktop-pet-_MSZvIDO.js} +1 -1
  32. package/dist/gateway/static/root/assets/{desktop-pet-settings-D_B8azDH.js → desktop-pet-settings-feBnJWkV.js} +1 -1
  33. package/dist/gateway/static/root/assets/device-pairing-wizard-D_fB0fml.js +1 -0
  34. package/dist/gateway/static/root/assets/{extension-debug-page-CoFKb01d.js → extension-debug-page-CotBlxJ-.js} +1 -1
  35. package/dist/gateway/static/root/assets/{extension-page-CEkNsCAY.js → extension-page-CFBmQ7U3.js} +1 -1
  36. package/dist/gateway/static/root/assets/{extension-settings-page-Bi2R8P5j.js → extension-settings-page-Cd1lfh8z.js} +1 -1
  37. package/dist/gateway/static/root/assets/gateway-settings-BH35C8RI.js +1 -0
  38. package/dist/gateway/static/root/assets/{heartbeat-settings-DQtelFiU.js → heartbeat-settings-DRG0u1ON.js} +1 -1
  39. package/dist/gateway/static/root/assets/home-page-BlZk-aD4.js +1 -0
  40. package/dist/gateway/static/root/assets/{index-SxqsTD4i.js → index-CfQor-TM.js} +34 -34
  41. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BuYmZS1K.js → keyboard-shortcuts-settings-BLHsdFIQ.js} +1 -1
  42. package/dist/gateway/static/root/assets/local-app-workbench-page-DJd71FlC.js +2 -0
  43. package/dist/gateway/static/root/assets/{local-apps-page-CExnZyGZ.js → local-apps-page-BjAj1xhq.js} +1 -1
  44. package/dist/gateway/static/root/assets/{logs-page-CA5ArMoN.js → logs-page-5MWeZ6Ae.js} +2 -2
  45. package/dist/gateway/static/root/assets/{management-settings-1UiJPrkX.js → management-settings-CPHxDaOw.js} +1 -1
  46. package/dist/gateway/static/root/assets/{media-plugins-nNFOk9HL.js → media-plugins-BUOzcXCL.js} +1 -1
  47. package/dist/gateway/static/root/assets/note-detail-panel-Bch98ut3.js +5 -0
  48. package/dist/gateway/static/root/assets/notes-page-CWO5dQKN.js +8 -0
  49. package/dist/gateway/static/root/assets/{page-tabs-CRombigq.js → page-tabs-BgEYFhqS.js} +1 -1
  50. package/dist/gateway/static/root/assets/{preview-open-alternatives-Bcuj43lk.js → preview-open-alternatives-C-pQIx6W.js} +1 -1
  51. package/dist/gateway/static/root/assets/{proactive-card-Cb3Xeoyx.js → proactive-card-DfjMVJUW.js} +1 -1
  52. package/dist/gateway/static/root/assets/{proactive-page-TbF9KVpm.js → proactive-page-BGVcVsbd.js} +1 -1
  53. package/dist/gateway/static/root/assets/project-detail-page-DZFVirjV.js +1 -0
  54. package/dist/gateway/static/root/assets/projects-page-B2g6inq-.js +1 -0
  55. package/dist/gateway/static/root/assets/remote-access-hub-CbkUOkQY.js +1 -0
  56. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-BudlvBu-.js → runtime-tools-settings-panel-DkAjFJ-9.js} +2 -2
  57. package/dist/gateway/static/root/assets/{schema-form-Ch3MEX4h.js → schema-form-BpDcuSsl.js} +1 -1
  58. package/dist/gateway/static/root/assets/sessions-page--7-w-0WS.js +1 -0
  59. package/dist/gateway/static/root/assets/settings-advanced-gate-BvZrH_sp.js +1 -0
  60. package/dist/gateway/static/root/assets/{settings-form-section-CIA1AA-A.js → settings-form-section-RElRijJw.js} +1 -1
  61. package/dist/gateway/static/root/assets/{settings-page-B0Khoduh.js → settings-page-CaixunoA.js} +1 -1
  62. package/dist/gateway/static/root/assets/setup-status-panel-Ul06Y5YX.js +1 -0
  63. package/dist/gateway/static/root/assets/{share-preview-page-Cj9XWg-X.js → share-preview-page-CC-bFOTf.js} +1 -1
  64. package/dist/gateway/static/root/assets/shares-settings-DwV2Wh7S.js +1 -0
  65. package/dist/gateway/static/root/assets/{skill-reload-api-BM4Ezjbx.js → skill-reload-api-CcOtxf53.js} +1 -1
  66. package/dist/gateway/static/root/assets/{skills-page-Dyh1kf9t.js → skills-page-DmRvRxtY.js} +1 -1
  67. package/dist/gateway/static/root/assets/{skills-page.utils-DsQZkMti.js → skills-page.utils-CT0-daZD.js} +1 -1
  68. package/dist/gateway/static/root/assets/{system-settings-panel-BSho_Tpk.js → system-settings-panel-DRIJ5mXq.js} +1 -1
  69. package/dist/gateway/static/root/assets/task-detail-page-BQjLHqSk.js +1 -0
  70. package/dist/gateway/static/root/assets/{tunnel-api-4DUHxSQG.js → tunnel-api-IvWcgZdD.js} +1 -1
  71. package/dist/gateway/static/root/assets/{use-autosave-BG8Yx7hj.js → use-autosave-Cq4--C1i.js} +1 -1
  72. package/dist/gateway/static/root/assets/user-model-page-hwc5-nqV.js +2 -0
  73. package/dist/gateway/static/root/assets/{voice-api-key-field-DYXwDhHq.js → voice-api-key-field-CbJ6tKGI.js} +1 -1
  74. package/dist/gateway/static/root/assets/{work-discovery-overlay-CQqjzaRo.js → work-discovery-overlay-BG2pmXL3.js} +1 -1
  75. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-C5H-Fd-U.js → workflow-run-setup-panel-vw1229Fh.js} +2 -2
  76. package/dist/gateway/static/root/assets/workflows-page-DZWpJHDr.js +6 -0
  77. package/dist/gateway/static/root/index.html +1 -1
  78. package/dist/package.js +1 -1
  79. package/dist/src/agent/child-agent-factory.d.ts +3 -0
  80. package/dist/src/agent/child-agent-factory.js +2 -1
  81. package/dist/src/agent/external-tools/endpoint-provider.js +1 -1
  82. package/dist/src/agent/tools/factory.d.ts +1 -0
  83. package/dist/src/agent/tools/factory.js +21 -4
  84. package/dist/src/agent/workflow/workflow-child-tools.js +2 -0
  85. package/dist/src/browser/providers/browser-ext-install.d.ts +1 -1
  86. package/dist/src/browser/providers/browser-ext-install.js +86 -14
  87. package/dist/src/browser/providers/realtime-extension.d.ts +4 -1
  88. package/dist/src/browser/providers/realtime-extension.js +26 -10
  89. package/dist/src/browser/readiness.d.ts +4 -1
  90. package/dist/src/browser/readiness.js +11 -5
  91. package/dist/src/cli/commands/channels.js +1 -1
  92. package/dist/src/cli/commands/connectors.js +1 -1
  93. package/dist/src/cli/commands/extension-marketplace.js +2 -2
  94. package/dist/src/cli/commands/models.js +1 -1
  95. package/dist/src/cli/commands/onboard.js +1 -1
  96. package/dist/src/extensions/sdk/index.js +1 -1
  97. package/dist/src/extensions/update.js +1 -1
  98. package/dist/src/gateway/gateway-workflow-host.types.d.ts +2 -0
  99. package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
  100. package/dist/src/gateway/hono/routes/browser.js +24 -11
  101. package/dist/src/gateway/hono/routes/endpoint-tools.js +9 -0
  102. package/dist/src/gateway/hono/routes/models.js +6 -0
  103. package/dist/src/gateway/service/config-coordinator.js +1 -1
  104. package/dist/src/gateway/service.js +3 -3
  105. package/dist/src/infra/update-runner.js +1 -1
  106. package/dist/src/providers/model-catalog-persistence.js +4 -0
  107. package/dist/src/providers/model-catalog-types.d.ts +1 -0
  108. package/dist/src/providers/xopc-cloud-model-source.js +12 -1
  109. package/dist/src/storage/sqlite/endpoint-session-binding-repository.d.ts +1 -0
  110. package/dist/src/storage/sqlite/endpoint-session-binding-repository.js +9 -1
  111. package/dist/src/storage/sqlite/index.d.ts +1 -1
  112. package/dist/src/storage/sqlite/index.js +2 -2
  113. package/dist/src/tui/backends/embedded-backend.js +1 -1
  114. package/dist/src/workflows/service/workflow-run-service.js +4 -1
  115. package/package.json +4 -4
  116. package/dist/gateway/static/root/assets/apps-page-CSMvkuot.js +0 -1
  117. package/dist/gateway/static/root/assets/automations-workspace-CpaI13J7.js +0 -6
  118. package/dist/gateway/static/root/assets/browser-settings-page-CbTooPd2.js +0 -8
  119. package/dist/gateway/static/root/assets/capabilities-settings-panel-C4LZWHhv.js +0 -8
  120. package/dist/gateway/static/root/assets/channels-settings-BAljmSa7.js +0 -1
  121. package/dist/gateway/static/root/assets/connectors-page-CVRMjNNr.js +0 -2
  122. package/dist/gateway/static/root/assets/device-pairing-wizard-Dmh3Nh4d.js +0 -1
  123. package/dist/gateway/static/root/assets/gateway-settings-q9p3sduo.js +0 -1
  124. package/dist/gateway/static/root/assets/home-page-COpGnTFZ.js +0 -1
  125. package/dist/gateway/static/root/assets/local-app-workbench-page-DV_17Zxc.js +0 -2
  126. package/dist/gateway/static/root/assets/note-detail-panel-B5OQ0fBY.js +0 -5
  127. package/dist/gateway/static/root/assets/notes-page-CVPPMVDl.js +0 -8
  128. package/dist/gateway/static/root/assets/project-detail-page-B9OQ2l9x.js +0 -1
  129. package/dist/gateway/static/root/assets/projects-page-DCqSTNkY.js +0 -1
  130. package/dist/gateway/static/root/assets/remote-access-hub-d9Fg70gX.js +0 -1
  131. package/dist/gateway/static/root/assets/sessions-page-D8RiQ-ag.js +0 -1
  132. package/dist/gateway/static/root/assets/settings-advanced-gate-DVH_l6Hr.js +0 -1
  133. package/dist/gateway/static/root/assets/setup-status-panel-COJFl4N_.js +0 -1
  134. package/dist/gateway/static/root/assets/shares-settings-_OuE6A0G.js +0 -1
  135. package/dist/gateway/static/root/assets/task-detail-page-B5HWM12k.js +0 -1
  136. package/dist/gateway/static/root/assets/user-model-page-IjVyv5jj.js +0 -2
  137. package/dist/gateway/static/root/assets/workflows-page-D1hMyPsY.js +0 -6
@@ -1,4 +1,4 @@
1
- import { R as RealtimeClient, j as gatewayFetch, a as activeTabId, r as readBrowserOutbox, w as writeBrowserOutbox, g as deleteBrowserOutbox, f as currentTabDescriptor, T as TAB_BINDING_PREFIX, k as getAccessProfile, q as runWithTabSiteAccess, P as PENDING_CONTEXT_KEY, d as captureTabWithPermission, p as pairGateway, s as setAutoConnectEnabled, l as readPendingBrowserPairing, m as readProfile, h as discoverLocalGateway, c as cancelPendingBrowserPairing, o as revokeAndForgetProfile } from './assets/auth-O8RiOF3l.js';
1
+ import { R as RealtimeClient, k as gatewayFetch, a as activeTabId, w as t, r as readBrowserOutbox, x as writeBrowserOutbox, g as deleteBrowserOutbox, f as currentTabDescriptor, T as TAB_BINDING_PREFIX, l as getAccessProfile, t as runWithTabSiteAccess, P as PENDING_CONTEXT_KEY, j as extensionLocale, d as captureTabWithPermission, p as pairGateway, v as setAutoConnectEnabled, n as readPendingBrowserPairing, o as readProfile, h as discoverLocalGateway, u as saveExtensionLocalePreference, s as revokeAndForgetProfile, c as cancelPendingBrowserPairing, m as loadExtensionLocalePreference } from './assets/auth-ClQTK565.js';
2
2
 
3
3
  true &&(function polyfill() {
4
4
  const relList = document.createElement("link").relList;
@@ -17364,14 +17364,14 @@ async function json(response) {
17364
17364
  body = raw ? JSON.parse(raw) : {};
17365
17365
  } catch {
17366
17366
  throw new GatewayRequestError(
17367
- response.ok ? "Gateway returned an invalid response" : `Gateway returned ${response.status}`,
17367
+ response.ok ? t("errorInvalidGatewayResponse") : t("errorGatewayStatus", String(response.status)),
17368
17368
  response.status
17369
17369
  );
17370
17370
  }
17371
17371
  if (!response.ok) {
17372
17372
  const error = body.error;
17373
17373
  throw new GatewayRequestError(
17374
- typeof error === "string" ? error : error?.message ?? `Gateway returned ${response.status}`,
17374
+ typeof error === "string" ? error : error?.message ?? t("errorGatewayStatus", String(response.status)),
17375
17375
  response.status
17376
17376
  );
17377
17377
  }
@@ -17398,7 +17398,7 @@ function mapMessage(value, index) {
17398
17398
  if (!value2 || typeof value2 !== "object") return [];
17399
17399
  const media = value2;
17400
17400
  const mimeType = typeof media.mimeType === "string" ? media.mimeType : void 0;
17401
- const name = typeof media.name === "string" && media.name.trim() ? media.name.trim() : "Attachment";
17401
+ const name = typeof media.name === "string" && media.name.trim() ? media.name.trim() : t("attachment");
17402
17402
  return [{
17403
17403
  type: media.type === "image" || media.type === "photo" || mimeType?.startsWith("image/") ? "image" : "file",
17404
17404
  name,
@@ -17459,12 +17459,14 @@ class BrowserChatClient {
17459
17459
  recoveringOutbox = false;
17460
17460
  lastOutboxRecoveryAt = 0;
17461
17461
  sessionsRequest = 0;
17462
+ endpointBinding;
17463
+ endpointBindingTask;
17462
17464
  endpointPoll;
17463
17465
  onRuntimeMessage = (message) => {
17464
17466
  if (message.type !== "browser/endpoint-ready" || !message.claim) return;
17465
17467
  this.turnClaim = message.claim;
17466
17468
  this.update({ endpointReady: true, error: void 0 });
17467
- void this.recoverOutbox();
17469
+ void this.bindSessionEndpoint().then(() => this.recoverOutbox()).catch((cause) => this.update({ error: cause instanceof Error ? cause.message : String(cause) }));
17468
17470
  };
17469
17471
  snapshot = {
17470
17472
  connection: "idle",
@@ -17557,6 +17559,8 @@ class BrowserChatClient {
17557
17559
  this.realtime?.disconnect();
17558
17560
  this.realtime = void 0;
17559
17561
  this.turnClaim = void 0;
17562
+ this.endpointBinding = void 0;
17563
+ this.endpointBindingTask = void 0;
17560
17564
  this.update({ endpointReady: false });
17561
17565
  }
17562
17566
  async reconnect() {
@@ -17567,18 +17571,55 @@ class BrowserChatClient {
17567
17571
  ok: false,
17568
17572
  error: cause instanceof Error ? cause.message : String(cause)
17569
17573
  }));
17570
- if (response?.ok === false) throw new Error(response.error ?? "Could not reconnect to the browser endpoint");
17574
+ if (response?.ok === false) throw new Error(response.error ?? t("errorReconnectEndpoint"));
17571
17575
  await this.refreshEndpointClaim();
17572
17576
  }
17573
17577
  async refreshEndpointClaim() {
17578
+ const previousEndpointId = this.turnClaim?.endpointId;
17574
17579
  const response = await chrome.runtime.sendMessage({ type: "browser/get-endpoint-claim" }).catch(() => void 0);
17575
17580
  this.turnClaim = response?.claim;
17576
17581
  this.update({
17577
17582
  endpointReady: Boolean(this.turnClaim),
17578
17583
  ...response?.error ? { error: response.error } : this.turnClaim ? { error: void 0 } : {}
17579
17584
  });
17585
+ if (this.turnClaim?.endpointId !== previousEndpointId) {
17586
+ this.endpointBinding = void 0;
17587
+ if (this.turnClaim) {
17588
+ void this.bindSessionEndpoint().catch((cause) => this.update({ error: cause instanceof Error ? cause.message : String(cause) }));
17589
+ }
17590
+ }
17580
17591
  if (this.turnClaim && this.snapshot.pendingDelivery) void this.recoverOutbox();
17581
17592
  }
17593
+ async bindSessionEndpoint() {
17594
+ const sessionKey = this.snapshot.sessionKey;
17595
+ const endpointId = this.turnClaim?.endpointId;
17596
+ if (!sessionKey || !endpointId) return;
17597
+ const desired = `${sessionKey}
17598
+ ${endpointId}`;
17599
+ if (this.endpointBinding === desired) return;
17600
+ if (this.endpointBindingTask) {
17601
+ await this.endpointBindingTask;
17602
+ if (this.endpointBinding === desired) return;
17603
+ }
17604
+ const task = json(await gatewayFetch(
17605
+ `/api/endpoint-tools/bindings/${encodeURIComponent(sessionKey)}`,
17606
+ {
17607
+ method: "PUT",
17608
+ headers: { "Content-Type": "application/json" },
17609
+ body: JSON.stringify({ endpointId })
17610
+ }
17611
+ )).then(() => {
17612
+ if (this.snapshot.sessionKey === sessionKey && this.turnClaim?.endpointId === endpointId) {
17613
+ this.endpointBinding = desired;
17614
+ }
17615
+ });
17616
+ this.endpointBindingTask = task;
17617
+ try {
17618
+ await task;
17619
+ } finally {
17620
+ if (this.endpointBindingTask === task) this.endpointBindingTask = void 0;
17621
+ }
17622
+ }
17582
17623
  async loadSessions(search) {
17583
17624
  const requestId = ++this.sessionsRequest;
17584
17625
  const params = new URLSearchParams({ channel: "webchat", limit: "30" });
@@ -17591,7 +17632,7 @@ class BrowserChatClient {
17591
17632
  return [{
17592
17633
  key: row.key,
17593
17634
  ...typeof row.sessionId === "string" ? { sessionId: row.sessionId } : {},
17594
- title: [row.name, row.title, row.displayName].find((item) => typeof item === "string" && item.trim()) || "New chat",
17635
+ title: [row.name, row.title, row.displayName].find((item) => typeof item === "string" && item.trim()) || t("newChat"),
17595
17636
  updatedAt: typeof row.updatedAt === "string" ? row.updatedAt : ""
17596
17637
  }];
17597
17638
  });
@@ -17637,6 +17678,8 @@ class BrowserChatClient {
17637
17678
  this.reloadTabBinding()
17638
17679
  ]);
17639
17680
  if (this.snapshot.sessionKey !== sessionKey) return;
17681
+ await this.bindSessionEndpoint();
17682
+ if (this.snapshot.sessionKey !== sessionKey) return;
17640
17683
  const run = await json(await gatewayFetch(
17641
17684
  `/api/sessions/${encodeURIComponent(sessionKey)}/run`
17642
17685
  ));
@@ -17659,11 +17702,12 @@ class BrowserChatClient {
17659
17702
  async send(content, browserContexts = [], attachments = []) {
17660
17703
  const text = content.trim();
17661
17704
  if (!text && attachments.length === 0) return "sent";
17662
- if (!this.snapshot.sessionKey) throw new Error("Open a chat before sending");
17705
+ if (!this.snapshot.sessionKey) throw new Error(t("errorOpenChatBeforeSending"));
17663
17706
  if (this.snapshot.submitting || this.snapshot.pendingDelivery || this.recoveringOutbox) {
17664
- throw new Error("Wait for the queued message to be delivered before sending another");
17707
+ throw new Error(t("errorWaitQueuedMessage"));
17665
17708
  }
17666
- if (!this.turnClaim) throw new Error("Realtime endpoint is not ready");
17709
+ if (!this.turnClaim) throw new Error(t("errorEndpointNotReady"));
17710
+ await this.bindSessionEndpoint();
17667
17711
  const sessionKey = this.snapshot.sessionKey;
17668
17712
  const clientMessageId = crypto.randomUUID();
17669
17713
  const request = {
@@ -17720,7 +17764,7 @@ class BrowserChatClient {
17720
17764
  if (this.snapshot.sessionKey === sessionKey) {
17721
17765
  this.update({
17722
17766
  pendingDelivery: false,
17723
- error: "Message sent, but the response status could not be refreshed. Reopen this chat to sync it."
17767
+ error: t("errorSentStatusUnknown")
17724
17768
  });
17725
17769
  }
17726
17770
  return "sent";
@@ -17771,7 +17815,7 @@ class BrowserChatClient {
17771
17815
  if (deliveryAccepted) {
17772
17816
  this.update({
17773
17817
  pendingDelivery: false,
17774
- error: "Message sent, but the response status could not be refreshed. Reopen this chat to sync it."
17818
+ error: t("errorSentStatusUnknown")
17775
17819
  });
17776
17820
  } else if (isRetryableDeliveryError(cause)) {
17777
17821
  this.update({ pendingDelivery: true, error: void 0 });
@@ -17807,7 +17851,7 @@ class BrowserChatClient {
17807
17851
  async updateModel(model) {
17808
17852
  const sessionKey = this.snapshot.sessionKey;
17809
17853
  const current = this.snapshot.modelConfig;
17810
- if (!sessionKey || !current || current.fixedModel) return;
17854
+ if (!sessionKey || !current) return;
17811
17855
  const selected = this.snapshot.models.find((candidate) => candidate.id === model);
17812
17856
  const thinkingLevel = selected?.thinking?.options?.includes(current.thinkingLevel) ? current.thinkingLevel : selected?.thinking?.default;
17813
17857
  await this.patchModelConfig({ model, thinkingLevel });
@@ -17833,7 +17877,7 @@ class BrowserChatClient {
17833
17877
  }
17834
17878
  async bindActiveTab(mode) {
17835
17879
  const sessionKey = this.snapshot.sessionKey;
17836
- if (!sessionKey || !this.turnClaim) throw new Error("Open a chat and wait for the browser endpoint");
17880
+ if (!sessionKey || !this.turnClaim) throw new Error(t("errorOpenChatForEndpoint"));
17837
17881
  const descriptor = await currentTabDescriptor();
17838
17882
  const result = await json(await gatewayFetch(
17839
17883
  `/api/browser/tab-bindings/${encodeURIComponent(sessionKey)}`,
@@ -17970,7 +18014,7 @@ class BrowserChatClient {
17970
18014
  ));
17971
18015
  const config = result.payload;
17972
18016
  if (!config || typeof config.model !== "string" || typeof config.thinkingLevel !== "string") {
17973
- throw new Error("Gateway returned an invalid session model configuration");
18017
+ throw new Error(t("errorInvalidModelConfig"));
17974
18018
  }
17975
18019
  if (this.snapshot.sessionKey !== sessionKey) return;
17976
18020
  this.update({ modelConfig: {
@@ -17994,7 +18038,7 @@ class BrowserChatClient {
17994
18038
  ));
17995
18039
  const config = result.payload;
17996
18040
  if (!config || typeof config.model !== "string" || typeof config.thinkingLevel !== "string") {
17997
- throw new Error("Gateway returned an invalid session model configuration");
18041
+ throw new Error(t("errorInvalidModelConfig"));
17998
18042
  }
17999
18043
  if (this.snapshot.sessionKey !== sessionKey) return;
18000
18044
  this.update({ modelConfig: {
@@ -18067,7 +18111,7 @@ class BrowserChatClient {
18067
18111
  }
18068
18112
  requireProfileUrl() {
18069
18113
  const profile = this.profileUrl;
18070
- if (!profile) throw new Error("Gateway profile is not loaded");
18114
+ if (!profile) throw new Error(t("errorProfileNotLoaded"));
18071
18115
  return profile;
18072
18116
  }
18073
18117
  profileUrl;
@@ -18101,14 +18145,14 @@ function encodeBase64(bytes) {
18101
18145
  }
18102
18146
  function assertSize(size) {
18103
18147
  if (size > MAX_BROWSER_ATTACHMENT_BYTES) {
18104
- throw new Error("Browser attachments must be 6 MiB or smaller");
18148
+ throw new Error(t("errorAttachmentTooLarge"));
18105
18149
  }
18106
18150
  }
18107
18151
  function assertSupportedFile(file) {
18108
18152
  const mimeType = file.type.toLowerCase();
18109
18153
  const name = file.name.toLowerCase();
18110
18154
  if (mimeType.startsWith("image/") || SUPPORTED_DOCUMENT_MIME_TYPES.has(mimeType) || SUPPORTED_DOCUMENT_EXTENSIONS.some((extension) => name.endsWith(extension))) return;
18111
- throw new Error("Attach an image, PDF, text, Markdown, JSON, or CSV file");
18155
+ throw new Error(t("errorUnsupportedAttachment"));
18112
18156
  }
18113
18157
  async function fileToBrowserAttachment(file) {
18114
18158
  assertSupportedFile(file);
@@ -18124,14 +18168,14 @@ async function fileToBrowserAttachment(file) {
18124
18168
  }
18125
18169
  async function captureVisibleScreenshot() {
18126
18170
  const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true });
18127
- if (activeTab.id === void 0) throw new Error("No active web page");
18171
+ if (activeTab.id === void 0) throw new Error(t("errorNoActivePage"));
18128
18172
  const dataUrl = await runWithTabSiteAccess(activeTab.id, async (accessibleTab) => {
18129
18173
  const [currentTab] = await chrome.tabs.query({ active: true, currentWindow: true });
18130
- if (currentTab.id !== accessibleTab.id) throw new Error("The active tab changed. Try the screenshot again.");
18174
+ if (currentTab.id !== accessibleTab.id) throw new Error(t("errorActiveTabChanged"));
18131
18175
  return chrome.tabs.captureVisibleTab(accessibleTab.windowId, { format: "png" });
18132
18176
  });
18133
18177
  const separator = dataUrl.indexOf(",");
18134
- if (separator < 0) throw new Error("Chrome returned an invalid screenshot");
18178
+ if (separator < 0) throw new Error(t("errorInvalidScreenshot"));
18135
18179
  const data = dataUrl.slice(separator + 1);
18136
18180
  const size = Math.floor(data.length * 3 / 4);
18137
18181
  assertSize(size);
@@ -18255,6 +18299,24 @@ function AlertIcon(props) {
18255
18299
  /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 8v5M12 16.5h.01" })
18256
18300
  ] });
18257
18301
  }
18302
+ function SettingsIcon(props) {
18303
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(IconBase, { ...props, children: [
18304
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "3" }),
18305
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19.4 15a1.7 1.7 0 0 0 .34 1.88l.06.06-2.86 2.86-.06-.06A1.7 1.7 0 0 0 15 19.4a1.7 1.7 0 0 0-1 .6 1.7 1.7 0 0 0-.4 1.1V21H9.55v-.1A1.7 1.7 0 0 0 8.5 19.4a1.7 1.7 0 0 0-1.88.34l-.06.06-2.86-2.86.06-.06A1.7 1.7 0 0 0 4.1 15a1.7 1.7 0 0 0-.6-1 1.7 1.7 0 0 0-1.1-.4H2V9.55h.4A1.7 1.7 0 0 0 4.1 8.5a1.7 1.7 0 0 0-.34-1.88l-.06-.06L6.56 3.7l.06.06A1.7 1.7 0 0 0 8.5 4.1a1.7 1.7 0 0 0 1-.6 1.7 1.7 0 0 0 .4-1.1V2h4.05v.4A1.7 1.7 0 0 0 15 4.1a1.7 1.7 0 0 0 1.88-.34l.06-.06 2.86 2.86-.06.06A1.7 1.7 0 0 0 19.4 8.5a1.7 1.7 0 0 0 .6 1 1.7 1.7 0 0 0 1.1.4h.4v4.05h-.4A1.7 1.7 0 0 0 19.4 15Z" })
18306
+ ] });
18307
+ }
18308
+ function SunIcon(props) {
18309
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(IconBase, { ...props, children: [
18310
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "4" }),
18311
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 2v2M12 20v2M4.93 4.93l1.42 1.42M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.42-1.42M17.66 6.34l1.41-1.41" })
18312
+ ] });
18313
+ }
18314
+ function GlobeIcon(props) {
18315
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(IconBase, { ...props, children: [
18316
+ /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "12", cy: "12", r: "9" }),
18317
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M3 12h18M12 3a15 15 0 0 1 0 18M12 3a15 15 0 0 0 0 18" })
18318
+ ] });
18319
+ }
18258
18320
 
18259
18321
  /* eslint-disable no-bitwise */
18260
18322
 
@@ -26745,7 +26807,7 @@ function MarkdownContent({ children, streaming = false }) {
26745
26807
  const html = reactExports.useMemo(() => renderSafeMarkdown(children), [children]);
26746
26808
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `markdown-content${streaming ? " is-streaming" : ""}`, children: [
26747
26809
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { dangerouslySetInnerHTML: { __html: html } }),
26748
- streaming ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "streaming-caret", "aria-label": "xopc is responding" }) : null
26810
+ streaming ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "streaming-caret", "aria-label": t("xopcResponding") }) : null
26749
26811
  ] });
26750
26812
  }
26751
26813
 
@@ -26763,7 +26825,7 @@ const EMPTY = {
26763
26825
  };
26764
26826
  function formatMessageTime(timestamp) {
26765
26827
  if (!timestamp) return "";
26766
- return new Intl.DateTimeFormat(void 0, { hour: "2-digit", minute: "2-digit" }).format(timestamp);
26828
+ return new Intl.DateTimeFormat(extensionLocale(), { hour: "2-digit", minute: "2-digit" }).format(timestamp);
26767
26829
  }
26768
26830
  function formatSessionDate(timestamp) {
26769
26831
  if (!timestamp) return "";
@@ -26771,7 +26833,28 @@ function formatSessionDate(timestamp) {
26771
26833
  if (!Number.isFinite(date.getTime())) return "";
26772
26834
  const now = /* @__PURE__ */ new Date();
26773
26835
  if (date.toDateString() === now.toDateString()) return formatMessageTime(date.getTime());
26774
- return new Intl.DateTimeFormat(void 0, { month: "short", day: "numeric" }).format(date);
26836
+ return new Intl.DateTimeFormat(extensionLocale(), { month: "short", day: "numeric" }).format(date);
26837
+ }
26838
+ const RISK_MESSAGE_KEYS = {
26839
+ external_effect: "riskExternalEffect",
26840
+ destructive: "riskDestructive",
26841
+ sensitive: "riskSensitive",
26842
+ draft: "riskDraft",
26843
+ read: "riskRead"
26844
+ };
26845
+ const THINKING_MESSAGE_KEYS = {
26846
+ off: "thinkingOff",
26847
+ minimal: "thinkingMinimal",
26848
+ low: "thinkingLow",
26849
+ medium: "thinkingMedium",
26850
+ high: "thinkingHigh",
26851
+ xhigh: "thinkingXhigh",
26852
+ max: "thinkingMax",
26853
+ ultra: "thinkingUltra"
26854
+ };
26855
+ function thinkingLabel(level) {
26856
+ const key = THINKING_MESSAGE_KEYS[level];
26857
+ return key ? t(key) : level;
26775
26858
  }
26776
26859
  function formatFileSize(size) {
26777
26860
  if (typeof size !== "number" || !Number.isFinite(size) || size < 0) return "";
@@ -26921,7 +27004,7 @@ function ChatPanel() {
26921
27004
  setAtBottom(true);
26922
27005
  try {
26923
27006
  if (!snapshot.sessionKey) await client.createSession();
26924
- if (pageContext?.stale) throw new Error("The attached page changed. Refresh or remove it before sending.");
27007
+ if (pageContext?.stale) throw new Error(t("errorAttachedPageChanged"));
26925
27008
  await client.send(sending, pageContext ? [pageContext.context] : [], sendingAttachments);
26926
27009
  setPageContext(void 0);
26927
27010
  } catch (cause) {
@@ -26937,7 +27020,7 @@ function ChatPanel() {
26937
27020
  setError("");
26938
27021
  try {
26939
27022
  const remaining = MAX_BROWSER_ATTACHMENTS - attachments.length;
26940
- if (incoming.length > remaining) throw new Error(`Attach up to ${MAX_BROWSER_ATTACHMENTS} files`);
27023
+ if (incoming.length > remaining) throw new Error(t("errorAttachmentLimit", String(MAX_BROWSER_ATTACHMENTS)));
26941
27024
  const added = await Promise.all(incoming.map(fileToBrowserAttachment));
26942
27025
  setAttachments((current) => [...current, ...added]);
26943
27026
  } catch (cause) {
@@ -26950,7 +27033,7 @@ function ChatPanel() {
26950
27033
  async function addScreenshot() {
26951
27034
  setError("");
26952
27035
  try {
26953
- if (attachments.length >= MAX_BROWSER_ATTACHMENTS) throw new Error(`Attach up to ${MAX_BROWSER_ATTACHMENTS} files`);
27036
+ if (attachments.length >= MAX_BROWSER_ATTACHMENTS) throw new Error(t("errorAttachmentLimit", String(MAX_BROWSER_ATTACHMENTS)));
26954
27037
  const screenshot = await captureVisibleScreenshot();
26955
27038
  setAttachments((current) => [...current, screenshot]);
26956
27039
  } catch (cause) {
@@ -26971,7 +27054,7 @@ function ChatPanel() {
26971
27054
  setError("");
26972
27055
  try {
26973
27056
  const tabId = await activeTabId();
26974
- if (tabId === void 0) throw new Error("No active web page");
27057
+ if (tabId === void 0) throw new Error(t("errorNoActivePage"));
26975
27058
  setPageContext({
26976
27059
  context: await captureTabWithPermission(tabId, mode),
26977
27060
  tabId,
@@ -27069,13 +27152,13 @@ function ChatPanel() {
27069
27152
  }
27070
27153
  }
27071
27154
  const activeSession = snapshot.sessions.find((session) => session.key === snapshot.sessionKey);
27072
- const connectionText = snapshot.endpointReady ? "Ready" : snapshot.connection === "connected" ? "Waking up…" : snapshot.connection === "connecting" || snapshot.connection === "reconnecting" ? "Connecting…" : "Offline";
27155
+ const connectionText = snapshot.endpointReady ? t("ready") : snapshot.connection === "connected" ? t("wakingUp") : snapshot.connection === "connecting" || snapshot.connection === "reconnecting" ? t("connecting") : t("offline");
27073
27156
  const connectionProblem = !snapshot.endpointReady && (snapshot.connection === "error" || snapshot.connection === "disconnected");
27074
27157
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "chat", children: [
27075
27158
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "chat-toolbar", children: [
27076
27159
  /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { ref: sessionTrigger, className: "session-trigger", "aria-expanded": menuOpen, onClick: () => setMenuOpen((open) => !open), children: [
27077
27160
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "session-trigger-copy", children: [
27078
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "session-title", children: activeSession?.title ?? "New chat" }),
27161
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "session-title", children: activeSession?.title ?? t("newChat") }),
27079
27162
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "session-status", children: [
27080
27163
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `mini-status ${snapshot.endpointReady ? "online" : ""}` }),
27081
27164
  connectionText
@@ -27083,16 +27166,16 @@ function ChatPanel() {
27083
27166
  ] }),
27084
27167
  /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDownIcon, { className: "icon-sm" })
27085
27168
  ] }),
27086
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "icon-button", type: "button", "aria-label": "Start a new chat", title: "New chat", disabled: snapshot.submitting, onClick: () => void runControlAction(() => client.createSession()), children: /* @__PURE__ */ jsxRuntimeExports.jsx(PlusIcon, {}) })
27169
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "icon-button", type: "button", "aria-label": t("startNewChat"), title: t("newChat"), disabled: snapshot.submitting, onClick: () => void runControlAction(() => client.createSession()), children: /* @__PURE__ */ jsxRuntimeExports.jsx(PlusIcon, {}) })
27087
27170
  ] }),
27088
27171
  menuOpen ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "session-menu", ref: sessionMenu, children: [
27089
27172
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "session-menu-header", children: [
27090
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Recent chats" }),
27091
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", "aria-label": "Close recent chats", onClick: () => setMenuOpen(false), children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {}) })
27173
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: t("recentChats") }),
27174
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", "aria-label": t("closeRecentChats"), onClick: () => setMenuOpen(false), children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {}) })
27092
27175
  ] }),
27093
27176
  /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "session-search", children: [
27094
27177
  /* @__PURE__ */ jsxRuntimeExports.jsx(SearchIcon, {}),
27095
- /* @__PURE__ */ jsxRuntimeExports.jsx("input", { value: search, onChange: (event) => void runSearch(event.target.value), placeholder: "Search chats", autoFocus: true })
27178
+ /* @__PURE__ */ jsxRuntimeExports.jsx("input", { value: search, onChange: (event) => void runSearch(event.target.value), placeholder: t("searchChats"), autoFocus: true })
27096
27179
  ] }),
27097
27180
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "session-list", children: [
27098
27181
  snapshot.sessions.map((session) => /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { disabled: snapshot.submitting, className: session.key === snapshot.sessionKey ? "active" : "", onClick: () => {
@@ -27105,43 +27188,43 @@ function ChatPanel() {
27105
27188
  ] }),
27106
27189
  session.key === snapshot.sessionKey ? /* @__PURE__ */ jsxRuntimeExports.jsx(CheckIcon, {}) : null
27107
27190
  ] }, session.key)),
27108
- !snapshot.sessions.length ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "session-list-empty", children: "No chats found" }) : null
27191
+ !snapshot.sessions.length ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "session-list-empty", children: t("noChatsFound") }) : null
27109
27192
  ] })
27110
27193
  ] }) : null,
27111
27194
  connectionProblem ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "connection-banner", role: "alert", children: [
27112
27195
  /* @__PURE__ */ jsxRuntimeExports.jsx(AlertIcon, {}),
27113
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: snapshot.error || "Gateway connection is offline." }),
27114
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => void runControlAction(() => client.reconnect()), children: "Reconnect" })
27196
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: snapshot.error || t("gatewayOffline") }),
27197
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => void runControlAction(() => client.reconnect()), children: t("reconnect") })
27115
27198
  ] }) : null,
27116
27199
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "messages-shell", children: [
27117
27200
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "messages", ref: viewport, onScroll: trackScrollPosition, children: [
27118
- snapshot.sessionLoading ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "message-skeleton", "aria-label": "Loading chat", children: [
27201
+ snapshot.sessionLoading ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "message-skeleton", "aria-label": t("loadingChat"), children: [
27119
27202
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", {}),
27120
27203
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", {}),
27121
27204
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", {})
27122
27205
  ] }) : null,
27123
27206
  !snapshot.sessionLoading && !snapshot.messages.length && !snapshot.streamingText ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "empty-chat", children: [
27124
27207
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "empty-mark", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SparkleIcon, {}) }),
27125
- /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { children: "What can I help with?" }),
27126
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Ask a question, bring in the current page, or let xopc help you complete a browser task." }),
27208
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { children: t("emptyChatTitle") }),
27209
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: t("emptyChatDescription") }),
27127
27210
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "quick-prompts", children: [
27128
- /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => void startQuickPrompt("Summarize this page and highlight the key points.", "page"), children: [
27211
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => void startQuickPrompt(t("promptSummarizePage"), "page"), children: [
27129
27212
  /* @__PURE__ */ jsxRuntimeExports.jsx(PageIcon, {}),
27130
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Summarize this page" })
27213
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("summarizePage") })
27131
27214
  ] }),
27132
- /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => void startQuickPrompt("Explain the selected text in plain language.", "selection"), children: [
27215
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => void startQuickPrompt(t("promptExplainSelection"), "selection"), children: [
27133
27216
  /* @__PURE__ */ jsxRuntimeExports.jsx(SelectionIcon, {}),
27134
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Explain selection" })
27217
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("explainSelection") })
27135
27218
  ] }),
27136
- /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => void startQuickPrompt("Help me complete the task on this page. Ask before taking any consequential action."), children: [
27219
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => void startQuickPrompt(t("promptHelpTask")), children: [
27137
27220
  /* @__PURE__ */ jsxRuntimeExports.jsx(CursorIcon, {}),
27138
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Help with a task" })
27221
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("helpWithTask") })
27139
27222
  ] })
27140
27223
  ] })
27141
27224
  ] }) : null,
27142
27225
  snapshot.messages.map((message) => /* @__PURE__ */ jsxRuntimeExports.jsxs("article", { className: `message ${message.role}`, children: [
27143
27226
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "message-heading", children: [
27144
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "message-author", children: message.role === "user" ? "You" : message.role === "assistant" ? "xopc" : "System" }),
27227
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "message-author", children: message.role === "user" ? t("you") : message.role === "assistant" ? "xopc" : t("system") }),
27145
27228
  formatMessageTime(message.timestamp) ? /* @__PURE__ */ jsxRuntimeExports.jsx("time", { children: formatMessageTime(message.timestamp) }) : null
27146
27229
  ] }),
27147
27230
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "message-body", children: [
@@ -27149,7 +27232,7 @@ function ChatPanel() {
27149
27232
  source.kind === "browser_page" ? /* @__PURE__ */ jsxRuntimeExports.jsx(PageIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(FileIcon, {}),
27150
27233
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
27151
27234
  source.title,
27152
- source.truncated ? " · truncated" : ""
27235
+ source.truncated ? ` · ${t("truncated")}` : ""
27153
27236
  ] })
27154
27237
  ] }, `${source.title}-${index}`)) }) : null,
27155
27238
  message.attachments?.length ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "message-attachments", children: message.attachments.map((attachment, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "message-attachment", title: attachment.mimeType, children: [
@@ -27161,20 +27244,20 @@ function ChatPanel() {
27161
27244
  ] }, `${attachment.name}-${index}`)) }) : null,
27162
27245
  message.text ? message.role === "assistant" ? /* @__PURE__ */ jsxRuntimeExports.jsx(MarkdownContent, { children: message.text }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "plain-message-content", children: message.text }) : null
27163
27246
  ] }),
27164
- message.role === "assistant" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "message-actions", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", "aria-label": "Copy response", title: "Copy response", onClick: () => void copyMessage(message.id, message.text), children: [
27247
+ message.role === "assistant" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "message-actions", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", "aria-label": t("copyResponse"), title: t("copyResponse"), onClick: () => void copyMessage(message.id, message.text), children: [
27165
27248
  copiedMessageId === message.id ? /* @__PURE__ */ jsxRuntimeExports.jsx(CheckIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}),
27166
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: copiedMessageId === message.id ? "Copied" : "Copy" })
27249
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: copiedMessageId === message.id ? t("copied") : t("copy") })
27167
27250
  ] }) }) : null
27168
27251
  ] }, message.id)),
27169
27252
  snapshot.streamingText ? /* @__PURE__ */ jsxRuntimeExports.jsxs("article", { className: "message assistant streaming", children: [
27170
27253
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "message-heading", children: [
27171
27254
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "message-author", children: "xopc" }),
27172
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "responding-label", children: "Responding" })
27255
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "responding-label", children: t("responding") })
27173
27256
  ] }),
27174
27257
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "message-body", children: /* @__PURE__ */ jsxRuntimeExports.jsx(MarkdownContent, { streaming: true, children: snapshot.streamingText }) })
27175
27258
  ] }) : null
27176
27259
  ] }),
27177
- !atBottom ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "scroll-bottom-button", type: "button", "aria-label": "Scroll to latest message", title: "Scroll to latest message", onClick: () => scrollToBottom(true), children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowDownIcon, {}) }) : null
27260
+ !atBottom ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "scroll-bottom-button", type: "button", "aria-label": t("scrollLatest"), title: t("scrollLatest"), onClick: () => scrollToBottom(true), children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowDownIcon, {}) }) : null
27178
27261
  ] }),
27179
27262
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
27180
27263
  "form",
@@ -27203,26 +27286,27 @@ function ChatPanel() {
27203
27286
  children: [
27204
27287
  draggingFiles ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "file-drop-overlay", children: [
27205
27288
  /* @__PURE__ */ jsxRuntimeExports.jsx(PaperclipIcon, {}),
27206
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Drop files to attach" })
27289
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("dropFiles") })
27207
27290
  ] }) : null,
27208
- snapshot.browserApproval ? /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "request-card browser-approval", "aria-label": "Browser approval", children: [
27291
+ snapshot.browserApproval ? /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "request-card browser-approval", "aria-label": t("browserApproval"), children: [
27209
27292
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "request-card-title", children: [
27210
27293
  /* @__PURE__ */ jsxRuntimeExports.jsx(AlertIcon, {}),
27211
27294
  /* @__PURE__ */ jsxRuntimeExports.jsxs("strong", { children: [
27212
- "Browser approval · ",
27213
- snapshot.browserApproval.risk.replace("_", " ")
27295
+ t("browserApproval"),
27296
+ " · ",
27297
+ t(RISK_MESSAGE_KEYS[snapshot.browserApproval.risk])
27214
27298
  ] })
27215
27299
  ] }),
27216
27300
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: snapshot.browserApproval.summary }),
27217
27301
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "request-actions", children: [
27218
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "request-primary", type: "button", onClick: () => void runControlAction(() => client.respondToBrowserApproval("approved")), children: "Allow once" }),
27219
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => void runControlAction(() => client.respondToBrowserApproval("denied")), children: "Deny" })
27302
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "request-primary", type: "button", onClick: () => void runControlAction(() => client.respondToBrowserApproval("approved")), children: t("allowOnce") }),
27303
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => void runControlAction(() => client.respondToBrowserApproval("denied")), children: t("deny") })
27220
27304
  ] })
27221
27305
  ] }) : null,
27222
- snapshot.clarification ? /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "request-card", "aria-label": "Agent question", children: [
27306
+ snapshot.clarification ? /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "request-card", "aria-label": t("agentQuestion"), children: [
27223
27307
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "request-card-title", children: [
27224
27308
  /* @__PURE__ */ jsxRuntimeExports.jsx(SparkleIcon, {}),
27225
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: snapshot.clarification.kind === "approval" ? "Approval required" : "xopc needs your input" })
27309
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: snapshot.clarification.kind === "approval" ? t("approvalRequired") : t("xopcNeedsInput") })
27226
27310
  ] }),
27227
27311
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: snapshot.clarification.question }),
27228
27312
  snapshot.clarification.choices?.length ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "choice-list", children: snapshot.clarification.choices.map((choice) => /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", disabled: clarificationSubmitting, onClick: () => void respondToClarification("answer", choice), children: choice }, choice)) }) : null,
@@ -27232,7 +27316,7 @@ function ChatPanel() {
27232
27316
  {
27233
27317
  value: clarificationAnswer,
27234
27318
  onChange: (event) => setClarificationAnswer(event.target.value),
27235
- placeholder: snapshot.clarification.suggestedAnswer ?? "Type your answer"
27319
+ placeholder: snapshot.clarification.suggestedAnswer ?? t("typeAnswer")
27236
27320
  }
27237
27321
  ),
27238
27322
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -27241,16 +27325,16 @@ function ChatPanel() {
27241
27325
  type: "button",
27242
27326
  disabled: clarificationSubmitting || !clarificationAnswer.trim(),
27243
27327
  onClick: () => void respondToClarification("answer", clarificationAnswer.trim()),
27244
- children: "Send"
27328
+ children: t("send")
27245
27329
  }
27246
27330
  )
27247
27331
  ] }),
27248
27332
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "clarification-actions", children: [
27249
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", disabled: clarificationSubmitting, onClick: () => void respondToClarification("agent_decide"), children: "Let xopc decide" }),
27250
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", disabled: clarificationSubmitting, onClick: () => void respondToClarification("cancel"), children: "Cancel task" })
27333
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", disabled: clarificationSubmitting, onClick: () => void respondToClarification("agent_decide"), children: t("letXopcDecide") }),
27334
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", disabled: clarificationSubmitting, onClick: () => void respondToClarification("cancel"), children: t("cancelTask") })
27251
27335
  ] })
27252
27336
  ] }) : null,
27253
- snapshot.pendingDelivery ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "composer-notice", role: "status", children: "Connection interrupted. Your message is queued and will send after xopc reconnects." }) : null,
27337
+ snapshot.pendingDelivery ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "composer-notice", role: "status", children: t("queuedMessageNotice") }) : null,
27254
27338
  error || !connectionProblem && snapshot.error ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "composer-error", role: "alert", children: [
27255
27339
  /* @__PURE__ */ jsxRuntimeExports.jsx(AlertIcon, {}),
27256
27340
  error || snapshot.error
@@ -27259,23 +27343,23 @@ function ChatPanel() {
27259
27343
  pageContext ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `context-chip${pageContext.stale ? " stale" : ""}`, children: [
27260
27344
  pageContext.context.selection ? /* @__PURE__ */ jsxRuntimeExports.jsx(SelectionIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(PageIcon, {}),
27261
27345
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
27262
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: pageContext.source === "tab_mention" ? "Tab" : pageContext.context.selection ? "Selection" : "Page" }),
27346
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: pageContext.source === "tab_mention" ? t("tab") : pageContext.context.selection ? t("selection") : t("page") }),
27263
27347
  " · ",
27264
27348
  new URL(pageContext.context.url).hostname,
27265
27349
  " · ",
27266
27350
  pageContext.context.title
27267
27351
  ] }),
27268
- pageContext.stale ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => void refreshPageContext(), children: "Refresh" }) : null,
27269
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "chip-remove", "aria-label": "Remove page context", onClick: () => setPageContext(void 0), children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {}) })
27352
+ pageContext.stale ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => void refreshPageContext(), children: t("refresh") }) : null,
27353
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "chip-remove", "aria-label": t("removePageContext"), onClick: () => setPageContext(void 0), children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {}) })
27270
27354
  ] }) : null,
27271
27355
  attachments.map((attachment, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "attachment-chip", children: [
27272
27356
  attachment.type === "image" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CameraIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(FileIcon, {}),
27273
27357
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
27274
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: attachment.type === "image" ? "Image" : "File" }),
27358
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: attachment.type === "image" ? t("image") : t("file") }),
27275
27359
  " · ",
27276
27360
  attachment.name
27277
27361
  ] }),
27278
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "chip-remove", "aria-label": `Remove ${attachment.name}`, onClick: () => setAttachments((current) => current.filter((_, candidate) => candidate !== index)), children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {}) })
27362
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "chip-remove", "aria-label": t("removeAttachment", attachment.name), onClick: () => setAttachments((current) => current.filter((_, candidate) => candidate !== index)), children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {}) })
27279
27363
  ] }, `${attachment.name}-${index}`))
27280
27364
  ] }) : null,
27281
27365
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -27289,7 +27373,7 @@ function ChatPanel() {
27289
27373
  onChange: (event) => void addFiles(event.target.files)
27290
27374
  }
27291
27375
  ),
27292
- tabMention ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tab-mention-menu", role: "listbox", "aria-label": "Open tabs", children: mentionTabs.length ? mentionTabs.map((tab, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
27376
+ tabMention ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tab-mention-menu", role: "listbox", "aria-label": t("openTabs"), children: mentionTabs.length ? mentionTabs.map((tab, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
27293
27377
  "button",
27294
27378
  {
27295
27379
  type: "button",
@@ -27304,167 +27388,169 @@ function ChatPanel() {
27304
27388
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: tab.title }),
27305
27389
  /* @__PURE__ */ jsxRuntimeExports.jsxs("small", { children: [
27306
27390
  tab.hostname,
27307
- tab.active ? " · current" : ""
27391
+ tab.active ? ` · ${t("currentTab")}` : ""
27308
27392
  ] })
27309
27393
  ]
27310
27394
  },
27311
27395
  tab.id
27312
- )) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tab-mention-empty", children: "No matching tabs" }) }) : null,
27313
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "composer-input-row", children: [
27314
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tools-anchor", children: [
27315
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { ref: toolsTrigger, type: "button", className: `composer-icon-button${toolsOpen ? " active" : ""}`, "aria-label": "Add context or attachment", "aria-expanded": toolsOpen, onClick: () => setToolsOpen((open) => !open), children: /* @__PURE__ */ jsxRuntimeExports.jsx(PaperclipIcon, {}) }),
27316
- toolsOpen ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tools-menu", ref: toolsMenu, children: [
27317
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tools-menu-section", children: [
27318
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "tools-menu-label", children: "Add to this message" }),
27319
- /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => {
27320
- setToolsOpen(false);
27321
- void attachPage("page");
27322
- }, children: [
27323
- /* @__PURE__ */ jsxRuntimeExports.jsx(PageIcon, {}),
27324
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
27325
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Current page" }),
27326
- /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: "Share readable page content" })
27327
- ] })
27328
- ] }),
27329
- /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => {
27330
- setToolsOpen(false);
27331
- void attachPage("selection");
27332
- }, children: [
27333
- /* @__PURE__ */ jsxRuntimeExports.jsx(SelectionIcon, {}),
27334
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
27335
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Selected text" }),
27336
- /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: "Share your current selection" })
27337
- ] })
27338
- ] }),
27339
- /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => {
27340
- setToolsOpen(false);
27341
- void addScreenshot();
27342
- }, children: [
27343
- /* @__PURE__ */ jsxRuntimeExports.jsx(CameraIcon, {}),
27344
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
27345
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Screenshot" }),
27346
- /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: "Capture the visible tab" })
27347
- ] })
27348
- ] }),
27349
- /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => {
27350
- setToolsOpen(false);
27351
- fileInput.current?.click();
27352
- }, children: [
27353
- /* @__PURE__ */ jsxRuntimeExports.jsx(FileIcon, {}),
27354
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
27355
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "File" }),
27356
- /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: "Image, PDF, text, JSON, or CSV" })
27357
- ] })
27358
- ] })
27359
- ] }),
27360
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tools-menu-section tab-access-section", children: [
27361
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "tools-menu-label", children: "Live tab access" }),
27362
- snapshot.tabBinding ? /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: "active-access", onClick: () => {
27363
- setToolsOpen(false);
27364
- void runControlAction(() => client.unbindActiveTab());
27365
- }, children: [
27366
- snapshot.tabBinding.mode === "act" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CursorIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(EyeIcon, {}),
27367
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
27368
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: snapshot.tabBinding.mode === "act" ? "Control enabled" : "Read enabled" }),
27369
- /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: "Click to stop access" })
27396
+ )) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tab-mention-empty", children: t("noMatchingTabs") }) }) : null,
27397
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "composer-input-row", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
27398
+ "textarea",
27399
+ {
27400
+ ref: textarea,
27401
+ value: draft,
27402
+ onChange: (event) => {
27403
+ setDraft(event.target.value);
27404
+ void updateTabMention(event.target.value, event.target.selectionStart);
27405
+ },
27406
+ onSelect: (event) => void updateTabMention(event.currentTarget.value, event.currentTarget.selectionStart),
27407
+ onPaste: (event) => {
27408
+ if (!event.clipboardData.files.length) return;
27409
+ event.preventDefault();
27410
+ void addFiles(event.clipboardData.files);
27411
+ },
27412
+ onKeyDown: (event) => {
27413
+ if (tabMention) {
27414
+ if (event.key === "ArrowDown" && mentionTabs.length) {
27415
+ event.preventDefault();
27416
+ setMentionIndex((current) => (current + 1) % mentionTabs.length);
27417
+ return;
27418
+ }
27419
+ if (event.key === "ArrowUp" && mentionTabs.length) {
27420
+ event.preventDefault();
27421
+ setMentionIndex((current) => (current - 1 + mentionTabs.length) % mentionTabs.length);
27422
+ return;
27423
+ }
27424
+ if (event.key === "Escape") {
27425
+ event.preventDefault();
27426
+ mentionRequest.current += 1;
27427
+ setTabMention(void 0);
27428
+ setMentionTabs([]);
27429
+ return;
27430
+ }
27431
+ if (event.key === "Enter") {
27432
+ event.preventDefault();
27433
+ const selectedTab = mentionTabs[mentionIndex];
27434
+ if (selectedTab) void chooseMentionedTab(selectedTab);
27435
+ return;
27436
+ }
27437
+ }
27438
+ if (event.key === "Enter" && !event.shiftKey) {
27439
+ event.preventDefault();
27440
+ event.currentTarget.form?.requestSubmit();
27441
+ }
27442
+ },
27443
+ onBlur: () => {
27444
+ mentionRequest.current += 1;
27445
+ setTabMention(void 0);
27446
+ setMentionTabs([]);
27447
+ },
27448
+ placeholder: t("composerPlaceholder"),
27449
+ disabled: snapshot.submitting,
27450
+ rows: 1
27451
+ }
27452
+ ) }),
27453
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "composer-footer", children: [
27454
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "composer-toolbar-start", children: [
27455
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tools-anchor", children: [
27456
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { ref: toolsTrigger, type: "button", className: `composer-icon-button${toolsOpen ? " active" : ""}`, "aria-label": t("addContext"), "aria-expanded": toolsOpen, onClick: () => setToolsOpen((open) => !open), children: /* @__PURE__ */ jsxRuntimeExports.jsx(PlusIcon, {}) }),
27457
+ toolsOpen ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tools-menu", ref: toolsMenu, children: [
27458
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tools-menu-section", children: [
27459
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "tools-menu-label", children: t("addToMessage") }),
27460
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => {
27461
+ setToolsOpen(false);
27462
+ void attachPage("page");
27463
+ }, children: [
27464
+ /* @__PURE__ */ jsxRuntimeExports.jsx(PageIcon, {}),
27465
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
27466
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: t("currentPage") }),
27467
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: t("sharePageContent") })
27468
+ ] })
27469
+ ] }),
27470
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => {
27471
+ setToolsOpen(false);
27472
+ void attachPage("selection");
27473
+ }, children: [
27474
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SelectionIcon, {}),
27475
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
27476
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: t("selectedText") }),
27477
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: t("shareSelection") })
27478
+ ] })
27370
27479
  ] }),
27371
- /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {})
27372
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
27373
27480
  /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => {
27374
27481
  setToolsOpen(false);
27375
- void runControlAction(() => client.bindActiveTab("read"));
27482
+ void addScreenshot();
27376
27483
  }, children: [
27377
- /* @__PURE__ */ jsxRuntimeExports.jsx(EyeIcon, {}),
27484
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CameraIcon, {}),
27378
27485
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
27379
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Read this tab" }),
27380
- /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: "Keep page content available" })
27486
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: t("screenshot") }),
27487
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: t("captureVisibleTab") })
27381
27488
  ] })
27382
27489
  ] }),
27383
27490
  /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => {
27384
27491
  setToolsOpen(false);
27385
- void runControlAction(() => client.bindActiveTab("act"));
27492
+ fileInput.current?.click();
27386
27493
  }, children: [
27387
- /* @__PURE__ */ jsxRuntimeExports.jsx(CursorIcon, {}),
27494
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FileIcon, {}),
27388
27495
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
27389
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Control this tab" }),
27390
- /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: "Allow browser actions with safeguards" })
27496
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: t("file") }),
27497
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: t("supportedFileTypes") })
27498
+ ] })
27499
+ ] })
27500
+ ] }),
27501
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tools-menu-section tab-access-section", children: [
27502
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "tools-menu-label", children: t("liveTabAccess") }),
27503
+ snapshot.tabBinding ? /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: "active-access", onClick: () => {
27504
+ setToolsOpen(false);
27505
+ void runControlAction(() => client.unbindActiveTab());
27506
+ }, children: [
27507
+ snapshot.tabBinding.mode === "act" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CursorIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(EyeIcon, {}),
27508
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
27509
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: snapshot.tabBinding.mode === "act" ? t("controlEnabled") : t("readEnabled") }),
27510
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: t("stopAccess") })
27511
+ ] }),
27512
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {})
27513
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
27514
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => {
27515
+ setToolsOpen(false);
27516
+ void runControlAction(() => client.bindActiveTab("read"));
27517
+ }, children: [
27518
+ /* @__PURE__ */ jsxRuntimeExports.jsx(EyeIcon, {}),
27519
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
27520
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: t("readThisTab") }),
27521
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: t("keepPageAvailable") })
27522
+ ] })
27523
+ ] }),
27524
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: () => {
27525
+ setToolsOpen(false);
27526
+ void runControlAction(() => client.bindActiveTab("act"));
27527
+ }, children: [
27528
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CursorIcon, {}),
27529
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
27530
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: t("controlThisTab") }),
27531
+ /* @__PURE__ */ jsxRuntimeExports.jsx("small", { children: t("controlSafeguards") })
27532
+ ] })
27391
27533
  ] })
27392
27534
  ] })
27393
27535
  ] })
27394
- ] })
27395
- ] }) : null
27396
- ] }),
27397
- /* @__PURE__ */ jsxRuntimeExports.jsx(
27398
- "textarea",
27399
- {
27400
- ref: textarea,
27401
- value: draft,
27402
- onChange: (event) => {
27403
- setDraft(event.target.value);
27404
- void updateTabMention(event.target.value, event.target.selectionStart);
27405
- },
27406
- onSelect: (event) => void updateTabMention(event.currentTarget.value, event.currentTarget.selectionStart),
27407
- onPaste: (event) => {
27408
- if (!event.clipboardData.files.length) return;
27409
- event.preventDefault();
27410
- void addFiles(event.clipboardData.files);
27411
- },
27412
- onKeyDown: (event) => {
27413
- if (tabMention) {
27414
- if (event.key === "ArrowDown" && mentionTabs.length) {
27415
- event.preventDefault();
27416
- setMentionIndex((current) => (current + 1) % mentionTabs.length);
27417
- return;
27418
- }
27419
- if (event.key === "ArrowUp" && mentionTabs.length) {
27420
- event.preventDefault();
27421
- setMentionIndex((current) => (current - 1 + mentionTabs.length) % mentionTabs.length);
27422
- return;
27423
- }
27424
- if (event.key === "Escape") {
27425
- event.preventDefault();
27426
- mentionRequest.current += 1;
27427
- setTabMention(void 0);
27428
- setMentionTabs([]);
27429
- return;
27430
- }
27431
- if (event.key === "Enter") {
27432
- event.preventDefault();
27433
- const selectedTab = mentionTabs[mentionIndex];
27434
- if (selectedTab) void chooseMentionedTab(selectedTab);
27435
- return;
27436
- }
27437
- }
27438
- if (event.key === "Enter" && !event.shiftKey) {
27439
- event.preventDefault();
27440
- event.currentTarget.form?.requestSubmit();
27441
- }
27442
- },
27443
- onBlur: () => {
27444
- mentionRequest.current += 1;
27445
- setTabMention(void 0);
27446
- setMentionTabs([]);
27447
- },
27448
- placeholder: "Message xopc… Use @ to mention a tab",
27449
- disabled: snapshot.submitting,
27450
- rows: 1
27451
- }
27452
- ),
27453
- snapshot.runId ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "send-button stop-button", "aria-label": "Stop response", title: snapshot.stopping ? "Stopping response" : "Stop response", disabled: snapshot.stopping, onClick: () => void runControlAction(() => client.abort()), children: /* @__PURE__ */ jsxRuntimeExports.jsx(StopIcon, {}) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "send-button", type: "submit", "aria-label": "Send message", title: "Send message", disabled: snapshot.submitting || snapshot.pendingDelivery || snapshot.sessionLoading || !draft.trim() && attachments.length === 0 || !snapshot.endpointReady || Boolean(tabMention), children: /* @__PURE__ */ jsxRuntimeExports.jsx(SendIcon, {}) })
27454
- ] }),
27455
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "composer-footer", children: [
27456
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "composer-state", children: [
27457
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `mini-status ${snapshot.endpointReady ? "online" : ""}` }),
27458
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: snapshot.stopping ? "Stopping…" : snapshot.submitting ? "Sending…" : snapshot.pendingDelivery ? "Queued" : connectionText }),
27459
- snapshot.tabBinding ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "access-pill", children: [
27460
- snapshot.tabBinding.mode === "act" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CursorIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(EyeIcon, {}),
27461
- snapshot.tabBinding.mode === "act" ? "Control" : "Read"
27462
- ] }) : null
27536
+ ] }) : null
27537
+ ] }),
27538
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "composer-state", children: [
27539
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `mini-status ${snapshot.endpointReady ? "online" : ""}` }),
27540
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "composer-state-label", children: snapshot.stopping ? t("stopping") : snapshot.submitting ? t("sending") : snapshot.pendingDelivery ? t("queued") : connectionText }),
27541
+ snapshot.tabBinding ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "access-pill", children: [
27542
+ snapshot.tabBinding.mode === "act" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CursorIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(EyeIcon, {}),
27543
+ snapshot.tabBinding.mode === "act" ? t("control") : t("read")
27544
+ ] }) : null
27545
+ ] })
27463
27546
  ] }),
27464
- snapshot.modelConfig ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "model-controls", children: [
27465
- /* @__PURE__ */ jsxRuntimeExports.jsx("select", { "aria-label": "Model", title: "Session model", value: snapshot.modelConfig.model, disabled: snapshot.modelConfig.fixedModel || Boolean(snapshot.runId), onChange: (event) => void runControlAction(() => client.updateModel(event.target.value)), children: snapshot.models.map((model) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: model.id, children: model.name }, model.id)) }),
27466
- thinkingOptions.length ? /* @__PURE__ */ jsxRuntimeExports.jsx("select", { "aria-label": "Thinking level", title: "Thinking level", value: snapshot.modelConfig.thinkingLevel, disabled: Boolean(snapshot.runId), onChange: (event) => void runControlAction(() => client.updateThinking(event.target.value)), children: thinkingOptions.map((level) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: level, children: level }, level)) }) : null
27467
- ] }) : null
27547
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "composer-toolbar-end", children: [
27548
+ snapshot.modelConfig ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "model-controls", children: [
27549
+ /* @__PURE__ */ jsxRuntimeExports.jsx("select", { "aria-label": t("model"), title: t("sessionModel"), value: snapshot.modelConfig.model, disabled: Boolean(snapshot.runId), onChange: (event) => void runControlAction(() => client.updateModel(event.target.value)), children: snapshot.models.map((model) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: model.id, children: model.name }, model.id)) }),
27550
+ thinkingOptions.length ? /* @__PURE__ */ jsxRuntimeExports.jsx("select", { "aria-label": t("thinkingLevel"), title: t("thinkingLevel"), value: snapshot.modelConfig.thinkingLevel, disabled: Boolean(snapshot.runId), onChange: (event) => void runControlAction(() => client.updateThinking(event.target.value)), children: thinkingOptions.map((level) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: level, children: thinkingLabel(level) }, level)) }) : null
27551
+ ] }) : null,
27552
+ snapshot.runId ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "send-button stop-button", "aria-label": t("stopResponse"), title: snapshot.stopping ? t("stoppingResponse") : t("stopResponse"), disabled: snapshot.stopping, onClick: () => void runControlAction(() => client.abort()), children: /* @__PURE__ */ jsxRuntimeExports.jsx(StopIcon, {}) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "send-button", type: "submit", "aria-label": t("sendMessage"), title: t("sendMessage"), disabled: snapshot.submitting || snapshot.pendingDelivery || snapshot.sessionLoading || !draft.trim() && attachments.length === 0 || !snapshot.endpointReady || Boolean(tabMention), children: /* @__PURE__ */ jsxRuntimeExports.jsx(SendIcon, {}) })
27553
+ ] })
27468
27554
  ] })
27469
27555
  ]
27470
27556
  }
@@ -27472,6 +27558,7 @@ function ChatPanel() {
27472
27558
  ] });
27473
27559
  }
27474
27560
 
27561
+ const THEME_STORAGE_KEY = "xopc.sidepanel.theme";
27475
27562
  function isSidePanelTheme(value) {
27476
27563
  return value === "light" || value === "dark";
27477
27564
  }
@@ -27479,66 +27566,15 @@ function applySidePanelTheme(theme) {
27479
27566
  document.documentElement.dataset.theme = theme;
27480
27567
  document.documentElement.style.colorScheme = theme;
27481
27568
  }
27482
- function applySystemSidePanelTheme() {
27483
- applySidePanelTheme(globalThis.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light");
27569
+ async function loadSidePanelThemePreference() {
27570
+ const stored = await chrome.storage.local.get(THEME_STORAGE_KEY);
27571
+ const theme = isSidePanelTheme(stored[THEME_STORAGE_KEY]) ? stored[THEME_STORAGE_KEY] : globalThis.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
27572
+ applySidePanelTheme(theme);
27573
+ return theme;
27484
27574
  }
27485
- function isGatewayThemePageUrl(value, gatewayUrl) {
27486
- try {
27487
- const url = new URL(value);
27488
- if (url.protocol === "http:" && (url.hostname === "127.0.0.1" || url.hostname === "localhost")) {
27489
- return true;
27490
- }
27491
- return Boolean(gatewayUrl && url.origin === new URL(gatewayUrl).origin);
27492
- } catch {
27493
- return false;
27494
- }
27495
- }
27496
- async function pageTheme(tabId) {
27497
- try {
27498
- const [{ result } = {}] = await chrome.scripting.executeScript({
27499
- target: { tabId },
27500
- func: () => ({
27501
- theme: document.documentElement.dataset.theme
27502
- })
27503
- });
27504
- return isSidePanelTheme(result?.theme) ? result.theme : void 0;
27505
- } catch {
27506
- return void 0;
27507
- }
27508
- }
27509
- async function syncSidePanelTheme(gatewayUrl) {
27510
- const tabs = (await chrome.tabs.query({})).filter((tab) => {
27511
- return typeof tab.id === "number" && Boolean(tab.url) && isGatewayThemePageUrl(tab.url, gatewayUrl);
27512
- }).sort((left, right) => Number(right.active) - Number(left.active));
27513
- for (const tab of tabs) {
27514
- const theme = await pageTheme(tab.id);
27515
- if (theme) {
27516
- applySidePanelTheme(theme);
27517
- return;
27518
- }
27519
- }
27520
- }
27521
- function watchSidePanelTheme(gatewayUrl) {
27522
- const sync = () => {
27523
- void syncSidePanelTheme(gatewayUrl);
27524
- };
27525
- const onUpdated = (_tabId, change) => {
27526
- if (change.status === "complete" || change.url) sync();
27527
- };
27528
- const onVisibilityChange = () => {
27529
- if (document.visibilityState === "visible") sync();
27530
- };
27531
- sync();
27532
- window.addEventListener("focus", sync);
27533
- document.addEventListener("visibilitychange", onVisibilityChange);
27534
- chrome.tabs.onActivated.addListener(sync);
27535
- chrome.tabs.onUpdated.addListener(onUpdated);
27536
- return () => {
27537
- window.removeEventListener("focus", sync);
27538
- document.removeEventListener("visibilitychange", onVisibilityChange);
27539
- chrome.tabs.onActivated.removeListener(sync);
27540
- chrome.tabs.onUpdated.removeListener(onUpdated);
27541
- };
27575
+ async function saveSidePanelThemePreference(theme) {
27576
+ await chrome.storage.local.set({ [THEME_STORAGE_KEY]: theme });
27577
+ applySidePanelTheme(theme);
27542
27578
  }
27543
27579
 
27544
27580
  async function reconnectBrowserBridge() {
@@ -27547,23 +27583,23 @@ async function reconnectBrowserBridge() {
27547
27583
  function pairingErrorMessage(cause) {
27548
27584
  const message = cause instanceof Error ? cause.message : String(cause);
27549
27585
  if (message === "Gateway site permission was not granted") {
27550
- return "Chrome needs access to the Gateway address. Select Connect again and allow the site permission.";
27586
+ return t("errorGatewayPermissionHelp");
27551
27587
  }
27552
27588
  if (message === "PAIRING_EXPIRED" || message === "Pairing expired" || message === "Pairing invitation has expired") {
27553
- return "This invitation expired. Create and copy a new invitation from Device access.";
27589
+ return t("errorInvitationExpiredHelp");
27554
27590
  }
27555
27591
  if (message === "PAIRING_DEVICE_MISMATCH") {
27556
- return "This invitation was created for a phone. Create a browser invitation instead.";
27592
+ return t("errorPairingDeviceMismatch");
27557
27593
  }
27558
27594
  if (message === "No Gateway route could be reached") {
27559
- return "None of the Gateway addresses responded. Check that the Gateway is online and its secure connection is active.";
27595
+ return t("errorNoReachableGatewayRouteHelp");
27560
27596
  }
27561
27597
  if (message.startsWith("PAIRING_ROUTE_PERMISSION_REQUIRED:")) {
27562
- return "The current Gateway address did not respond. Select Continue to allow the next address listed in the invitation.";
27598
+ return t("errorNextRoutePermission");
27563
27599
  }
27564
27600
  return message;
27565
27601
  }
27566
- function SidePanelApp() {
27602
+ function SidePanelApp({ initialLocale, initialTheme }) {
27567
27603
  const [profile, setProfile] = reactExports.useState();
27568
27604
  const [invitation, setInvitation] = reactExports.useState("");
27569
27605
  const [confirmationCode, setConfirmationCode] = reactExports.useState("");
@@ -27571,8 +27607,13 @@ function SidePanelApp() {
27571
27607
  const [error, setError] = reactExports.useState("");
27572
27608
  const [localInvitation, setLocalInvitation] = reactExports.useState("");
27573
27609
  const [nextOrigin, setNextOrigin] = reactExports.useState();
27610
+ const [locale, setLocale] = reactExports.useState(initialLocale);
27611
+ const [theme, setTheme] = reactExports.useState(initialTheme);
27612
+ const [settingsOpen, setSettingsOpen] = reactExports.useState(false);
27574
27613
  const initializationStarted = reactExports.useRef(false);
27575
27614
  const pairingStarted = reactExports.useRef(false);
27615
+ const settingsMenu = reactExports.useRef(null);
27616
+ const settingsTrigger = reactExports.useRef(null);
27576
27617
  const connect = reactExports.useCallback(async (value, origin) => {
27577
27618
  if (pairingStarted.current) return;
27578
27619
  pairingStarted.current = true;
@@ -27622,7 +27663,31 @@ function SidePanelApp() {
27622
27663
  await connect(local.invitation);
27623
27664
  })();
27624
27665
  }, [connect]);
27625
- reactExports.useEffect(() => watchSidePanelTheme(profile?.gatewayUrl), [profile?.gatewayUrl]);
27666
+ reactExports.useEffect(() => {
27667
+ function closeSettings(event) {
27668
+ const target = event.target;
27669
+ if (!settingsMenu.current?.contains(target) && !settingsTrigger.current?.contains(target)) {
27670
+ setSettingsOpen(false);
27671
+ }
27672
+ }
27673
+ function closeOnEscape(event) {
27674
+ if (event.key === "Escape") setSettingsOpen(false);
27675
+ }
27676
+ document.addEventListener("mousedown", closeSettings);
27677
+ document.addEventListener("keydown", closeOnEscape);
27678
+ return () => {
27679
+ document.removeEventListener("mousedown", closeSettings);
27680
+ document.removeEventListener("keydown", closeOnEscape);
27681
+ };
27682
+ }, []);
27683
+ async function changeTheme(nextTheme) {
27684
+ setTheme(nextTheme);
27685
+ await saveSidePanelThemePreference(nextTheme);
27686
+ }
27687
+ async function changeLocale(nextLocale) {
27688
+ await saveExtensionLocalePreference(nextLocale);
27689
+ setLocale(nextLocale);
27690
+ }
27626
27691
  async function disconnect() {
27627
27692
  await revokeAndForgetProfile();
27628
27693
  setProfile(void 0);
@@ -27634,7 +27699,7 @@ function SidePanelApp() {
27634
27699
  setError("");
27635
27700
  try {
27636
27701
  const local = await discoverLocalGateway({ force: true });
27637
- if (!local) throw new Error("No local xopc Gateway was found");
27702
+ if (!local) throw new Error(t("errorNoLocalGateway"));
27638
27703
  setLocalInvitation(local.invitation);
27639
27704
  await connect(local.invitation);
27640
27705
  } catch (cause) {
@@ -27664,44 +27729,96 @@ function SidePanelApp() {
27664
27729
  ] }),
27665
27730
  state !== "online" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "status", children: [
27666
27731
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `status-dot ${error ? "error" : ""}` }),
27667
- state === "pairing" ? "Pairing" : state === "loading" ? "Starting" : "Setup required"
27668
- ] }) : null
27732
+ state === "pairing" ? t("statusPairing") : state === "loading" ? t("statusStarting") : t("statusSetupRequired")
27733
+ ] }) : null,
27734
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "header-actions", children: [
27735
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
27736
+ "button",
27737
+ {
27738
+ ref: settingsTrigger,
27739
+ className: "header-icon-button",
27740
+ onClick: () => setSettingsOpen((open) => !open),
27741
+ title: t("settings"),
27742
+ "aria-label": t("settings"),
27743
+ "aria-expanded": settingsOpen,
27744
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsIcon, {})
27745
+ }
27746
+ ),
27747
+ settingsOpen ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: settingsMenu, className: "settings-menu", children: [
27748
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "settings-menu-title", children: t("settings") }),
27749
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "settings-group", children: [
27750
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "settings-label", children: [
27751
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SunIcon, {}),
27752
+ t("appearance")
27753
+ ] }),
27754
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "settings-options", children: [
27755
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: theme === "light" ? "active" : "", onClick: () => void changeTheme("light"), children: [
27756
+ t("themeLight"),
27757
+ theme === "light" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CheckIcon, {}) : null
27758
+ ] }),
27759
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: theme === "dark" ? "active" : "", onClick: () => void changeTheme("dark"), children: [
27760
+ t("themeDark"),
27761
+ theme === "dark" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CheckIcon, {}) : null
27762
+ ] })
27763
+ ] })
27764
+ ] }),
27765
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "settings-group", children: [
27766
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "settings-label", children: [
27767
+ /* @__PURE__ */ jsxRuntimeExports.jsx(GlobeIcon, {}),
27768
+ t("language")
27769
+ ] }),
27770
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "settings-options", children: [
27771
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: locale === "zh-CN" ? "active" : "", onClick: () => void changeLocale("zh-CN"), children: [
27772
+ t("languageChinese"),
27773
+ locale === "zh-CN" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CheckIcon, {}) : null
27774
+ ] }),
27775
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: locale === "en" ? "active" : "", onClick: () => void changeLocale("en"), children: [
27776
+ t("languageEnglish"),
27777
+ locale === "en" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CheckIcon, {}) : null
27778
+ ] })
27779
+ ] })
27780
+ ] }),
27781
+ state === "online" && profile ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "settings-disconnect", onClick: () => void disconnect(), title: t("disconnectGateway", profile.gatewayName), children: t("disconnect") }) : null
27782
+ ] }) : null
27783
+ ] })
27669
27784
  ] }),
27670
27785
  state !== "online" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "center", children: [
27671
- state === "loading" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "muted", children: "Loading…" }) : null,
27786
+ state === "loading" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "muted", children: t("loading") }) : null,
27672
27787
  state === "unpaired" || state === "pairing" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "card", children: [
27673
- /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { children: "Connect a Gateway" }),
27788
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { children: t("connectToXopc") }),
27674
27789
  localInvitation ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
27675
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "muted", children: "A local xopc Gateway was found. xopc connects automatically on this computer." }),
27676
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "primary local-connect", onClick: () => void connect(localInvitation), disabled: state === "pairing", children: state === "pairing" ? "Connecting…" : "Retry local connection" }),
27677
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pairing-divider", children: "or paste an invitation" })
27790
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "muted", children: t("localGatewayFound") }),
27791
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "primary local-connect", onClick: () => void connect(localInvitation), disabled: state === "pairing", children: state === "pairing" ? t("connecting") : t("retryLocalConnection") }),
27792
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pairing-divider", children: t("pairingLinkDivider") })
27678
27793
  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
27679
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "muted", children: "Connect locally, or paste the one-time invitation copied from Device access." }),
27680
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "primary local-connect", onClick: () => void connectLocalGateway(), disabled: state === "pairing", children: state === "pairing" ? "Connecting…" : "Connect local Gateway" }),
27681
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pairing-divider", children: "or paste an invitation" })
27794
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "muted", children: t("connectGatewayHelp") }),
27795
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "primary local-connect", onClick: () => void connectLocalGateway(), disabled: state === "pairing", children: state === "pairing" ? t("connecting") : t("connectLocalGateway") }),
27796
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pairing-divider", children: t("pairingLinkDivider") })
27682
27797
  ] }),
27683
27798
  /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "field", children: [
27684
- "One-time invitation",
27799
+ t("pairingLink"),
27685
27800
  /* @__PURE__ */ jsxRuntimeExports.jsx("textarea", { rows: 4, value: invitation, onChange: (event) => setInvitation(event.target.value), disabled: state === "pairing" })
27686
27801
  ] }),
27687
27802
  confirmationCode ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
27688
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "muted", children: "Confirm this code in the Gateway:" }),
27803
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "muted", children: t("confirmGatewayCode") }),
27689
27804
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "approval-code", children: confirmationCode })
27690
27805
  ] }) : null,
27691
27806
  error ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "error-text", children: error }) : null,
27692
27807
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "actions", children: [
27693
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "primary", onClick: () => void connect(invitation, nextOrigin), disabled: !invitation.trim() || state === "pairing", children: state === "pairing" ? "Waiting for approval…" : nextOrigin ? `Allow ${new URL(nextOrigin).host} and continue` : "Connect" }),
27694
- invitation ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => void cancelPairing(), disabled: state === "pairing", children: "Cancel pairing" }) : null
27808
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "primary", onClick: () => void connect(invitation, nextOrigin), disabled: !invitation.trim() || state === "pairing", children: state === "pairing" ? t("waitingForApproval") : nextOrigin ? t("allowGatewayAndContinue", new URL(nextOrigin).host) : t("connect") }),
27809
+ invitation ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => void cancelPairing(), disabled: state === "pairing", children: t("cancelPairing") }) : null
27695
27810
  ] })
27696
27811
  ] }) : null
27697
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChatPanel, {}),
27698
- state === "online" && profile ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "disconnect-link", onClick: disconnect, title: `Disconnect ${profile.gatewayName}`, children: "Disconnect" }) : null
27812
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChatPanel, {})
27699
27813
  ] });
27700
27814
  }
27701
27815
 
27702
27816
  const root = document.getElementById("root");
27703
27817
  if (!root) throw new Error("Side Panel root is missing");
27704
- applySystemSidePanelTheme();
27818
+ const [locale, theme] = await Promise.all([
27819
+ loadExtensionLocalePreference(),
27820
+ loadSidePanelThemePreference()
27821
+ ]);
27705
27822
  clientExports.createRoot(root).render(
27706
- /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.StrictMode, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(SidePanelApp, {}) })
27823
+ /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.StrictMode, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(SidePanelApp, { initialLocale: locale, initialTheme: theme }) })
27707
27824
  );