local-operator-ui 0.4.0 → 0.4.2

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.
package/README.md CHANGED
@@ -13,7 +13,9 @@
13
13
 
14
14
  <br />
15
15
 
16
- <video src="https://github.com/user-attachments/assets/a7a97781-40a0-46ce-8f53-09ff6fb9ae86" alt="Local Operator Dashboard Example" autoplay loop muted playsinline></video>
16
+ <div style="display: flex; justify-content: center; border-radius: 8px; overflow: hidden;">
17
+ <img src="./resources/preview-example.gif" alt="Local Operator UI Dashboard Example" style="width: 100%; max-width: 800px;">
18
+ </div>
17
19
 
18
20
  <br />
19
21
 
Binary file
Binary file
@@ -29848,12 +29848,10 @@ class WebSocketClient extends EventEmitter {
29848
29848
  const error = new Error(
29849
29849
  `WebSocket connection failed after ${this.options.maxReconnectAttempts} attempts. In cooldown period.`
29850
29850
  );
29851
- console.error(error.message);
29852
29851
  return Promise.reject(error);
29853
29852
  }
29854
29853
  if (this.isReconnecting) {
29855
29854
  const error = new Error("WebSocket reconnection already in progress");
29856
- console.error(error.message);
29857
29855
  return Promise.reject(error);
29858
29856
  }
29859
29857
  const now = Date.now();
@@ -30202,11 +30200,7 @@ class WebSocketClient extends EventEmitter {
30202
30200
  if (this.status === "disconnected" || this.status === "reconnecting") {
30203
30201
  this.reconnectAttempts++;
30204
30202
  this.reconnectTimeoutId = null;
30205
- this.connect().catch((error) => {
30206
- console.error(
30207
- `Reconnection attempt ${this.reconnectAttempts} failed for ${this.messageId}:`,
30208
- error
30209
- );
30203
+ this.connect().catch(() => {
30210
30204
  if (this.reconnectAttempts >= (this.options.maxReconnectAttempts || 0)) {
30211
30205
  this.enterCooldown();
30212
30206
  } else if (this.status !== "failed") {
@@ -35040,13 +35034,21 @@ const useScrollToBottom = (dependencies = [], threshold = 100, buttonThreshold =
35040
35034
  while (parent) {
35041
35035
  const { overflow, overflowY } = window.getComputedStyle(parent);
35042
35036
  if (overflow === "auto" || overflow === "scroll" || overflowY === "auto" || overflowY === "scroll") {
35043
- containerRef.current = parent;
35044
- checkScrollPosition();
35037
+ if (containerRef.current !== parent) {
35038
+ containerRef.current = parent;
35039
+ checkScrollPosition();
35040
+ parent.addEventListener("scroll", handleScroll, { passive: true });
35041
+ }
35045
35042
  break;
35046
35043
  }
35047
35044
  parent = parent.parentElement;
35048
35045
  }
35049
- }, [checkScrollPosition]);
35046
+ return () => {
35047
+ if (containerRef.current) {
35048
+ containerRef.current.removeEventListener("scroll", handleScroll);
35049
+ }
35050
+ };
35051
+ }, [checkScrollPosition, handleScroll]);
35050
35052
  return {
35051
35053
  ref,
35052
35054
  containerRef,
@@ -46908,29 +46910,33 @@ const ModelSelect = ({
46908
46910
  getOptionLabel: (option) => option.name,
46909
46911
  isOptionEqualToValue: (option, value2) => option.id === value2.id,
46910
46912
  groupBy: (option) => getProviderFromId(option.id),
46911
- renderOption: (props, option) => /* @__PURE__ */ jsxRuntimeExports.jsx("li", { ...props, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(OptionContainer, { children: [
46912
- /* @__PURE__ */ jsxRuntimeExports.jsxs(OptionLabelContainer, { children: [
46913
- /* @__PURE__ */ jsxRuntimeExports.jsx(OptionLabel, { children: option.name }),
46914
- option.recommended && /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Recommended based on community usage and feedback", children: /* @__PURE__ */ jsxRuntimeExports.jsx(RecommendedStar, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(FontAwesomeIcon, { icon: faStar, size: "xs" }) }) })
46915
- ] }),
46916
- option.description && /* @__PURE__ */ jsxRuntimeExports.jsx(OptionDescription, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
46917
- Markdown,
46918
- {
46919
- components: {
46920
- a: ({ href, children }) => /* @__PURE__ */ jsxRuntimeExports.jsx(
46921
- "a",
46922
- {
46923
- href,
46924
- target: "_blank",
46925
- rel: "noopener noreferrer",
46926
- children
46927
- }
46928
- )
46929
- },
46930
- children: option.description
46931
- }
46932
- ) })
46933
- ] }) }),
46913
+ renderOption: (props, option) => {
46914
+ const { key, ...rest } = props;
46915
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("li", { ...rest, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(OptionContainer, { children: [
46916
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(OptionLabelContainer, { children: [
46917
+ /* @__PURE__ */ jsxRuntimeExports.jsx(OptionLabel, { children: option.name }),
46918
+ option.recommended && // @ts-ignore
46919
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Recommended based on community usage and feedback", children: /* @__PURE__ */ jsxRuntimeExports.jsx(RecommendedStar, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(FontAwesomeIcon, { icon: faStar, size: "xs" }) }) })
46920
+ ] }),
46921
+ option.description && /* @__PURE__ */ jsxRuntimeExports.jsx(OptionDescription, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
46922
+ Markdown,
46923
+ {
46924
+ components: {
46925
+ a: ({ href, children }) => /* @__PURE__ */ jsxRuntimeExports.jsx(
46926
+ "a",
46927
+ {
46928
+ href,
46929
+ target: "_blank",
46930
+ rel: "noopener noreferrer",
46931
+ children
46932
+ }
46933
+ )
46934
+ },
46935
+ children: option.description
46936
+ }
46937
+ ) })
46938
+ ] }) }, key);
46939
+ },
46934
46940
  freeSolo: allowCustom,
46935
46941
  renderInput: (params) => /* @__PURE__ */ jsxRuntimeExports.jsx(
46936
46942
  TextField,
@@ -107462,12 +107468,11 @@ const useWebSocketMessage = (options) => {
107462
107468
  reactExports.useEffect(() => {
107463
107469
  if (autoConnect && messageId && baseUrl && connectRef.current && !clientRef.current && !globalClientsRef.current[messageId]) {
107464
107470
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().substring(11, 23);
107465
- console.log(`[${timestamp}] Auto-connecting WebSocket for ${messageId}`);
107466
107471
  globalClientsRef.current[messageId] = true;
107467
107472
  setTimeout(() => {
107468
107473
  if (connectRef.current) {
107469
107474
  connectRef.current().catch((err) => {
107470
- console.error(
107475
+ console.warn(
107471
107476
  `[${timestamp}] Error auto-connecting WebSocket for ${messageId}:`,
107472
107477
  err
107473
107478
  );
@@ -110376,6 +110381,7 @@ const ChatPage = () => {
110376
110381
  },
110377
110382
  [addMessage, conversationId, setCurrentJobId, setIsLoading]
110378
110383
  );
110384
+ const { data: configData } = useConfig();
110379
110385
  const handleSendMessage = reactExports.useCallback(
110380
110386
  async (content2, attachments) => {
110381
110387
  if (!conversationId) return;
@@ -110404,16 +110410,15 @@ const ChatPage = () => {
110404
110410
  ([_2, value]) => value !== void 0 && value !== null
110405
110411
  )
110406
110412
  );
110413
+ const resolvedHosting = !agentData?.hosting || agentData.hosting === "default" || agentData.hosting.trim() === "" ? configData?.values.hosting || "" : agentData.hosting;
110414
+ const resolvedModel = !agentData?.model || agentData.model === "default" || agentData.model.trim() === "" ? configData?.values.model_name || "" : agentData.model;
110407
110415
  const jobDetails = await apiClient.chat.processAgentChatAsync(
110408
110416
  conversationId,
110409
110417
  {
110410
- hosting: agentData?.hosting || "openrouter",
110411
- // Use agent's hosting or default
110412
- model: agentData?.model || "google/gemini-2.0-flash-001",
110413
- // Use agent's model or default
110418
+ hosting: resolvedHosting,
110419
+ model: resolvedModel,
110414
110420
  prompt: content2,
110415
110421
  persist_conversation: true,
110416
- // Persist conversation history
110417
110422
  user_message_id: userMessage.id,
110418
110423
  options: Object.keys(filteredOptions).length > 0 ? filteredOptions : void 0,
110419
110424
  attachments: attachments.length > 0 ? attachments : void 0
@@ -110452,7 +110457,8 @@ ${error2.stack || ""}`;
110452
110457
  setIsLoading,
110453
110458
  agentData,
110454
110459
  apiClient,
110455
- setCurrentJobId
110460
+ setCurrentJobId,
110461
+ configData
110456
110462
  ]
110457
110463
  );
110458
110464
  const rawInfoContent = reactExports.useMemo(() => {
@@ -8,7 +8,7 @@
8
8
  <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
9
9
  <meta http-equiv="Content-Security-Policy"
10
10
  content="default-src 'self'; connect-src 'self' http://localhost:1111 http://127.0.0.1:1111 ws://localhost:1111 ws://127.0.0.1:1111; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: http://localhost:1111 http://127.0.0.1:1111; media-src 'self' http://localhost:1111 http://127.0.0.1:1111; font-src 'self'" />
11
- <script type="module" crossorigin src="./assets/index-Cu-Ty0jC.js"></script>
11
+ <script type="module" crossorigin src="./assets/index-DHvoVezG.js"></script>
12
12
  <link rel="modulepreload" crossorigin href="./assets/error-boundary-HK_94N6W.js">
13
13
  <link rel="stylesheet" crossorigin href="./assets/error-boundary-B9KBGB-g.css">
14
14
  <link rel="stylesheet" crossorigin href="./assets/index-94BTFuhu.css">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-operator-ui",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "productName": "Local Operator",
5
5
  "description": "User interface for AI agent assistants that run Python code safely on your device through an intuitive chat interface. Supports local and cloud LLM models with built-in security features.",
6
6
  "main": "./out/main/index.js",
@@ -167,8 +167,18 @@
167
167
  "gatekeeperAssess": false,
168
168
  "entitlements": "build/entitlements.mac.plist",
169
169
  "entitlementsInherit": "build/entitlements.mac.plist",
170
- "target": ["dmg", "zip"],
171
- "artifactName": "${name}-${version}-${arch}.${ext}"
170
+ "target": [
171
+ {
172
+ "target": "dmg",
173
+ "arch": ["universal"]
174
+ },
175
+ {
176
+ "target": "zip",
177
+ "arch": ["universal"]
178
+ }
179
+ ],
180
+ "artifactName": "${name}-${version}-${arch}.${ext}",
181
+ "x64ArchFiles": "Contents/Resources/python/**/*"
172
182
  },
173
183
  "dmg": {
174
184
  "sign": false,