local-operator-ui 0.4.4 → 0.4.6

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.
Binary file
Binary file
@@ -35031,7 +35031,7 @@ const useJobPolling = ({
35031
35031
  currentExecution
35032
35032
  };
35033
35033
  };
35034
- const useScrollToBottom = (dependencies = [], threshold = 100, buttonThreshold = 50) => {
35034
+ const useScrollToBottom = (dependencies = [], threshold = 150, buttonThreshold = 50) => {
35035
35035
  const ref = reactExports.useRef(null);
35036
35036
  const containerRef = reactExports.useRef(null);
35037
35037
  const [shouldScrollToBottom, setShouldScrollToBottom] = reactExports.useState(true);
@@ -35149,7 +35149,7 @@ const SidebarContainer$6 = styled$1(Box)({
35149
35149
  width: 280,
35150
35150
  height: "100%"
35151
35151
  });
35152
- const ContentContainer$4 = styled$1(Box)({
35152
+ const ContentContainer$5 = styled$1(Box)({
35153
35153
  flexGrow: 1,
35154
35154
  height: "100%",
35155
35155
  overflow: "hidden"
@@ -35161,7 +35161,7 @@ styled$1(Divider)(({ theme }) => ({
35161
35161
  const ChatLayout = ({ sidebar, content: content2 }) => {
35162
35162
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Container$1, { children: [
35163
35163
  /* @__PURE__ */ jsxRuntimeExports.jsx(SidebarContainer$6, { children: sidebar }),
35164
- /* @__PURE__ */ jsxRuntimeExports.jsx(ContentContainer$4, { children: content2 })
35164
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ContentContainer$5, { children: content2 })
35165
35165
  ] });
35166
35166
  };
35167
35167
  const normalizePath = (path2) => {
@@ -35480,7 +35480,7 @@ const useUpdateAgent = () => {
35480
35480
  }
35481
35481
  });
35482
35482
  };
35483
- const StyledDialog = styled$1(Dialog)(({ theme }) => ({
35483
+ const StyledDialog$1 = styled$1(Dialog)(({ theme }) => ({
35484
35484
  "& .MuiBackdrop-root": {
35485
35485
  backdropFilter: "blur(8px)",
35486
35486
  backgroundColor: alpha(theme.palette.background.default, 0.7)
@@ -35527,7 +35527,7 @@ const StyledDialog = styled$1(Dialog)(({ theme }) => ({
35527
35527
  backgroundColor: theme.palette.background.default
35528
35528
  }
35529
35529
  }));
35530
- const StyledDialogActions = styled$1(DialogActions)(({ theme }) => ({
35530
+ const StyledDialogActions$1 = styled$1(DialogActions)(({ theme }) => ({
35531
35531
  padding: "12px 24px 20px 24px",
35532
35532
  gap: 12,
35533
35533
  borderTop: `1px solid ${alpha(theme.palette.divider, 0.1)}`
@@ -35555,7 +35555,7 @@ const BaseDialog = ({
35555
35555
  dialogProps = {}
35556
35556
  }) => {
35557
35557
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
35558
- StyledDialog,
35558
+ StyledDialog$1,
35559
35559
  {
35560
35560
  open: open2,
35561
35561
  onClose,
@@ -35565,7 +35565,7 @@ const BaseDialog = ({
35565
35565
  children: [
35566
35566
  /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle, { children: typeof title === "string" ? /* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", fontWeight: 600, children: title }) : title }),
35567
35567
  /* @__PURE__ */ jsxRuntimeExports.jsx(DialogContent, { children }),
35568
- actions && /* @__PURE__ */ jsxRuntimeExports.jsx(StyledDialogActions, { children: actions })
35568
+ actions && /* @__PURE__ */ jsxRuntimeExports.jsx(StyledDialogActions$1, { children: actions })
35569
35569
  ]
35570
35570
  }
35571
35571
  );
@@ -48437,7 +48437,7 @@ const MessageInput = ({
48437
48437
  StyledTextField$2,
48438
48438
  {
48439
48439
  fullWidth: true,
48440
- placeholder: isInputDisabled ? "Agent is busy" : "Ask me for help",
48440
+ placeholder: isInputDisabled ? "Agent is busy" : "Ask me for help",
48441
48441
  value: newMessage,
48442
48442
  onChange: (e) => setNewMessage(e.target.value),
48443
48443
  onKeyDown: handleKeyDown,
@@ -85917,7 +85917,7 @@ const AgentAvatar$2 = styled$1(Avatar)(({ theme }) => ({
85917
85917
  boxShadow: "none",
85918
85918
  border: "none"
85919
85919
  }));
85920
- const ContentContainer$3 = styled$1(Box)(() => ({
85920
+ const ContentContainer$4 = styled$1(Box)(() => ({
85921
85921
  display: "flex",
85922
85922
  flexDirection: "column",
85923
85923
  gap: 8,
@@ -86111,7 +86111,7 @@ const LoadingIndicator = ({ status, currentExecution, scrollToBottom }) => {
86111
86111
  ref,
86112
86112
  scrollToBottom: scrollToBottomHook,
86113
86113
  isFarFromBottom
86114
- } = useScrollToBottom([status, currentExecution, message2], 100, 50);
86114
+ } = useScrollToBottom([status, currentExecution, message2], 150, 50);
86115
86115
  reactExports.useEffect(() => {
86116
86116
  if (codeSnippet && isCodeExpanded && !isStreaming && !isFarFromBottom) {
86117
86117
  requestAnimationFrame(() => {
@@ -86139,7 +86139,7 @@ const LoadingIndicator = ({ status, currentExecution, scrollToBottom }) => {
86139
86139
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(LoadingContainer$5, { children: [
86140
86140
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref, style: { height: 0, width: 0 } }),
86141
86141
  /* @__PURE__ */ jsxRuntimeExports.jsx(AgentAvatar$2, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(FontAwesomeIcon, { icon: faRobot, size: "sm" }) }),
86142
- /* @__PURE__ */ jsxRuntimeExports.jsxs(ContentContainer$3, { children: [
86142
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(ContentContainer$4, { children: [
86143
86143
  /* @__PURE__ */ jsxRuntimeExports.jsx(StatusContainer, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(StatusText, { variant: "body2", children: [
86144
86144
  /* @__PURE__ */ jsxRuntimeExports.jsx(StatusTextContent, { children: message2 ? message2 : `${statusText}` }),
86145
86145
  /* @__PURE__ */ jsxRuntimeExports.jsxs(StatusControls, { children: [
@@ -105955,12 +105955,12 @@ const MessageContainer = ({
105955
105955
  }) => {
105956
105956
  return /* @__PURE__ */ jsxRuntimeExports.jsx(StyledContainer$1, { isUser, children });
105957
105957
  };
105958
- const ContentContainer$2 = styled$1(Box)(() => ({
105958
+ const ContentContainer$3 = styled$1(Box)(() => ({
105959
105959
  marginBottom: 16
105960
105960
  }));
105961
105961
  const MessageContent = ({ content: content2 }) => {
105962
105962
  if (!content2) return null;
105963
- return /* @__PURE__ */ jsxRuntimeExports.jsx(ContentContainer$2, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(MarkdownRenderer, { content: content2 }) });
105963
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(ContentContainer$3, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(MarkdownRenderer, { content: content2 }) });
105964
105964
  };
105965
105965
  const ControlsContainer = styled$1(Box, {
105966
105966
  shouldForwardProp: (prop) => prop !== "isUser"
@@ -108493,7 +108493,8 @@ const MessagePaper = ({
108493
108493
  children,
108494
108494
  content: content2,
108495
108495
  message: message2,
108496
- onMessageComplete
108496
+ onMessageComplete,
108497
+ isLastMessage
108497
108498
  }) => {
108498
108499
  const theme = useTheme$1();
108499
108500
  if (isUser) {
@@ -108552,29 +108553,34 @@ const MessagePaper = ({
108552
108553
  }
108553
108554
  return children;
108554
108555
  }, [children]);
108555
- const {
108556
- ref: scrollRef,
108557
- forceScrollToBottom,
108558
- isNearBottom
108559
- } = useScrollToBottom(
108560
- // Dependencies that should trigger scroll evaluation
108561
- [content2, isStreamable, message2?.is_complete],
108562
- 150,
108563
- // Threshold for "near bottom" in pixels
108564
- 50
108565
- // Threshold for showing scroll button
108566
- );
108567
- reactExports.useEffect(() => {
108568
- if (!isStreamable || !message2) return;
108569
- const scrollInterval = setInterval(() => {
108570
- if (isNearBottom) {
108571
- forceScrollToBottom();
108572
- }
108573
- }, 350);
108574
- return () => {
108575
- clearInterval(scrollInterval);
108576
- };
108577
- }, [isStreamable, message2, forceScrollToBottom, isNearBottom]);
108556
+ let scrollRef = null;
108557
+ let forceScrollToBottom = () => {
108558
+ };
108559
+ let isNearBottom = false;
108560
+ if (isLastMessage) {
108561
+ const scrollHook = useScrollToBottom(
108562
+ // Dependencies that should trigger scroll evaluation
108563
+ [content2, isStreamable, message2?.is_complete],
108564
+ 250,
108565
+ // Threshold for "near bottom" in pixels
108566
+ 50
108567
+ // Threshold for showing scroll button
108568
+ );
108569
+ scrollRef = scrollHook.ref;
108570
+ forceScrollToBottom = scrollHook.forceScrollToBottom;
108571
+ isNearBottom = scrollHook.isNearBottom;
108572
+ reactExports.useEffect(() => {
108573
+ if (!isStreamable || !message2) return;
108574
+ const scrollInterval = setInterval(() => {
108575
+ if (isNearBottom) {
108576
+ forceScrollToBottom();
108577
+ }
108578
+ }, 350);
108579
+ return () => {
108580
+ clearInterval(scrollInterval);
108581
+ };
108582
+ }, [isStreamable, message2, forceScrollToBottom, isNearBottom]);
108583
+ }
108578
108584
  const streamingMessageComponent = reactExports.useMemo(() => {
108579
108585
  if (!isStreamable || !message2) return null;
108580
108586
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -108588,20 +108594,13 @@ const MessagePaper = ({
108588
108594
  conversationId: message2.conversation_id,
108589
108595
  refetchOnComplete: true,
108590
108596
  onComplete: () => {
108591
- forceScrollToBottom();
108592
108597
  if (onMessageComplete) {
108593
108598
  onMessageComplete();
108594
108599
  }
108595
108600
  }
108596
108601
  }
108597
108602
  );
108598
- }, [
108599
- isStreamable,
108600
- message2,
108601
- messageStyles,
108602
- onMessageComplete,
108603
- forceScrollToBottom
108604
- ]);
108603
+ }, [isStreamable, message2, messageStyles, onMessageComplete]);
108605
108604
  const regularMessageComponents = reactExports.useMemo(() => {
108606
108605
  if (isStreamable) return null;
108607
108606
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
@@ -108749,7 +108748,7 @@ const getAttachmentUrl = (client, path2) => {
108749
108748
  return path2;
108750
108749
  };
108751
108750
  const MessageItem = reactExports.memo(
108752
- ({ message: message2, onMessageComplete }) => {
108751
+ ({ message: message2, onMessageComplete, isLastMessage }) => {
108753
108752
  const shouldHide = (message2.action === "DONE" || message2.action === "ASK") && message2.execution_type === "action" && message2.task_classification === "conversation" || !message2.message && (!message2.files || message2.files.length === 0) && !message2.code && !message2.stdout && !message2.stderr && !message2.logging && message2.is_complete;
108754
108753
  if (shouldHide) {
108755
108754
  return null;
@@ -108802,6 +108801,7 @@ const MessageItem = reactExports.memo(
108802
108801
  content: message2.message,
108803
108802
  message: message2,
108804
108803
  onMessageComplete,
108804
+ isLastMessage,
108805
108805
  children: [
108806
108806
  message2.files && message2.files.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { mb: 2 }, children: message2.files.filter((file) => isImage(file)).map((file) => /* @__PURE__ */ jsxRuntimeExports.jsx(
108807
108807
  ImageAttachment,
@@ -108868,6 +108868,7 @@ const MessageItem = reactExports.memo(
108868
108868
  content: message2.message,
108869
108869
  message: message2,
108870
108870
  onMessageComplete,
108871
+ isLastMessage,
108871
108872
  children: [
108872
108873
  message2.files && message2.files.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { mb: 2 }, children: message2.files.filter((file) => isImage(file)).map((file) => /* @__PURE__ */ jsxRuntimeExports.jsx(
108873
108874
  ImageAttachment,
@@ -109012,7 +109013,7 @@ const MessagesView = ({
109012
109013
  messages.length > 0 ? (
109013
109014
  // Only render visible messages plus a buffer
109014
109015
  /* @__PURE__ */ jsxRuntimeExports.jsxs(CenteredMessagesContainer, { children: [
109015
- messages.map((message2) => /* @__PURE__ */ jsxRuntimeExports.jsx(
109016
+ messages.map((message2, index2) => /* @__PURE__ */ jsxRuntimeExports.jsx(
109016
109017
  MessageItem,
109017
109018
  {
109018
109019
  message: {
@@ -109020,6 +109021,7 @@ const MessagesView = ({
109020
109021
  conversation_id: conversationId
109021
109022
  // Add conversation ID to message
109022
109023
  },
109024
+ isLastMessage: index2 === messages.length - 1,
109023
109025
  onMessageComplete: () => {
109024
109026
  if (refetch) {
109025
109027
  refetch();
@@ -110502,6 +110504,7 @@ const PlaceholderView = ({
110502
110504
  ] });
110503
110505
  };
110504
110506
  const ChatPage = () => {
110507
+ const didAutoScrollRef = React$1.useRef(false);
110505
110508
  const { agentId, navigateToAgent, clearAgentId } = useAgentRouteParam();
110506
110509
  const navigate = useNavigate();
110507
110510
  const queryClient2 = useQueryClient();
@@ -110555,7 +110558,7 @@ const ChatPage = () => {
110555
110558
  isFarFromBottom,
110556
110559
  scrollToBottom,
110557
110560
  forceScrollToBottom
110558
- } = useScrollToBottom(scrollDependencies, 100);
110561
+ } = useScrollToBottom(scrollDependencies, 150);
110559
110562
  reactExports.useEffect(() => {
110560
110563
  if (effectiveAgentId && agents.length > 0) {
110561
110564
  const agentExists = agents.some((agent) => agent.id === effectiveAgentId);
@@ -110585,6 +110588,39 @@ const ChatPage = () => {
110585
110588
  forceScrollToBottom,
110586
110589
  getMessages
110587
110590
  ]);
110591
+ reactExports.useEffect(() => {
110592
+ didAutoScrollRef.current = false;
110593
+ }, [agentId]);
110594
+ reactExports.useEffect(() => {
110595
+ if (!didAutoScrollRef.current && agentId && !isLoadingMessages && messages.length > 0) {
110596
+ const container = messagesContainerRef.current;
110597
+ if (!container) return;
110598
+ let lastScrollHeight = container.scrollHeight;
110599
+ let stableCounter = 0;
110600
+ const maxStableCount = 3;
110601
+ const interval = setInterval(() => {
110602
+ const currentScrollHeight = container.scrollHeight;
110603
+ if (currentScrollHeight !== lastScrollHeight) {
110604
+ lastScrollHeight = currentScrollHeight;
110605
+ stableCounter = 0;
110606
+ } else {
110607
+ stableCounter++;
110608
+ }
110609
+ if (stableCounter >= maxStableCount) {
110610
+ clearInterval(interval);
110611
+ forceScrollToBottom();
110612
+ didAutoScrollRef.current = true;
110613
+ }
110614
+ }, 100);
110615
+ setTimeout(() => clearInterval(interval), 5e3);
110616
+ }
110617
+ }, [
110618
+ agentId,
110619
+ isLoadingMessages,
110620
+ messages.length,
110621
+ forceScrollToBottom,
110622
+ messagesContainerRef
110623
+ ]);
110588
110624
  reactExports.useEffect(() => {
110589
110625
  if (conversationId) {
110590
110626
  checkForActiveJobs(conversationId);
@@ -112423,7 +112459,7 @@ const SidebarContainer$2 = styled$1(Box)({
112423
112459
  width: 280,
112424
112460
  height: "100%"
112425
112461
  });
112426
- const ContentContainer$1 = styled$1(Box)({
112462
+ const ContentContainer$2 = styled$1(Box)({
112427
112463
  flexGrow: 1,
112428
112464
  height: "100%",
112429
112465
  overflow: "hidden"
@@ -112517,7 +112553,7 @@ const AgentsPage = () => {
112517
112553
  onSelectAgent: handleSelectAgent
112518
112554
  }
112519
112555
  ) }),
112520
- /* @__PURE__ */ jsxRuntimeExports.jsx(ContentContainer$1, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(ContentInnerContainer, { children: [
112556
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ContentContainer$2, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(ContentInnerContainer, { children: [
112521
112557
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { position: "relative", width: "100%", mb: 2 }, children: [
112522
112558
  /* @__PURE__ */ jsxRuntimeExports.jsx(
112523
112559
  PageHeader,
@@ -113257,6 +113293,128 @@ const useOnboardingStore = create$1()(
113257
113293
  }
113258
113294
  )
113259
113295
  );
113296
+ const StyledDialog = styled$1(Dialog)(({ theme }) => ({
113297
+ "& .MuiBackdrop-root": {
113298
+ // Apply gradient to the backdrop for full screen effect
113299
+ background: "linear-gradient(135deg, rgba(30,30,30,1), rgba(15,15,15,0.95), rgba(5,5,5,1), rgba(56,201,106,0.1))",
113300
+ opacity: 1
113301
+ },
113302
+ "& .MuiPaper-root": {
113303
+ margin: 0,
113304
+ width: "100%",
113305
+ height: "100%",
113306
+ display: "flex",
113307
+ flexDirection: "column",
113308
+ justifyContent: "center",
113309
+ alignItems: "center",
113310
+ backgroundColor: "transparent",
113311
+ backgroundImage: "none",
113312
+ boxShadow: "none",
113313
+ overflow: "hidden",
113314
+ [theme.breakpoints.down("sm")]: {
113315
+ padding: theme.spacing(3, 2, 2)
113316
+ },
113317
+ [theme.breakpoints.up("xs")]: {
113318
+ minWidth: "300px"
113319
+ },
113320
+ [theme.breakpoints.up("sm")]: {
113321
+ minWidth: "400px"
113322
+ },
113323
+ [theme.breakpoints.up("md")]: {
113324
+ minWidth: "500px"
113325
+ }
113326
+ },
113327
+ "& .MuiDialogContent-root": {
113328
+ // Remove explicit background to let parent gradient show through
113329
+ "&:first-of-type": {
113330
+ paddingTop: 16
113331
+ },
113332
+ overflow: "auto",
113333
+ "&::-webkit-scrollbar": {
113334
+ width: "8px"
113335
+ },
113336
+ "&::-webkit-scrollbar-thumb": {
113337
+ backgroundColor: alpha(
113338
+ theme.palette.mode === "dark" ? theme.palette.common.white : theme.palette.common.black,
113339
+ 0.1
113340
+ ),
113341
+ borderRadius: "4px"
113342
+ }
113343
+ }
113344
+ }));
113345
+ const ContentContainer$1 = styled$1("div")(() => ({
113346
+ width: "100%",
113347
+ height: "100%",
113348
+ maxWidth: "780px",
113349
+ maxHeight: "680px",
113350
+ boxSizing: "border-box",
113351
+ display: "flex",
113352
+ flexDirection: "column",
113353
+ flexGrow: 1
113354
+ }));
113355
+ const StyledDialogTitle = styled$1(DialogTitle)(({ theme }) => ({
113356
+ margin: 0,
113357
+ paddingLeft: theme.spacing(3),
113358
+ // Add horizontal padding
113359
+ paddingRight: theme.spacing(3),
113360
+ // Add horizontal padding
113361
+ color: theme.palette.text.primary,
113362
+ // Remove explicit background
113363
+ "& .MuiTypography-root": {
113364
+ fontWeight: 400,
113365
+ fontSize: "2rem"
113366
+ }
113367
+ }));
113368
+ const StyledDialogContent = styled$1(DialogContent)(({ theme }) => ({
113369
+ paddingLeft: theme.spacing(3),
113370
+ // Add horizontal padding
113371
+ paddingRight: theme.spacing(3),
113372
+ // Add horizontal padding
113373
+ flexGrow: 1,
113374
+ overflowY: "auto",
113375
+ width: "100%",
113376
+ // Ensure it takes full width before padding
113377
+ boxSizing: "border-box"
113378
+ }));
113379
+ const StyledDialogActions = styled$1(DialogActions)(({ theme }) => ({
113380
+ paddingLeft: theme.spacing(3),
113381
+ // Add horizontal padding
113382
+ paddingRight: theme.spacing(3),
113383
+ // Add horizontal padding
113384
+ marginTop: theme.spacing(2),
113385
+ display: "flex",
113386
+ justifyContent: "space-between",
113387
+ alignItems: "center",
113388
+ width: "100%",
113389
+ boxSizing: "border-box"
113390
+ // Remove explicit background
113391
+ }));
113392
+ const OnboardingDialog = ({
113393
+ open: open2,
113394
+ title,
113395
+ stepIndicators,
113396
+ children,
113397
+ actions,
113398
+ dialogProps = {}
113399
+ }) => {
113400
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
113401
+ StyledDialog,
113402
+ {
113403
+ open: open2,
113404
+ onClose: () => {
113405
+ },
113406
+ fullScreen: true,
113407
+ disableEscapeKeyDown: true,
113408
+ ...dialogProps,
113409
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs(ContentContainer$1, { children: [
113410
+ title && /* @__PURE__ */ jsxRuntimeExports.jsx(StyledDialogTitle, { children: title }),
113411
+ stepIndicators,
113412
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StyledDialogContent, { children }),
113413
+ actions && /* @__PURE__ */ jsxRuntimeExports.jsx(StyledDialogActions, { children: actions })
113414
+ ] })
113415
+ }
113416
+ );
113417
+ };
113260
113418
  const fadeIn = keyframes`
113261
113419
  from {
113262
113420
  opacity: 0;
@@ -113278,46 +113436,6 @@ const pulse = keyframes`
113278
113436
  transform: scale(1);
113279
113437
  }
113280
113438
  `;
113281
- styled$1(Dialog)(({ theme }) => ({
113282
- "& .MuiBackdrop-root": {
113283
- backdropFilter: "blur(12px)",
113284
- backgroundColor: alpha(theme.palette.background.default, 0.7)
113285
- },
113286
- "& .MuiDialog-paper": {
113287
- borderRadius: 16,
113288
- maxWidth: 700,
113289
- width: "100%",
113290
- backgroundColor: alpha(theme.palette.background.paper, 0.95),
113291
- boxShadow: `0 15px 50px ${alpha(theme.palette.primary.main, 0.2)}, 0 10px 40px ${alpha(theme.palette.common.black, 0.4)}`,
113292
- border: `1px solid ${alpha(theme.palette.primary.main, 0.1)}`,
113293
- overflow: "hidden",
113294
- animation: `${fadeIn} 0.4s ease-out`
113295
- }
113296
- }));
113297
- styled$1(DialogTitle)(({ theme }) => ({
113298
- padding: "24px 28px 16px 28px",
113299
- fontSize: "1.35rem",
113300
- fontWeight: 700,
113301
- textAlign: "center",
113302
- borderBottom: `1px solid ${alpha(theme.palette.divider, 0.1)}`,
113303
- background: `linear-gradient(to right, ${alpha(theme.palette.primary.main, 0.05)}, ${alpha(theme.palette.background.default, 0.8)}, ${alpha(theme.palette.primary.main, 0.05)})`,
113304
- color: theme.palette.primary.main
113305
- }));
113306
- styled$1(DialogContent)(({ theme }) => ({
113307
- padding: "20px 28px",
113308
- display: "flex",
113309
- flexDirection: "column",
113310
- gap: theme.spacing(3),
113311
- backgroundColor: theme.palette.background.default,
113312
- animation: `${fadeIn} 0.5s ease-out`
113313
- }));
113314
- styled$1(DialogActions)(({ theme }) => ({
113315
- padding: "16px 28px 24px 28px",
113316
- justifyContent: "space-between",
113317
- gap: 16,
113318
- borderTop: `1px solid ${alpha(theme.palette.divider, 0.1)}`,
113319
- background: `linear-gradient(to right, ${alpha(theme.palette.primary.main, 0.05)}, ${alpha(theme.palette.background.default, 0.8)}, ${alpha(theme.palette.primary.main, 0.05)})`
113320
- }));
113321
113439
  styled$1(Button)(({ theme }) => ({
113322
113440
  borderRadius: 8,
113323
113441
  padding: "10px 24px",
@@ -113371,28 +113489,35 @@ const StepIndicatorContainer = styled$1(Box)(({ theme }) => ({
113371
113489
  padding: theme.spacing(1, 0)
113372
113490
  }));
113373
113491
  const StepDot = styled$1(Box, {
113374
- shouldForwardProp: (prop) => prop !== "active"
113375
- })(({ theme, active }) => ({
113376
- width: active ? 12 : 10,
113377
- height: active ? 12 : 10,
113378
- borderRadius: "50%",
113379
- backgroundColor: active ? theme.palette.primary.main : alpha(theme.palette.primary.main, 0.25),
113380
- boxShadow: active ? `0 0 10px ${alpha(theme.palette.primary.main, 0.5)}` : "none",
113381
- transition: "all 0.3s ease-in-out",
113382
- animation: active ? `${pulse} 2s infinite ease-in-out` : "none"
113383
- }));
113384
- const SectionContainer = styled$1(Paper)(({ theme }) => ({
113385
- padding: theme.spacing(3.5),
113386
- borderRadius: 12,
113387
- boxShadow: `0 4px 20px ${alpha(theme.palette.common.black, 0.08)}`,
113388
- border: `1px solid ${alpha(theme.palette.primary.main, 0.1)}`,
113389
- background: `linear-gradient(135deg, ${alpha(theme.palette.background.paper, 0.95)} 0%, ${alpha(theme.palette.background.paper, 0.85)} 100%)`,
113390
- animation: `${fadeIn} 0.5s ease-out`,
113391
- transition: "transform 0.3s ease-in-out, box-shadow 0.3s ease-in-out",
113392
- "&:hover": {
113393
- boxShadow: `0 6px 25px ${alpha(theme.palette.primary.main, 0.15)}`,
113394
- transform: "translateY(-2px)"
113492
+ shouldForwardProp: (prop) => prop !== "active" && prop !== "visited"
113493
+ })(({ theme, active, visited }) => {
113494
+ const baseSize = 10;
113495
+ const activeSize = 12;
113496
+ let backgroundColor = alpha(theme.palette.primary.main, 0.25);
113497
+ let boxShadow = "none";
113498
+ let animationStyle = "none";
113499
+ if (active) {
113500
+ backgroundColor = theme.palette.primary.main;
113501
+ boxShadow = `0 0 10px ${alpha(theme.palette.primary.main, 0.5)}`;
113502
+ animationStyle = `${pulse} 2s infinite ease-in-out`;
113503
+ } else if (visited) {
113504
+ backgroundColor = theme.palette.primary.main;
113395
113505
  }
113506
+ return {
113507
+ width: active ? activeSize : baseSize,
113508
+ height: active ? activeSize : baseSize,
113509
+ borderRadius: "50%",
113510
+ backgroundColor,
113511
+ boxShadow,
113512
+ transition: "all 0.3s ease-in-out",
113513
+ animation: animationStyle,
113514
+ cursor: visited ? "pointer" : "default",
113515
+ opacity: !visited && !active ? 0.4 : 1
113516
+ };
113517
+ });
113518
+ const SectionContainer = styled$1("div")(() => ({
113519
+ animation: `${fadeIn} 0.5s ease-out`,
113520
+ transition: "transform 0.3s ease-in-out, box-shadow 0.3s ease-in-out"
113396
113521
  }));
113397
113522
  const SectionTitle$2 = styled$1(Typography)(({ theme }) => ({
113398
113523
  fontSize: "1.25rem",
@@ -114223,13 +114348,6 @@ const ModelCredentialStep = () => {
114223
114348
  const { data: credentialsData } = useCredentials();
114224
114349
  const updateCredentialMutation = useUpdateCredential();
114225
114350
  const { refreshModels } = useModels();
114226
- reactExports.useEffect(() => {
114227
- if (credentialsData?.keys.includes(selectedCredential) && !saveSuccess) {
114228
- setError("This credential is already set");
114229
- } else {
114230
- setError("");
114231
- }
114232
- }, [selectedCredential, credentialsData, saveSuccess]);
114233
114351
  const handleCredentialChange = (event) => {
114234
114352
  setSelectedCredential(event.target.value);
114235
114353
  setCredentialValue("");
@@ -114282,7 +114400,6 @@ const ModelCredentialStep = () => {
114282
114400
  (cred) => cred.key === selectedCredential
114283
114401
  );
114284
114402
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(SectionContainer, { children: [
114285
- /* @__PURE__ */ jsxRuntimeExports.jsx(SectionTitle$2, { children: "Add Model Provider Credential" }),
114286
114403
  /* @__PURE__ */ jsxRuntimeExports.jsxs(SectionDescription$2, { children: [
114287
114404
  /* @__PURE__ */ jsxRuntimeExports.jsx(EmojiContainer, { children: "✨" }),
114288
114405
  " To unlock the power of AI models, you need to add at least one model provider API key. Your key will be securely stored on your device and used to access amazing AI capabilities!"
@@ -114472,10 +114589,15 @@ const ModelCredentialStep = () => {
114472
114589
  ] })
114473
114590
  ] });
114474
114591
  };
114592
+ const RECOMMENDED_CREDENTIAL = "TAVILY_API_KEY";
114475
114593
  const SearchApiStep = () => {
114476
114594
  const searchApiCredentials = CREDENTIAL_MANIFEST.filter(
114477
114595
  (cred) => cred.key === "TAVILY_API_KEY" || cred.key === "SERP_API_KEY"
114478
- );
114596
+ ).sort((a, b2) => {
114597
+ if (a.key === RECOMMENDED_CREDENTIAL) return -1;
114598
+ if (b2.key === RECOMMENDED_CREDENTIAL) return 1;
114599
+ return 0;
114600
+ });
114479
114601
  const [selectedCredential, setSelectedCredential] = reactExports.useState(
114480
114602
  searchApiCredentials[0]?.key || ""
114481
114603
  );
@@ -114486,13 +114608,6 @@ const SearchApiStep = () => {
114486
114608
  const successTimeoutRef = reactExports.useRef(null);
114487
114609
  const { data: credentialsData } = useCredentials();
114488
114610
  const updateCredentialMutation = useUpdateCredential();
114489
- reactExports.useEffect(() => {
114490
- if (credentialsData?.keys.includes(selectedCredential) && !saveSuccess) {
114491
- setError("This credential is already set");
114492
- } else {
114493
- setError("");
114494
- }
114495
- }, [selectedCredential, credentialsData, saveSuccess]);
114496
114611
  const handleCredentialChange = (event) => {
114497
114612
  setSelectedCredential(event.target.value);
114498
114613
  setCredentialValue("");
@@ -114541,7 +114656,6 @@ const SearchApiStep = () => {
114541
114656
  (cred) => cred.key === selectedCredential
114542
114657
  );
114543
114658
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(SectionContainer, { children: [
114544
- /* @__PURE__ */ jsxRuntimeExports.jsx(SectionTitle$2, { children: "Enable Web Search (Optional)" }),
114545
114659
  /* @__PURE__ */ jsxRuntimeExports.jsxs(SectionDescription$2, { children: [
114546
114660
  /* @__PURE__ */ jsxRuntimeExports.jsx(EmojiContainer, { children: "🌐" }),
114547
114661
  " Supercharge your AI agents with the ability to search the web for real-time information! This optional feature gives your AI assistants access to the latest data from across the internet."
@@ -114605,7 +114719,7 @@ const SearchApiStep = () => {
114605
114719
  value: selectedCredential,
114606
114720
  onChange: handleCredentialChange,
114607
114721
  label: "🔎 Search API Provider",
114608
- children: searchApiCredentials.map((cred) => /* @__PURE__ */ jsxRuntimeExports.jsx(MenuItem, { value: cred.key, children: cred.name }, cred.key))
114722
+ children: searchApiCredentials.map((cred) => /* @__PURE__ */ jsxRuntimeExports.jsx(MenuItem, { value: cred.key, children: cred.key === RECOMMENDED_CREDENTIAL ? `${cred.name} (Recommended)` : cred.name }, cred.key))
114609
114723
  }
114610
114724
  ),
114611
114725
  /* @__PURE__ */ jsxRuntimeExports.jsx(FormHelperText, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", alignItems: "center" }, children: [
@@ -114795,7 +114909,6 @@ const UserProfileStep = () => {
114795
114909
  setEmail(e.target.value);
114796
114910
  };
114797
114911
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(SectionContainer, { children: [
114798
- /* @__PURE__ */ jsxRuntimeExports.jsx(SectionTitle$2, { children: "Your Profile Information" }),
114799
114912
  /* @__PURE__ */ jsxRuntimeExports.jsxs(SectionDescription$2, { children: [
114800
114913
  /* @__PURE__ */ jsxRuntimeExports.jsx(EmojiContainer, { children: "🔒" }),
114801
114914
  " This information is stored locally and is only used to personalize your AI experience. It's never shared with any external services - your privacy matters to us!"
@@ -114811,7 +114924,7 @@ const UserProfileStep = () => {
114811
114924
  onChange: handleNameChange,
114812
114925
  error: !!nameError,
114813
114926
  helperText: nameError || "This will be used to create a personalized AI experience",
114814
- placeholder: "Enter your awesome name",
114927
+ placeholder: "Enter your name",
114815
114928
  required: true,
114816
114929
  InputProps: {
114817
114930
  startAdornment: /* @__PURE__ */ jsxRuntimeExports.jsx(EmojiContainer, { style: { marginRight: "8px" }, children: "😎" })
@@ -114870,7 +114983,7 @@ const UserProfileStep = () => {
114870
114983
  };
114871
114984
  const WelcomeStep = () => {
114872
114985
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { animation: "fadeIn 0.6s ease-out" }, children: [
114873
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
114986
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
114874
114987
  Typography,
114875
114988
  {
114876
114989
  variant: "body1",
@@ -114880,10 +114993,7 @@ const WelcomeStep = () => {
114880
114993
  fontWeight: 500,
114881
114994
  lineHeight: 1.6
114882
114995
  },
114883
- children: [
114884
- /* @__PURE__ */ jsxRuntimeExports.jsx(EmojiContainer, { children: "🚀" }),
114885
- " Welcome to Local Operator! We're thrilled to have you here! Let's set up your AI environment so you can start creating amazing things with AI agents."
114886
- ]
114996
+ children: "Let's set up your AI environment so you can start creating amazing things with AI agents!"
114887
114997
  }
114888
114998
  ),
114889
114999
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -114941,20 +115051,52 @@ const WelcomeStep = () => {
114941
115051
  const OnboardingModal = ({ open: open2 }) => {
114942
115052
  const { currentStep, setCurrentStep, completeOnboarding } = useOnboardingStore();
114943
115053
  const navigate = useNavigate();
115054
+ const steps = reactExports.useMemo(
115055
+ () => [
115056
+ OnboardingStep.WELCOME,
115057
+ OnboardingStep.USER_PROFILE,
115058
+ OnboardingStep.MODEL_CREDENTIAL,
115059
+ OnboardingStep.SEARCH_API,
115060
+ OnboardingStep.DEFAULT_MODEL,
115061
+ OnboardingStep.CREATE_AGENT,
115062
+ OnboardingStep.CONGRATULATIONS
115063
+ ],
115064
+ []
115065
+ );
115066
+ const stepTitles = {
115067
+ [OnboardingStep.WELCOME]: "Welcome to Local Operator",
115068
+ [OnboardingStep.USER_PROFILE]: "Set Up Your Profile",
115069
+ [OnboardingStep.MODEL_CREDENTIAL]: "Add Model Provider Credentials",
115070
+ [OnboardingStep.SEARCH_API]: "Enable Web Search (Recommended)",
115071
+ [OnboardingStep.DEFAULT_MODEL]: "Choose Your Default Model",
115072
+ [OnboardingStep.CREATE_AGENT]: "Create Your First Agent",
115073
+ [OnboardingStep.CONGRATULATIONS]: "🎉 Setup Complete!"
115074
+ };
115075
+ const [visitedSteps, setVisitedSteps] = reactExports.useState(
115076
+ /* @__PURE__ */ new Set([currentStep])
115077
+ );
115078
+ reactExports.useEffect(() => {
115079
+ setVisitedSteps((prev2) => {
115080
+ if (prev2.has(currentStep)) return prev2;
115081
+ const updated = new Set(prev2);
115082
+ updated.add(currentStep);
115083
+ return updated;
115084
+ });
115085
+ }, [currentStep]);
114944
115086
  const stepTitle = reactExports.useMemo(() => {
114945
115087
  switch (currentStep) {
114946
115088
  case OnboardingStep.WELCOME:
114947
- return "👋 Welcome to Local Operator";
115089
+ return "Welcome to Local Operator";
114948
115090
  case OnboardingStep.USER_PROFILE:
114949
- return "👤 Set Up Your Profile";
115091
+ return "Set Up Your Profile";
114950
115092
  case OnboardingStep.MODEL_CREDENTIAL:
114951
- return "🔑 Add Model Provider Credential";
115093
+ return "Add Model Provider Credentials";
114952
115094
  case OnboardingStep.SEARCH_API:
114953
- return "🔍 Enable Web Search (Optional)";
115095
+ return "Enable Web Search (Recommended)";
114954
115096
  case OnboardingStep.DEFAULT_MODEL:
114955
- return "🤖 Choose Your Default Model";
115097
+ return "Choose Your Default Model";
114956
115098
  case OnboardingStep.CREATE_AGENT:
114957
- return "Create Your First Agent";
115099
+ return "Create Your First Agent";
114958
115100
  case OnboardingStep.CONGRATULATIONS:
114959
115101
  return "🎉 Setup Complete!";
114960
115102
  default:
@@ -115042,48 +115184,58 @@ const OnboardingModal = ({ open: open2 }) => {
115042
115184
  const handleSkip = reactExports.useCallback(() => {
115043
115185
  if (currentStep === OnboardingStep.SEARCH_API) {
115044
115186
  setCurrentStep(OnboardingStep.DEFAULT_MODEL);
115187
+ } else if (currentStep === OnboardingStep.CREATE_AGENT) {
115188
+ setCurrentStep(OnboardingStep.CONGRATULATIONS);
115045
115189
  }
115046
115190
  }, [currentStep, setCurrentStep]);
115047
- const canSkip = currentStep === OnboardingStep.SEARCH_API;
115191
+ const canSkip = currentStep === OnboardingStep.SEARCH_API || currentStep === OnboardingStep.CREATE_AGENT;
115048
115192
  const canGoBack = currentStep !== OnboardingStep.WELCOME && currentStep !== OnboardingStep.CONGRATULATIONS;
115049
115193
  const nextButtonText = currentStep === OnboardingStep.CONGRATULATIONS ? "🚀 Get Started" : "Next →";
115050
- const renderStepIndicators = () => {
115051
- const steps = [
115052
- OnboardingStep.WELCOME,
115053
- OnboardingStep.USER_PROFILE,
115054
- OnboardingStep.MODEL_CREDENTIAL,
115055
- OnboardingStep.SEARCH_API,
115056
- OnboardingStep.DEFAULT_MODEL,
115057
- OnboardingStep.CREATE_AGENT,
115058
- OnboardingStep.CONGRATULATIONS
115059
- ];
115060
- return /* @__PURE__ */ jsxRuntimeExports.jsx(StepIndicatorContainer, { children: steps.map((step) => /* @__PURE__ */ jsxRuntimeExports.jsx(StepDot, { active: currentStep === step }, step)) });
115061
- };
115194
+ const hasCreatedAgent = Boolean(
115195
+ sessionStorage.getItem("onboarding_created_agent_id")
115196
+ );
115062
115197
  const dialogTitle = /* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", fontWeight: 600, children: stepTitle });
115063
115198
  const dialogActions = /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
115064
115199
  /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { children: canGoBack ? /* @__PURE__ */ jsxRuntimeExports.jsx(SecondaryButton, { onClick: handleBack, children: "Back" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Box, {}) }),
115065
115200
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", gap: 2, alignItems: "center" }, children: [
115066
115201
  canSkip && /* @__PURE__ */ jsxRuntimeExports.jsx(SkipButton, { onClick: handleSkip, children: "Skip" }),
115067
- /* @__PURE__ */ jsxRuntimeExports.jsx(PrimaryButton, { onClick: handleNext, children: nextButtonText })
115202
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
115203
+ PrimaryButton,
115204
+ {
115205
+ onClick: handleNext,
115206
+ disabled: currentStep === OnboardingStep.CREATE_AGENT && !hasCreatedAgent,
115207
+ children: nextButtonText
115208
+ }
115209
+ )
115068
115210
  ] })
115069
115211
  ] });
115070
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
115071
- BaseDialog,
115212
+ const stepIndicators = /* @__PURE__ */ jsxRuntimeExports.jsx(StepIndicatorContainer, { children: steps.map((step) => {
115213
+ const isActive = currentStep === step;
115214
+ const isVisited = visitedSteps.has(step);
115215
+ const canNavigate = isVisited && step !== currentStep;
115216
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: stepTitles[step], arrow: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
115217
+ Box,
115218
+ {
115219
+ onClick: () => {
115220
+ if (canNavigate) {
115221
+ setCurrentStep(step);
115222
+ }
115223
+ },
115224
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(StepDot, { active: isActive, visited: isVisited })
115225
+ }
115226
+ ) }, step);
115227
+ }) });
115228
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
115229
+ OnboardingDialog,
115072
115230
  {
115073
115231
  open: open2,
115074
- onClose: () => {
115075
- },
115076
115232
  title: dialogTitle,
115233
+ stepIndicators,
115077
115234
  actions: dialogActions,
115078
- maxWidth: "md",
115079
- fullWidth: true,
115080
115235
  dialogProps: {
115081
115236
  disableEscapeKeyDown: true
115082
115237
  },
115083
- children: [
115084
- renderStepIndicators(),
115085
- stepContent
115086
- ]
115238
+ children: stepContent
115087
115239
  }
115088
115240
  );
115089
115241
  };
@@ -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-ChdP-bmN.js"></script>
11
+ <script type="module" crossorigin src="./assets/index-BNTp0j7e.js"></script>
12
12
  <link rel="modulepreload" crossorigin href="./assets/error-boundary-BSN19Qr4.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.4",
3
+ "version": "0.4.6",
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",
@@ -76,7 +76,9 @@
76
76
  "@tanstack/react-query": "^5.67.1",
77
77
  "@tanstack/react-query-devtools": "^5.67.1",
78
78
  "date-fns": "^4.1.0",
79
+ "dmg-builder": "24.13.3",
79
80
  "dotenv": "^16.4.7",
81
+ "electron-builder-squirrel-windows": "26.0.12",
80
82
  "electron-debug": "^3.2.0",
81
83
  "electron-log": "^4.4.8",
82
84
  "electron-updater": "^6.1.4",
@@ -200,7 +202,8 @@
200
202
  "arch": ["x64", "arm64"]
201
203
  }
202
204
  ],
203
- "artifactName": "${name}-setup-${version}-${arch}.${ext}"
205
+ "artifactName": "${name}-setup-${version}-${arch}.${ext}",
206
+ "verifyUpdateCodeSignature": false
204
207
  },
205
208
  "nsis": {
206
209
  "createDesktopShortcut": true,