@iblai/iblai-js 1.20.6 → 1.20.8

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.
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import React__default, { useState, useEffect, forwardRef, createElement as createElement$3, useLayoutEffect, useMemo, createContext, useReducer, useRef, useImperativeHandle, useCallback, useContext, useId as useId$2, useDebugValue, cloneElement, lazy, Suspense, Component as Component$1 } from 'react';
3
+ import React__default, { useState, useEffect, forwardRef, createElement as createElement$3, useLayoutEffect, useMemo, useCallback, createContext, useReducer, useRef, useImperativeHandle, useContext, useId as useId$2, useDebugValue, cloneElement, lazy, Suspense, Component as Component$1 } from 'react';
4
4
  import { useGetUserMetadataQuery, useGetUserMetadataEdxQuery, useUpdateUserMetadataMutation, useUpdateUserMetadataEdxMutation, useUploadProfileImageMutation, useResetPasswordMutation, useSelfRetireMutation, useCreateUserInstitutionMutation, useGetUserInstitutionsQuery, useCreateUserEducationMutation, useUpdateUserEducationMutation, useDeleteUserEducationMutation, useGetUserEducationQuery, useCreateUserCompanyMutation, useGetUserCompaniesQuery, useCreateUserExperienceMutation, useUpdateUserExperienceMutation, useDeleteUserExperienceMutation, useGetUserExperienceQuery, useGetUserResumeQuery, useCreateUserResumeMutation, useGetMySubscriptionsQuery, useGetItemSubscriptionQuery, useCancelSubscriptionMutation, useCreateGlobalMemoryMutation, useGetMemsearchStatusQuery, useGetUserMemorySettingsQuery, useUpdateUserMemorySettingsMutation, useGetGlobalMemoriesQuery, useDeleteGlobalMemoryMutation, ChatPrivacyModeEnum, useGetUserChatPrivacySettingsQuery, useUpdateUserChatPrivacySettingsMutation, CHAT_PRIVACY_MODES, useGetTenantChatPrivacyConfigQuery, useInviteUserMutation, usePlatformInvitationsQuery, useCreateCatalogInvitationCourseBulkMutation, useGetCatalogInvitationsCourseQuery, useLazyPlatformUsersQuery, useLazyPlatformUserGroupsQuery, useGetPersonnalizedSearchQuery, useCreateCatalogInvitationProgramBulkMutation, useGetCatalogInvitationsProgramQuery, useUpdateUserRoleMutation, useUpdateUserStatusMutation, useUpdatePlatformUserRoleWithPoliciesMutation, usePlatformUsersQuery, isPoliciesResponse, platformApiSlice, featureTags, useLazyGetRbacTeamsAccessListQuery, useCreateRbacTeamsAccessMutation, useGetRbacGroupsQuery, usePlatformUserGroupsQuery, useCreateRbacGroupMutation, useUpdateRbacGroupMutation, useDeleteRbacGroupMutation, useCreatePlatformUserGroupMutation, useUpdatePlatformUserGroupMutation, useDeletePlatformUserGroupMutation, useGetRbacGroupDetailsQuery, useGetPlatformUserGroupDetailsQuery, useGetRbacPermissionsMutation, useGetRbacRolesQuery, useCreateRbacRoleMutation, useUpdateRbacRoleMutation, useDeleteRbacRoleMutation, useGetRbacRoleDetailsQuery, useGetRbacActionsDefinitionQuery, useGetRbacPoliciesQuery, useCreateRbacPolicyMutation, useUpdateRbacPolicyMutation, useDeleteRbacPolicyMutation, useGetRbacPolicyDetailsQuery, useGetWatchedGroupsQuery, useCreateWatchedGroupMutation, useUpdateWatchedGroupMutation, useDeleteWatchedGroupMutation, useAddWatchedUserMutation, useRemoveWatchedUserMutation, useAddWatcherMutation, useUpdateWatcherMutation, useDeleteWatcherMutation, useGetWatchedUsersQuery, useGetWatchersQuery, WATCHER_NOTIFICATION_EVENTS, WATCHER_NOTIFICATION_EVENT_LABELS, useDeleteApiKeyMutation, useGetApiKeysQuery, useCreateApiKeyMutation, useCreateLLMCredentialMutation, useGetCredentialsSchemaQuery, useGetMaskedLLMCredentialsQuery, useGetLlmsQuery, useDeleteIntegrationCredentialMutation, useDeleteCredentialMutation, useCreateIntegrationCredentialMutation, useGetIntegrationCredentialsSchemaQuery, useGetMaskedIntegrationCredentialsQuery, useGetAccountBillingInfoQuery, useUpdateAutoRechargeInfoMutation, useTriggerAutoRechargeMutation, useCreateStripeCustomerPortalMutation, useSetPlatformConfigurationsMutation, useGetPlatformConfigurationsQuery, useUpdatePlatformMembershipMutation, useGetPlatformMembershipQuery, useGetCustomDomainsQuery, useCreateCustomDomainMutation, useDeleteCustomDomainMutation, useGetStudentMentorCreationStatusQuery, useSetStudentMentorCreationStatusMutation, coreApiSlice, recommendationPromptTypeEnum, useGetRecommendedPromptsListQuery, useCreateRecommendedPromptMutation, useUpdateRecommendedPromptMutation, useDeleteRecommendedPromptMutation, useLazyGetPublicPlatformImageAssetFileUrlQuery, useUpdateTenantMetadataMutation, useCreatePlatformImageAssetMutation, useGetProviderConfigQuery, useCreateProviderConfigMutation, useDeleteProviderConfigMutation, useGetExternalMappingQuery, useGetCredentialsListQuery, useCreateExternalMappingMutation, useDeleteExternalMappingMutation, useGetMemsearchConfigQuery, useUpdateMemsearchConfigMutation, useUpdateTenantChatPrivacyConfigMutation, useGetCustomMentorsQuery, useGetStripeConnectStatusQuery, useStartStripeConnectOnboardingMutation, useLazyGetStripeConnectDashboardQuery, useGetAiSearchMentorsQuery, useListPaywallsQuery, useListPricesQuery, useCreatePriceMutation, useUpdatePriceMutation, useDeletePriceMutation, useGetPaywallConfigQuery, useEnablePaywallMutation, useUpdatePaywallMutation, useLazyGetCourseMetaDataQuery, useLazyGetCourseCompletionOutlinesQuery, useLazyGetCourseEligibilityQuery, useLazyGetEdxSSOTokenQuery, useCreateCourseEnrollmentMutation, useCreateStripeCheckoutSessionMutation, useLazyGetCourseProgressQuery, useLazyGetCourseCompletionQuery, useUpdateExamAttemptMutation, useStartExamMutation, useLazyGetExamInfoQuery, useUpdateUserProjectMutation, useGetTrainingDocumentsQuery, useGetVectorDocumentsQuery, useGetMentorMemoriesListQuery, useGetMemoryCategoriesAdminQuery, useDeleteMentorMemoryMutation, useUpdateMentorMemoryMutation, useCreateMentorMemoryMutation, useLazyGetConnectedServiceAuthUrlQuery, useGetMentorsQuery, useGetPublicMentorsQuery, useCreateUserProjectMutation, useDeleteUserProjectMutation, useGetUserProjectDetailsQuery, useEditTrainingDocumentMutation, useAddTrainingDocumentMutation, useLazyGetCredentialsQuery, useGetMentorSettingsQuery, useEditMentorMutation, useUploadLightLogoMutation, useUploadDarkLogoMutation, useUpdatePlatformInfoMutation, LOGO_ENDPOINTS, useGetUserProjectsQuery, useCreateSessionIdMutation, useGetMentorCategoriesQuery, useDeleteMentorMutation, useForkMentorMutation, useGetToolsQuery, useLazyGetMCPServersQuery, useOauthFindMutation, useLazyStartOAuthFlowQuery, useCreateMCPServerMutation, usePartialUpdateMCPServerMutation, useCreateMCPServerConnectionMutation, usePatchMCPServerConnectionMutation, useGetConnectedServicesQuery, useGetMCPServersQuery, useGetMCPServerConnectionsQuery, useUpdateMCPServerMutation, useDeleteMCPServerMutation, useEditMentorJsonMutation, useDisconnectServiceMutation, useGetPromptCategoriesQuery, PRIVACY_ACTIONS, PRIVACY_ENTITY_TYPES, useGetVoicesQuery, useGetVoiceQuery, useGetCallConfigurationsQuery, useCreateCallConfigurationMutation, useUpdateCallConfigurationMutation, useGetMentorPublicSettingsQuery, useGetChatHistoryFilterQuery, useGetChatHistoryQuery, useGetMentorSummariesQuery, useGetConversationMemoriesQuery, useCreatePromptMutation, useGetPromptsSearchQuery, useUpdatePromptMutation, useCreateRedirectTokenMutation, useGetShareableLinkQuery, useCreateShareableLinkMutation, useUpdateShareableLinkMutation, useCreateDisclaimerMutation, useUpdateDisclaimerMutation, useGetDisclaimersQuery, useUpdateRbacMentorAccessMutation, useGetRbacMentorAccessListQuery, useStarMentorMutation, useUnstarMentorMutation, useGetPersonnalizedMentorsQuery, useCreateCallCredentialsMutation, useGetGuidedPromptsQuery, useUpdateChatSessionSharedMutation, useUpdateMessageFeedbackMutation, useLazyGetPromptsSearchQuery, useLazyGetGuidedPromptsQuery, useGetPeriodicAgentsQuery, useGetPeriodicAgentLogsListQuery, useCreatePeriodicAgentMutation, useDeletePeriodicAgentMutation, useDeletePromptMutation, useDeleteTrainingDocumentMutation, useGetTrainingDocumentRetrainScheduleQuery, useCreateTrainingDocumentRetrainScheduleMutation, useCreateMemoryCategoryMutation, useUpdateMemoryCategoryMutation, useDeleteMemoryCategoryMutation, useUpdateArtifactMutation, useLazyGetArtifactVersionQuery, useLazyListArtifactVersionsQuery, useSetCurrentVersionMutation, useListArtifactVersionsQuery, useLazyGetArtifactQuery, useLazyListArtifactsQuery, useEditSessionMutation } from '@iblai/data-layer';
5
5
  import { getInitials, useTenantMetadata, WithPermissions, useStripeUpgrade, CHAT_AREA_SIZE, isAlphaNumeric32, checkRbacPermission, selectNumberOfActiveChatMessages, useUsername, selectStreaming, isLoggedIn, TOOLS, isSafariBrowser, useShowAttachment, useShowVoiceCall, useShowVoiceRecorder, useMentorSettings, selectShowingSharedChat, selectRbacPermissions, useShowFreeTrialDialog, useEmbedMode, chatInputSliceSelectors, useResponsive, useAccessingPublicRoute, useVisitingTenant, useModelFileUploadCapabilities, useChatFileUpload, useVoiceChat, selectAttachedFiles, MENTOR_CHAT_DOCUMENTS_EXTENSIONS, removeFile, chatInputSliceActions, chatActions, WithFormPermissions, useTenantContext, TimeTracker, advancedTabsProperties, defaultSessionIds, ANONYMOUS_USERNAME as ANONYMOUS_USERNAME$2, redirectToAuthSpaJoinTenant, selectActiveTab, useAxdToken, useWelcomeMessage, markdownToPlainText, useCachedSessionId, use402ErrorCheck, selectTokenEnabled, selectToken, useServiceWorker, useUserAgreement, useAdvancedChat, sendMessageToParentWebsite, useMentorTools, useFileDragDrop, eventBus, RemoteEvents, selectEnableChatActionsPopup, advancedTabs, isInIframe, getAuthSpaJoinUrl, addMessage, clearFiles, MENTOR_VISIBILITY } from '@iblai/web-utils';
6
6
  import { useDispatch, useSelector } from 'react-redux';
@@ -95448,138 +95448,13 @@ ToastTitle.displayName = Title.displayName;
95448
95448
  const ToastDescription = React.forwardRef(({ className, ...props }, ref) => (jsx(Description, { ref: ref, className: cn("text-sm opacity-90", className), ...props })));
95449
95449
  ToastDescription.displayName = Description.displayName;
95450
95450
 
95451
- function useStateMachine$2(initialState, machine) {
95452
- return React.useReducer((state, event) => {
95453
- const nextState = machine[state][event];
95454
- return nextState ?? state;
95455
- }, initialState);
95456
- }
95457
-
95458
- // src/presence.tsx
95459
- var Presence$1 = (props) => {
95460
- const { present, children } = props;
95461
- const presence = usePresence$1(present);
95462
- const child = typeof children === "function" ? children({ present: presence.isPresent }) : React.Children.only(children);
95463
- const ref = useComposedRefs$1(presence.ref, getElementRef$2(child));
95464
- const forceMount = typeof children === "function";
95465
- return forceMount || presence.isPresent ? React.cloneElement(child, { ref }) : null;
95466
- };
95467
- Presence$1.displayName = "Presence";
95468
- function usePresence$1(present) {
95469
- const [node, setNode] = React.useState();
95470
- const stylesRef = React.useRef(null);
95471
- const prevPresentRef = React.useRef(present);
95472
- const prevAnimationNameRef = React.useRef("none");
95473
- const initialState = present ? "mounted" : "unmounted";
95474
- const [state, send] = useStateMachine$2(initialState, {
95475
- mounted: {
95476
- UNMOUNT: "unmounted",
95477
- ANIMATION_OUT: "unmountSuspended"
95478
- },
95479
- unmountSuspended: {
95480
- MOUNT: "mounted",
95481
- ANIMATION_END: "unmounted"
95482
- },
95483
- unmounted: {
95484
- MOUNT: "mounted"
95485
- }
95486
- });
95487
- React.useEffect(() => {
95488
- const currentAnimationName = getAnimationName$1(stylesRef.current);
95489
- prevAnimationNameRef.current = state === "mounted" ? currentAnimationName : "none";
95490
- }, [state]);
95491
- useLayoutEffect2$1(() => {
95492
- const styles = stylesRef.current;
95493
- const wasPresent = prevPresentRef.current;
95494
- const hasPresentChanged = wasPresent !== present;
95495
- if (hasPresentChanged) {
95496
- const prevAnimationName = prevAnimationNameRef.current;
95497
- const currentAnimationName = getAnimationName$1(styles);
95498
- if (present) {
95499
- send("MOUNT");
95500
- } else if (currentAnimationName === "none" || styles?.display === "none") {
95501
- send("UNMOUNT");
95502
- } else {
95503
- const isAnimating = prevAnimationName !== currentAnimationName;
95504
- if (wasPresent && isAnimating) {
95505
- send("ANIMATION_OUT");
95506
- } else {
95507
- send("UNMOUNT");
95508
- }
95509
- }
95510
- prevPresentRef.current = present;
95511
- }
95512
- }, [present, send]);
95513
- useLayoutEffect2$1(() => {
95514
- if (node) {
95515
- let timeoutId;
95516
- const ownerWindow = node.ownerDocument.defaultView ?? window;
95517
- const handleAnimationEnd = (event) => {
95518
- const currentAnimationName = getAnimationName$1(stylesRef.current);
95519
- const isCurrentAnimation = currentAnimationName.includes(event.animationName);
95520
- if (event.target === node && isCurrentAnimation) {
95521
- send("ANIMATION_END");
95522
- if (!prevPresentRef.current) {
95523
- const currentFillMode = node.style.animationFillMode;
95524
- node.style.animationFillMode = "forwards";
95525
- timeoutId = ownerWindow.setTimeout(() => {
95526
- if (node.style.animationFillMode === "forwards") {
95527
- node.style.animationFillMode = currentFillMode;
95528
- }
95529
- });
95530
- }
95531
- }
95532
- };
95533
- const handleAnimationStart = (event) => {
95534
- if (event.target === node) {
95535
- prevAnimationNameRef.current = getAnimationName$1(stylesRef.current);
95536
- }
95537
- };
95538
- node.addEventListener("animationstart", handleAnimationStart);
95539
- node.addEventListener("animationcancel", handleAnimationEnd);
95540
- node.addEventListener("animationend", handleAnimationEnd);
95541
- return () => {
95542
- ownerWindow.clearTimeout(timeoutId);
95543
- node.removeEventListener("animationstart", handleAnimationStart);
95544
- node.removeEventListener("animationcancel", handleAnimationEnd);
95545
- node.removeEventListener("animationend", handleAnimationEnd);
95546
- };
95547
- } else {
95548
- send("ANIMATION_END");
95549
- }
95550
- }, [node, send]);
95551
- return {
95552
- isPresent: ["mounted", "unmountSuspended"].includes(state),
95553
- ref: React.useCallback((node2) => {
95554
- stylesRef.current = node2 ? getComputedStyle(node2) : null;
95555
- setNode(node2);
95556
- }, [])
95557
- };
95558
- }
95559
- function getAnimationName$1(styles) {
95560
- return styles?.animationName || "none";
95561
- }
95562
- function getElementRef$2(element) {
95563
- let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
95564
- let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
95565
- if (mayWarn) {
95566
- return element.ref;
95567
- }
95568
- getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
95569
- mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
95570
- if (mayWarn) {
95571
- return element.props.ref;
95572
- }
95573
- return element.props.ref || element.ref;
95574
- }
95575
-
95576
- var CHECKBOX_NAME = "Checkbox";
95577
- var [createCheckboxContext, createCheckboxScope] = createContextScope$1(CHECKBOX_NAME);
95578
- var [CheckboxProvider, useCheckboxContext] = createCheckboxContext(CHECKBOX_NAME);
95579
- var Checkbox$1 = React.forwardRef(
95451
+ var SWITCH_NAME = "Switch";
95452
+ var [createSwitchContext, createSwitchScope] = createContextScope$1(SWITCH_NAME);
95453
+ var [SwitchProvider, useSwitchContext] = createSwitchContext(SWITCH_NAME);
95454
+ var Switch$1 = React.forwardRef(
95580
95455
  (props, forwardedRef) => {
95581
95456
  const {
95582
- __scopeCheckbox,
95457
+ __scopeSwitch,
95583
95458
  name,
95584
95459
  checked: checkedProp,
95585
95460
  defaultChecked,
@@ -95588,7 +95463,7 @@ var Checkbox$1 = React.forwardRef(
95588
95463
  value = "on",
95589
95464
  onCheckedChange,
95590
95465
  form,
95591
- ...checkboxProps
95466
+ ...switchProps
95592
95467
  } = props;
95593
95468
  const [button, setButton] = React.useState(null);
95594
95469
  const composedRefs = useComposedRefs$1(forwardedRef, (node) => setButton(node));
@@ -95598,36 +95473,24 @@ var Checkbox$1 = React.forwardRef(
95598
95473
  prop: checkedProp,
95599
95474
  defaultProp: defaultChecked ?? false,
95600
95475
  onChange: onCheckedChange,
95601
- caller: CHECKBOX_NAME
95476
+ caller: SWITCH_NAME
95602
95477
  });
95603
- const initialCheckedStateRef = React.useRef(checked);
95604
- React.useEffect(() => {
95605
- const form2 = button?.form;
95606
- if (form2) {
95607
- const reset = () => setChecked(initialCheckedStateRef.current);
95608
- form2.addEventListener("reset", reset);
95609
- return () => form2.removeEventListener("reset", reset);
95610
- }
95611
- }, [button, setChecked]);
95612
- return /* @__PURE__ */ jsxs(CheckboxProvider, { scope: __scopeCheckbox, state: checked, disabled, children: [
95478
+ return /* @__PURE__ */ jsxs(SwitchProvider, { scope: __scopeSwitch, checked, disabled, children: [
95613
95479
  /* @__PURE__ */ jsx(
95614
- Primitive$3.button,
95480
+ Primitive$2.button,
95615
95481
  {
95616
95482
  type: "button",
95617
- role: "checkbox",
95618
- "aria-checked": isIndeterminate(checked) ? "mixed" : checked,
95483
+ role: "switch",
95484
+ "aria-checked": checked,
95619
95485
  "aria-required": required,
95620
95486
  "data-state": getState$3(checked),
95621
95487
  "data-disabled": disabled ? "" : void 0,
95622
95488
  disabled,
95623
95489
  value,
95624
- ...checkboxProps,
95490
+ ...switchProps,
95625
95491
  ref: composedRefs,
95626
- onKeyDown: composeEventHandlers$2(props.onKeyDown, (event) => {
95627
- if (event.key === "Enter") event.preventDefault();
95628
- }),
95629
- onClick: composeEventHandlers$2(props.onClick, (event) => {
95630
- setChecked((prevChecked) => isIndeterminate(prevChecked) ? true : !prevChecked);
95492
+ onClick: composeEventHandlers$1(props.onClick, (event) => {
95493
+ setChecked((prevChecked) => !prevChecked);
95631
95494
  if (isFormControl) {
95632
95495
  hasConsumerStoppedPropagationRef.current = event.isPropagationStopped();
95633
95496
  if (!hasConsumerStoppedPropagationRef.current) event.stopPropagation();
@@ -95636,7 +95499,7 @@ var Checkbox$1 = React.forwardRef(
95636
95499
  }
95637
95500
  ),
95638
95501
  isFormControl && /* @__PURE__ */ jsx(
95639
- CheckboxBubbleInput,
95502
+ SwitchBubbleInput,
95640
95503
  {
95641
95504
  control: button,
95642
95505
  bubbles: !hasConsumerStoppedPropagationRef.current,
@@ -95646,40 +95509,37 @@ var Checkbox$1 = React.forwardRef(
95646
95509
  required,
95647
95510
  disabled,
95648
95511
  form,
95649
- style: { transform: "translateX(-100%)" },
95650
- defaultChecked: isIndeterminate(defaultChecked) ? false : defaultChecked
95512
+ style: { transform: "translateX(-100%)" }
95651
95513
  }
95652
95514
  )
95653
95515
  ] });
95654
95516
  }
95655
95517
  );
95656
- Checkbox$1.displayName = CHECKBOX_NAME;
95657
- var INDICATOR_NAME$2 = "CheckboxIndicator";
95658
- var CheckboxIndicator = React.forwardRef(
95518
+ Switch$1.displayName = SWITCH_NAME;
95519
+ var THUMB_NAME$1 = "SwitchThumb";
95520
+ var SwitchThumb = React.forwardRef(
95659
95521
  (props, forwardedRef) => {
95660
- const { __scopeCheckbox, forceMount, ...indicatorProps } = props;
95661
- const context = useCheckboxContext(INDICATOR_NAME$2, __scopeCheckbox);
95662
- return /* @__PURE__ */ jsx(Presence$1, { present: forceMount || isIndeterminate(context.state) || context.state === true, children: /* @__PURE__ */ jsx(
95663
- Primitive$3.span,
95522
+ const { __scopeSwitch, ...thumbProps } = props;
95523
+ const context = useSwitchContext(THUMB_NAME$1, __scopeSwitch);
95524
+ return /* @__PURE__ */ jsx(
95525
+ Primitive$2.span,
95664
95526
  {
95665
- "data-state": getState$3(context.state),
95527
+ "data-state": getState$3(context.checked),
95666
95528
  "data-disabled": context.disabled ? "" : void 0,
95667
- ...indicatorProps,
95668
- ref: forwardedRef,
95669
- style: { pointerEvents: "none", ...props.style }
95529
+ ...thumbProps,
95530
+ ref: forwardedRef
95670
95531
  }
95671
- ) });
95532
+ );
95672
95533
  }
95673
95534
  );
95674
- CheckboxIndicator.displayName = INDICATOR_NAME$2;
95675
- var BUBBLE_INPUT_NAME$1 = "CheckboxBubbleInput";
95676
- var CheckboxBubbleInput = React.forwardRef(
95535
+ SwitchThumb.displayName = THUMB_NAME$1;
95536
+ var BUBBLE_INPUT_NAME$1 = "SwitchBubbleInput";
95537
+ var SwitchBubbleInput = React.forwardRef(
95677
95538
  ({
95678
- __scopeCheckbox,
95539
+ __scopeSwitch,
95679
95540
  control,
95680
95541
  checked,
95681
95542
  bubbles = true,
95682
- defaultChecked,
95683
95543
  ...props
95684
95544
  }, forwardedRef) => {
95685
95545
  const ref = React.useRef(null);
@@ -95697,18 +95557,16 @@ var CheckboxBubbleInput = React.forwardRef(
95697
95557
  const setChecked = descriptor.set;
95698
95558
  if (prevChecked !== checked && setChecked) {
95699
95559
  const event = new Event("click", { bubbles });
95700
- input.indeterminate = isIndeterminate(checked);
95701
- setChecked.call(input, isIndeterminate(checked) ? false : checked);
95560
+ setChecked.call(input, checked);
95702
95561
  input.dispatchEvent(event);
95703
95562
  }
95704
95563
  }, [prevChecked, checked, bubbles]);
95705
- const defaultCheckedRef = React.useRef(isIndeterminate(checked) ? false : checked);
95706
95564
  return /* @__PURE__ */ jsx(
95707
- Primitive$3.input,
95565
+ "input",
95708
95566
  {
95709
95567
  type: "checkbox",
95710
95568
  "aria-hidden": true,
95711
- defaultChecked: defaultChecked ?? defaultCheckedRef.current,
95569
+ defaultChecked: checked,
95712
95570
  ...props,
95713
95571
  tabIndex: -1,
95714
95572
  ref: composedRefs,
@@ -95724,18 +95582,15 @@ var CheckboxBubbleInput = React.forwardRef(
95724
95582
  );
95725
95583
  }
95726
95584
  );
95727
- CheckboxBubbleInput.displayName = BUBBLE_INPUT_NAME$1;
95728
- function isIndeterminate(checked) {
95729
- return checked === "indeterminate";
95730
- }
95585
+ SwitchBubbleInput.displayName = BUBBLE_INPUT_NAME$1;
95731
95586
  function getState$3(checked) {
95732
- return isIndeterminate(checked) ? "indeterminate" : checked ? "checked" : "unchecked";
95587
+ return checked ? "checked" : "unchecked";
95733
95588
  }
95734
- var Root$a = Checkbox$1;
95735
- var Indicator$2 = CheckboxIndicator;
95589
+ var Root$a = Switch$1;
95590
+ var Thumb = SwitchThumb;
95736
95591
 
95737
- const Checkbox = React.forwardRef(({ className, ...props }, ref) => (jsx(Root$a, { ref: ref, className: cn("peer border-primary focus-visible:ring-ring h-4 w-4 shrink-0 rounded-sm border shadow-sm focus-visible:ring-1 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-blue-500 data-[state=checked]:text-white", className), ...props, children: jsx(Indicator$2, { className: cn("flex items-center justify-center text-current"), children: jsx(Check, { className: "h-4 w-4" }) }) })));
95738
- Checkbox.displayName = Root$a.displayName;
95592
+ const Switch = React.forwardRef(({ className, ...props }, ref) => (jsx(Root$a, { className: cn("peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input", className), ...props, ref: ref, children: jsx(Thumb, { className: cn("pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0") }) })));
95593
+ Switch.displayName = Root$a.displayName;
95739
95594
 
95740
95595
  const __storeToDerived = /* @__PURE__ */ new WeakMap();
95741
95596
  const __derivedToStore = /* @__PURE__ */ new WeakMap();
@@ -100340,150 +100195,6 @@ var dist = Gravatar;
100340
100195
 
100341
100196
  var Gravatar$1 = /*@__PURE__*/getDefaultExportFromCjs(dist);
100342
100197
 
100343
- var SWITCH_NAME = "Switch";
100344
- var [createSwitchContext, createSwitchScope] = createContextScope$1(SWITCH_NAME);
100345
- var [SwitchProvider, useSwitchContext] = createSwitchContext(SWITCH_NAME);
100346
- var Switch$1 = React.forwardRef(
100347
- (props, forwardedRef) => {
100348
- const {
100349
- __scopeSwitch,
100350
- name,
100351
- checked: checkedProp,
100352
- defaultChecked,
100353
- required,
100354
- disabled,
100355
- value = "on",
100356
- onCheckedChange,
100357
- form,
100358
- ...switchProps
100359
- } = props;
100360
- const [button, setButton] = React.useState(null);
100361
- const composedRefs = useComposedRefs$1(forwardedRef, (node) => setButton(node));
100362
- const hasConsumerStoppedPropagationRef = React.useRef(false);
100363
- const isFormControl = button ? form || !!button.closest("form") : true;
100364
- const [checked, setChecked] = useControllableState$1({
100365
- prop: checkedProp,
100366
- defaultProp: defaultChecked ?? false,
100367
- onChange: onCheckedChange,
100368
- caller: SWITCH_NAME
100369
- });
100370
- return /* @__PURE__ */ jsxs(SwitchProvider, { scope: __scopeSwitch, checked, disabled, children: [
100371
- /* @__PURE__ */ jsx(
100372
- Primitive$2.button,
100373
- {
100374
- type: "button",
100375
- role: "switch",
100376
- "aria-checked": checked,
100377
- "aria-required": required,
100378
- "data-state": getState$2(checked),
100379
- "data-disabled": disabled ? "" : void 0,
100380
- disabled,
100381
- value,
100382
- ...switchProps,
100383
- ref: composedRefs,
100384
- onClick: composeEventHandlers$1(props.onClick, (event) => {
100385
- setChecked((prevChecked) => !prevChecked);
100386
- if (isFormControl) {
100387
- hasConsumerStoppedPropagationRef.current = event.isPropagationStopped();
100388
- if (!hasConsumerStoppedPropagationRef.current) event.stopPropagation();
100389
- }
100390
- })
100391
- }
100392
- ),
100393
- isFormControl && /* @__PURE__ */ jsx(
100394
- SwitchBubbleInput,
100395
- {
100396
- control: button,
100397
- bubbles: !hasConsumerStoppedPropagationRef.current,
100398
- name,
100399
- value,
100400
- checked,
100401
- required,
100402
- disabled,
100403
- form,
100404
- style: { transform: "translateX(-100%)" }
100405
- }
100406
- )
100407
- ] });
100408
- }
100409
- );
100410
- Switch$1.displayName = SWITCH_NAME;
100411
- var THUMB_NAME$1 = "SwitchThumb";
100412
- var SwitchThumb = React.forwardRef(
100413
- (props, forwardedRef) => {
100414
- const { __scopeSwitch, ...thumbProps } = props;
100415
- const context = useSwitchContext(THUMB_NAME$1, __scopeSwitch);
100416
- return /* @__PURE__ */ jsx(
100417
- Primitive$2.span,
100418
- {
100419
- "data-state": getState$2(context.checked),
100420
- "data-disabled": context.disabled ? "" : void 0,
100421
- ...thumbProps,
100422
- ref: forwardedRef
100423
- }
100424
- );
100425
- }
100426
- );
100427
- SwitchThumb.displayName = THUMB_NAME$1;
100428
- var BUBBLE_INPUT_NAME = "SwitchBubbleInput";
100429
- var SwitchBubbleInput = React.forwardRef(
100430
- ({
100431
- __scopeSwitch,
100432
- control,
100433
- checked,
100434
- bubbles = true,
100435
- ...props
100436
- }, forwardedRef) => {
100437
- const ref = React.useRef(null);
100438
- const composedRefs = useComposedRefs$1(ref, forwardedRef);
100439
- const prevChecked = usePrevious$1(checked);
100440
- const controlSize = useSize$1(control);
100441
- React.useEffect(() => {
100442
- const input = ref.current;
100443
- if (!input) return;
100444
- const inputProto = window.HTMLInputElement.prototype;
100445
- const descriptor = Object.getOwnPropertyDescriptor(
100446
- inputProto,
100447
- "checked"
100448
- );
100449
- const setChecked = descriptor.set;
100450
- if (prevChecked !== checked && setChecked) {
100451
- const event = new Event("click", { bubbles });
100452
- setChecked.call(input, checked);
100453
- input.dispatchEvent(event);
100454
- }
100455
- }, [prevChecked, checked, bubbles]);
100456
- return /* @__PURE__ */ jsx(
100457
- "input",
100458
- {
100459
- type: "checkbox",
100460
- "aria-hidden": true,
100461
- defaultChecked: checked,
100462
- ...props,
100463
- tabIndex: -1,
100464
- ref: composedRefs,
100465
- style: {
100466
- ...props.style,
100467
- ...controlSize,
100468
- position: "absolute",
100469
- pointerEvents: "none",
100470
- opacity: 0,
100471
- margin: 0
100472
- }
100473
- }
100474
- );
100475
- }
100476
- );
100477
- SwitchBubbleInput.displayName = BUBBLE_INPUT_NAME;
100478
- function getState$2(checked) {
100479
- return checked ? "checked" : "unchecked";
100480
- }
100481
- var Root$9 = Switch$1;
100482
- var Thumb = SwitchThumb;
100483
-
100484
- const Switch = React.forwardRef(({ className, ...props }, ref) => (jsx(Root$9, { className: cn("peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input", className), ...props, ref: ref, children: jsx(Thumb, { className: cn("pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0") }) })));
100485
- Switch.displayName = Root$9.displayName;
100486
-
100487
100198
  var DISMISSABLE_LAYER_NAME = "DismissableLayer";
100488
100199
  var CONTEXT_UPDATE = "dismissableLayer.update";
100489
100200
  var POINTER_DOWN_OUTSIDE = "dismissableLayer.pointerDownOutside";
@@ -100704,7 +100415,7 @@ var Arrow$1 = React.forwardRef((props, forwardedRef) => {
100704
100415
  );
100705
100416
  });
100706
100417
  Arrow$1.displayName = NAME$4;
100707
- var Root$8 = Arrow$1;
100418
+ var Root$9 = Arrow$1;
100708
100419
 
100709
100420
  var POPPER_NAME = "Popper";
100710
100421
  var [createPopperContext, createPopperScope] = createContextScope$1(POPPER_NAME);
@@ -100912,7 +100623,7 @@ var PopperArrow = React.forwardRef(function PopperArrow2(props, forwardedRef) {
100912
100623
  visibility: contentContext.shouldHideArrow ? "hidden" : void 0
100913
100624
  },
100914
100625
  children: /* @__PURE__ */ jsx(
100915
- Root$8,
100626
+ Root$9,
100916
100627
  {
100917
100628
  ...arrowProps,
100918
100629
  ref: forwardedRef,
@@ -100981,6 +100692,131 @@ var Portal$2 = React.forwardRef((props, forwardedRef) => {
100981
100692
  });
100982
100693
  Portal$2.displayName = PORTAL_NAME$3;
100983
100694
 
100695
+ function useStateMachine$2(initialState, machine) {
100696
+ return React.useReducer((state, event) => {
100697
+ const nextState = machine[state][event];
100698
+ return nextState ?? state;
100699
+ }, initialState);
100700
+ }
100701
+
100702
+ // src/presence.tsx
100703
+ var Presence$1 = (props) => {
100704
+ const { present, children } = props;
100705
+ const presence = usePresence$1(present);
100706
+ const child = typeof children === "function" ? children({ present: presence.isPresent }) : React.Children.only(children);
100707
+ const ref = useComposedRefs$1(presence.ref, getElementRef$2(child));
100708
+ const forceMount = typeof children === "function";
100709
+ return forceMount || presence.isPresent ? React.cloneElement(child, { ref }) : null;
100710
+ };
100711
+ Presence$1.displayName = "Presence";
100712
+ function usePresence$1(present) {
100713
+ const [node, setNode] = React.useState();
100714
+ const stylesRef = React.useRef(null);
100715
+ const prevPresentRef = React.useRef(present);
100716
+ const prevAnimationNameRef = React.useRef("none");
100717
+ const initialState = present ? "mounted" : "unmounted";
100718
+ const [state, send] = useStateMachine$2(initialState, {
100719
+ mounted: {
100720
+ UNMOUNT: "unmounted",
100721
+ ANIMATION_OUT: "unmountSuspended"
100722
+ },
100723
+ unmountSuspended: {
100724
+ MOUNT: "mounted",
100725
+ ANIMATION_END: "unmounted"
100726
+ },
100727
+ unmounted: {
100728
+ MOUNT: "mounted"
100729
+ }
100730
+ });
100731
+ React.useEffect(() => {
100732
+ const currentAnimationName = getAnimationName$1(stylesRef.current);
100733
+ prevAnimationNameRef.current = state === "mounted" ? currentAnimationName : "none";
100734
+ }, [state]);
100735
+ useLayoutEffect2$1(() => {
100736
+ const styles = stylesRef.current;
100737
+ const wasPresent = prevPresentRef.current;
100738
+ const hasPresentChanged = wasPresent !== present;
100739
+ if (hasPresentChanged) {
100740
+ const prevAnimationName = prevAnimationNameRef.current;
100741
+ const currentAnimationName = getAnimationName$1(styles);
100742
+ if (present) {
100743
+ send("MOUNT");
100744
+ } else if (currentAnimationName === "none" || styles?.display === "none") {
100745
+ send("UNMOUNT");
100746
+ } else {
100747
+ const isAnimating = prevAnimationName !== currentAnimationName;
100748
+ if (wasPresent && isAnimating) {
100749
+ send("ANIMATION_OUT");
100750
+ } else {
100751
+ send("UNMOUNT");
100752
+ }
100753
+ }
100754
+ prevPresentRef.current = present;
100755
+ }
100756
+ }, [present, send]);
100757
+ useLayoutEffect2$1(() => {
100758
+ if (node) {
100759
+ let timeoutId;
100760
+ const ownerWindow = node.ownerDocument.defaultView ?? window;
100761
+ const handleAnimationEnd = (event) => {
100762
+ const currentAnimationName = getAnimationName$1(stylesRef.current);
100763
+ const isCurrentAnimation = currentAnimationName.includes(event.animationName);
100764
+ if (event.target === node && isCurrentAnimation) {
100765
+ send("ANIMATION_END");
100766
+ if (!prevPresentRef.current) {
100767
+ const currentFillMode = node.style.animationFillMode;
100768
+ node.style.animationFillMode = "forwards";
100769
+ timeoutId = ownerWindow.setTimeout(() => {
100770
+ if (node.style.animationFillMode === "forwards") {
100771
+ node.style.animationFillMode = currentFillMode;
100772
+ }
100773
+ });
100774
+ }
100775
+ }
100776
+ };
100777
+ const handleAnimationStart = (event) => {
100778
+ if (event.target === node) {
100779
+ prevAnimationNameRef.current = getAnimationName$1(stylesRef.current);
100780
+ }
100781
+ };
100782
+ node.addEventListener("animationstart", handleAnimationStart);
100783
+ node.addEventListener("animationcancel", handleAnimationEnd);
100784
+ node.addEventListener("animationend", handleAnimationEnd);
100785
+ return () => {
100786
+ ownerWindow.clearTimeout(timeoutId);
100787
+ node.removeEventListener("animationstart", handleAnimationStart);
100788
+ node.removeEventListener("animationcancel", handleAnimationEnd);
100789
+ node.removeEventListener("animationend", handleAnimationEnd);
100790
+ };
100791
+ } else {
100792
+ send("ANIMATION_END");
100793
+ }
100794
+ }, [node, send]);
100795
+ return {
100796
+ isPresent: ["mounted", "unmountSuspended"].includes(state),
100797
+ ref: React.useCallback((node2) => {
100798
+ stylesRef.current = node2 ? getComputedStyle(node2) : null;
100799
+ setNode(node2);
100800
+ }, [])
100801
+ };
100802
+ }
100803
+ function getAnimationName$1(styles) {
100804
+ return styles?.animationName || "none";
100805
+ }
100806
+ function getElementRef$2(element) {
100807
+ let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
100808
+ let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
100809
+ if (mayWarn) {
100810
+ return element.ref;
100811
+ }
100812
+ getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
100813
+ mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
100814
+ if (mayWarn) {
100815
+ return element.props.ref;
100816
+ }
100817
+ return element.props.ref || element.ref;
100818
+ }
100819
+
100984
100820
  // src/visually-hidden.tsx
100985
100821
  var VISUALLY_HIDDEN_STYLES = Object.freeze({
100986
100822
  // See: https://github.com/twbs/bootstrap/blob/main/scss/mixins/_visually-hidden.scss
@@ -101009,7 +100845,7 @@ var VisuallyHidden = React.forwardRef(
101009
100845
  }
101010
100846
  );
101011
100847
  VisuallyHidden.displayName = NAME$3;
101012
- var Root$7 = VisuallyHidden;
100848
+ var Root$8 = VisuallyHidden;
101013
100849
 
101014
100850
  var [createTooltipContext, createTooltipScope] = createContextScope$1("Tooltip", [
101015
100851
  createPopperScope
@@ -101342,7 +101178,7 @@ var TooltipContentImpl = React.forwardRef(
101342
101178
  },
101343
101179
  children: [
101344
101180
  /* @__PURE__ */ jsx(Slottable$2, { children }),
101345
- /* @__PURE__ */ jsx(VisuallyHiddenContentContextProvider, { scope: __scopeTooltip, isInside: true, children: /* @__PURE__ */ jsx(Root$7, { id: context.contentId, role: "tooltip", children: ariaLabel || children }) })
101181
+ /* @__PURE__ */ jsx(VisuallyHiddenContentContextProvider, { scope: __scopeTooltip, isInside: true, children: /* @__PURE__ */ jsx(Root$8, { id: context.contentId, role: "tooltip", children: ariaLabel || children }) })
101346
101182
  ]
101347
101183
  }
101348
101184
  )
@@ -101742,11 +101578,11 @@ var Progress$1 = React.forwardRef(
101742
101578
  }
101743
101579
  );
101744
101580
  Progress$1.displayName = PROGRESS_NAME;
101745
- var INDICATOR_NAME$1 = "ProgressIndicator";
101581
+ var INDICATOR_NAME$2 = "ProgressIndicator";
101746
101582
  var ProgressIndicator = React.forwardRef(
101747
101583
  (props, forwardedRef) => {
101748
101584
  const { __scopeProgress, ...indicatorProps } = props;
101749
- const context = useProgressContext(INDICATOR_NAME$1, __scopeProgress);
101585
+ const context = useProgressContext(INDICATOR_NAME$2, __scopeProgress);
101750
101586
  return /* @__PURE__ */ jsx(
101751
101587
  Primitive$1.div,
101752
101588
  {
@@ -101759,7 +101595,7 @@ var ProgressIndicator = React.forwardRef(
101759
101595
  );
101760
101596
  }
101761
101597
  );
101762
- ProgressIndicator.displayName = INDICATOR_NAME$1;
101598
+ ProgressIndicator.displayName = INDICATOR_NAME$2;
101763
101599
  function defaultGetValueLabel(value, max) {
101764
101600
  return `${Math.round(value / max * 100)}%`;
101765
101601
  }
@@ -101786,13 +101622,497 @@ function getInvalidValueError(propValue, componentName) {
101786
101622
 
101787
101623
  Defaulting to \`null\`.`;
101788
101624
  }
101789
- var Root$6 = Progress$1;
101790
- var Indicator$1 = ProgressIndicator;
101625
+ var Root$7 = Progress$1;
101626
+ var Indicator$2 = ProgressIndicator;
101791
101627
 
101792
- const Progress = React.forwardRef(({ className, value, ...props }, ref) => (jsx(Root$6, { ref: ref, className: cn('bg-secondary relative h-4 w-full overflow-hidden rounded-full', className), ...props, children: jsx(Indicator$1, { className: "bg-primary h-full w-full flex-1 transition-all", style: { transform: `translateX(-${100 - (value || 0)}%)` } }) })));
101793
- Progress.displayName = Root$6.displayName;
101628
+ const Progress = React.forwardRef(({ className, value, ...props }, ref) => (jsx(Root$7, { ref: ref, className: cn('bg-secondary relative h-4 w-full overflow-hidden rounded-full', className), ...props, children: jsx(Indicator$2, { className: "bg-primary h-full w-full flex-1 transition-all", style: { transform: `translateX(-${100 - (value || 0)}%)` } }) })));
101629
+ Progress.displayName = Root$7.displayName;
101630
+
101631
+ var SLOTTABLE_IDENTIFIER = Symbol("radix.slottable");
101632
+ // @__NO_SIDE_EFFECTS__
101633
+ function createSlottable(ownerName) {
101634
+ const Slottable2 = ({ children }) => {
101635
+ return /* @__PURE__ */ jsx(Fragment$1, { children });
101636
+ };
101637
+ Slottable2.displayName = `${ownerName}.Slottable`;
101638
+ Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
101639
+ return Slottable2;
101640
+ }
101641
+
101642
+ var ROOT_NAME = "AlertDialog";
101643
+ var [createAlertDialogContext, createAlertDialogScope] = createContextScope$1(ROOT_NAME, [
101644
+ createDialogScope
101645
+ ]);
101646
+ var useDialogScope = createDialogScope();
101647
+ var AlertDialog$1 = (props) => {
101648
+ const { __scopeAlertDialog, ...alertDialogProps } = props;
101649
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101650
+ return /* @__PURE__ */ jsx(DialogPrimitive.Root, { ...dialogScope, ...alertDialogProps, modal: true });
101651
+ };
101652
+ AlertDialog$1.displayName = ROOT_NAME;
101653
+ var TRIGGER_NAME$2 = "AlertDialogTrigger";
101654
+ var AlertDialogTrigger = React.forwardRef(
101655
+ (props, forwardedRef) => {
101656
+ const { __scopeAlertDialog, ...triggerProps } = props;
101657
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101658
+ return /* @__PURE__ */ jsx(DialogPrimitive.Trigger, { ...dialogScope, ...triggerProps, ref: forwardedRef });
101659
+ }
101660
+ );
101661
+ AlertDialogTrigger.displayName = TRIGGER_NAME$2;
101662
+ var PORTAL_NAME$1 = "AlertDialogPortal";
101663
+ var AlertDialogPortal$1 = (props) => {
101664
+ const { __scopeAlertDialog, ...portalProps } = props;
101665
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101666
+ return /* @__PURE__ */ jsx(DialogPrimitive.Portal, { ...dialogScope, ...portalProps });
101667
+ };
101668
+ AlertDialogPortal$1.displayName = PORTAL_NAME$1;
101669
+ var OVERLAY_NAME = "AlertDialogOverlay";
101670
+ var AlertDialogOverlay$1 = React.forwardRef(
101671
+ (props, forwardedRef) => {
101672
+ const { __scopeAlertDialog, ...overlayProps } = props;
101673
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101674
+ return /* @__PURE__ */ jsx(DialogPrimitive.Overlay, { ...dialogScope, ...overlayProps, ref: forwardedRef });
101675
+ }
101676
+ );
101677
+ AlertDialogOverlay$1.displayName = OVERLAY_NAME;
101678
+ var CONTENT_NAME$2 = "AlertDialogContent";
101679
+ var [AlertDialogContentProvider, useAlertDialogContentContext] = createAlertDialogContext(CONTENT_NAME$2);
101680
+ var Slottable = createSlottable("AlertDialogContent");
101681
+ var AlertDialogContent$1 = React.forwardRef(
101682
+ (props, forwardedRef) => {
101683
+ const { __scopeAlertDialog, children, ...contentProps } = props;
101684
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101685
+ const contentRef = React.useRef(null);
101686
+ const composedRefs = useComposedRefs$1(forwardedRef, contentRef);
101687
+ const cancelRef = React.useRef(null);
101688
+ return /* @__PURE__ */ jsx(
101689
+ DialogPrimitive.WarningProvider,
101690
+ {
101691
+ contentName: CONTENT_NAME$2,
101692
+ titleName: TITLE_NAME,
101693
+ docsSlug: "alert-dialog",
101694
+ children: /* @__PURE__ */ jsx(AlertDialogContentProvider, { scope: __scopeAlertDialog, cancelRef, children: /* @__PURE__ */ jsxs(
101695
+ DialogPrimitive.Content,
101696
+ {
101697
+ role: "alertdialog",
101698
+ ...dialogScope,
101699
+ ...contentProps,
101700
+ ref: composedRefs,
101701
+ onOpenAutoFocus: composeEventHandlers$2(contentProps.onOpenAutoFocus, (event) => {
101702
+ event.preventDefault();
101703
+ cancelRef.current?.focus({ preventScroll: true });
101704
+ }),
101705
+ onPointerDownOutside: (event) => event.preventDefault(),
101706
+ onInteractOutside: (event) => event.preventDefault(),
101707
+ children: [
101708
+ /* @__PURE__ */ jsx(Slottable, { children }),
101709
+ /* @__PURE__ */ jsx(DescriptionWarning, { contentRef })
101710
+ ]
101711
+ }
101712
+ ) })
101713
+ }
101714
+ );
101715
+ }
101716
+ );
101717
+ AlertDialogContent$1.displayName = CONTENT_NAME$2;
101718
+ var TITLE_NAME = "AlertDialogTitle";
101719
+ var AlertDialogTitle$1 = React.forwardRef(
101720
+ (props, forwardedRef) => {
101721
+ const { __scopeAlertDialog, ...titleProps } = props;
101722
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101723
+ return /* @__PURE__ */ jsx(DialogPrimitive.Title, { ...dialogScope, ...titleProps, ref: forwardedRef });
101724
+ }
101725
+ );
101726
+ AlertDialogTitle$1.displayName = TITLE_NAME;
101727
+ var DESCRIPTION_NAME = "AlertDialogDescription";
101728
+ var AlertDialogDescription$1 = React.forwardRef((props, forwardedRef) => {
101729
+ const { __scopeAlertDialog, ...descriptionProps } = props;
101730
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101731
+ return /* @__PURE__ */ jsx(DialogPrimitive.Description, { ...dialogScope, ...descriptionProps, ref: forwardedRef });
101732
+ });
101733
+ AlertDialogDescription$1.displayName = DESCRIPTION_NAME;
101734
+ var ACTION_NAME = "AlertDialogAction";
101735
+ var AlertDialogAction$1 = React.forwardRef(
101736
+ (props, forwardedRef) => {
101737
+ const { __scopeAlertDialog, ...actionProps } = props;
101738
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101739
+ return /* @__PURE__ */ jsx(DialogPrimitive.Close, { ...dialogScope, ...actionProps, ref: forwardedRef });
101740
+ }
101741
+ );
101742
+ AlertDialogAction$1.displayName = ACTION_NAME;
101743
+ var CANCEL_NAME = "AlertDialogCancel";
101744
+ var AlertDialogCancel$1 = React.forwardRef(
101745
+ (props, forwardedRef) => {
101746
+ const { __scopeAlertDialog, ...cancelProps } = props;
101747
+ const { cancelRef } = useAlertDialogContentContext(CANCEL_NAME, __scopeAlertDialog);
101748
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101749
+ const ref = useComposedRefs$1(forwardedRef, cancelRef);
101750
+ return /* @__PURE__ */ jsx(DialogPrimitive.Close, { ...dialogScope, ...cancelProps, ref });
101751
+ }
101752
+ );
101753
+ AlertDialogCancel$1.displayName = CANCEL_NAME;
101754
+ var DescriptionWarning = ({ contentRef }) => {
101755
+ const MESSAGE = `\`${CONTENT_NAME$2}\` requires a description for the component to be accessible for screen reader users.
101756
+
101757
+ You can add a description to the \`${CONTENT_NAME$2}\` by passing a \`${DESCRIPTION_NAME}\` component as a child, which also benefits sighted users by adding visible context to the dialog.
101758
+
101759
+ Alternatively, you can use your own component as a description by assigning it an \`id\` and passing the same value to the \`aria-describedby\` prop in \`${CONTENT_NAME$2}\`. If the description is confusing or duplicative for sighted users, you can use the \`@radix-ui/react-visually-hidden\` primitive as a wrapper around your description component.
101760
+
101761
+ For more information, see https://radix-ui.com/primitives/docs/components/alert-dialog`;
101762
+ React.useEffect(() => {
101763
+ const hasDescription = document.getElementById(
101764
+ contentRef.current?.getAttribute("aria-describedby")
101765
+ );
101766
+ if (!hasDescription) console.warn(MESSAGE);
101767
+ }, [MESSAGE, contentRef]);
101768
+ return null;
101769
+ };
101770
+ var Root2$3 = AlertDialog$1;
101771
+ var Portal2 = AlertDialogPortal$1;
101772
+ var Overlay2 = AlertDialogOverlay$1;
101773
+ var Content2$1 = AlertDialogContent$1;
101774
+ var Action = AlertDialogAction$1;
101775
+ var Cancel = AlertDialogCancel$1;
101776
+ var Title2 = AlertDialogTitle$1;
101777
+ var Description2 = AlertDialogDescription$1;
101778
+
101779
+ const AlertDialog = Root2$3;
101780
+ const AlertDialogPortal = Portal2;
101781
+ const AlertDialogOverlay = React.forwardRef(({ className, ...props }, ref) => (jsx(Overlay2, { className: cn('data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80', className), ...props, ref: ref })));
101782
+ AlertDialogOverlay.displayName = Overlay2.displayName;
101783
+ const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => (jsxs(AlertDialogPortal, { children: [jsx(AlertDialogOverlay, {}), jsx(Content2$1, { ref: ref, className: cn('bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg', className), ...props })] })));
101784
+ AlertDialogContent.displayName = Content2$1.displayName;
101785
+ const AlertDialogHeader = ({ className, ...props }) => (jsx("div", { className: cn('flex flex-col space-y-2 text-center sm:text-left', className), ...props }));
101786
+ AlertDialogHeader.displayName = 'AlertDialogHeader';
101787
+ const AlertDialogFooter = ({ className, ...props }) => (jsx("div", { className: cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className), ...props }));
101788
+ AlertDialogFooter.displayName = 'AlertDialogFooter';
101789
+ const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => (jsx(Title2, { ref: ref, className: cn('text-lg font-semibold', className), ...props })));
101790
+ AlertDialogTitle.displayName = Title2.displayName;
101791
+ const AlertDialogDescription = React.forwardRef(({ className, ...props }, ref) => (jsx(Description2, { ref: ref, className: cn('text-muted-foreground text-sm', className), ...props })));
101792
+ AlertDialogDescription.displayName = Description2.displayName;
101793
+ const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => (jsx(Action, { ref: ref, className: cn(buttonVariants(), className), ...props })));
101794
+ AlertDialogAction.displayName = Action.displayName;
101795
+ const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => (jsx(Cancel, { ref: ref, className: cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className), ...props })));
101796
+ AlertDialogCancel.displayName = Cancel.displayName;
101797
+
101798
+ // Tauri types for model download functionality
101799
+ /**
101800
+ * Initial state for model download
101801
+ */
101802
+ const initialModelDownloadState = {
101803
+ status: 'idle',
101804
+ progress: 0,
101805
+ message: '',
101806
+ logs: [],
101807
+ lastUpdated: new Date().toISOString(),
101808
+ };
101809
+ /**
101810
+ * Check if the app is running inside Tauri
101811
+ * In Tauri 2.0, the global is __TAURI_INTERNALS__ (with withGlobalTauri: true)
101812
+ * We also check for __TAURI__ for backwards compatibility
101813
+ */
101814
+ const isTauriApp$1 = () => {
101815
+ if (typeof window === 'undefined')
101816
+ return false;
101817
+ return '__TAURI_INTERNALS__' in window || '__TAURI__' in window;
101818
+ };
101819
+ /**
101820
+ * Tauri event names
101821
+ */
101822
+ const TAURI_EVENTS = {
101823
+ DOWNLOAD_PROGRESS: 'model:download-progress',
101824
+ INSTALLATION_LOG: 'model:installation-log',
101825
+ DISK_SPACE_ERROR: 'model:disk-space-error',
101826
+ OLLAMA_STATUS: 'model:ollama-status',
101827
+ };
101828
+ /**
101829
+ * Tauri command names
101830
+ */
101831
+ const TAURI_COMMANDS = {
101832
+ INSTALL_OLLAMA: 'install_ollama',
101833
+ STOP_OLLAMA: 'stop_ollama',
101834
+ CHECK_OLLAMA_STATUS: 'check_ollama_status',
101835
+ CHECK_DISK_SPACE: 'check_disk_space_for_model',
101836
+ GET_SYSTEM_MEMORY: 'get_system_memory',
101837
+ DOWNLOAD_MODEL: 'download_model',
101838
+ CANCEL_DOWNLOAD: 'cancel_model_download',
101839
+ CHECK_NETWORK_STATUS: 'check_network_status',
101840
+ GET_OS_TYPE: 'get_os_type',
101841
+ CHECK_FOUNDRY_STATUS: 'check_foundry_local_status',
101842
+ START_FOUNDRY_SERVICE: 'start_foundry_local_service',
101843
+ LOAD_FOUNDRY_MODEL: 'load_foundry_local_model',
101844
+ SET_SELECTED_FOUNDRY_MODEL: 'set_selected_foundry_model',
101845
+ GET_SELECTED_FOUNDRY_MODEL: 'get_selected_foundry_model',
101846
+ INSTALL_FOUNDRY: 'install_foundry',
101847
+ DOWNLOAD_FOUNDRY_MODEL: 'download_foundry_model_cmd',
101848
+ GET_RECOMMENDED_FOUNDRY_MODELS: 'get_recommended_foundry_models',
101849
+ LOG_STDOUT: 'log_stdout',
101850
+ };
101851
+ /**
101852
+ * Initial state for the System Control install flow.
101853
+ */
101854
+ const initialGhostOsInstallState = {
101855
+ status: 'idle',
101856
+ progress: 0,
101857
+ message: '',
101858
+ logs: [],
101859
+ lastUpdated: new Date().toISOString(),
101860
+ };
101861
+ /**
101862
+ * Tauri event names emitted by the host during GhostOS install/setup.
101863
+ */
101864
+ const GHOST_OS_TAURI_EVENTS = {
101865
+ INSTALL_PROGRESS: 'ghost-os:install-progress',
101866
+ INSTALLATION_LOG: 'ghost-os:installation-log',
101867
+ STATUS: 'ghost-os:status',
101868
+ };
101869
+ /**
101870
+ * Tauri command names the host implements for GhostOS install/setup. The web
101871
+ * layer invokes these; the native side performs the clone + setup.
101872
+ */
101873
+ const GHOST_OS_TAURI_COMMANDS = {
101874
+ /** Install and set up GhostOS (clone repo + run setup). */
101875
+ INSTALL_GHOST_OS: 'install_ghost_os',
101876
+ /** Stop the running GhostOS manager. */
101877
+ STOP_GHOST_OS: 'stop_ghost_os',
101878
+ /** Report current {@link GhostOsStatus}. */
101879
+ CHECK_GHOST_OS_STATUS: 'check_ghost_os_status',
101880
+ };
101881
+ /**
101882
+ * Tauri command strings exposed by `tauri-plugin-macos-permissions`
101883
+ * (https://github.com/ayangweb/tauri-plugin-macos-permissions). GhostOS needs
101884
+ * macOS Accessibility permission to control the device, so the System Control
101885
+ * card surfaces it. These are invoked directly via `useTauri().invoke(...)` —
101886
+ * no extra npm dependency. The host must register the plugin in its Tauri app
101887
+ * (`tauri_plugin_macos_permissions::init()`). Each `CHECK_*` returns a boolean;
101888
+ * each `REQUEST_*` prompts / opens the relevant System Settings pane.
101889
+ */
101890
+ const MACOS_PERMISSIONS_COMMANDS = {
101891
+ CHECK_ACCESSIBILITY: 'plugin:macos-permissions|check_accessibility_permission',
101892
+ REQUEST_ACCESSIBILITY: 'plugin:macos-permissions|request_accessibility_permission',
101893
+ CHECK_SCREEN_RECORDING: 'plugin:macos-permissions|check_screen_recording_permission',
101894
+ REQUEST_SCREEN_RECORDING: 'plugin:macos-permissions|request_screen_recording_permission',
101895
+ CHECK_INPUT_MONITORING: 'plugin:macos-permissions|check_input_monitoring_permission',
101896
+ REQUEST_INPUT_MONITORING: 'plugin:macos-permissions|request_input_monitoring_permission',
101897
+ CHECK_FULL_DISK_ACCESS: 'plugin:macos-permissions|check_full_disk_access_permission',
101898
+ REQUEST_FULL_DISK_ACCESS: 'plugin:macos-permissions|request_full_disk_access_permission',
101899
+ };
101900
+
101901
+ /**
101902
+ * Dynamically import Tauri APIs to avoid SSR issues
101903
+ */
101904
+ const getTauriAPIs = async () => {
101905
+ try {
101906
+ const { invoke } = await import('@tauri-apps/api/core');
101907
+ const { listen } = await import('@tauri-apps/api/event');
101908
+ return { invoke, listen };
101909
+ }
101910
+ catch (error) {
101911
+ console.error('Failed to load Tauri APIs:', error);
101912
+ return null;
101913
+ }
101914
+ };
101915
+ /**
101916
+ * Hook to access Tauri APIs with SSR safety
101917
+ *
101918
+ * @returns Object with isAvailable flag and Tauri invoke/listen functions
101919
+ */
101920
+ function useTauri() {
101921
+ const [isAvailable, setIsAvailable] = useState(false);
101922
+ const [apis, setApis] = useState(null);
101923
+ useEffect(() => {
101924
+ // Check if we're in a Tauri environment
101925
+ const inTauri = isTauriApp$1();
101926
+ if (inTauri) {
101927
+ getTauriAPIs().then((result) => {
101928
+ setApis(result);
101929
+ setIsAvailable(!!result);
101930
+ });
101931
+ }
101932
+ }, []);
101933
+ /**
101934
+ * Invoke a Tauri command
101935
+ */
101936
+ const invoke = useCallback(async (command, args) => {
101937
+ if (!(apis === null || apis === void 0 ? void 0 : apis.invoke)) {
101938
+ throw new Error('Tauri is not available');
101939
+ }
101940
+ return apis.invoke(command, args);
101941
+ }, [apis]);
101942
+ /**
101943
+ * Listen to a Tauri event
101944
+ * Returns an unlisten function that should be called on cleanup
101945
+ */
101946
+ const listen = useCallback(async (event, handler) => {
101947
+ // The synchronous global (`__TAURI_INTERNALS__`) exposes `invoke` but
101948
+ // frequently NOT `event.listen`, so `apis.listen` can be undefined even
101949
+ // though Tauri is fully available. Fall back to the real event API via
101950
+ // dynamic import so download progress/completion events are actually
101951
+ // delivered — otherwise the UI never leaves the "downloading" state.
101952
+ let listenFn = apis === null || apis === void 0 ? void 0 : apis.listen;
101953
+ if (!listenFn) {
101954
+ const { listen: importedListen } = await import('@tauri-apps/api/event');
101955
+ listenFn = importedListen;
101956
+ }
101957
+ return listenFn(event, (e) => handler(e.payload));
101958
+ }, [apis]);
101959
+ return {
101960
+ isAvailable,
101961
+ invoke,
101962
+ listen,
101963
+ };
101964
+ }
101794
101965
 
101795
101966
  const LOCAL_LLM_ENABLED_KEY = 'ibl_local_llm_enabled';
101967
+ const LOCAL_LLM_MODEL_KEY = 'ibl_local_llm_model';
101968
+ const LOCAL_LLM_TOOL_SUPPORT_KEY = 'ibl_local_llm_tool_support';
101969
+ /**
101970
+ * Curated catalog of small, on-device-friendly models from the Ollama library
101971
+ * (https://ollama.com/library). The model manager itself (Ollama) is not a
101972
+ * model and is intentionally absent — enabling Local Models installs it.
101973
+ */
101974
+ // `tool_support` reflects whether the model advertises Tools / function calling
101975
+ // on its Ollama library page (ollama.com/library/<model>), used by the host to
101976
+ // route to the MCP/tool proxy on :8000 vs plain Ollama on :11434. Verified Jun
101977
+ // 2026 from the per-model capability badges; adjust as Ollama support changes.
101978
+ // Only tool / function-calling capable models are listed (the local manager is
101979
+ // for tool-using chat). `tool_support` is kept on the type/contract — persisted
101980
+ // on select and read by the host to route to the MCP/tool proxy (:8000).
101981
+ const LOCAL_MODELS = [
101982
+ { name: 'Phi-4 Mini', provider: 'Microsoft', id: 'phi4-mini:latest', size: '2.5 GB', tool_support: true },
101983
+ { name: 'Llama 3.2', provider: 'Meta', id: 'llama3.2', size: '2.0 GB', tool_support: true },
101984
+ { name: 'Qwen 3', provider: 'Alibaba', id: 'qwen3', size: '5.2 GB', tool_support: true },
101985
+ { name: 'Mistral', provider: 'Mistral AI', id: 'mistral', size: '4.4 GB', tool_support: true },
101986
+ { name: 'DeepSeek-R1', provider: 'DeepSeek', id: 'deepseek-r1', size: '5.2 GB', tool_support: true },
101987
+ { name: 'Granite 4.1', provider: 'IBM', id: 'granite4.1:8b', size: '5.3 GB', tool_support: true },
101988
+ { name: 'GPT-OSS 20B', provider: 'OpenAI', id: 'gpt-oss:20b', size: '14 GB', tool_support: true },
101989
+ // Larger models. Sizes use "GB" so they parse for the System Control size gate
101990
+ // (only models above the configured gate can use System Control).
101991
+ { name: 'Qwen 3.6', provider: 'Alibaba', id: 'qwen3.6:latest', size: '24 GB', tool_support: true },
101992
+ { name: 'Gemma 4 31B', provider: 'Google', id: 'gemma4:31b', size: '20 GB', tool_support: true },
101993
+ { name: 'Nemotron 3 33B', provider: 'NVIDIA', id: 'nemotron3:33b', size: '28 GB', tool_support: true },
101994
+ { name: 'Nemotron 3 Super 120B', provider: 'NVIDIA', id: 'nemotron-3-super:120b', size: '87 GB', tool_support: true },
101995
+ ];
101996
+ /** The model wired to the existing single-model download flow (default selection). */
101997
+ const PRIMARY_MODEL_ID = 'phi4-mini:latest';
101998
+ /**
101999
+ * Whether a catalog model id (e.g. "llama3.2", "phi3:mini") is present among the
102000
+ * installed Ollama tags reported by the backend (e.g. "llama3.2:latest"). Matches
102001
+ * an exact tag or the same base model with any tag suffix, so a model pulled as
102002
+ * `<id>:latest` still counts as installed.
102003
+ */
102004
+ function isModelInstalled(modelId, installedTags) {
102005
+ if (!installedTags)
102006
+ return false;
102007
+ return installedTags.some((tag) => tag === modelId || tag.startsWith(`${modelId}:`));
102008
+ }
102009
+ /**
102010
+ * Whether a model id is an Ollama cloud-hosted model — either the plain ":cloud"
102011
+ * tag (e.g. "glm-5.2:cloud") or a sized cloud tag (e.g. "gemma4:31b-cloud").
102012
+ * Cloud models run on Ollama's servers, so they are available by default — never
102013
+ * downloaded, no local memory required, always "ready".
102014
+ */
102015
+ function isCloudModelId(modelId) {
102016
+ return (modelId.endsWith(':cloud') ||
102017
+ (modelId.includes(':') && modelId.endsWith('-cloud')));
102018
+ }
102019
+ /**
102020
+ * Fraction of the machine's memory above which a model is treated as "too large"
102021
+ * and a confirmation is required before downloading. TESTING VALUE — set low (1%)
102022
+ * so the warning is easy to trigger; raise toward real capacity (e.g. 0.8) later.
102023
+ */
102024
+ const MODEL_SIZE_WARN_FRACTION = 0.01;
102025
+ /** Multipliers for the size units used in the catalog (binary / 1024-based). */
102026
+ const SIZE_UNIT_BYTES = {
102027
+ B: 1,
102028
+ KB: 1024,
102029
+ MB: 1024 ** 2,
102030
+ GB: 1024 ** 3,
102031
+ TB: 1024 ** 4,
102032
+ };
102033
+ /**
102034
+ * Parse a human catalog size like "2.2 GB" into bytes, or null if unrecognized.
102035
+ */
102036
+ function parseModelSizeBytes(size) {
102037
+ var _a;
102038
+ const match = /([\d.]+)\s*(TB|GB|MB|KB|B)/i.exec(size);
102039
+ if (!match)
102040
+ return null;
102041
+ const value = parseFloat(match[1]);
102042
+ if (Number.isNaN(value))
102043
+ return null;
102044
+ return value * ((_a = SIZE_UNIT_BYTES[match[2].toUpperCase()]) !== null && _a !== void 0 ? _a : 1);
102045
+ }
102046
+ /** Format a byte count as a short, human-readable size (e.g. "16.0 GB"). */
102047
+ function formatBytes(bytes) {
102048
+ if (bytes <= 0)
102049
+ return '0 GB';
102050
+ const gb = bytes / 1024 ** 3;
102051
+ if (gb >= 1)
102052
+ return `${gb.toFixed(1)} GB`;
102053
+ return `${Math.round(bytes / 1024 ** 2)} MB`;
102054
+ }
102055
+ /**
102056
+ * The memory a model can use on this machine: the larger of system RAM and GPU
102057
+ * VRAM (a model runs in one or the other). Returns 0 when memory is unknown.
102058
+ */
102059
+ function usableMemoryBytes(systemMemory) {
102060
+ if (!systemMemory)
102061
+ return 0;
102062
+ return Math.max(systemMemory.ram_total, systemMemory.vram_total);
102063
+ }
102064
+ /**
102065
+ * Whether `model` is large enough relative to this machine's memory to warrant a
102066
+ * "might not run" confirmation. False when memory or the model size is unknown,
102067
+ * so the download simply proceeds rather than warning on incomplete data.
102068
+ */
102069
+ function modelExceedsCapacity(model, systemMemory) {
102070
+ const capacity = usableMemoryBytes(systemMemory);
102071
+ const modelBytes = parseModelSizeBytes(model.size);
102072
+ if (modelBytes == null)
102073
+ return false;
102074
+ return modelBytes > capacity * MODEL_SIZE_WARN_FRACTION;
102075
+ }
102076
+ /**
102077
+ * Default minimum model size (GB) that can use the System Control Manager
102078
+ * (GhostOS MCP/tools). Smaller models don't support tools/MCP well, so the
102079
+ * manager is gated to models above this size. Per-app overridable via the
102080
+ * System Control card's `requiredSizeGb` prop (see SystemControlTabProps).
102081
+ */
102082
+ const DEFAULT_SYSTEM_CONTROL_REQUIRED_SIZE_GB = 12;
102083
+ /**
102084
+ * Whether the given model (by Ollama id) is large enough to use the System
102085
+ * Control Manager — i.e. its catalog size exceeds `gb` (default
102086
+ * {@link DEFAULT_SYSTEM_CONTROL_REQUIRED_SIZE_GB}). Unknown ids or unparseable
102087
+ * sizes return false (gated off).
102088
+ */
102089
+ function modelSupportsSystemControl(modelId, gb = DEFAULT_SYSTEM_CONTROL_REQUIRED_SIZE_GB) {
102090
+ if (!modelId)
102091
+ return false;
102092
+ const model = LOCAL_MODELS.find((m) => m.id === modelId);
102093
+ if (!model)
102094
+ return false;
102095
+ const bytes = parseModelSizeBytes(model.size);
102096
+ if (bytes == null)
102097
+ return false;
102098
+ return bytes > gb * 1024 ** 3;
102099
+ }
102100
+ /**
102101
+ * The smallest catalog model that can use the System Control Manager (size
102102
+ * exceeds `gb`), or null if none qualify. Used by the "Upgrade" action to pick
102103
+ * the lightest capable model. Ties / unparseable sizes sort last.
102104
+ */
102105
+ function smallestSystemControlModel(gb = DEFAULT_SYSTEM_CONTROL_REQUIRED_SIZE_GB) {
102106
+ const usable = LOCAL_MODELS.filter((m) => modelSupportsSystemControl(m.id, gb));
102107
+ if (usable.length === 0)
102108
+ return null;
102109
+ return usable.reduce((smallest, m) => {
102110
+ var _a, _b;
102111
+ const a = (_a = parseModelSizeBytes(m.size)) !== null && _a !== void 0 ? _a : Infinity;
102112
+ const b = (_b = parseModelSizeBytes(smallest.size)) !== null && _b !== void 0 ? _b : Infinity;
102113
+ return a < b ? m : smallest;
102114
+ });
102115
+ }
101796
102116
  /**
101797
102117
  * Check if local LLM is enabled from localStorage
101798
102118
  */
@@ -101809,13 +102129,64 @@ function setLocalLLMEnabled(enabled) {
101809
102129
  return;
101810
102130
  localStorage.setItem(LOCAL_LLM_ENABLED_KEY, String(enabled));
101811
102131
  }
101812
- function LocalLLMTab({ isAvailable, state, ollamaStatus, isUsingFoundry = false, foundryModels = [], selectedFoundryModel = null, foundryStatus, onStartDownload, onCancelDownload, onInstallOllama, onInstallFoundry, onCheckStatus, onResetState, onSelectFoundryModel, }) {
101813
- var _a, _b, _c, _d, _e;
102132
+ /**
102133
+ * Get the model the user has chosen to chat with (Ollama pull id), or null.
102134
+ */
102135
+ function getLocalLLMModel() {
102136
+ if (typeof window === 'undefined')
102137
+ return null;
102138
+ return localStorage.getItem(LOCAL_LLM_MODEL_KEY);
102139
+ }
102140
+ /**
102141
+ * Set the model the user has chosen to chat with (Ollama pull id).
102142
+ */
102143
+ function setLocalLLMModel(modelId) {
102144
+ if (typeof window === 'undefined')
102145
+ return;
102146
+ localStorage.setItem(LOCAL_LLM_MODEL_KEY, modelId);
102147
+ }
102148
+ /**
102149
+ * Persist whether the selected model supports tools (set when a model is chosen).
102150
+ */
102151
+ function setLocalLLMToolSupport(toolSupport) {
102152
+ if (typeof window === 'undefined')
102153
+ return;
102154
+ localStorage.setItem(LOCAL_LLM_TOOL_SUPPORT_KEY, String(toolSupport));
102155
+ }
102156
+ function LocalLLMTab({ isAvailable, state, ollamaStatus, systemMemory, isUsingFoundry = false, foundryModels = [], selectedFoundryModel = null, foundryStatus, onStartDownload, onCancelDownload, onInstallOllama, onStopManager, onInstallFoundry, onCheckStatus, onResetState, onSelectFoundryModel, }) {
102157
+ var _a, _b, _c, _d, _e, _f, _g;
102158
+ // Direct Tauri access so the model-size check can read system memory fresh at
102159
+ // download-click time (the threaded `systemMemory` prop is only a fast path).
102160
+ const { invoke } = useTauri();
101814
102161
  const isDownloading = state.status === 'downloading';
101815
- const isModelReady = state.status === 'completed' || (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed);
101816
102162
  const isManagerInstalling = state.managerInstalling || (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installing);
101817
102163
  // User preference state - independent of whether model is installed
101818
102164
  const [isEnabled, setIsEnabled] = useState(() => isLocalLLMEnabled());
102165
+ // Which model the shared download flow currently targets. Prefer the model
102166
+ // recorded in the persisted download state, so the correct row shows progress
102167
+ // even after the dialog is closed and reopened mid-download; fall back to a
102168
+ // local value for immediate feedback the instant a Download button is clicked.
102169
+ const [localActiveModelId, setLocalActiveModelId] = useState(PRIMARY_MODEL_ID);
102170
+ const activeModelId = state.status === 'downloading' && state.activeModel ? state.activeModel : localActiveModelId;
102171
+ // Which model the user has chosen to chat with (exclusive selection).
102172
+ const [selectedModelId, setSelectedModelId] = useState(() => getLocalLLMModel() || PRIMARY_MODEL_ID);
102173
+ // A model the user asked to download that is large relative to this machine's
102174
+ // memory. Set when we need the user to confirm a "might not run" warning before
102175
+ // starting the download; null when no confirmation is pending.
102176
+ const [pendingModel, setPendingModel] = useState(null);
102177
+ // "Enable Local Models" === run the model manager (Ollama). The toggle is a
102178
+ // user preference that is always operable: turning it on installs (if needed)
102179
+ // and starts the manager; turning it off stops it. The card + model table are
102180
+ // shown whenever the toggle is on (or mid-install), independent of install
102181
+ // state, so the user always gets feedback for their choice.
102182
+ const managerRunning = (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.running) === true;
102183
+ const localModelsEnabled = isEnabled || isManagerInstalling;
102184
+ // A model can be chosen for chat only once it is downloaded/ready. The backend
102185
+ // reports every installed model tag, so any downloaded model qualifies.
102186
+ const selectedModelReady = isCloudModelId(selectedModelId) ||
102187
+ isModelInstalled(selectedModelId, ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed_models) ||
102188
+ (selectedModelId === PRIMARY_MODEL_ID &&
102189
+ (state.status === 'completed' || (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed) === true));
101819
102190
  // Track previous model installation state to detect when it first becomes installed
101820
102191
  const [, setWasModelInstalled] = useState(() => {
101821
102192
  return (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed) === true;
@@ -101834,21 +102205,35 @@ function LocalLLMTab({ isAvailable, state, ollamaStatus, isUsingFoundry = false,
101834
102205
  const isNowInstalled = (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed) === true;
101835
102206
  setWasModelInstalled(isNowInstalled);
101836
102207
  }, [ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed]);
101837
- // Sync localStorage when user preference changes
102208
+ // Sync the app-facing "local LLM enabled" flag. It means "local models are
102209
+ // usable" (manager + a model ready), which is what the rest of the app reads.
101838
102210
  useEffect(() => {
101839
- // Save enabled state: when user enables it AND system is ready,
101840
- // it should stay enabled (even in offline mode)
101841
- if (isEnabled && isFullyReady) {
101842
- setLocalLLMEnabled(true);
101843
- }
101844
- else if (!isEnabled) {
101845
- // User explicitly disabled it
101846
- setLocalLLMEnabled(false);
102211
+ if (isUsingFoundry) {
102212
+ if (isEnabled && isFullyReady) {
102213
+ setLocalLLMEnabled(true);
102214
+ }
102215
+ else if (!isEnabled) {
102216
+ setLocalLLMEnabled(false);
102217
+ }
101847
102218
  }
101848
- // If user enabled but system not ready, don't save yet
101849
- }, [isEnabled, isFullyReady]);
102219
+ else {
102220
+ // Ollama path: the app-facing flag mirrors the user's toggle choice so the
102221
+ // rest of the app uses local models exactly when the user asked for it.
102222
+ setLocalLLMEnabled(isEnabled);
102223
+ }
102224
+ }, [isEnabled, isFullyReady, isUsingFoundry]);
102225
+ // While Local Models is enabled but the manager isn't running yet (it may still
102226
+ // be starting, or an earlier status check ran before Ollama was up and answered),
102227
+ // keep re-checking so the card flips to "Running" as soon as Ollama answers —
102228
+ // instead of being stuck showing a stale "stopped". Stops once running/disabled.
102229
+ useEffect(() => {
102230
+ if (isUsingFoundry || !isEnabled || managerRunning)
102231
+ return;
102232
+ const id = setInterval(() => onCheckStatus(), 2500);
102233
+ return () => clearInterval(id);
102234
+ }, [isUsingFoundry, isEnabled, managerRunning, onCheckStatus]);
101850
102235
  if (!isAvailable) {
101851
- return (jsx("div", { className: "max-w-2xl space-y-6", children: jsx("div", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Local LLM features are only available in the desktop app." }) }));
102236
+ return (jsx("div", { className: "space-y-6", children: jsx("div", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Local Models features are only available in the desktop app." }) }));
101852
102237
  }
101853
102238
  // Check if we should show Foundry installation UI
101854
102239
  const shouldShowFoundryInstall = foundryStatus && foundryStatus.is_supported && !foundryStatus.has_models && !isUsingFoundry;
@@ -101895,56 +102280,328 @@ function LocalLLMTab({ isAvailable, state, ollamaStatus, isUsingFoundry = false,
101895
102280
  }, 100);
101896
102281
  }
101897
102282
  }, [shouldShowFoundryInstall, onInstallFoundry, hasAutoStarted, state.status]);
101898
- const showInstallManagerButton = ollamaStatus !== null && !(ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed);
101899
- const needsModelDownload = (state.status === 'idle' || state.status === 'cancelled' || state.status === 'error') &&
101900
- (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed) &&
101901
- !(ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed);
101902
102283
  const handleToggle = (checked) => {
102284
+ // Always reflect the user's intent — the toggle is operable in both directions.
102285
+ setIsEnabled(checked);
102286
+ // Foundry readiness is driven separately (download/load a model), so for the
102287
+ // Foundry path the toggle is just a preference.
102288
+ if (isUsingFoundry)
102289
+ return;
102290
+ // Ollama path: "Enable Local Models" === run the model manager.
101903
102291
  if (checked) {
101904
- // User wants to enable
101905
- if (isModelReady) {
101906
- // Model already installed, just enable the preference
101907
- setIsEnabled(true);
101908
- }
101909
- else if (showInstallManagerButton && !isManagerInstalling) {
101910
- // Need to install Model Manager first
102292
+ // Ensure the manager is installed AND running (install_ollama does both;
102293
+ // it also starts it if it's installed but stopped).
102294
+ if (!isManagerInstalling) {
101911
102295
  onInstallOllama();
101912
102296
  }
101913
- else if (needsModelDownload) {
101914
- // Need to download the model
101915
- onStartDownload();
101916
- }
101917
102297
  }
101918
102298
  else {
101919
- // User wants to disable - always allow this
101920
- setIsEnabled(false);
102299
+ // Disabling stops the model manager.
102300
+ onStopManager === null || onStopManager === void 0 ? void 0 : onStopManager();
102301
+ }
102302
+ };
102303
+ const startDownloadNow = (modelId) => {
102304
+ setLocalActiveModelId(modelId);
102305
+ onStartDownload(modelId);
102306
+ };
102307
+ const handleDownloadModel = async (modelId) => {
102308
+ // Always read system memory FIRST — this is the call that hits the backend
102309
+ // `get_system_memory`. Doing it before the catalog lookup guarantees it runs
102310
+ // on every Download click (falling back to the value fetched on mount).
102311
+ const model = LOCAL_MODELS.find((m) => modelId.startsWith(m.id));
102312
+ if (!model) {
102313
+ await invoke(TAURI_COMMANDS.LOG_STDOUT, { s: `[Local Models] -> Download: bad download request, model ${model}` });
102314
+ console.log(`[Local Models] -> Download: bad download request, model ${model}`);
102315
+ return;
102316
+ }
102317
+ let mem = null;
102318
+ try {
102319
+ mem = await invoke(TAURI_COMMANDS.GET_SYSTEM_MEMORY);
102320
+ }
102321
+ catch (e) {
102322
+ await invoke(TAURI_COMMANDS.LOG_STDOUT, { s: `[Local Models] -> Download: couldn't detect system memory ${modelId} ${e}` });
102323
+ console.log(`[Local Models] -> Download: couldn't detect system memory {e}`);
102324
+ return;
102325
+ }
102326
+ const exceeds = modelExceedsCapacity(model, mem);
102327
+ console.warn('[Local Models] -> Download: download click', {
102328
+ modelId,
102329
+ systemMemory: mem,
102330
+ capacityBytes: usableMemoryBytes(mem),
102331
+ modelBytes: parseModelSizeBytes(model.size),
102332
+ thresholdBytes: usableMemoryBytes(mem) * MODEL_SIZE_WARN_FRACTION,
102333
+ exceeds,
102334
+ });
102335
+ if (exceeds) {
102336
+ setPendingModel(model);
102337
+ return;
102338
+ }
102339
+ startDownloadNow(modelId);
102340
+ };
102341
+ const confirmPendingDownload = () => {
102342
+ if (pendingModel) {
102343
+ startDownloadNow(pendingModel.id);
101921
102344
  }
102345
+ setPendingModel(null);
101922
102346
  };
101923
- // The switch should be checked if user preference is enabled AND system is ready
101924
- const switchChecked = isEnabled && isFullyReady;
102347
+ const handleSelectModel = (modelId) => {
102348
+ var _a, _b;
102349
+ setSelectedModelId(modelId);
102350
+ setLocalLLMModel(modelId);
102351
+ // Persist the model's tool-calling support so the host can route chat to the
102352
+ // MCP/tool proxy (:8000) vs plain Ollama (:11434).
102353
+ setLocalLLMToolSupport((_b = (_a = LOCAL_MODELS.find((m) => m.id === modelId)) === null || _a === void 0 ? void 0 : _a.tool_support) !== null && _b !== void 0 ? _b : false);
102354
+ };
102355
+ // For Ollama the switch reflects the user's toggle preference (always
102356
+ // operable, so it can be turned off). Foundry additionally requires a
102357
+ // downloaded model to be considered "on".
102358
+ const switchChecked = isUsingFoundry ? isEnabled && isFullyReady : isEnabled;
101925
102359
  // Determine switch disabled state based on which system is being used:
101926
- // For Foundry:
101927
- // - Disabled if Foundry is supported but no models are downloaded
101928
- // - Disabled if downloading or checking
101929
- // For Ollama (fallback):
101930
- // - Disabled if Ollama is not installed
101931
- // - Disabled if Phi model is not downloaded
101932
- // - Disabled if downloading, installing, or checking
102360
+ // For Foundry: disabled if no downloaded models, or while downloading/checking.
102361
+ // For Ollama: disabled only during a transient operation (installing/starting,
102362
+ // a model download, or a status check) — when idle it is always operable so
102363
+ // the user can turn local models off. (The "can't disable" bug was the
102364
+ // checked state being forced on, not this; see switchChecked.)
101933
102365
  const switchDisabled = isUsingFoundry
101934
- ? // Foundry: disable if no downloaded models or if currently downloading
101935
- !hasDownloadedFoundryModel || isDownloading || state.status === 'checking'
101936
- : // Ollama: disable if not fully set up
101937
- isDownloading ||
101938
- isManagerInstalling ||
101939
- state.status === 'checking' ||
101940
- !(ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed) ||
101941
- !(ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed);
101942
- return (jsx("div", { className: "max-w-2xl space-y-6", children: jsxs("div", { className: "space-y-4", children: [jsxs("div", { className: "flex items-center justify-between rounded-lg border px-6 py-4", style: { borderColor: 'oklch(.922 0 0)' }, children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "text-sm font-medium text-[#646464]", children: "Enable Local LLM" }), jsx(TooltipProvider, { children: jsxs(Tooltip, { children: [jsx(TooltipTrigger, { "aria-label": "More info about Local LLM", className: "hidden sm:block", children: jsx(Info$3, { className: "h-4 w-4 text-gray-400" }) }), jsx(TooltipContent, { className: "rounded-lg bg-gray-700 px-3 py-2 text-sm font-medium max-w-xs text-white shadow-sm transition-opacity duration-300 z-50", children: jsxs("div", { className: "space-y-2", children: [jsx("p", { children: "Work offline with AI capabilities on your device." }), (foundryStatus === null || foundryStatus === void 0 ? void 0 : foundryStatus.is_supported) && (jsx("p", { className: "text-xs text-gray-300", children: "Your device automatically uses the best offline AI option available." }))] }) })] }) })] }), jsx(Switch, { checked: switchChecked, onCheckedChange: handleToggle, disabled: switchDisabled, "aria-label": `Enable Local LLM ${switchChecked ? 'enabled' : 'disabled'}`, className: "cursor-pointer data-[state=checked]:bg-blue-500" })] }), shouldShowFoundryInstall ? (jsx("div", { className: "rounded-lg border px-6 py-4 bg-blue-50/30 dark:bg-blue-950/10", style: { borderColor: 'oklch(.922 0 0)' }, children: jsxs("div", { className: "space-y-4", children: [jsx("div", { className: "flex items-start gap-3", children: jsxs("div", { className: "flex items-center gap-2 flex-shrink-0 mt-0.5", children: [jsx(Star$1, { className: "h-5 w-5 text-blue-600 fill-blue-600" }), jsx("span", { className: "px-2 py-0.5 text-xs font-semibold bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300 rounded-full", children: "PREFERRED" })] }) }), jsxs("div", { className: "flex items-start gap-3", children: [jsx(Info$3, { className: "h-5 w-5 text-blue-500 flex-shrink-0 mt-0.5" }), jsxs("div", { className: "flex-1", children: [jsx("h3", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Setting up offline AI" }), jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Downloading optimized AI capabilities for your device. This enables you to work offline with full AI features." })] })] }), state.status === 'downloading' || state.status === 'checking' ? (jsxs("div", { className: "space-y-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(LoaderCircle, { className: "h-4 w-4 animate-spin text-blue-500" }), jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: state.message || 'Preparing download...' })] }), state.progress > 0 && jsx(Progress, { value: state.progress, className: "h-2" })] })) : state.status === 'error' ? (jsxs("div", { className: "space-y-2", children: [jsxs("div", { className: "flex items-center gap-2 text-red-600 dark:text-red-400", children: [jsx(CircleX, { className: "h-4 w-4" }), jsx("span", { className: "text-sm", children: state.error || 'Download failed' })] }), jsx("div", { className: "flex gap-2", children: jsxs(Button$1, { onClick: onInstallFoundry, size: "sm", variant: "outline", children: [jsx(RefreshCw, { className: "h-4 w-4 mr-2" }), "Try Again"] }) })] })) : (jsx("div", { className: "space-y-2", children: jsxs("div", { className: "flex items-center gap-2", children: [jsx(LoaderCircle, { className: "h-4 w-4 animate-spin text-blue-500" }), jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: "Starting download..." })] }) })), jsxs("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: [jsx("p", { className: "font-medium mb-1", children: "Download size: ~500MB" }), jsx("p", { children: "This is a one-time setup to enable offline capabilities." })] })] }) })) : /* Requirements Table - Hidden when using Foundry Local */ !isUsingFoundry ? (jsxs(Fragment$1, { children: [(foundryStatus === null || foundryStatus === void 0 ? void 0 : foundryStatus.is_supported) && (jsx("div", { className: "rounded-lg border px-4 py-3 bg-blue-50/20 dark:bg-blue-950/10", style: { borderColor: 'oklch(.922 0 0)' }, children: jsxs("div", { className: "flex items-start gap-2", children: [jsx(Info$3, { className: "h-4 w-4 text-blue-600 flex-shrink-0 mt-0.5" }), jsxs("div", { className: "text-xs text-gray-600 dark:text-gray-400", children: [jsx("span", { className: "font-medium text-gray-900 dark:text-gray-100", children: "Note:" }), " Your device supports an optimized offline AI option. Currently using alternative setup. For best performance, download the recommended option above."] })] }) })), jsx("div", { className: "rounded-lg border overflow-hidden", style: { borderColor: 'oklch(.922 0 0)' }, children: jsxs("table", { className: "w-full", children: [jsx("thead", { children: jsxs("tr", { className: "bg-gray-50 dark:bg-gray-800/50", children: [jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Component" }), jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Status" }), jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Action" })] }) }), jsxs("tbody", { className: "bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-800", children: [jsxs("tr", { children: [jsxs("td", { className: "px-6 py-4 whitespace-nowrap", children: [jsx("div", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Model Manager" }), jsx("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Required to run AI models locally" })] }), jsx("td", { className: "px-6 py-4", children: jsx("div", { className: "space-y-2", children: isManagerInstalling ? (jsxs("div", { className: "space-y-2", children: [jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300 rounded-full", children: [jsx(LoaderCircle, { className: "h-3 w-3 animate-spin" }), "Installing..."] }), jsx("div", { className: "w-full max-w-[200px]", children: jsx(Progress, { value: state.managerInstallProgress || 0, className: "h-1.5" }) })] })) : (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed) ? (jsxs("div", { className: "flex items-center gap-2", children: [jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300 rounded-full", children: [jsx(CircleCheckBig, { className: "h-3 w-3" }), "Installed"] }), ollamaStatus.running && (jsx("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300 rounded-full", children: "Running" }))] })) : (jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300 rounded-full", children: [jsx(CircleX, { className: "h-3 w-3" }), "Not Installed"] })) }) }), jsxs("td", { className: "px-6 py-4 text-right", children: [showInstallManagerButton && !isManagerInstalling && (jsxs(Button$1, { onClick: onInstallOllama, size: "sm", variant: "outline", children: [jsx(Download, { className: "h-4 w-4 mr-2" }), "Install"] })), (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed) && (jsx(Button$1, { onClick: onCheckStatus, variant: "ghost", size: "sm", children: jsx(RefreshCw, { className: "h-4 w-4" }) }))] })] }), jsxs("tr", { children: [jsxs("td", { className: "px-6 py-4 whitespace-nowrap", children: [jsx("div", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "AI Model (Phi-3 Mini)" }), jsx("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: isDownloading ? state.message : 'Local language model for offline use' })] }), jsx("td", { className: "px-6 py-4", children: jsx("div", { className: "space-y-2", children: isDownloading ? (jsxs("div", { className: "space-y-2", children: [jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300 rounded-full", children: [jsx(LoaderCircle, { className: "h-3 w-3 animate-spin" }), Math.round(state.progress), "%"] }), jsx("div", { className: "w-full max-w-[200px]", children: jsx(Progress, { value: state.progress, className: "h-1.5" }) })] })) : state.status === 'checking' ? (jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300 rounded-full", children: [jsx(LoaderCircle, { className: "h-3 w-3 animate-spin" }), "Checking"] })) : state.status === 'completed' || (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed) ? (jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300 rounded-full", children: [jsx(CircleCheckBig, { className: "h-3 w-3" }), "Ready"] })) : state.status === 'error' ? (jsxs("div", { className: "space-y-1", children: [jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300 rounded-full", children: [jsx(CircleX, { className: "h-3 w-3" }), "Error"] }), state.error && (jsx("p", { className: "text-xs text-red-600 dark:text-red-400 max-w-[200px]", children: state.error }))] })) : state.status === 'cancelled' ? (jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300 rounded-full", children: [jsx(X$2, { className: "h-3 w-3" }), "Cancelled"] })) : (jsx("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300 rounded-full", children: "Not Downloaded" })) }) }), jsx("td", { className: "px-6 py-4 text-right", children: jsxs("div", { className: "flex items-center justify-end gap-2", children: [isDownloading && (jsxs(Button$1, { onClick: onCancelDownload, variant: "outline", size: "sm", children: [jsx(X$2, { className: "h-4 w-4 mr-2" }), "Cancel"] })), needsModelDownload && !isDownloading && (jsxs(Button$1, { onClick: onStartDownload, size: "sm", variant: "outline", children: [jsx(Download, { className: "h-4 w-4 mr-2" }), "Download"] })), state.status === 'error' && (jsx(Button$1, { onClick: onResetState, variant: "ghost", size: "sm", children: "Reset" })), (state.status === 'completed' || state.status === 'cancelled') && (jsx(Button$1, { onClick: onCheckStatus, variant: "ghost", size: "sm", children: jsx(RefreshCw, { className: "h-4 w-4" }) }))] }) })] })] })] }) })] })) : (jsx("div", { className: "rounded-lg border px-6 py-4 bg-green-50/30 dark:bg-green-950/10", style: { borderColor: 'oklch(.922 0 0)' }, children: jsxs("div", { className: "space-y-4", children: [jsx("div", { className: "flex items-center justify-between", children: jsxs("div", { className: "flex items-center gap-3", children: [jsx(CircleCheckBig, { className: "h-8 w-8 text-green-500 flex-shrink-0" }), jsxs("div", { className: "flex-1", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("h3", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Offline AI Ready" }), jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-semibold bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300 rounded-full", children: [jsx(Star$1, { className: "h-3 w-3 fill-blue-700 dark:fill-blue-300" }), "PREFERRED"] })] }), jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Your device is using the optimized offline AI setup. You can work with full capabilities even without internet." })] })] }) }), foundryModels.length > 0 && (jsxs("div", { className: "space-y-2", children: [jsx("label", { className: "text-xs font-medium text-gray-700 dark:text-gray-300", children: "Select Model" }), jsxs(Select$1, { value: selectedFoundryModel || ((_b = foundryModels[0]) === null || _b === void 0 ? void 0 : _b.id), onValueChange: (value) => onSelectFoundryModel === null || onSelectFoundryModel === void 0 ? void 0 : onSelectFoundryModel(value), children: [jsx(SelectTrigger, { className: "w-full bg-gray-50 border-gray-200 dark:bg-gray-800 dark:border-gray-700", children: jsx(SelectValue, { placeholder: "Select a model" }) }), jsx(SelectContent, { children: foundryModels.map((model) => (jsx(SelectItem, { value: model.id, children: jsxs("div", { className: "flex items-center justify-between w-full gap-4", children: [jsx("span", { className: "font-medium", children: model.name || model.id }), jsxs("div", { className: "flex items-center gap-2 text-xs text-gray-500", children: [model.device && (jsx("span", { className: "px-2 py-0.5 bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300 rounded", children: model.device })), model.size && jsx("span", { children: model.size }), !model.is_downloaded && (jsx("span", { className: "px-2 py-0.5 bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-300 rounded", children: "Not Downloaded" })), model.is_downloaded && (jsx("span", { className: "px-2 py-0.5 bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300 rounded", children: "Downloaded" }))] })] }) }, model.id))) })] }), jsxs("div", { className: "flex flex-col gap-1", children: [jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: [foundryModels.length, " model", foundryModels.length !== 1 ? 's' : '', " available"] }), selectedFoundryModel && (jsxs("div", { className: "flex items-center gap-1.5 text-xs text-green-600 dark:text-green-400", children: [jsx(CircleCheckBig, { className: "h-3.5 w-3.5" }), jsxs("span", { children: ["Using:", ' ', ((_c = foundryModels.find((m) => m.id === selectedFoundryModel)) === null || _c === void 0 ? void 0 : _c.name) ||
101943
- selectedFoundryModel, ((_d = foundryModels.find((m) => m.id === selectedFoundryModel)) === null || _d === void 0 ? void 0 : _d.device) && (jsxs("span", { className: "ml-1 text-gray-500 dark:text-gray-400", children: ["(", (_e = foundryModels.find((m) => m.id === selectedFoundryModel)) === null || _e === void 0 ? void 0 : _e.device, ")"] }))] })] }))] })] })), state.status === 'downloading' && (jsxs("div", { className: "mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg", children: [jsxs("div", { className: "flex items-center gap-3", children: [jsx("div", { className: "animate-spin h-5 w-5 border-2 border-blue-600 dark:border-blue-400 border-t-transparent rounded-full" }), jsxs("div", { className: "flex-1", children: [jsx("p", { className: "text-sm font-medium text-blue-900 dark:text-blue-100", children: state.message }), state.progress > 0 && (jsx("div", { className: "mt-2 w-full bg-blue-200 dark:bg-blue-800 rounded-full h-2", children: jsx("div", { className: "bg-blue-600 dark:bg-blue-400 h-2 rounded-full transition-all duration-300", style: { width: `${state.progress}%` } }) }))] })] }), state.logs.length > 0 && (jsx("div", { className: "mt-3 max-h-32 overflow-y-auto bg-white dark:bg-gray-800 rounded border border-blue-200 dark:border-blue-700 p-2", children: state.logs.slice(-10).map((log, idx) => (jsx("div", { className: `text-xs font-mono ${log.level === 'error'
101944
- ? 'text-red-600 dark:text-red-400'
101945
- : log.level === 'warn'
101946
- ? 'text-yellow-600 dark:text-yellow-400'
101947
- : 'text-gray-700 dark:text-gray-300'}`, children: log.message }, idx))) }))] }))] }) }))] }) }));
102366
+ ? !hasDownloadedFoundryModel || isDownloading || state.status === 'checking'
102367
+ : isDownloading || isManagerInstalling || state.status === 'checking';
102368
+ return (jsxs("div", { className: "space-y-6", children: [jsxs("div", { className: "space-y-4", children: [jsxs("div", { className: "flex items-center justify-between rounded-lg border px-6 py-4", style: { borderColor: 'oklch(.922 0 0)' }, children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "text-sm font-medium text-[#646464]", children: "Enable Local Models" }), jsx(TooltipProvider, { children: jsxs(Tooltip, { children: [jsx(TooltipTrigger, { "aria-label": "More info about Local Models", className: "hidden sm:block", children: jsx(Info$3, { className: "h-4 w-4 text-gray-400" }) }), jsx(TooltipContent, { className: "rounded-lg bg-gray-700 px-3 py-2 text-sm font-medium max-w-xs text-white shadow-sm transition-opacity duration-300 z-50", children: jsxs("div", { className: "space-y-2", children: [jsx("p", { children: "Work offline with AI capabilities on your device." }), (foundryStatus === null || foundryStatus === void 0 ? void 0 : foundryStatus.is_supported) && (jsx("p", { className: "text-xs text-gray-300", children: "Your device automatically uses the best offline AI option available." }))] }) })] }) })] }), jsxs("div", { className: "flex items-center gap-2", children: [isManagerInstalling && !isUsingFoundry && (jsx(LoaderCircle, { className: "h-4 w-4 animate-spin text-blue-500", "aria-label": "Starting model manager" })), jsx(Switch, { checked: switchChecked, onCheckedChange: handleToggle, disabled: switchDisabled, "aria-label": `Enable Local Models ${switchChecked ? 'enabled' : 'disabled'}`, className: "cursor-pointer data-[state=checked]:bg-blue-500" })] })] }), shouldShowFoundryInstall ? (jsx("div", { className: "rounded-lg border px-6 py-4 bg-blue-50/30 dark:bg-blue-950/10", style: { borderColor: 'oklch(.922 0 0)' }, children: jsxs("div", { className: "space-y-4", children: [jsx("div", { className: "flex items-start gap-3", children: jsxs("div", { className: "flex items-center gap-2 flex-shrink-0 mt-0.5", children: [jsx(Star$1, { className: "h-5 w-5 text-blue-600 fill-blue-600" }), jsx("span", { className: "px-2 py-0.5 text-xs font-semibold bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300 rounded-full", children: "PREFERRED" })] }) }), jsxs("div", { className: "flex items-start gap-3", children: [jsx(Info$3, { className: "h-5 w-5 text-blue-500 flex-shrink-0 mt-0.5" }), jsxs("div", { className: "flex-1", children: [jsx("h3", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Setting up offline AI" }), jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Downloading optimized AI capabilities for your device. This enables you to work offline with full AI features." })] })] }), state.status === 'downloading' || state.status === 'checking' ? (jsxs("div", { className: "space-y-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(LoaderCircle, { className: "h-4 w-4 animate-spin text-blue-500" }), jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: state.message || 'Preparing download...' })] }), state.progress > 0 && jsx(Progress, { value: state.progress, className: "h-2" })] })) : state.status === 'error' ? (jsxs("div", { className: "space-y-2", children: [jsxs("div", { className: "flex items-center gap-2 text-red-600 dark:text-red-400", children: [jsx(CircleX, { className: "h-4 w-4" }), jsx("span", { className: "text-sm", children: state.error || 'Download failed' })] }), jsx("div", { className: "flex gap-2", children: jsxs(Button$1, { onClick: onInstallFoundry, size: "sm", variant: "outline", children: [jsx(RefreshCw, { className: "h-4 w-4 mr-2" }), "Try Again"] }) })] })) : (jsx("div", { className: "space-y-2", children: jsxs("div", { className: "flex items-center gap-2", children: [jsx(LoaderCircle, { className: "h-4 w-4 animate-spin text-blue-500" }), jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: "Starting download..." })] }) })), jsxs("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: [jsx("p", { className: "font-medium mb-1", children: "Download size: ~500MB" }), jsx("p", { children: "This is a one-time setup to enable offline capabilities." })] })] }) })) : /* Requirements Table - Hidden when using Foundry Local */ !isUsingFoundry ? (jsxs(Fragment$1, { children: [(foundryStatus === null || foundryStatus === void 0 ? void 0 : foundryStatus.is_supported) && (jsx("div", { className: "rounded-lg border px-4 py-3 bg-blue-50/20 dark:bg-blue-950/10", style: { borderColor: 'oklch(.922 0 0)' }, children: jsxs("div", { className: "flex items-start gap-2", children: [jsx(Info$3, { className: "h-4 w-4 text-blue-600 flex-shrink-0 mt-0.5" }), jsxs("div", { className: "text-xs text-gray-600 dark:text-gray-400", children: [jsx("span", { className: "font-medium text-gray-900 dark:text-gray-100", children: "Note:" }), " Your device supports an optimized offline AI option. Currently using alternative setup. For best performance, download the recommended option above."] })] }) })), localModelsEnabled && (jsxs("div", { className: "rounded-lg border px-6 py-4", style: { borderColor: 'oklch(.922 0 0)' }, children: [jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsxs("div", { className: "flex items-center gap-3", children: [jsx(Server, { className: "h-5 w-5 text-gray-400", "aria-hidden": "true" }), jsxs("div", { children: [jsx("div", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Model Manager" }), jsx("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Model Manager runs the models on your device" })] })] }), jsx("div", { className: "flex items-center gap-2", children: managerRunning ? (jsxs(Fragment$1, { children: [jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300 rounded-full", children: [jsx(CircleCheckBig, { className: "h-3 w-3" }), "Running"] }), jsx(Button$1, { onClick: onCheckStatus, variant: "ghost", size: "sm", "aria-label": "Refresh model manager status", children: jsx(RefreshCw, { className: "h-4 w-4" }) })] })) : (jsxs(Fragment$1, { children: [jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300 rounded-full", children: [jsx(LoaderCircle, { className: "h-3 w-3 animate-spin" }), "Starting\u2026"] }), jsx(Button$1, { onClick: onCheckStatus, variant: "ghost", size: "sm", "aria-label": "Refresh model manager status", children: jsx(RefreshCw, { className: "h-4 w-4" }) })] })) })] }), isManagerInstalling && (jsx("div", { className: "mt-3 w-full max-w-[240px]", children: jsx(Progress, { value: state.managerInstallProgress || 0, className: "h-1.5" }) }))] })), localModelsEnabled && (jsxs("div", { className: "rounded-lg border overflow-hidden", style: { borderColor: 'oklch(.922 0 0)' }, children: [jsxs("table", { className: "w-full", children: [jsx("thead", { children: jsxs("tr", { className: "bg-gray-50 dark:bg-gray-800/50", children: [jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-12", children: "Use" }), jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Model" }), jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Status" }), jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Action" })] }) }), jsx("tbody", { className: "bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-800", children: LOCAL_MODELS.map((model) => {
102369
+ const isPrimary = model.id === PRIMARY_MODEL_ID;
102370
+ const isActive = activeModelId === model.id;
102371
+ // Cloud-hosted models are always available — they run on
102372
+ // Ollama's cloud, so there's nothing to download.
102373
+ const isCloud = isCloudModelId(model.id);
102374
+ // A model is ready once Ollama reports its tag as installed.
102375
+ // Fall back to (a) this row's own download having just
102376
+ // completed — so it flips to "Ready" before the status
102377
+ // refresh lands — and (b) the primary-model status flag.
102378
+ // Cloud models are ready by default.
102379
+ const isInstalled = isCloud ||
102380
+ isModelInstalled(model.id, ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed_models) ||
102381
+ (isActive && state.status === 'completed') ||
102382
+ (isPrimary && (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed) === true);
102383
+ // An installed model must never render a download/cancel
102384
+ // state — guards against a stale persisted "downloading"
102385
+ // status making an already-ready model look like it's
102386
+ // still downloading (and wrongly cancellable) on reopen.
102387
+ const rowDownloading = isActive && isDownloading && !isInstalled;
102388
+ const rowChecking = isActive && state.status === 'checking';
102389
+ const rowError = isActive && state.status === 'error';
102390
+ const rowCancelled = isActive && state.status === 'cancelled';
102391
+ const canDownload = !!(ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed) &&
102392
+ !isInstalled &&
102393
+ !rowDownloading &&
102394
+ !rowChecking;
102395
+ const isSelected = selectedModelId === model.id;
102396
+ return (jsxs("tr", { className: isSelected ? 'bg-blue-50/40 dark:bg-blue-950/20' : undefined, children: [jsx("td", { className: "px-6 py-4", children: jsx("input", { type: "radio", name: "local-llm-model", checked: isSelected, disabled: !isInstalled, onChange: () => handleSelectModel(model.id), "aria-label": `Use ${model.name} for chat`, title: isInstalled ? undefined : 'Download this model to chat with it', className: isInstalled
102397
+ ? 'h-4 w-4 cursor-pointer accent-blue-500'
102398
+ : 'h-4 w-4 cursor-not-allowed opacity-40' }) }), jsxs("td", { className: "px-6 py-4 whitespace-nowrap", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: model.name }), jsx("span", { className: "text-xs text-gray-400 dark:text-gray-500", children: model.size })] }), jsx("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: model.provider })] }), jsx("td", { className: "px-6 py-4", children: jsx("div", { className: "space-y-2", children: isCloud ? (jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300 rounded-full", children: [jsx(Cloud, { className: "h-3 w-3" }), "Cloud"] })) : rowDownloading ? (jsxs("div", { className: "space-y-2", children: [jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300 rounded-full", children: [jsx(LoaderCircle, { className: "h-3 w-3 animate-spin" }), Math.round(state.progress), "%"] }), jsx("div", { className: "w-full max-w-[200px]", children: jsx(Progress, { value: state.progress, className: "h-1.5" }) })] })) : rowChecking ? (jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300 rounded-full", children: [jsx(LoaderCircle, { className: "h-3 w-3 animate-spin" }), "Checking"] })) : isInstalled ? (jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300 rounded-full", children: [jsx(CircleCheckBig, { className: "h-3 w-3" }), "Ready"] })) : rowError ? (jsxs("div", { className: "space-y-1", children: [jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300 rounded-full", children: [jsx(CircleX, { className: "h-3 w-3" }), "Error"] }), state.error && (jsx("p", { className: "text-xs text-red-600 dark:text-red-400 max-w-[200px]", children: state.error }))] })) : rowCancelled ? (jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300 rounded-full", children: [jsx(X$2, { className: "h-3 w-3" }), "Cancelled"] })) : (jsx("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300 rounded-full", children: "Not Downloaded" })) }) }), jsx("td", { className: "px-6 py-4 text-right", children: jsxs("div", { className: "flex items-center justify-end gap-2", children: [rowDownloading && (jsxs(Button$1, { onClick: onCancelDownload, variant: "outline", size: "sm", children: [jsx(X$2, { className: "h-4 w-4 mr-2" }), "Cancel"] })), canDownload && (jsxs(Button$1, { onClick: () => handleDownloadModel(model.id), size: "sm", variant: "outline", children: [jsx(Download, { className: "h-4 w-4 mr-2" }), "Download"] })), rowError && (jsx(Button$1, { onClick: onResetState, variant: "ghost", size: "sm", children: "Reset" })), isInstalled && !isCloud && (jsx(Button$1, { onClick: onCheckStatus, variant: "ghost", size: "sm", children: jsx(RefreshCw, { className: "h-4 w-4" }) }))] }) })] }, model.id));
102399
+ }) })] }), jsx("div", { className: "px-6 py-3 border-t text-xs text-gray-500 dark:text-gray-400", style: { borderColor: 'oklch(.922 0 0)' }, children: selectedModelReady ? (jsxs(Fragment$1, { children: ["Chatting with:", ' ', jsx("span", { className: "font-medium text-gray-900 dark:text-gray-100", children: (_c = (_b = LOCAL_MODELS.find((m) => m.id === selectedModelId)) === null || _b === void 0 ? void 0 : _b.name) !== null && _c !== void 0 ? _c : selectedModelId })] })) : (jsx(Fragment$1, { children: "Download a model and select it to start chatting." })) })] }))] })) : (jsx("div", { className: "rounded-lg border px-6 py-4 bg-green-50/30 dark:bg-green-950/10", style: { borderColor: 'oklch(.922 0 0)' }, children: jsxs("div", { className: "space-y-4", children: [jsx("div", { className: "flex items-center justify-between", children: jsxs("div", { className: "flex items-center gap-3", children: [jsx(CircleCheckBig, { className: "h-8 w-8 text-green-500 flex-shrink-0" }), jsxs("div", { className: "flex-1", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("h3", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Offline AI Ready" }), jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-semibold bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300 rounded-full", children: [jsx(Star$1, { className: "h-3 w-3 fill-blue-700 dark:fill-blue-300" }), "PREFERRED"] })] }), jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Your device is using the optimized offline AI setup. You can work with full capabilities even without internet." })] })] }) }), foundryModels.length > 0 && (jsxs("div", { className: "space-y-2", children: [jsx("label", { className: "text-xs font-medium text-gray-700 dark:text-gray-300", children: "Select Model" }), jsxs(Select$1, { value: selectedFoundryModel || ((_d = foundryModels[0]) === null || _d === void 0 ? void 0 : _d.id), onValueChange: (value) => onSelectFoundryModel === null || onSelectFoundryModel === void 0 ? void 0 : onSelectFoundryModel(value), children: [jsx(SelectTrigger, { className: "w-full bg-gray-50 border-gray-200 dark:bg-gray-800 dark:border-gray-700", children: jsx(SelectValue, { placeholder: "Select a model" }) }), jsx(SelectContent, { children: foundryModels.map((model) => (jsx(SelectItem, { value: model.id, children: jsxs("div", { className: "flex items-center justify-between w-full gap-4", children: [jsx("span", { className: "font-medium", children: model.name || model.id }), jsxs("div", { className: "flex items-center gap-2 text-xs text-gray-500", children: [model.device && (jsx("span", { className: "px-2 py-0.5 bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300 rounded", children: model.device })), model.size && jsx("span", { children: model.size }), !model.is_downloaded && (jsx("span", { className: "px-2 py-0.5 bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-300 rounded", children: "Not Downloaded" })), model.is_downloaded && (jsx("span", { className: "px-2 py-0.5 bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300 rounded", children: "Downloaded" }))] })] }) }, model.id))) })] }), jsxs("div", { className: "flex flex-col gap-1", children: [jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: [foundryModels.length, " model", foundryModels.length !== 1 ? 's' : '', " available"] }), selectedFoundryModel && (jsxs("div", { className: "flex items-center gap-1.5 text-xs text-green-600 dark:text-green-400", children: [jsx(CircleCheckBig, { className: "h-3.5 w-3.5" }), jsxs("span", { children: ["Using:", ' ', ((_e = foundryModels.find((m) => m.id === selectedFoundryModel)) === null || _e === void 0 ? void 0 : _e.name) ||
102400
+ selectedFoundryModel, ((_f = foundryModels.find((m) => m.id === selectedFoundryModel)) === null || _f === void 0 ? void 0 : _f.device) && (jsxs("span", { className: "ml-1 text-gray-500 dark:text-gray-400", children: ["(", (_g = foundryModels.find((m) => m.id === selectedFoundryModel)) === null || _g === void 0 ? void 0 : _g.device, ")"] }))] })] }))] })] })), state.status === 'downloading' && (jsxs("div", { className: "mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg", children: [jsxs("div", { className: "flex items-center gap-3", children: [jsx("div", { className: "animate-spin h-5 w-5 border-2 border-blue-600 dark:border-blue-400 border-t-transparent rounded-full" }), jsxs("div", { className: "flex-1", children: [jsx("p", { className: "text-sm font-medium text-blue-900 dark:text-blue-100", children: state.message }), state.progress > 0 && (jsx("div", { className: "mt-2 w-full bg-blue-200 dark:bg-blue-800 rounded-full h-2", children: jsx("div", { className: "bg-blue-600 dark:bg-blue-400 h-2 rounded-full transition-all duration-300", style: { width: `${state.progress}%` } }) }))] })] }), state.logs.length > 0 && (jsx("div", { className: "mt-3 max-h-32 overflow-y-auto bg-white dark:bg-gray-800 rounded border border-blue-200 dark:border-blue-700 p-2", children: state.logs.slice(-10).map((log, idx) => (jsx("div", { className: `text-xs font-mono ${log.level === 'error'
102401
+ ? 'text-red-600 dark:text-red-400'
102402
+ : log.level === 'warn'
102403
+ ? 'text-yellow-600 dark:text-yellow-400'
102404
+ : 'text-gray-700 dark:text-gray-300'}`, children: log.message }, idx))) }))] }))] }) }))] }), jsx(AlertDialog, { open: !!pendingModel, onOpenChange: (open) => {
102405
+ if (!open)
102406
+ setPendingModel(null);
102407
+ }, children: jsxs(AlertDialogContent, { children: [jsxs(AlertDialogHeader, { children: [jsx(AlertDialogTitle, { children: "This model may be too large for your system" }), jsxs(AlertDialogDescription, { children: [pendingModel === null || pendingModel === void 0 ? void 0 : pendingModel.name, " needs about ", pendingModel === null || pendingModel === void 0 ? void 0 : pendingModel.size, ", but your system has", ' ', formatBytes(usableMemoryBytes(systemMemory)), " of memory available to run it. It may run very slowly or fail to load. Download anyway?"] })] }), jsxs(AlertDialogFooter, { children: [jsx(AlertDialogCancel, { onClick: () => setPendingModel(null), children: "Cancel" }), jsx(AlertDialogAction, { onClick: confirmPendingDownload, children: "Download anyway" })] })] }) })] }));
102408
+ }
102409
+
102410
+ function LocalModelsContent(props) {
102411
+ const [isCollapsed, setIsCollapsed] = useState(true);
102412
+ if (!props.isAvailable) {
102413
+ return null;
102414
+ }
102415
+ return (jsxs("div", { className: "rounded-lg px-6 py-5 border border-gray-200 dark:border-gray-700", style: { borderColor: 'oklch(.922 0 0)' }, children: [jsxs("div", { className: "flex items-center justify-between", children: [jsxs("div", { className: "flex items-center gap-3", children: [jsx("span", { className: "text-sm font-medium text-[#646464]", children: "Local Models" }), jsx(TooltipProvider, { children: jsxs(Tooltip, { children: [jsx(TooltipTrigger, { "aria-label": "More info about Local Models", className: "hidden sm:block", children: jsx(Info$3, { className: "h-4 w-4 text-gray-400" }) }), jsx(TooltipContent, { className: "rounded-lg bg-gray-700 px-3 py-2 text-sm font-medium max-w-xs text-white shadow-sm transition-opacity duration-300 z-50", children: jsx("p", { children: "Manage on-device AI models for offline use." }) })] }) })] }), jsx("button", { type: "button", onClick: () => setIsCollapsed((previous) => !previous), className: "p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1", "aria-label": isCollapsed ? 'Expand Local Models' : 'Collapse Local Models', children: isCollapsed ? (jsx(ChevronDown, { className: "h-5 w-5 text-gray-400", "aria-hidden": "true" })) : (jsx(ChevronUp, { className: "h-5 w-5 text-gray-400", "aria-hidden": "true" })) })] }), !isCollapsed && (jsx("div", { className: "mt-4", children: jsx(LocalLLMTab, { ...props }) }))] }));
102416
+ }
102417
+
102418
+ const SYSTEM_CONTROL_ENABLED_KEY = 'ibl_system_control_enabled';
102419
+ /**
102420
+ * Whether the Computer Assistant is enabled, read from localStorage. This is a
102421
+ * user preference (mirrors {@link isLocalLLMEnabled}); it reflects the toggle the
102422
+ * user last chose, independent of whether the helper is installed yet.
102423
+ */
102424
+ function isSystemControlEnabled() {
102425
+ if (typeof window === 'undefined')
102426
+ return false;
102427
+ return localStorage.getItem(SYSTEM_CONTROL_ENABLED_KEY) === 'true';
102428
+ }
102429
+ /**
102430
+ * Persist the Computer Assistant enabled preference to localStorage.
102431
+ */
102432
+ function setSystemControlEnabled(enabled) {
102433
+ if (typeof window === 'undefined')
102434
+ return;
102435
+ localStorage.setItem(SYSTEM_CONTROL_ENABLED_KEY, String(enabled));
102436
+ }
102437
+ /** Green "done" pill. */
102438
+ function DonePill({ children }) {
102439
+ return (jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800 dark:bg-green-900/30 dark:text-green-300", children: [jsx(Check, { className: "h-3 w-3" }), children] }));
102440
+ }
102441
+ /** Blue "in progress" pill. */
102442
+ function WorkingPill({ children }) {
102443
+ return (jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900/30 dark:text-blue-300", children: [jsx(LoaderCircle, { className: "h-3 w-3 animate-spin" }), children] }));
102444
+ }
102445
+ /** A single numbered step row. */
102446
+ function Step$1({ number, done, title, description, action, }) {
102447
+ return (jsxs("div", { className: "flex items-start justify-between gap-3", children: [jsxs("div", { className: "flex items-start gap-3", children: [jsx("span", { className: `flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full text-xs font-semibold ${done
102448
+ ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300'
102449
+ : 'bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400'}`, "aria-hidden": "true", children: done ? jsx(Check, { className: "h-3.5 w-3.5" }) : number }), jsxs("div", { children: [jsx("div", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: title }), jsx("div", { className: "mt-0.5 text-xs text-gray-500 dark:text-gray-400", children: description })] })] }), jsx("div", { className: "flex-shrink-0", children: action })] }));
102450
+ }
102451
+ /**
102452
+ * Body of the "Computer Assistant" card: a plain-language on/off with two simple
102453
+ * steps — set up, then allow control — so non-technical users can follow along.
102454
+ * Mirrors the Local Models tab so the two cards behave the same.
102455
+ */
102456
+ function SystemControlTab({ isAvailable, state, status, requiredSizeGb = DEFAULT_SYSTEM_CONTROL_REQUIRED_SIZE_GB, ollamaStatus, systemMemory, accessibilityPermission, onInstall, onStop, onCheckStatus, onResetState, onRequestAccessibilityPermission, onDownloadModel, }) {
102457
+ const isInstalling = state.status === 'installing' || (status === null || status === void 0 ? void 0 : status.installing) === true;
102458
+ // User preference — independent of whether the helper is installed. Always
102459
+ // operable so the user can turn it off again.
102460
+ const [isEnabled, setIsEnabled] = useState(() => isSystemControlEnabled());
102461
+ // The currently-selected local model (persisted by the Local Models card).
102462
+ // Polled so a selection change in the sibling card is reflected here without a
102463
+ // remount. The assistant only works well with larger models, so it's gated to
102464
+ // a selected model above the size threshold.
102465
+ const [selectedModel, setSelectedModel] = useState(() => getLocalLLMModel());
102466
+ // Whether "Local Models" is turned on. The assistant runs on a local model, so
102467
+ // it's gated off until Local Models is enabled. Polled alongside the model so a
102468
+ // toggle in the sibling card is reflected without a remount.
102469
+ const [localModelsEnabled, setLocalModelsEnabled] = useState(() => isLocalLLMEnabled());
102470
+ useEffect(() => {
102471
+ const id = setInterval(() => {
102472
+ setSelectedModel(getLocalLLMModel());
102473
+ setLocalModelsEnabled(isLocalLLMEnabled());
102474
+ }, 1500);
102475
+ return () => clearInterval(id);
102476
+ }, []);
102477
+ const localModelsOff = !localModelsEnabled;
102478
+ const modelTooSmall = !modelSupportsSystemControl(selectedModel, requiredSizeGb);
102479
+ // "Upgrade" target: the smallest catalog model that clears the size gate.
102480
+ const upgradeTarget = smallestSystemControlModel(requiredSizeGb);
102481
+ // Set while an upgrade is downloading a model, before we auto-enable.
102482
+ const [pendingEnableModelId, setPendingEnableModelId] = useState(null);
102483
+ // Model awaiting a RAM/VRAM "may be too large" confirmation before download.
102484
+ const [pendingCapacityModel, setPendingCapacityModel] = useState(null);
102485
+ const isUpgrading = pendingEnableModelId !== null;
102486
+ const managerRunning = (status === null || status === void 0 ? void 0 : status.running) === true;
102487
+ // Steps are shown whenever the toggle is on (or mid-setup), so the user always
102488
+ // gets feedback for their choice.
102489
+ const managerEnabled = isEnabled || isInstalling;
102490
+ // Permission is known (macOS + plugin wired) and granted.
102491
+ const permissionKnown = typeof accessibilityPermission === 'boolean';
102492
+ const permissionGranted = accessibilityPermission === true;
102493
+ const allSet = managerRunning && (!permissionKnown || permissionGranted);
102494
+ // Keep the app-facing preference flag in sync with the toggle.
102495
+ useEffect(() => {
102496
+ setSystemControlEnabled(isEnabled);
102497
+ }, [isEnabled]);
102498
+ // While enabled but not yet running, keep polling so the step flips to "Ready"
102499
+ // as soon as the host reports the helper is up.
102500
+ useEffect(() => {
102501
+ if (!isEnabled || managerRunning)
102502
+ return;
102503
+ const id = setInterval(() => onCheckStatus(), 2500);
102504
+ return () => clearInterval(id);
102505
+ }, [isEnabled, managerRunning, onCheckStatus]);
102506
+ // While permission isn't granted yet, keep re-checking (the grant happens in
102507
+ // System Settings) so the step flips to "Allowed" on its own.
102508
+ useEffect(() => {
102509
+ if (accessibilityPermission !== false)
102510
+ return;
102511
+ const id = setInterval(() => onCheckStatus(), 2500);
102512
+ return () => clearInterval(id);
102513
+ }, [accessibilityPermission, onCheckStatus]);
102514
+ // After an upgrade kicks off a download, turn the assistant on once the model
102515
+ // finishes downloading (it shows up in the installed list).
102516
+ useEffect(() => {
102517
+ if (!pendingEnableModelId)
102518
+ return;
102519
+ if (isModelInstalled(pendingEnableModelId, ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed_models)) {
102520
+ setPendingEnableModelId(null);
102521
+ setIsEnabled(true);
102522
+ onInstall();
102523
+ }
102524
+ }, [pendingEnableModelId, ollamaStatus, onInstall]);
102525
+ if (!isAvailable) {
102526
+ return (jsx("div", { className: "text-sm text-gray-500 dark:text-gray-400", children: "The Computer Assistant is only available in the desktop app." }));
102527
+ }
102528
+ const handleToggle = (checked) => {
102529
+ // Guard: needs Local Models on and a large-enough model.
102530
+ if (checked && (localModelsOff || modelTooSmall))
102531
+ return;
102532
+ setIsEnabled(checked);
102533
+ if (checked) {
102534
+ if (!isInstalling)
102535
+ onInstall();
102536
+ }
102537
+ else {
102538
+ onStop === null || onStop === void 0 ? void 0 : onStop();
102539
+ }
102540
+ };
102541
+ // Upgrade = switch to the smallest usable model: select it, then (if it isn't
102542
+ // already downloaded) download it and auto-enable the assistant once it's
102543
+ // ready. The RAM/VRAM check is preserved via the confirm below.
102544
+ const runUpgrade = (model) => {
102545
+ setLocalLLMModel(model.id);
102546
+ setLocalLLMToolSupport(model.tool_support);
102547
+ setLocalLLMEnabled(true);
102548
+ setSelectedModel(model.id); // recompute the size gate immediately
102549
+ if (isModelInstalled(model.id, ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed_models)) {
102550
+ // Already downloaded → enable now.
102551
+ setIsEnabled(true);
102552
+ if (!isInstalling)
102553
+ onInstall();
102554
+ }
102555
+ else {
102556
+ // Not downloaded → download, then auto-enable when it lands (effect above).
102557
+ setPendingEnableModelId(model.id);
102558
+ onDownloadModel === null || onDownloadModel === void 0 ? void 0 : onDownloadModel(model.id);
102559
+ }
102560
+ };
102561
+ const handleUpgrade = () => {
102562
+ if (!upgradeTarget)
102563
+ return;
102564
+ // Keep the RAM/VRAM check: confirm before pulling a model that may not fit.
102565
+ if (modelExceedsCapacity(upgradeTarget, systemMemory)) {
102566
+ setPendingCapacityModel(upgradeTarget);
102567
+ return;
102568
+ }
102569
+ runUpgrade(upgradeTarget);
102570
+ };
102571
+ const switchChecked = isEnabled;
102572
+ const switchDisabled = isInstalling ||
102573
+ state.status === 'checking' ||
102574
+ ((localModelsOff || modelTooSmall) && !isEnabled);
102575
+ return (jsxs("div", { className: "space-y-4", children: [jsxs("div", { className: "flex items-center justify-between gap-4 rounded-lg border px-6 py-4", style: { borderColor: 'oklch(.922 0 0)' }, children: [jsxs("div", { children: [jsx("div", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Let your assistant control this computer" }), jsx("div", { className: "mt-0.5 text-xs text-gray-500 dark:text-gray-400", children: "It can click, type, and open apps to do tasks for you. You stay in control and can turn this off anytime." })] }), jsxs("div", { className: "flex flex-shrink-0 items-center gap-2", children: [isInstalling && (jsx(LoaderCircle, { className: "h-4 w-4 animate-spin text-blue-500", "aria-label": "Setting up" })), jsx(Switch, { checked: switchChecked, onCheckedChange: handleToggle, disabled: switchDisabled, "aria-label": "Let your assistant control this computer", className: "cursor-pointer data-[state=checked]:bg-blue-500" })] })] }), localModelsOff && (jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 dark:border-amber-900/40 dark:bg-amber-900/10", children: [jsx(Info$3, { className: "mt-0.5 h-4 w-4 flex-shrink-0 text-amber-600", "aria-hidden": "true" }), jsx("p", { className: "text-xs text-amber-800 dark:text-amber-300", children: "Turn on \u201CLocal Models\u201D above first \u2014 the Computer Assistant uses a local AI model to do its work." })] })), !localModelsOff && modelTooSmall && (jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 dark:border-amber-900/40 dark:bg-amber-900/10", children: [jsx(Info$3, { className: "mt-0.5 h-4 w-4 flex-shrink-0 text-amber-600", "aria-hidden": "true" }), jsxs("div", { className: "flex-1", children: [jsxs("p", { className: "text-xs text-amber-800 dark:text-amber-300", children: ["Your current AI model is too small for this.", ' ', upgradeTarget && onDownloadModel
102576
+ ? `Upgrade to ${upgradeTarget.name} (${upgradeTarget.size}) to use it.`
102577
+ : 'Pick a larger one under “Local Models” above, then come back.'] }), upgradeTarget && onDownloadModel && (jsx(Button$1, { onClick: handleUpgrade, size: "sm", className: "mt-2", disabled: isUpgrading, "aria-label": `Upgrade to ${upgradeTarget.name}`, children: isUpgrading ? (jsxs(Fragment$1, { children: [jsx(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), "Upgrading\u2026"] })) : (jsxs(Fragment$1, { children: [jsx(CircleArrowUp, { className: "mr-2 h-4 w-4" }), "Upgrade to ", upgradeTarget.name] })) }))] })] })), isUpgrading && !modelTooSmall && (jsxs("div", { className: "flex items-start gap-2 rounded-lg border px-4 py-3", style: { borderColor: 'oklch(.922 0 0)' }, children: [jsx(LoaderCircle, { className: "mt-0.5 h-4 w-4 flex-shrink-0 animate-spin text-blue-500", "aria-hidden": "true" }), jsx("p", { className: "text-xs text-gray-600 dark:text-gray-300", children: "Downloading your new model\u2026 it\u2019ll turn on automatically when it\u2019s ready. You can watch progress under \u201CLocal Models\u201D above." })] })), managerEnabled && (jsxs("div", { className: "space-y-4 rounded-lg border px-6 py-5", style: { borderColor: 'oklch(.922 0 0)' }, children: [allSet ? (jsxs("div", { className: "flex items-start gap-2", children: [jsx(Check, { className: "mt-0.5 h-4 w-4 flex-shrink-0 text-green-600", "aria-hidden": "true" }), jsx("p", { className: "text-sm text-gray-700 dark:text-gray-300", children: "You\u2019re all set \u2014 your assistant can now help out on this computer." })] })) : (jsx("div", { className: "text-xs font-medium uppercase tracking-wide text-gray-400", children: "A couple of quick steps" })), jsx(Step$1, { number: 1, done: managerRunning, title: "Set up your assistant", description: "A quick, one-time setup so it can work on this computer.", action: managerRunning ? (jsx(DonePill, { children: "Ready" })) : (jsx(WorkingPill, { children: isInstalling ? 'Setting up…' : 'Starting…' })) }), (state.status === 'installing' || state.status === 'checking') && state.progress > 0 && (jsx(Progress, { value: state.progress, className: "h-1.5" })), permissionKnown && (jsx(Step$1, { number: 2, done: permissionGranted, title: "Allow it to control the computer", description: "Your Mac will ask you to confirm. This lets your assistant move the mouse and type for you.", action: permissionGranted ? (jsx(DonePill, { children: "Allowed" })) : onRequestAccessibilityPermission ? (jsx(Button$1, { onClick: onRequestAccessibilityPermission, size: "sm", "aria-label": "Allow your assistant to control the computer", children: "Allow" })) : (jsx("span", { className: "inline-flex items-center rounded-full bg-amber-100 px-2 py-0.5 text-xs font-medium text-amber-800 dark:bg-amber-900/30 dark:text-amber-300", children: "Needed" })) }))] })), state.status === 'error' && (jsxs("div", { className: "rounded-lg border border-red-200 bg-red-50 px-4 py-3 dark:border-red-900/40 dark:bg-red-900/10", children: [jsxs("div", { className: "flex items-center gap-2 text-sm text-red-700 dark:text-red-300", children: [jsx(CircleX, { className: "h-4 w-4 flex-shrink-0" }), jsx("span", { children: "Something went wrong while setting up. Please try again." })] }), jsxs(Button$1, { onClick: () => {
102578
+ onResetState();
102579
+ onInstall();
102580
+ }, size: "sm", variant: "outline", className: "mt-3", children: [jsx(RefreshCw, { className: "mr-2 h-4 w-4" }), "Try again"] })] })), jsx(AlertDialog, { open: pendingCapacityModel !== null, onOpenChange: (open) => {
102581
+ if (!open)
102582
+ setPendingCapacityModel(null);
102583
+ }, children: jsxs(AlertDialogContent, { children: [jsxs(AlertDialogHeader, { children: [jsx(AlertDialogTitle, { children: "This model may be large for your computer" }), jsxs(AlertDialogDescription, { children: [pendingCapacityModel === null || pendingCapacityModel === void 0 ? void 0 : pendingCapacityModel.name, " (", pendingCapacityModel === null || pendingCapacityModel === void 0 ? void 0 : pendingCapacityModel.size, ") may be more than your computer\u2019s memory can handle, so it could run slowly or fail to load. Download and use it anyway?"] })] }), jsxs(AlertDialogFooter, { children: [jsx(AlertDialogCancel, { children: "Cancel" }), jsx(AlertDialogAction, { onClick: () => {
102584
+ const model = pendingCapacityModel;
102585
+ setPendingCapacityModel(null);
102586
+ if (model)
102587
+ runUpgrade(model);
102588
+ }, children: "Download anyway" })] })] }) })] }));
102589
+ }
102590
+
102591
+ /**
102592
+ * Collapsible "System Control" card for the UserProfile → Advanced section.
102593
+ * Mirrors {@link LocalModelsContent}: a purely presentational, titled,
102594
+ * collapsible container whose body is the {@link SystemControlTab}. Availability
102595
+ * and handlers are threaded in (see UserProfileDropdown, which falls back to
102596
+ * {@link useGhostOs} when the host doesn't supply them). Renders nothing outside
102597
+ * the desktop app.
102598
+ */
102599
+ function SystemControlContent(props) {
102600
+ const [isCollapsed, setIsCollapsed] = useState(true);
102601
+ if (!props.isAvailable) {
102602
+ return null;
102603
+ }
102604
+ return (jsxs("div", { className: "rounded-lg px-6 py-5 border border-gray-200 dark:border-gray-700", style: { borderColor: 'oklch(.922 0 0)' }, children: [jsxs("div", { className: "flex items-center justify-between", children: [jsxs("div", { className: "flex items-center gap-3", children: [jsx("span", { className: "text-sm font-medium text-[#646464]", children: "Computer Assistant" }), jsx(TooltipProvider, { children: jsxs(Tooltip, { children: [jsx(TooltipTrigger, { "aria-label": "More info about the Computer Assistant", className: "hidden sm:block", children: jsx(Info$3, { className: "h-4 w-4 text-gray-400" }) }), jsx(TooltipContent, { className: "rounded-lg bg-gray-700 px-3 py-2 text-sm font-medium max-w-xs text-white shadow-sm transition-opacity duration-300 z-50", children: jsx("p", { children: "Let your assistant do things on your computer \u2014 click, type, and open apps. You stay in control and can turn it off anytime." }) })] }) })] }), jsx("button", { type: "button", onClick: () => setIsCollapsed((previous) => !previous), className: "p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1", "aria-label": isCollapsed ? 'Expand Computer Assistant' : 'Collapse Computer Assistant', children: isCollapsed ? (jsx(ChevronDown, { className: "h-5 w-5 text-gray-400", "aria-hidden": "true" })) : (jsx(ChevronUp, { className: "h-5 w-5 text-gray-400", "aria-hidden": "true" })) })] }), !isCollapsed && (jsx("div", { className: "mt-4", children: jsx(SystemControlTab, { ...props }) }))] }));
101948
102605
  }
101949
102606
 
101950
102607
  // src/primitive.tsx
@@ -101998,11 +102655,11 @@ var Label$1 = React.forwardRef((props, forwardedRef) => {
101998
102655
  );
101999
102656
  });
102000
102657
  Label$1.displayName = NAME$2;
102001
- var Root$5 = Label$1;
102658
+ var Root$6 = Label$1;
102002
102659
 
102003
102660
  const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70");
102004
- const Label = React.forwardRef(({ className, ...props }, ref) => (jsx(Root$5, { ref: ref, className: cn(labelVariants(), className), ...props })));
102005
- Label.displayName = Root$5.displayName;
102661
+ const Label = React.forwardRef(({ className, ...props }, ref) => (jsx(Root$6, { ref: ref, className: cn(labelVariants(), className), ...props })));
102662
+ Label.displayName = Root$6.displayName;
102006
102663
 
102007
102664
  var dayjs_min = {exports: {}};
102008
102665
 
@@ -125048,7 +125705,7 @@ function ChatPrivacyTab({ org, username }) {
125048
125705
  const renderLucideIcon = (Icon) => function RenderedIcon(props) {
125049
125706
  return jsx(Icon, { ...props });
125050
125707
  };
125051
- function Profile({ tenant, username, tenants, onClose, customization = {}, isAdmin = false, targetTab = 'basic', onAccountDeleted, enableMemoryTab = false, localLLMProps, }) {
125708
+ function Profile({ tenant, username, tenants, onClose, customization = {}, isAdmin = false, targetTab = 'basic', onAccountDeleted, enableMemoryTab = false, localLLMProps, systemControlProps, }) {
125052
125709
  var _a, _b, _c, _d, _e, _f, _g, _h;
125053
125710
  console.log('[Profile] localLLMProps received:', {
125054
125711
  isAvailable: localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.isAvailable,
@@ -125093,8 +125750,10 @@ function Profile({ tenant, username, tenants, onClose, customization = {}, isAdm
125093
125750
  : []),
125094
125751
  { id: 'security', label: 'Security', renderIcon: renderLucideIcon(Shield) },
125095
125752
  ];
125096
- // Add Advanced tab only when in Tauri app (localLLMProps is provided and available)
125097
- const TABS = (localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.isAvailable)
125753
+ // Add Advanced tab only when in Tauri app (Local Models or System Control is
125754
+ // provided and available)
125755
+ const isAdvancedAvailable = (localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.isAvailable) === true || (systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.isAvailable) === true;
125756
+ const TABS = isAdvancedAvailable
125098
125757
  ? [...baseTabs, { id: 'advanced', label: 'Advanced', renderIcon: renderLucideIcon(Settings$1) }]
125099
125758
  : baseTabs;
125100
125759
  const RoleIcon = renderLucideIcon(User);
@@ -125202,10 +125861,10 @@ function Profile({ tenant, username, tenants, onClose, customization = {}, isAdm
125202
125861
  })] }), (customization.showLeaderboardDisplayCheckbox ||
125203
125862
  customization.showMentorAIDisplayCheckbox) && (jsxs("div", { className: "grid grid-cols-2 gap-6 pt-4", children: [customization.showMentorAIDisplayCheckbox && (jsx("div", { className: "space-y-2", children: jsxs("div", { className: "flex items-center space-x-2", children: [basicForm.Field({
125204
125863
  name: 'displayMentor',
125205
- children: (field) => (jsx(Checkbox, { onCheckedChange: (checked) => field.handleChange(checked), checked: field.state.value, disabled: basicForm.state.isSubmitting || isUserMetadataLoading, id: "display-mentor" })),
125206
- }), jsx("label", { htmlFor: "display-mentor", className: "text-sm leading-none font-medium text-gray-700 peer-disabled:cursor-not-allowed peer-disabled:opacity-70", children: "Display Mentor AI" })] }) })), customization.showLeaderboardDisplayCheckbox && (jsx("div", { className: "space-y-2", children: jsxs("div", { className: "flex items-center space-x-2", children: [basicForm.Field({
125864
+ children: (field) => (jsx(Switch, { className: "data-[state=checked]:bg-blue-600", onCheckedChange: (checked) => field.handleChange(checked), checked: field.state.value, disabled: basicForm.state.isSubmitting || isUserMetadataLoading, id: "display-mentor" })),
125865
+ }), jsx("label", { htmlFor: "display-mentor", className: "text-sm leading-none font-medium text-gray-700 peer-disabled:cursor-not-allowed peer-disabled:opacity-70", children: "Display Agent Sidebar" })] }) })), customization.showLeaderboardDisplayCheckbox && (jsx("div", { className: "space-y-2", children: jsxs("div", { className: "flex items-center space-x-2", children: [basicForm.Field({
125207
125866
  name: 'displayLeaderBoard',
125208
- children: (field) => (jsx(Checkbox, { onCheckedChange: (checked) => field.handleChange(checked), checked: field.state.value, disabled: basicForm.state.isSubmitting || isUserMetadataLoading, id: "display-leaderboard" })),
125867
+ children: (field) => (jsx(Switch, { className: "data-[state=checked]:bg-blue-600", onCheckedChange: (checked) => field.handleChange(checked), checked: field.state.value, disabled: basicForm.state.isSubmitting || isUserMetadataLoading, id: "display-leaderboard" })),
125209
125868
  }), jsx("label", { htmlFor: "display-leaderboard", className: "text-sm leading-none font-medium text-gray-700 peer-disabled:cursor-not-allowed peer-disabled:opacity-70", children: "Display Leaderboard" })] }) }))] }))] }) })), activeTab === 'social' && (jsx("div", { className: "max-w-2xl space-y-6", children: jsxs("form", { className: "space-y-6", onSubmit: (formEvent) => {
125210
125869
  formEvent.preventDefault();
125211
125870
  formEvent.stopPropagation();
@@ -125237,7 +125896,7 @@ function Profile({ tenant, username, tenants, onClose, customization = {}, isAdm
125237
125896
  var _a;
125238
125897
  return (jsxs(Fragment$1, { children: [jsxs("div", { className: "relative mb-0", children: [jsx("div", { className: "absolute inset-y-0 left-4 flex items-center pointer-events-none", "aria-hidden": "true", children: jsx("div", { className: "bg-black text-white rounded w-8 h-8 flex items-center justify-center", children: jsx("span", { className: "font-bold text-sm", children: "X" }) }) }), jsx(Input, { id: "x", className: "w-full bg-gray-50 border-gray-200 focus:ring-blue-500 focus:border-blue-500 text-base py-3 pl-16 h-10", placeholder: "X", value: field.state.value, onChange: (e) => handleSocialLinkChange(e.target.value, field.handleChange, X_URL_PREFIX), disabled: socialForm.state.isSubmitting })] }), jsx("p", { className: "text-red-500 dark:text-gray-400 text-sm", role: ((_a = field.state.meta.errors) === null || _a === void 0 ? void 0 : _a.length) ? 'alert' : undefined, "aria-live": "polite", children: field.state.meta.errors })] }));
125239
125898
  },
125240
- })] })] }) })), activeTab === 'education' && jsx(EducationTab, { org: tenant, username: username }), activeTab === 'experience' && jsx(ExperienceTab, { org: tenant, username: username }), activeTab === 'resume' && jsx(ResumeTab, { org: tenant, username: username }), activeTab === 'purchases' && jsx(PurchasesTab, { org: tenant, username: username }), activeTab === 'memory' && jsx(MemoryTab, { org: tenant, username: username }), activeTab === 'privacy' && jsx(ChatPrivacyTab, { org: tenant, username: username }), activeTab === 'security' && (jsx(Security, { email: userMetadata === null || userMetadata === void 0 ? void 0 : userMetadata.email, onAccountDeleted: onAccountDeleted })), activeTab === 'advanced' && localLLMProps && (jsx(LocalLLMTab, { isAvailable: localLLMProps.isAvailable, state: localLLMProps.state, ollamaStatus: localLLMProps.ollamaStatus, isUsingFoundry: localLLMProps.isUsingFoundry, foundryModels: localLLMProps.foundryModels, selectedFoundryModel: localLLMProps.selectedFoundryModel, foundryStatus: localLLMProps.foundryStatus, onStartDownload: localLLMProps.onStartDownload, onCancelDownload: localLLMProps.onCancelDownload, onInstallOllama: localLLMProps.onInstallOllama, onInstallFoundry: localLLMProps.onInstallFoundry, onCheckStatus: localLLMProps.onCheckStatus, onResetState: localLLMProps.onResetState, onSelectFoundryModel: localLLMProps.onSelectFoundryModel }))] }), ['basic', 'social'].includes(activeTab) && (jsx("div", { className: "flex-shrink-0 border-t border-gray-200 p-6 bg-white dark:bg-gray-900", children: jsxs("div", { className: "flex justify-end gap-3", children: [jsx(Button$1, { onClick: onClose, variant: "outline", children: "Cancel" }), jsx(Button$1, { onClick: () => {
125899
+ })] })] }) })), activeTab === 'education' && jsx(EducationTab, { org: tenant, username: username }), activeTab === 'experience' && jsx(ExperienceTab, { org: tenant, username: username }), activeTab === 'resume' && jsx(ResumeTab, { org: tenant, username: username }), activeTab === 'purchases' && jsx(PurchasesTab, { org: tenant, username: username }), activeTab === 'memory' && jsx(MemoryTab, { org: tenant, username: username }), activeTab === 'privacy' && jsx(ChatPrivacyTab, { org: tenant, username: username }), activeTab === 'security' && (jsx(Security, { email: userMetadata === null || userMetadata === void 0 ? void 0 : userMetadata.email, onAccountDeleted: onAccountDeleted })), activeTab === 'advanced' && (localLLMProps || systemControlProps) && (jsxs("div", { className: "space-y-6", children: [localLLMProps && (jsx(LocalModelsContent, { isAvailable: localLLMProps.isAvailable, state: localLLMProps.state, ollamaStatus: localLLMProps.ollamaStatus, systemMemory: localLLMProps.systemMemory, isUsingFoundry: localLLMProps.isUsingFoundry, foundryModels: localLLMProps.foundryModels, selectedFoundryModel: localLLMProps.selectedFoundryModel, foundryStatus: localLLMProps.foundryStatus, onStartDownload: localLLMProps.onStartDownload, onCancelDownload: localLLMProps.onCancelDownload, onInstallOllama: localLLMProps.onInstallOllama, onStopManager: localLLMProps.onStopManager, onInstallFoundry: localLLMProps.onInstallFoundry, onCheckStatus: localLLMProps.onCheckStatus, onResetState: localLLMProps.onResetState, onSelectFoundryModel: localLLMProps.onSelectFoundryModel })), systemControlProps && (jsx(SystemControlContent, { isAvailable: systemControlProps.isAvailable, state: systemControlProps.state, status: systemControlProps.status, requiredSizeGb: systemControlProps.requiredSizeGb, ollamaStatus: systemControlProps.ollamaStatus, systemMemory: systemControlProps.systemMemory, accessibilityPermission: systemControlProps.accessibilityPermission, onInstall: systemControlProps.onInstall, onStop: systemControlProps.onStop, onCheckStatus: systemControlProps.onCheckStatus, onResetState: systemControlProps.onResetState, onRequestAccessibilityPermission: systemControlProps.onRequestAccessibilityPermission, onDownloadModel: systemControlProps.onDownloadModel }))] }))] }), ['basic', 'social'].includes(activeTab) && (jsx("div", { className: "flex-shrink-0 border-t border-gray-200 p-6 bg-white dark:bg-gray-900", children: jsxs("div", { className: "flex justify-end gap-3", children: [jsx(Button$1, { onClick: onClose, variant: "outline", children: "Cancel" }), jsx(Button$1, { onClick: () => {
125241
125900
  if (activeTab === 'basic') {
125242
125901
  basicForm.handleSubmit();
125243
125902
  }
@@ -125332,11 +125991,11 @@ var TabsList$1 = React.forwardRef(
125332
125991
  }
125333
125992
  );
125334
125993
  TabsList$1.displayName = TAB_LIST_NAME;
125335
- var TRIGGER_NAME$2 = "TabsTrigger";
125994
+ var TRIGGER_NAME$1 = "TabsTrigger";
125336
125995
  var TabsTrigger$1 = React.forwardRef(
125337
125996
  (props, forwardedRef) => {
125338
125997
  const { __scopeTabs, value, disabled = false, ...triggerProps } = props;
125339
- const context = useTabsContext(TRIGGER_NAME$2, __scopeTabs);
125998
+ const context = useTabsContext(TRIGGER_NAME$1, __scopeTabs);
125340
125999
  const rovingFocusGroupScope = useRovingFocusGroupScope$1(__scopeTabs);
125341
126000
  const triggerId = makeTriggerId(context.baseId, value);
125342
126001
  const contentId = makeContentId(context.baseId, value);
@@ -125383,12 +126042,12 @@ var TabsTrigger$1 = React.forwardRef(
125383
126042
  );
125384
126043
  }
125385
126044
  );
125386
- TabsTrigger$1.displayName = TRIGGER_NAME$2;
125387
- var CONTENT_NAME$2 = "TabsContent";
126045
+ TabsTrigger$1.displayName = TRIGGER_NAME$1;
126046
+ var CONTENT_NAME$1 = "TabsContent";
125388
126047
  var TabsContent$1 = React.forwardRef(
125389
126048
  (props, forwardedRef) => {
125390
126049
  const { __scopeTabs, value, forceMount, children, ...contentProps } = props;
125391
- const context = useTabsContext(CONTENT_NAME$2, __scopeTabs);
126050
+ const context = useTabsContext(CONTENT_NAME$1, __scopeTabs);
125392
126051
  const triggerId = makeTriggerId(context.baseId, value);
125393
126052
  const contentId = makeContentId(context.baseId, value);
125394
126053
  const isSelected = value === context.value;
@@ -125418,19 +126077,19 @@ var TabsContent$1 = React.forwardRef(
125418
126077
  ) });
125419
126078
  }
125420
126079
  );
125421
- TabsContent$1.displayName = CONTENT_NAME$2;
126080
+ TabsContent$1.displayName = CONTENT_NAME$1;
125422
126081
  function makeTriggerId(baseId, value) {
125423
126082
  return `${baseId}-trigger-${value}`;
125424
126083
  }
125425
126084
  function makeContentId(baseId, value) {
125426
126085
  return `${baseId}-content-${value}`;
125427
126086
  }
125428
- var Root2$3 = Tabs$1;
126087
+ var Root2$2 = Tabs$1;
125429
126088
  var List = TabsList$1;
125430
126089
  var Trigger$1 = TabsTrigger$1;
125431
126090
  var Content = TabsContent$1;
125432
126091
 
125433
- const Tabs = Root2$3;
126092
+ const Tabs = Root2$2;
125434
126093
  const TabsList = React.forwardRef(({ className, ...props }, ref) => (jsx(List, { ref: ref, className: cn("bg-muted text-muted-foreground inline-flex h-9 items-center justify-center rounded-lg p-1", className), ...props })));
125435
126094
  TabsList.displayName = List.displayName;
125436
126095
  const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => (jsx(Trigger$1, { ref: ref, className: cn("ring-offset-background focus-visible:ring-ring data-[state=active]:bg-background data-[state=active]:text-foreground inline-flex items-center justify-center rounded-md px-3 py-1 text-sm font-medium whitespace-nowrap transition-all focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow", className), ...props })));
@@ -125765,6 +126424,170 @@ function UsersTab$1({ tenant, currentPage, itemsPerPage, onInviteSuccess }) {
125765
126424
  jsx("tr", { children: jsx("td", { colSpan: 2, className: "px-6 py-12 text-center", children: jsxs("div", { className: "flex flex-col items-center justify-center text-gray-500", children: [jsx(Mail, { className: "h-12 w-12 mb-3 text-gray-300", "aria-hidden": "true" }), jsx("p", { className: "text-sm font-medium", children: "No invitations yet" }), jsx("p", { className: "text-xs mt-1", children: "Start by inviting users above or uploading a CSV file" })] }) }) })) : (currentUsers.map((user) => (jsxs("tr", { className: "hover:bg-gray-50 transition-colors duration-150", children: [jsx("td", { className: "px-6 py-4 whitespace-nowrap", children: jsxs("div", { className: "flex items-center truncate max-w-[200px] sm:max-w-full", children: [jsx("div", { className: "flex-shrink-0 h-8 w-8", children: jsx("div", { className: "h-8 w-8 rounded-full bg-blue-100 flex items-center justify-center", children: jsx(Mail, { className: "h-4 w-4 text-blue-600", "aria-hidden": "true" }) }) }), jsx("div", { className: "ml-3", children: jsx("div", { className: "text-sm font-medium text-gray-900", children: user.email || 'No email' }) })] }) }), jsx("td", { className: "px-6 py-4 whitespace-nowrap", children: jsxs("span", { className: `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getStatusColor(user.active, user.expired)}`, children: [user.active && !user.expired && (jsx(Clock, { className: "h-3 w-3 mr-1 text-blue-500", "aria-hidden": "true" })), !user.active && !user.expired && (jsx(CircleCheck, { className: "h-3 w-3 mr-1 text-green-700", "aria-hidden": "true" })), user.expired && (jsx(CircleX, { className: "h-3 w-3 mr-1 text-gray-500", "aria-hidden": "true" })), getStatusText(user.active, user.expired)] }) })] }, user.id)))) })] }) }) }) }) }), isCSVEditorOpen && (jsx(CSVEditor, { csvData: parsedCSVData, onSave: handleCSVEditorSave, onCancel: handleCSVEditorCancel }))] }));
125766
126425
  }
125767
126426
 
126427
+ var CHECKBOX_NAME = "Checkbox";
126428
+ var [createCheckboxContext, createCheckboxScope] = createContextScope$1(CHECKBOX_NAME);
126429
+ var [CheckboxProvider, useCheckboxContext] = createCheckboxContext(CHECKBOX_NAME);
126430
+ var Checkbox$1 = React.forwardRef(
126431
+ (props, forwardedRef) => {
126432
+ const {
126433
+ __scopeCheckbox,
126434
+ name,
126435
+ checked: checkedProp,
126436
+ defaultChecked,
126437
+ required,
126438
+ disabled,
126439
+ value = "on",
126440
+ onCheckedChange,
126441
+ form,
126442
+ ...checkboxProps
126443
+ } = props;
126444
+ const [button, setButton] = React.useState(null);
126445
+ const composedRefs = useComposedRefs$1(forwardedRef, (node) => setButton(node));
126446
+ const hasConsumerStoppedPropagationRef = React.useRef(false);
126447
+ const isFormControl = button ? form || !!button.closest("form") : true;
126448
+ const [checked, setChecked] = useControllableState$1({
126449
+ prop: checkedProp,
126450
+ defaultProp: defaultChecked ?? false,
126451
+ onChange: onCheckedChange,
126452
+ caller: CHECKBOX_NAME
126453
+ });
126454
+ const initialCheckedStateRef = React.useRef(checked);
126455
+ React.useEffect(() => {
126456
+ const form2 = button?.form;
126457
+ if (form2) {
126458
+ const reset = () => setChecked(initialCheckedStateRef.current);
126459
+ form2.addEventListener("reset", reset);
126460
+ return () => form2.removeEventListener("reset", reset);
126461
+ }
126462
+ }, [button, setChecked]);
126463
+ return /* @__PURE__ */ jsxs(CheckboxProvider, { scope: __scopeCheckbox, state: checked, disabled, children: [
126464
+ /* @__PURE__ */ jsx(
126465
+ Primitive$3.button,
126466
+ {
126467
+ type: "button",
126468
+ role: "checkbox",
126469
+ "aria-checked": isIndeterminate(checked) ? "mixed" : checked,
126470
+ "aria-required": required,
126471
+ "data-state": getState$2(checked),
126472
+ "data-disabled": disabled ? "" : void 0,
126473
+ disabled,
126474
+ value,
126475
+ ...checkboxProps,
126476
+ ref: composedRefs,
126477
+ onKeyDown: composeEventHandlers$2(props.onKeyDown, (event) => {
126478
+ if (event.key === "Enter") event.preventDefault();
126479
+ }),
126480
+ onClick: composeEventHandlers$2(props.onClick, (event) => {
126481
+ setChecked((prevChecked) => isIndeterminate(prevChecked) ? true : !prevChecked);
126482
+ if (isFormControl) {
126483
+ hasConsumerStoppedPropagationRef.current = event.isPropagationStopped();
126484
+ if (!hasConsumerStoppedPropagationRef.current) event.stopPropagation();
126485
+ }
126486
+ })
126487
+ }
126488
+ ),
126489
+ isFormControl && /* @__PURE__ */ jsx(
126490
+ CheckboxBubbleInput,
126491
+ {
126492
+ control: button,
126493
+ bubbles: !hasConsumerStoppedPropagationRef.current,
126494
+ name,
126495
+ value,
126496
+ checked,
126497
+ required,
126498
+ disabled,
126499
+ form,
126500
+ style: { transform: "translateX(-100%)" },
126501
+ defaultChecked: isIndeterminate(defaultChecked) ? false : defaultChecked
126502
+ }
126503
+ )
126504
+ ] });
126505
+ }
126506
+ );
126507
+ Checkbox$1.displayName = CHECKBOX_NAME;
126508
+ var INDICATOR_NAME$1 = "CheckboxIndicator";
126509
+ var CheckboxIndicator = React.forwardRef(
126510
+ (props, forwardedRef) => {
126511
+ const { __scopeCheckbox, forceMount, ...indicatorProps } = props;
126512
+ const context = useCheckboxContext(INDICATOR_NAME$1, __scopeCheckbox);
126513
+ return /* @__PURE__ */ jsx(Presence$1, { present: forceMount || isIndeterminate(context.state) || context.state === true, children: /* @__PURE__ */ jsx(
126514
+ Primitive$3.span,
126515
+ {
126516
+ "data-state": getState$2(context.state),
126517
+ "data-disabled": context.disabled ? "" : void 0,
126518
+ ...indicatorProps,
126519
+ ref: forwardedRef,
126520
+ style: { pointerEvents: "none", ...props.style }
126521
+ }
126522
+ ) });
126523
+ }
126524
+ );
126525
+ CheckboxIndicator.displayName = INDICATOR_NAME$1;
126526
+ var BUBBLE_INPUT_NAME = "CheckboxBubbleInput";
126527
+ var CheckboxBubbleInput = React.forwardRef(
126528
+ ({
126529
+ __scopeCheckbox,
126530
+ control,
126531
+ checked,
126532
+ bubbles = true,
126533
+ defaultChecked,
126534
+ ...props
126535
+ }, forwardedRef) => {
126536
+ const ref = React.useRef(null);
126537
+ const composedRefs = useComposedRefs$1(ref, forwardedRef);
126538
+ const prevChecked = usePrevious$1(checked);
126539
+ const controlSize = useSize$1(control);
126540
+ React.useEffect(() => {
126541
+ const input = ref.current;
126542
+ if (!input) return;
126543
+ const inputProto = window.HTMLInputElement.prototype;
126544
+ const descriptor = Object.getOwnPropertyDescriptor(
126545
+ inputProto,
126546
+ "checked"
126547
+ );
126548
+ const setChecked = descriptor.set;
126549
+ if (prevChecked !== checked && setChecked) {
126550
+ const event = new Event("click", { bubbles });
126551
+ input.indeterminate = isIndeterminate(checked);
126552
+ setChecked.call(input, isIndeterminate(checked) ? false : checked);
126553
+ input.dispatchEvent(event);
126554
+ }
126555
+ }, [prevChecked, checked, bubbles]);
126556
+ const defaultCheckedRef = React.useRef(isIndeterminate(checked) ? false : checked);
126557
+ return /* @__PURE__ */ jsx(
126558
+ Primitive$3.input,
126559
+ {
126560
+ type: "checkbox",
126561
+ "aria-hidden": true,
126562
+ defaultChecked: defaultChecked ?? defaultCheckedRef.current,
126563
+ ...props,
126564
+ tabIndex: -1,
126565
+ ref: composedRefs,
126566
+ style: {
126567
+ ...props.style,
126568
+ ...controlSize,
126569
+ position: "absolute",
126570
+ pointerEvents: "none",
126571
+ opacity: 0,
126572
+ margin: 0
126573
+ }
126574
+ }
126575
+ );
126576
+ }
126577
+ );
126578
+ CheckboxBubbleInput.displayName = BUBBLE_INPUT_NAME;
126579
+ function isIndeterminate(checked) {
126580
+ return checked === "indeterminate";
126581
+ }
126582
+ function getState$2(checked) {
126583
+ return isIndeterminate(checked) ? "indeterminate" : checked ? "checked" : "unchecked";
126584
+ }
126585
+ var Root$5 = Checkbox$1;
126586
+ var Indicator$1 = CheckboxIndicator;
126587
+
126588
+ const Checkbox = React.forwardRef(({ className, ...props }, ref) => (jsx(Root$5, { ref: ref, className: cn("peer border-primary focus-visible:ring-ring h-4 w-4 shrink-0 rounded-sm border shadow-sm focus-visible:ring-1 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-blue-500 data-[state=checked]:text-white", className), ...props, children: jsx(Indicator$1, { className: cn("flex items-center justify-center text-current"), children: jsx(Check, { className: "h-4 w-4" }) }) })));
126589
+ Checkbox.displayName = Root$5.displayName;
126590
+
125768
126591
  function CoursesTab({ tenant, currentPage, itemsPerPage, hasManageUsersPermission = false, }) {
125769
126592
  var _a;
125770
126593
  const [inviteSent, setInviteSent] = useState(false);
@@ -126713,11 +127536,11 @@ var PopoverAnchor$1 = React.forwardRef(
126713
127536
  }
126714
127537
  );
126715
127538
  PopoverAnchor$1.displayName = ANCHOR_NAME;
126716
- var TRIGGER_NAME$1 = "PopoverTrigger";
127539
+ var TRIGGER_NAME = "PopoverTrigger";
126717
127540
  var PopoverTrigger$1 = React.forwardRef(
126718
127541
  (props, forwardedRef) => {
126719
127542
  const { __scopePopover, ...triggerProps } = props;
126720
- const context = usePopoverContext(TRIGGER_NAME$1, __scopePopover);
127543
+ const context = usePopoverContext(TRIGGER_NAME, __scopePopover);
126721
127544
  const popperScope = usePopperScope(__scopePopover);
126722
127545
  const composedTriggerRef = useComposedRefs$1(forwardedRef, context.triggerRef);
126723
127546
  const trigger = /* @__PURE__ */ jsx(
@@ -126736,31 +127559,31 @@ var PopoverTrigger$1 = React.forwardRef(
126736
127559
  return context.hasCustomAnchor ? trigger : /* @__PURE__ */ jsx(Anchor$2, { asChild: true, ...popperScope, children: trigger });
126737
127560
  }
126738
127561
  );
126739
- PopoverTrigger$1.displayName = TRIGGER_NAME$1;
126740
- var PORTAL_NAME$1 = "PopoverPortal";
126741
- var [PortalProvider, usePortalContext] = createPopoverContext(PORTAL_NAME$1, {
127562
+ PopoverTrigger$1.displayName = TRIGGER_NAME;
127563
+ var PORTAL_NAME = "PopoverPortal";
127564
+ var [PortalProvider, usePortalContext] = createPopoverContext(PORTAL_NAME, {
126742
127565
  forceMount: void 0
126743
127566
  });
126744
127567
  var PopoverPortal = (props) => {
126745
127568
  const { __scopePopover, forceMount, children, container } = props;
126746
- const context = usePopoverContext(PORTAL_NAME$1, __scopePopover);
127569
+ const context = usePopoverContext(PORTAL_NAME, __scopePopover);
126747
127570
  return /* @__PURE__ */ jsx(PortalProvider, { scope: __scopePopover, forceMount, children: /* @__PURE__ */ jsx(Presence$3, { present: forceMount || context.open, children: /* @__PURE__ */ jsx(Portal$6, { asChild: true, container, children }) }) });
126748
127571
  };
126749
- PopoverPortal.displayName = PORTAL_NAME$1;
126750
- var CONTENT_NAME$1 = "PopoverContent";
127572
+ PopoverPortal.displayName = PORTAL_NAME;
127573
+ var CONTENT_NAME = "PopoverContent";
126751
127574
  var PopoverContent$1 = React.forwardRef(
126752
127575
  (props, forwardedRef) => {
126753
- const portalContext = usePortalContext(CONTENT_NAME$1, props.__scopePopover);
127576
+ const portalContext = usePortalContext(CONTENT_NAME, props.__scopePopover);
126754
127577
  const { forceMount = portalContext.forceMount, ...contentProps } = props;
126755
- const context = usePopoverContext(CONTENT_NAME$1, props.__scopePopover);
127578
+ const context = usePopoverContext(CONTENT_NAME, props.__scopePopover);
126756
127579
  return /* @__PURE__ */ jsx(Presence$3, { present: forceMount || context.open, children: context.modal ? /* @__PURE__ */ jsx(PopoverContentModal, { ...contentProps, ref: forwardedRef }) : /* @__PURE__ */ jsx(PopoverContentNonModal, { ...contentProps, ref: forwardedRef }) });
126757
127580
  }
126758
127581
  );
126759
- PopoverContent$1.displayName = CONTENT_NAME$1;
127582
+ PopoverContent$1.displayName = CONTENT_NAME;
126760
127583
  var Slot = createSlot$1("PopoverContent.RemoveScroll");
126761
127584
  var PopoverContentModal = React.forwardRef(
126762
127585
  (props, forwardedRef) => {
126763
- const context = usePopoverContext(CONTENT_NAME$1, props.__scopePopover);
127586
+ const context = usePopoverContext(CONTENT_NAME, props.__scopePopover);
126764
127587
  const contentRef = React.useRef(null);
126765
127588
  const composedRefs = useComposedRefs$1(forwardedRef, contentRef);
126766
127589
  const isRightClickOutsideRef = React.useRef(false);
@@ -126800,7 +127623,7 @@ var PopoverContentModal = React.forwardRef(
126800
127623
  );
126801
127624
  var PopoverContentNonModal = React.forwardRef(
126802
127625
  (props, forwardedRef) => {
126803
- const context = usePopoverContext(CONTENT_NAME$1, props.__scopePopover);
127626
+ const context = usePopoverContext(CONTENT_NAME, props.__scopePopover);
126804
127627
  const hasInteractedOutsideRef = React.useRef(false);
126805
127628
  const hasPointerDownOutsideRef = React.useRef(false);
126806
127629
  return /* @__PURE__ */ jsx(
@@ -126852,7 +127675,7 @@ var PopoverContentImpl = React.forwardRef(
126852
127675
  onInteractOutside,
126853
127676
  ...contentProps
126854
127677
  } = props;
126855
- const context = usePopoverContext(CONTENT_NAME$1, __scopePopover);
127678
+ const context = usePopoverContext(CONTENT_NAME, __scopePopover);
126856
127679
  const popperScope = usePopperScope(__scopePopover);
126857
127680
  useFocusGuards();
126858
127681
  return /* @__PURE__ */ jsx(
@@ -126930,26 +127753,26 @@ PopoverArrow.displayName = ARROW_NAME;
126930
127753
  function getState$1(open) {
126931
127754
  return open ? "open" : "closed";
126932
127755
  }
126933
- var Root2$2 = Popover$1;
127756
+ var Root2$1 = Popover$1;
126934
127757
  var Anchor2 = PopoverAnchor$1;
126935
127758
  var Trigger = PopoverTrigger$1;
126936
127759
  var Portal = PopoverPortal;
126937
- var Content2$1 = PopoverContent$1;
127760
+ var Content2 = PopoverContent$1;
126938
127761
 
126939
- const Popover = Root2$2;
127762
+ const Popover = Root2$1;
126940
127763
  const PopoverTrigger = Trigger;
126941
127764
  const PopoverAnchor = Anchor2;
126942
127765
  const PopoverContent = React.forwardRef(({ className, align = 'center', sideOffset = 4, portalled = true, container, ...props }, ref) => {
126943
127766
  var _a;
126944
127767
  const portalContainer = useFloatingPortalContainer();
126945
127768
  const resolvedContainer = (_a = container !== null && container !== void 0 ? container : portalContainer) !== null && _a !== void 0 ? _a : undefined;
126946
- const content = (jsx(Content2$1, { ref: ref, "data-iblai-dialog-interaction-layer": true, align: align, sideOffset: sideOffset, className: cn('bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-none', className), ...props }));
127769
+ const content = (jsx(Content2, { ref: ref, "data-iblai-dialog-interaction-layer": true, align: align, sideOffset: sideOffset, className: cn('bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-none', className), ...props }));
126947
127770
  if (!portalled) {
126948
127771
  return content;
126949
127772
  }
126950
127773
  return jsx(Portal, { container: resolvedContainer, children: content });
126951
127774
  });
126952
- PopoverContent.displayName = Content2$1.displayName;
127775
+ PopoverContent.displayName = Content2.displayName;
126953
127776
 
126954
127777
  function UsersTab({ tenant, onInviteClick }) {
126955
127778
  const dispatch = useDispatch();
@@ -164510,184 +165333,17 @@ var RadioGroupIndicator = React.forwardRef(
164510
165333
  }
164511
165334
  );
164512
165335
  RadioGroupIndicator.displayName = INDICATOR_NAME2;
164513
- var Root2$1 = RadioGroup$1;
165336
+ var Root2 = RadioGroup$1;
164514
165337
  var Item2 = RadioGroupItem$1;
164515
165338
  var Indicator = RadioGroupIndicator;
164516
165339
 
164517
165340
  function RadioGroup({ className, ...props }) {
164518
- return (jsx(Root2$1, { "data-slot": "radio-group", className: cn('grid gap-3', className), ...props }));
165341
+ return (jsx(Root2, { "data-slot": "radio-group", className: cn('grid gap-3', className), ...props }));
164519
165342
  }
164520
165343
  function RadioGroupItem({ className, ...props }) {
164521
165344
  return (jsx(Item2, { "data-slot": "radio-group-item", className: cn('border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50', className), ...props, children: jsx(Indicator, { "data-slot": "radio-group-indicator", className: "relative flex items-center justify-center", children: jsx(Circle, { className: "fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" }) }) }));
164522
165345
  }
164523
165346
 
164524
- var SLOTTABLE_IDENTIFIER = Symbol("radix.slottable");
164525
- // @__NO_SIDE_EFFECTS__
164526
- function createSlottable(ownerName) {
164527
- const Slottable2 = ({ children }) => {
164528
- return /* @__PURE__ */ jsx(Fragment$1, { children });
164529
- };
164530
- Slottable2.displayName = `${ownerName}.Slottable`;
164531
- Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
164532
- return Slottable2;
164533
- }
164534
-
164535
- var ROOT_NAME = "AlertDialog";
164536
- var [createAlertDialogContext, createAlertDialogScope] = createContextScope$1(ROOT_NAME, [
164537
- createDialogScope
164538
- ]);
164539
- var useDialogScope = createDialogScope();
164540
- var AlertDialog$1 = (props) => {
164541
- const { __scopeAlertDialog, ...alertDialogProps } = props;
164542
- const dialogScope = useDialogScope(__scopeAlertDialog);
164543
- return /* @__PURE__ */ jsx(DialogPrimitive.Root, { ...dialogScope, ...alertDialogProps, modal: true });
164544
- };
164545
- AlertDialog$1.displayName = ROOT_NAME;
164546
- var TRIGGER_NAME = "AlertDialogTrigger";
164547
- var AlertDialogTrigger = React.forwardRef(
164548
- (props, forwardedRef) => {
164549
- const { __scopeAlertDialog, ...triggerProps } = props;
164550
- const dialogScope = useDialogScope(__scopeAlertDialog);
164551
- return /* @__PURE__ */ jsx(DialogPrimitive.Trigger, { ...dialogScope, ...triggerProps, ref: forwardedRef });
164552
- }
164553
- );
164554
- AlertDialogTrigger.displayName = TRIGGER_NAME;
164555
- var PORTAL_NAME = "AlertDialogPortal";
164556
- var AlertDialogPortal$1 = (props) => {
164557
- const { __scopeAlertDialog, ...portalProps } = props;
164558
- const dialogScope = useDialogScope(__scopeAlertDialog);
164559
- return /* @__PURE__ */ jsx(DialogPrimitive.Portal, { ...dialogScope, ...portalProps });
164560
- };
164561
- AlertDialogPortal$1.displayName = PORTAL_NAME;
164562
- var OVERLAY_NAME = "AlertDialogOverlay";
164563
- var AlertDialogOverlay$1 = React.forwardRef(
164564
- (props, forwardedRef) => {
164565
- const { __scopeAlertDialog, ...overlayProps } = props;
164566
- const dialogScope = useDialogScope(__scopeAlertDialog);
164567
- return /* @__PURE__ */ jsx(DialogPrimitive.Overlay, { ...dialogScope, ...overlayProps, ref: forwardedRef });
164568
- }
164569
- );
164570
- AlertDialogOverlay$1.displayName = OVERLAY_NAME;
164571
- var CONTENT_NAME = "AlertDialogContent";
164572
- var [AlertDialogContentProvider, useAlertDialogContentContext] = createAlertDialogContext(CONTENT_NAME);
164573
- var Slottable = createSlottable("AlertDialogContent");
164574
- var AlertDialogContent$1 = React.forwardRef(
164575
- (props, forwardedRef) => {
164576
- const { __scopeAlertDialog, children, ...contentProps } = props;
164577
- const dialogScope = useDialogScope(__scopeAlertDialog);
164578
- const contentRef = React.useRef(null);
164579
- const composedRefs = useComposedRefs$1(forwardedRef, contentRef);
164580
- const cancelRef = React.useRef(null);
164581
- return /* @__PURE__ */ jsx(
164582
- DialogPrimitive.WarningProvider,
164583
- {
164584
- contentName: CONTENT_NAME,
164585
- titleName: TITLE_NAME,
164586
- docsSlug: "alert-dialog",
164587
- children: /* @__PURE__ */ jsx(AlertDialogContentProvider, { scope: __scopeAlertDialog, cancelRef, children: /* @__PURE__ */ jsxs(
164588
- DialogPrimitive.Content,
164589
- {
164590
- role: "alertdialog",
164591
- ...dialogScope,
164592
- ...contentProps,
164593
- ref: composedRefs,
164594
- onOpenAutoFocus: composeEventHandlers$2(contentProps.onOpenAutoFocus, (event) => {
164595
- event.preventDefault();
164596
- cancelRef.current?.focus({ preventScroll: true });
164597
- }),
164598
- onPointerDownOutside: (event) => event.preventDefault(),
164599
- onInteractOutside: (event) => event.preventDefault(),
164600
- children: [
164601
- /* @__PURE__ */ jsx(Slottable, { children }),
164602
- /* @__PURE__ */ jsx(DescriptionWarning, { contentRef })
164603
- ]
164604
- }
164605
- ) })
164606
- }
164607
- );
164608
- }
164609
- );
164610
- AlertDialogContent$1.displayName = CONTENT_NAME;
164611
- var TITLE_NAME = "AlertDialogTitle";
164612
- var AlertDialogTitle$1 = React.forwardRef(
164613
- (props, forwardedRef) => {
164614
- const { __scopeAlertDialog, ...titleProps } = props;
164615
- const dialogScope = useDialogScope(__scopeAlertDialog);
164616
- return /* @__PURE__ */ jsx(DialogPrimitive.Title, { ...dialogScope, ...titleProps, ref: forwardedRef });
164617
- }
164618
- );
164619
- AlertDialogTitle$1.displayName = TITLE_NAME;
164620
- var DESCRIPTION_NAME = "AlertDialogDescription";
164621
- var AlertDialogDescription$1 = React.forwardRef((props, forwardedRef) => {
164622
- const { __scopeAlertDialog, ...descriptionProps } = props;
164623
- const dialogScope = useDialogScope(__scopeAlertDialog);
164624
- return /* @__PURE__ */ jsx(DialogPrimitive.Description, { ...dialogScope, ...descriptionProps, ref: forwardedRef });
164625
- });
164626
- AlertDialogDescription$1.displayName = DESCRIPTION_NAME;
164627
- var ACTION_NAME = "AlertDialogAction";
164628
- var AlertDialogAction$1 = React.forwardRef(
164629
- (props, forwardedRef) => {
164630
- const { __scopeAlertDialog, ...actionProps } = props;
164631
- const dialogScope = useDialogScope(__scopeAlertDialog);
164632
- return /* @__PURE__ */ jsx(DialogPrimitive.Close, { ...dialogScope, ...actionProps, ref: forwardedRef });
164633
- }
164634
- );
164635
- AlertDialogAction$1.displayName = ACTION_NAME;
164636
- var CANCEL_NAME = "AlertDialogCancel";
164637
- var AlertDialogCancel$1 = React.forwardRef(
164638
- (props, forwardedRef) => {
164639
- const { __scopeAlertDialog, ...cancelProps } = props;
164640
- const { cancelRef } = useAlertDialogContentContext(CANCEL_NAME, __scopeAlertDialog);
164641
- const dialogScope = useDialogScope(__scopeAlertDialog);
164642
- const ref = useComposedRefs$1(forwardedRef, cancelRef);
164643
- return /* @__PURE__ */ jsx(DialogPrimitive.Close, { ...dialogScope, ...cancelProps, ref });
164644
- }
164645
- );
164646
- AlertDialogCancel$1.displayName = CANCEL_NAME;
164647
- var DescriptionWarning = ({ contentRef }) => {
164648
- const MESSAGE = `\`${CONTENT_NAME}\` requires a description for the component to be accessible for screen reader users.
164649
-
164650
- You can add a description to the \`${CONTENT_NAME}\` by passing a \`${DESCRIPTION_NAME}\` component as a child, which also benefits sighted users by adding visible context to the dialog.
164651
-
164652
- Alternatively, you can use your own component as a description by assigning it an \`id\` and passing the same value to the \`aria-describedby\` prop in \`${CONTENT_NAME}\`. If the description is confusing or duplicative for sighted users, you can use the \`@radix-ui/react-visually-hidden\` primitive as a wrapper around your description component.
164653
-
164654
- For more information, see https://radix-ui.com/primitives/docs/components/alert-dialog`;
164655
- React.useEffect(() => {
164656
- const hasDescription = document.getElementById(
164657
- contentRef.current?.getAttribute("aria-describedby")
164658
- );
164659
- if (!hasDescription) console.warn(MESSAGE);
164660
- }, [MESSAGE, contentRef]);
164661
- return null;
164662
- };
164663
- var Root2 = AlertDialog$1;
164664
- var Portal2 = AlertDialogPortal$1;
164665
- var Overlay2 = AlertDialogOverlay$1;
164666
- var Content2 = AlertDialogContent$1;
164667
- var Action = AlertDialogAction$1;
164668
- var Cancel = AlertDialogCancel$1;
164669
- var Title2 = AlertDialogTitle$1;
164670
- var Description2 = AlertDialogDescription$1;
164671
-
164672
- const AlertDialog = Root2;
164673
- const AlertDialogPortal = Portal2;
164674
- const AlertDialogOverlay = React.forwardRef(({ className, ...props }, ref) => (jsx(Overlay2, { className: cn('data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80', className), ...props, ref: ref })));
164675
- AlertDialogOverlay.displayName = Overlay2.displayName;
164676
- const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => (jsxs(AlertDialogPortal, { children: [jsx(AlertDialogOverlay, {}), jsx(Content2, { ref: ref, className: cn('bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg', className), ...props })] })));
164677
- AlertDialogContent.displayName = Content2.displayName;
164678
- const AlertDialogHeader = ({ className, ...props }) => (jsx("div", { className: cn('flex flex-col space-y-2 text-center sm:text-left', className), ...props }));
164679
- AlertDialogHeader.displayName = 'AlertDialogHeader';
164680
- const AlertDialogFooter = ({ className, ...props }) => (jsx("div", { className: cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className), ...props }));
164681
- AlertDialogFooter.displayName = 'AlertDialogFooter';
164682
- const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => (jsx(Title2, { ref: ref, className: cn('text-lg font-semibold', className), ...props })));
164683
- AlertDialogTitle.displayName = Title2.displayName;
164684
- const AlertDialogDescription = React.forwardRef(({ className, ...props }, ref) => (jsx(Description2, { ref: ref, className: cn('text-muted-foreground text-sm', className), ...props })));
164685
- AlertDialogDescription.displayName = Description2.displayName;
164686
- const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => (jsx(Action, { ref: ref, className: cn(buttonVariants(), className), ...props })));
164687
- AlertDialogAction.displayName = Action.displayName;
164688
- const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => (jsx(Cancel, { ref: ref, className: cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className), ...props })));
164689
- AlertDialogCancel.displayName = Cancel.displayName;
164690
-
164691
165347
  /**
164692
165348
  * Checks if `value` is classified as an `Array` object.
164693
165349
  *
@@ -206411,7 +207067,7 @@ function Account({ tenant, tenants = [], username, onInviteClick, email, mainPla
206411
207067
  }, children: [jsxs("div", { className: "lg:hidden mb-6", children: [jsx("h3", { className: "text-lg font-medium text-gray-900 dark:text-gray-100 mb-2 capitalize", children: activeTab }), jsxs("p", { className: "text-gray-600 dark:text-gray-400 text-sm", children: [activeTab === 'basic' && 'Manage your basic account information and preferences.', activeTab === 'social' && 'Connect and manage your social media accounts.', activeTab === 'security' && 'Update your security settings and password.', activeTab === 'management' && 'Manage users and their permissions in the system.', activeTab === 'organization' && 'Manage your organization settings.', activeTab === 'monetization' && 'Configure paywalls, pricing, and revenue.', activeTab === 'advanced' && 'Configure advanced organization settings.'] })] }), activeTab === 'management' && hasManagementPermissions && (jsx(Admin, { onInviteClick: onInviteClick, tenant: tenant, hasUserTabPermission: hasUserTabPermission, hasGroupsTabPermission: hasGroupsTabPermission, hasPoliciesTabPermission: hasPoliciesTabPermission, hasRolesTabPermission: hasRolesTabPermission, hasTeamsTabPermission: hasTeamsTabPermission, hasInviteUserPermission: hasInviteUserPermission, hasCreateTeamPermission: hasCreateTeamPermission, onLoadGroupPermissions: onLoadGroupPermissions, hasAlertsTabPermission: hasAlertsTabPermission, rbacPermissions: rbacPermissions, enableRbac: enableRbac })), activeTab === 'organization' && (jsx(OrganizationTab, { platformKey: tenant, tenant: tenants.find((t) => t.key === tenant), onTenantUpdate: onTenantUpdate, setOrganizationLogoFromOutside: setOrganizationLogo, defaultSupportPhone: defaultSupportPhone })), activeTab === 'integrations' && (jsx(IntegrationsTab, { tenantKey: tenant, username: username })), activeTab === 'billing' && (jsx(BillingTab, { tenant: tenant, userActiveApp: userActiveApp, username: username, currentUserEmail: email, mainPlatformKey: mainPlatformKey })), activeTab === 'monetization' && (jsx(MonetizationTab, { platformKey: tenant, authURL: authURL })), activeTab === 'advanced' && (jsx(AdvancedTab, { platformKey: tenant, currentSPA: currentSPA, username: username, authURL: authURL, currentPlatformBaseDomain: currentPlatformBaseDomain }))] })] }), jsx(ToastProvider, {})] }));
206412
207068
  }
206413
207069
 
206414
- function UserProfileModal({ isOpen, onClose, params, email, mainPlatformKey, showMentorAIDisplayCheckbox = false, showLeaderboardDisplayCheckbox = false, showUsernameField = false, showPlatformName = false, useGravatarPicFallback = true, enableCatalogInvite = false, targetTab = 'basic', currentPlan = '', currentSPA = '', userActiveApp = null, authURL, tenants = [], onTenantUpdate, currentPlatformBaseDomain = '', rbacPermissions = {}, enableRbac = false, onLoadGroupPermissions, onTabChange, defaultSupportPhone = '', onBillingTabRequest, onAccountDeleted, enableMemoryTab = false, localLLMProps, }) {
207070
+ function UserProfileModal({ isOpen, onClose, params, email, mainPlatformKey, showMentorAIDisplayCheckbox = false, showLeaderboardDisplayCheckbox = false, showUsernameField = false, showPlatformName = false, useGravatarPicFallback = true, enableCatalogInvite = false, targetTab = 'basic', currentPlan = '', currentSPA = '', userActiveApp = null, authURL, tenants = [], onTenantUpdate, currentPlatformBaseDomain = '', rbacPermissions = {}, enableRbac = false, onLoadGroupPermissions, onTabChange, defaultSupportPhone = '', onBillingTabRequest, onAccountDeleted, enableMemoryTab = false, localLLMProps, systemControlProps, }) {
206415
207071
  console.log('[UserProfileModal] localLLMProps received:', {
206416
207072
  isAvailable: localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.isAvailable,
206417
207073
  foundryStatus: localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.foundryStatus,
@@ -206475,116 +207131,7 @@ function UserProfileModal({ isOpen, onClose, params, email, mainPlatformKey, sho
206475
207131
  showUsernameField: showUsernameField,
206476
207132
  showPlatformName: showPlatformName,
206477
207133
  useGravatarPicFallback: useGravatarPicFallback,
206478
- }, isAdmin: params.isAdmin, targetTab: targetTab, onClose: onClose, onAccountDeleted: onAccountDeleted, enableMemoryTab: enableMemoryTab, localLLMProps: localLLMProps })), ['organization', 'management', 'integrations', 'billing', 'monetization'].includes(targetTab) && (jsxs(Fragment$1, { children: [jsx(Account, { onInviteClick: () => setIsInviteUserDialogOpen(true), tenant: params.tenantKey, tenants: tenants, username: getUserName$1(), email: email, mainPlatformKey: mainPlatformKey, showUsernameField: showUsernameField, showPlatformName: showPlatformName, useGravatarPicFallback: useGravatarPicFallback, onClose: onClose, isAdmin: params.isAdmin, targetTab: targetTab, currentPlan: currentPlan, currentSPA: currentSPA, userActiveApp: userActiveApp, authURL: authURL, onTenantUpdate: onTenantUpdate, currentPlatformBaseDomain: currentPlatformBaseDomain, rbacPermissions: rbacPermissions, enableRbac: enableRbac, onLoadGroupPermissions: onLoadGroupPermissions, onTabChange: onTabChange, defaultSupportPhone: defaultSupportPhone }), isInviteUserDialogOpen && (jsx(InviteUserDialog, { tenant: params.tenantKey, onClose: () => setIsInviteUserDialogOpen(false), isOpen: isInviteUserDialogOpen, enableCatalogInvite: enableCatalogInvite, hasManageUsersPermission: hasManageUsersPermission })), isInvitedUsersDialogOpen && (jsx(InvitedUsersDialog, { onClose: () => setIsInvitedUsersDialogOpen(false), tenant: params.tenantKey }))] }))] }) }) }));
206479
- }
206480
-
206481
- // Tauri types for model download functionality
206482
- /**
206483
- * Initial state for model download
206484
- */
206485
- const initialModelDownloadState = {
206486
- status: 'idle',
206487
- progress: 0,
206488
- message: '',
206489
- logs: [],
206490
- lastUpdated: new Date().toISOString(),
206491
- };
206492
- /**
206493
- * Check if the app is running inside Tauri
206494
- * In Tauri 2.0, the global is __TAURI_INTERNALS__ (with withGlobalTauri: true)
206495
- * We also check for __TAURI__ for backwards compatibility
206496
- */
206497
- const isTauriApp$1 = () => {
206498
- if (typeof window === 'undefined')
206499
- return false;
206500
- return '__TAURI_INTERNALS__' in window || '__TAURI__' in window;
206501
- };
206502
- /**
206503
- * Tauri event names
206504
- */
206505
- const TAURI_EVENTS = {
206506
- DOWNLOAD_PROGRESS: 'model:download-progress',
206507
- INSTALLATION_LOG: 'model:installation-log',
206508
- DISK_SPACE_ERROR: 'model:disk-space-error',
206509
- OLLAMA_STATUS: 'model:ollama-status',
206510
- };
206511
- /**
206512
- * Tauri command names
206513
- */
206514
- const TAURI_COMMANDS = {
206515
- INSTALL_OLLAMA: 'install_ollama',
206516
- CHECK_OLLAMA_STATUS: 'check_ollama_status',
206517
- CHECK_DISK_SPACE: 'check_disk_space_for_model',
206518
- DOWNLOAD_MODEL: 'download_phi3_model',
206519
- CANCEL_DOWNLOAD: 'cancel_model_download',
206520
- CHECK_NETWORK_STATUS: 'check_network_status',
206521
- GET_OS_TYPE: 'get_os_type',
206522
- CHECK_FOUNDRY_STATUS: 'check_foundry_local_status',
206523
- START_FOUNDRY_SERVICE: 'start_foundry_local_service',
206524
- LOAD_FOUNDRY_MODEL: 'load_foundry_local_model',
206525
- SET_SELECTED_FOUNDRY_MODEL: 'set_selected_foundry_model',
206526
- GET_SELECTED_FOUNDRY_MODEL: 'get_selected_foundry_model',
206527
- INSTALL_FOUNDRY: 'install_foundry',
206528
- DOWNLOAD_FOUNDRY_MODEL: 'download_foundry_model_cmd',
206529
- GET_RECOMMENDED_FOUNDRY_MODELS: 'get_recommended_foundry_models',
206530
- };
206531
-
206532
- /**
206533
- * Dynamically import Tauri APIs to avoid SSR issues
206534
- */
206535
- const getTauriAPIs = async () => {
206536
- try {
206537
- const { invoke } = await import('@tauri-apps/api/core');
206538
- const { listen } = await import('@tauri-apps/api/event');
206539
- return { invoke, listen };
206540
- }
206541
- catch (error) {
206542
- console.error('Failed to load Tauri APIs:', error);
206543
- return null;
206544
- }
206545
- };
206546
- /**
206547
- * Hook to access Tauri APIs with SSR safety
206548
- *
206549
- * @returns Object with isAvailable flag and Tauri invoke/listen functions
206550
- */
206551
- function useTauri() {
206552
- const [isAvailable, setIsAvailable] = useState(false);
206553
- const [apis, setApis] = useState(null);
206554
- useEffect(() => {
206555
- // Check if we're in a Tauri environment
206556
- const inTauri = isTauriApp$1();
206557
- if (inTauri) {
206558
- getTauriAPIs().then((result) => {
206559
- setApis(result);
206560
- setIsAvailable(!!result);
206561
- });
206562
- }
206563
- }, []);
206564
- /**
206565
- * Invoke a Tauri command
206566
- */
206567
- const invoke = useCallback(async (command, args) => {
206568
- if (!(apis === null || apis === void 0 ? void 0 : apis.invoke)) {
206569
- throw new Error('Tauri is not available');
206570
- }
206571
- return apis.invoke(command, args);
206572
- }, [apis]);
206573
- /**
206574
- * Listen to a Tauri event
206575
- * Returns an unlisten function that should be called on cleanup
206576
- */
206577
- const listen = useCallback(async (event, handler) => {
206578
- if (!(apis === null || apis === void 0 ? void 0 : apis.listen)) {
206579
- throw new Error('Tauri is not available');
206580
- }
206581
- return apis.listen(event, (e) => handler(e.payload));
206582
- }, [apis]);
206583
- return {
206584
- isAvailable,
206585
- invoke,
206586
- listen,
206587
- };
207134
+ }, isAdmin: params.isAdmin, targetTab: targetTab, onClose: onClose, onAccountDeleted: onAccountDeleted, enableMemoryTab: enableMemoryTab, localLLMProps: localLLMProps, systemControlProps: systemControlProps })), ['organization', 'management', 'integrations', 'billing', 'monetization'].includes(targetTab) && (jsxs(Fragment$1, { children: [jsx(Account, { onInviteClick: () => setIsInviteUserDialogOpen(true), tenant: params.tenantKey, tenants: tenants, username: getUserName$1(), email: email, mainPlatformKey: mainPlatformKey, showUsernameField: showUsernameField, showPlatformName: showPlatformName, useGravatarPicFallback: useGravatarPicFallback, onClose: onClose, isAdmin: params.isAdmin, targetTab: targetTab, currentPlan: currentPlan, currentSPA: currentSPA, userActiveApp: userActiveApp, authURL: authURL, onTenantUpdate: onTenantUpdate, currentPlatformBaseDomain: currentPlatformBaseDomain, rbacPermissions: rbacPermissions, enableRbac: enableRbac, onLoadGroupPermissions: onLoadGroupPermissions, onTabChange: onTabChange, defaultSupportPhone: defaultSupportPhone }), isInviteUserDialogOpen && (jsx(InviteUserDialog, { tenant: params.tenantKey, onClose: () => setIsInviteUserDialogOpen(false), isOpen: isInviteUserDialogOpen, enableCatalogInvite: enableCatalogInvite, hasManageUsersPermission: hasManageUsersPermission })), isInvitedUsersDialogOpen && (jsx(InvitedUsersDialog, { onClose: () => setIsInvitedUsersDialogOpen(false), tenant: params.tenantKey }))] }))] }) }) }));
206588
207135
  }
206589
207136
 
206590
207137
  const IS_SERVER = typeof window === 'undefined';
@@ -206649,8 +207196,15 @@ function useLocalStorage(key, initialValue) {
206649
207196
  return [storedValue, setValue, removeValue];
206650
207197
  }
206651
207198
 
206652
- const LOCAL_STORAGE_KEY = 'model_download_state';
207199
+ const LOCAL_STORAGE_KEY$1 = 'model_download_state';
206653
207200
  const FIRST_LAUNCH_DISMISSED_KEY = 'model_download_prompt_dismissed';
207201
+ // Manager/download status is authoritative from Tauri, never the persisted
207202
+ // snapshot. This flips true the first time any hook instance mounts in a page
207203
+ // session so we wipe the persisted state exactly once — clearing a stale
207204
+ // `managerInstalling`/`downloading`/`checking` flag (e.g. left behind by an
207205
+ // operation interrupted by an app reload) that would otherwise wedge the UI in
207206
+ // a permanent "loading" state — then fall back to a fresh backend status check.
207207
+ let didResetPersistedState = false;
206654
207208
  /**
206655
207209
  * Hook to manage Phi3 Mini model download via Model Manager through Tauri
206656
207210
  *
@@ -206662,11 +207216,15 @@ const FIRST_LAUNCH_DISMISSED_KEY = 'model_download_prompt_dismissed';
206662
207216
  */
206663
207217
  function useModelDownload() {
206664
207218
  const { isAvailable, invoke, listen } = useTauri();
206665
- const [state, setState] = useLocalStorage(LOCAL_STORAGE_KEY, initialModelDownloadState);
207219
+ const [state, setState] = useLocalStorage(LOCAL_STORAGE_KEY$1, initialModelDownloadState);
206666
207220
  const [ollamaStatus, setOllamaStatus] = useState(null);
207221
+ const [systemMemory, setSystemMemory] = useState(null);
206667
207222
  const [isFirstLaunchDismissed, setIsFirstLaunchDismissed] = useLocalStorage(FIRST_LAUNCH_DISMISSED_KEY, false);
206668
207223
  const unlistenRefs = useRef([]);
206669
207224
  const hasCheckedStatus = useRef(false);
207225
+ // Stable handle to the latest checkStatus so the mount-time event listeners
207226
+ // can refresh status (e.g. after a download completes) without re-subscribing.
207227
+ const checkStatusRef = useRef(() => { });
206670
207228
  console.log('[ModelDownload] Hook initialized:', {
206671
207229
  isAvailable,
206672
207230
  stateStatus: state.status,
@@ -206726,7 +207284,12 @@ function useModelDownload() {
206726
207284
  });
206727
207285
  if (payload.status === 'completed') {
206728
207286
  console.log('[ModelDownload] Download completed successfully!');
206729
- toast.success('Phi Mini 3 model downloaded successfully!');
207287
+ // Refresh Ollama status so the freshly pulled model shows up in
207288
+ // `installed_models` — keeps the row marked "Ready" and selectable
207289
+ // even after the modal is closed and reopened. The success toast is
207290
+ // shown by startDownload (once per initiated download) to avoid a
207291
+ // duplicate from each mounted useModelDownload instance.
207292
+ checkStatusRef.current();
206730
207293
  }
206731
207294
  });
206732
207295
  unlistenRefs.current.push(unlistenProgress);
@@ -206778,9 +207341,36 @@ function useModelDownload() {
206778
207341
  if (isAvailable && !hasCheckedStatus.current) {
206779
207342
  console.log('[ModelDownload] First mount, checking status...');
206780
207343
  hasCheckedStatus.current = true;
207344
+ // Wipe any stale persisted manager/download state once per page load, so a
207345
+ // leftover `managerInstalling`/`downloading` flag can't keep the toggle or
207346
+ // status card stuck "loading". Tauri is the source of truth from here on.
207347
+ if (!didResetPersistedState) {
207348
+ didResetPersistedState = true;
207349
+ setState(initialModelDownloadState);
207350
+ }
206781
207351
  checkStatus();
206782
207352
  }
206783
207353
  }, [isAvailable]);
207354
+ /**
207355
+ * Read system memory (RAM/VRAM) once the desktop bridge is available, so the
207356
+ * UI can warn before downloading a model that's large relative to capacity.
207357
+ */
207358
+ useEffect(() => {
207359
+ if (!isAvailable)
207360
+ return;
207361
+ let cancelled = false;
207362
+ invoke(TAURI_COMMANDS.GET_SYSTEM_MEMORY)
207363
+ .then((mem) => {
207364
+ if (!cancelled)
207365
+ setSystemMemory(mem);
207366
+ })
207367
+ .catch((error) => {
207368
+ console.error('[ModelDownload] Failed to read system memory:', error);
207369
+ });
207370
+ return () => {
207371
+ cancelled = true;
207372
+ };
207373
+ }, [isAvailable, invoke]);
206784
207374
  /**
206785
207375
  * Check Model Manager installation and model status
206786
207376
  */
@@ -206820,11 +207410,16 @@ function useModelDownload() {
206820
207410
  }));
206821
207411
  }
206822
207412
  }, [isAvailable, invoke, setState]);
207413
+ // Keep the ref pointed at the latest checkStatus for use inside the once-on-mount
207414
+ // event listeners (which must not depend on checkStatus to avoid re-subscribing).
207415
+ useEffect(() => {
207416
+ checkStatusRef.current = checkStatus;
207417
+ }, [checkStatus]);
206823
207418
  /**
206824
207419
  * Start downloading the Phi3 Mini model
206825
207420
  */
206826
- const startDownload = useCallback(async () => {
206827
- console.log('[ModelDownload] startDownload called, isAvailable:', isAvailable);
207421
+ const startDownload = useCallback(async (modelId) => {
207422
+ console.log('[ModelDownload] startDownload called, isAvailable:', isAvailable, 'model:', modelId);
206828
207423
  if (!isAvailable) {
206829
207424
  console.log('[ModelDownload] Desktop app not available');
206830
207425
  toast.error('Desktop app required for local model download');
@@ -206839,6 +207434,9 @@ function useModelDownload() {
206839
207434
  message: 'Starting download...',
206840
207435
  error: undefined,
206841
207436
  logs: [],
207437
+ // Record which model is downloading so the right row shows progress even
207438
+ // if the dialog is closed and reopened mid-download (state is persisted).
207439
+ activeModel: modelId !== null && modelId !== void 0 ? modelId : 'phi3:mini',
206842
207440
  }));
206843
207441
  // Check disk space first
206844
207442
  console.log('[ModelDownload] Checking disk space...');
@@ -206848,20 +207446,43 @@ function useModelDownload() {
206848
207446
  console.log('[ModelDownload] Insufficient disk space');
206849
207447
  return; // Error will be emitted via event
206850
207448
  }
206851
- // Start the download
206852
- console.log('[ModelDownload] Starting model download...');
206853
- await invoke(TAURI_COMMANDS.DOWNLOAD_MODEL);
207449
+ // Start the download. The selected model identifier is forwarded so the
207450
+ // backend can pull it; the current backend defaults to Phi-3 Mini.
207451
+ // `invoke` resolves only once the Rust pull has fully finished, so treat a
207452
+ // successful resolution as completion. This guarantees the UI leaves the
207453
+ // "downloading" state even if the terminal progress event was missed (live
207454
+ // progress still streams from the progress events while the pull runs).
207455
+ console.log('[ModelDownload] Starting model download...', modelId);
207456
+ await invoke(TAURI_COMMANDS.DOWNLOAD_MODEL, modelId ? { model: modelId } : undefined);
206854
207457
  console.log('[ModelDownload] Download command invoked');
207458
+ let didComplete = false;
207459
+ setState((prev) => {
207460
+ // Don't clobber a cancellation/error that landed while awaiting.
207461
+ if (prev.status !== 'downloading')
207462
+ return prev;
207463
+ didComplete = true;
207464
+ return { ...prev, status: 'completed', progress: 100, message: 'Download complete' };
207465
+ });
207466
+ if (didComplete) {
207467
+ toast.success('Model downloaded successfully!');
207468
+ }
207469
+ // Refresh installed models so the row flips to "Ready" and is selectable.
207470
+ checkStatusRef.current();
206855
207471
  }
206856
207472
  catch (error) {
206857
207473
  const errorMessage = error instanceof Error ? error.message : String(error);
206858
207474
  console.error('[ModelDownload] Download failed:', errorMessage);
206859
- setState((prev) => ({
206860
- ...prev,
206861
- status: 'error',
206862
- error: errorMessage,
206863
- }));
206864
- toast.error(`Download failed: ${errorMessage}`);
207475
+ let wasCancelled = false;
207476
+ setState((prev) => {
207477
+ if (prev.status === 'cancelled') {
207478
+ wasCancelled = true;
207479
+ return prev;
207480
+ }
207481
+ return { ...prev, status: 'error', error: errorMessage };
207482
+ });
207483
+ if (!wasCancelled) {
207484
+ toast.error(`Download failed: ${errorMessage}`);
207485
+ }
206865
207486
  }
206866
207487
  }, [isAvailable, invoke, setState]);
206867
207488
  /**
@@ -206870,14 +207491,17 @@ function useModelDownload() {
206870
207491
  const cancelDownload = useCallback(async () => {
206871
207492
  if (!isAvailable)
206872
207493
  return;
207494
+ // Mark cancelled up front so the in-flight download promise (which resolves
207495
+ // cleanly once the backend aborts the pull) can't race ahead and mark the
207496
+ // row "completed".
207497
+ setState((prev) => ({
207498
+ ...prev,
207499
+ status: 'cancelled',
207500
+ progress: 0,
207501
+ message: 'Download cancelled',
207502
+ }));
206873
207503
  try {
206874
207504
  await invoke(TAURI_COMMANDS.CANCEL_DOWNLOAD);
206875
- setState((prev) => ({
206876
- ...prev,
206877
- status: 'cancelled',
206878
- progress: 0,
206879
- message: 'Download cancelled',
206880
- }));
206881
207505
  toast.info('Download cancelled');
206882
207506
  }
206883
207507
  catch (error) {
@@ -206951,6 +207575,26 @@ function useModelDownload() {
206951
207575
  });
206952
207576
  }
206953
207577
  }, [isAvailable, invoke, addLog, checkStatus, setState]);
207578
+ /**
207579
+ * Stop the model manager (Ollama). Backs turning "Enable Local Models" off.
207580
+ */
207581
+ const stopManager = useCallback(async () => {
207582
+ if (!isAvailable)
207583
+ return;
207584
+ try {
207585
+ addLog({
207586
+ timestamp: new Date().toISOString(),
207587
+ level: 'info',
207588
+ message: 'Stopping model manager...',
207589
+ });
207590
+ await invoke(TAURI_COMMANDS.STOP_OLLAMA);
207591
+ await checkStatus();
207592
+ }
207593
+ catch (error) {
207594
+ const errorMessage = error instanceof Error ? error.message : String(error);
207595
+ console.error('[ModelDownload] Failed to stop model manager:', errorMessage);
207596
+ }
207597
+ }, [isAvailable, invoke, addLog, checkStatus]);
206954
207598
  /**
206955
207599
  * Clear all logs
206956
207600
  */
@@ -206986,18 +207630,238 @@ function useModelDownload() {
206986
207630
  isAvailable: finalIsAvailable,
206987
207631
  state,
206988
207632
  ollamaStatus,
207633
+ systemMemory,
206989
207634
  shouldShowFirstLaunchPrompt,
206990
207635
  // Actions
206991
207636
  checkStatus,
206992
207637
  startDownload,
206993
207638
  cancelDownload,
206994
207639
  installOllama,
207640
+ stopManager,
206995
207641
  clearLogs,
206996
207642
  resetState,
206997
207643
  dismissFirstLaunchPrompt,
206998
207644
  };
206999
207645
  }
207000
207646
 
207647
+ const LOCAL_STORAGE_KEY = 'ghost_os_install_state';
207648
+ /**
207649
+ * Manage installing & setting up GhostOS (https://github.com/ghostwright/ghost-os)
207650
+ * via the Tauri host. This is the self-contained backing hook for the System
207651
+ * Control card and deliberately mirrors `useModelDownload`:
207652
+ *
207653
+ * - checks GhostOS install/run status through the host
207654
+ * - installs/sets up GhostOS with real-time progress + logs
207655
+ * - persists state across app restarts
207656
+ *
207657
+ * Outside the desktop (Tauri) app every action is a no-op and `isAvailable` is
207658
+ * false, so the card hides itself.
207659
+ */
207660
+ function useGhostOs() {
207661
+ const { isAvailable, invoke, listen } = useTauri();
207662
+ const [state, setState] = useLocalStorage(LOCAL_STORAGE_KEY, initialGhostOsInstallState);
207663
+ const [status, setStatus] = useState(null);
207664
+ // macOS Accessibility permission, which GhostOS needs to control the device.
207665
+ // null = unknown/not-yet-checked or unsupported (non-macOS / plugin absent).
207666
+ const [accessibilityPermission, setAccessibilityPermission] = useState(null);
207667
+ const unlistenRefs = useRef([]);
207668
+ const hasCheckedStatus = useRef(false);
207669
+ /**
207670
+ * Refresh the macOS Accessibility permission via tauri-plugin-macos-permissions.
207671
+ * Independent of the GhostOS status check so a missing host command for one
207672
+ * doesn't suppress the other. Stays null where unsupported.
207673
+ */
207674
+ const refreshAccessibilityPermission = useCallback(async () => {
207675
+ if (!isAvailable)
207676
+ return;
207677
+ try {
207678
+ const granted = await invoke(MACOS_PERMISSIONS_COMMANDS.CHECK_ACCESSIBILITY);
207679
+ setAccessibilityPermission(granted);
207680
+ }
207681
+ catch (error) {
207682
+ // Plugin not registered or non-macOS host — leave permission unknown.
207683
+ console.warn('[useGhostOs] Accessibility permission check unavailable:', error);
207684
+ setAccessibilityPermission(null);
207685
+ }
207686
+ }, [isAvailable, invoke]);
207687
+ /**
207688
+ * Open the Accessibility pane / prompt so the user can grant permission, then
207689
+ * re-check (the grant happens in System Settings, so we refresh after).
207690
+ */
207691
+ const requestAccessibilityPermission = useCallback(async () => {
207692
+ if (!isAvailable)
207693
+ return;
207694
+ try {
207695
+ await invoke(MACOS_PERMISSIONS_COMMANDS.REQUEST_ACCESSIBILITY);
207696
+ }
207697
+ catch (error) {
207698
+ console.error('[useGhostOs] Failed to request accessibility permission:', error);
207699
+ }
207700
+ await refreshAccessibilityPermission();
207701
+ }, [isAvailable, invoke, refreshAccessibilityPermission]);
207702
+ const addLog = useCallback((log) => {
207703
+ setState((prev) => ({
207704
+ ...prev,
207705
+ logs: [...prev.logs.slice(-99), log],
207706
+ lastUpdated: new Date().toISOString(),
207707
+ }));
207708
+ }, [setState]);
207709
+ /**
207710
+ * Query GhostOS status from the host.
207711
+ */
207712
+ const checkStatus = useCallback(async () => {
207713
+ if (!isAvailable)
207714
+ return;
207715
+ // Refresh the accessibility permission alongside install status (independent).
207716
+ refreshAccessibilityPermission();
207717
+ try {
207718
+ setState((prev) => ({ ...prev, status: 'checking' }));
207719
+ const result = await invoke(GHOST_OS_TAURI_COMMANDS.CHECK_GHOST_OS_STATUS);
207720
+ setStatus(result);
207721
+ setState((prev) => ({
207722
+ ...prev,
207723
+ status: result.installed ? 'completed' : 'idle',
207724
+ progress: result.installed ? 100 : prev.progress,
207725
+ lastUpdated: new Date().toISOString(),
207726
+ }));
207727
+ }
207728
+ catch (error) {
207729
+ console.error('[useGhostOs] Failed to check status:', error);
207730
+ setState((prev) => ({ ...prev, status: 'idle' }));
207731
+ }
207732
+ }, [isAvailable, invoke, setState, refreshAccessibilityPermission]);
207733
+ /**
207734
+ * Install and set up GhostOS. The host clones the repo + runs setup and emits
207735
+ * progress/log/status events along the way.
207736
+ */
207737
+ const install = useCallback(async () => {
207738
+ if (!isAvailable)
207739
+ return;
207740
+ try {
207741
+ setState((prev) => ({
207742
+ ...prev,
207743
+ status: 'installing',
207744
+ progress: 0,
207745
+ message: 'Setting up your assistant…',
207746
+ error: undefined,
207747
+ logs: [],
207748
+ }));
207749
+ addLog({
207750
+ timestamp: new Date().toISOString(),
207751
+ level: 'info',
207752
+ message: 'Starting setup…',
207753
+ });
207754
+ const result = await invoke(GHOST_OS_TAURI_COMMANDS.INSTALL_GHOST_OS);
207755
+ addLog({
207756
+ timestamp: new Date().toISOString(),
207757
+ level: 'info',
207758
+ message: typeof result === 'string' && result ? result : 'Setup complete',
207759
+ });
207760
+ setState((prev) => ({
207761
+ ...prev,
207762
+ status: 'completed',
207763
+ progress: 100,
207764
+ message: 'Your assistant is ready',
207765
+ lastUpdated: new Date().toISOString(),
207766
+ }));
207767
+ await checkStatus();
207768
+ }
207769
+ catch (error) {
207770
+ const message = error instanceof Error ? error.message : String(error);
207771
+ setState((prev) => ({
207772
+ ...prev,
207773
+ status: 'error',
207774
+ error: message,
207775
+ message: 'Setup didn’t finish',
207776
+ lastUpdated: new Date().toISOString(),
207777
+ }));
207778
+ addLog({
207779
+ timestamp: new Date().toISOString(),
207780
+ level: 'error',
207781
+ message: `Install failed: ${message}`,
207782
+ });
207783
+ }
207784
+ }, [isAvailable, invoke, addLog, checkStatus, setState]);
207785
+ /**
207786
+ * Stop the running GhostOS manager.
207787
+ */
207788
+ const stop = useCallback(async () => {
207789
+ if (!isAvailable)
207790
+ return;
207791
+ try {
207792
+ await invoke(GHOST_OS_TAURI_COMMANDS.STOP_GHOST_OS);
207793
+ await checkStatus();
207794
+ }
207795
+ catch (error) {
207796
+ console.error('[useGhostOs] Failed to stop GhostOS:', error);
207797
+ }
207798
+ }, [isAvailable, invoke, checkStatus]);
207799
+ /**
207800
+ * Reset the install state (for retrying after an error).
207801
+ */
207802
+ const resetState = useCallback(() => {
207803
+ setState(initialGhostOsInstallState);
207804
+ }, [setState]);
207805
+ // Wire up host event listeners (progress / logs / status).
207806
+ useEffect(() => {
207807
+ if (!isAvailable)
207808
+ return;
207809
+ const setupListeners = async () => {
207810
+ try {
207811
+ const unlistenProgress = await listen(GHOST_OS_TAURI_EVENTS.INSTALL_PROGRESS, (payload) => {
207812
+ const nextStatus = payload.status === 'completed'
207813
+ ? 'completed'
207814
+ : payload.status === 'cancelled'
207815
+ ? 'cancelled'
207816
+ : payload.status === 'error'
207817
+ ? 'error'
207818
+ : 'installing';
207819
+ setState((prev) => ({
207820
+ ...prev,
207821
+ status: nextStatus,
207822
+ progress: payload.percentage,
207823
+ message: payload.message,
207824
+ lastUpdated: new Date().toISOString(),
207825
+ }));
207826
+ });
207827
+ unlistenRefs.current.push(unlistenProgress);
207828
+ const unlistenLogs = await listen(GHOST_OS_TAURI_EVENTS.INSTALLATION_LOG, addLog);
207829
+ unlistenRefs.current.push(unlistenLogs);
207830
+ const unlistenStatus = await listen(GHOST_OS_TAURI_EVENTS.STATUS, setStatus);
207831
+ unlistenRefs.current.push(unlistenStatus);
207832
+ }
207833
+ catch (error) {
207834
+ console.error('[useGhostOs] Failed to setup event listeners:', error);
207835
+ }
207836
+ };
207837
+ setupListeners();
207838
+ return () => {
207839
+ unlistenRefs.current.forEach((unlisten) => unlisten());
207840
+ unlistenRefs.current = [];
207841
+ };
207842
+ }, [isAvailable, listen, setState, addLog]);
207843
+ // Check status once on mount (in the desktop app).
207844
+ useEffect(() => {
207845
+ if (!isAvailable || hasCheckedStatus.current)
207846
+ return;
207847
+ hasCheckedStatus.current = true;
207848
+ checkStatus();
207849
+ }, [isAvailable, checkStatus]);
207850
+ // Only expose the feature as available inside the desktop (Tauri) app.
207851
+ const finalIsAvailable = isAvailable && isTauriApp$1();
207852
+ return {
207853
+ isAvailable: finalIsAvailable,
207854
+ state,
207855
+ status,
207856
+ accessibilityPermission,
207857
+ install,
207858
+ stop,
207859
+ checkStatus,
207860
+ resetState,
207861
+ requestAccessibilityPermission,
207862
+ };
207863
+ }
207864
+
207001
207865
  function UserProfileDropdown({ email, mainPlatformKey,
207002
207866
  // User data
207003
207867
  username, userIsAdmin = false, userIsStudent = false, userIsVisiting = false,
@@ -207021,9 +207885,11 @@ metadata, metadataLoaded = false,
207021
207885
  currentSPA = '', showMentorAIDisplayCheckbox = false, showLeaderboardDisplayCheckbox = false, showUsernameField = false, showPlatformName = false, enableCatalogInvite = false, authURL, onTenantUpdate, currentPlatformBaseDomain = '', rbacPermissions = {}, enableRbac = false, onLoadGroupPermissions, defaultSupportPhone = '',
207022
207886
  // Local LLM props
207023
207887
  localLLMProps,
207888
+ // System Control props
207889
+ systemControlProps,
207024
207890
  // Controlled modal state
207025
207891
  isModalOpen, onModalOpenChange, defaultActiveTab, onAccountDeleted, enableMemoryTab, }) {
207026
- var _a, _b, _c, _d, _e, _f, _g, _h;
207892
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
207027
207893
  // Use hooks to fetch user metadata
207028
207894
  const { data: userMetadata } = useGetUserMetadataQuery({
207029
207895
  params: { username: username !== null && username !== void 0 ? username : '' },
@@ -207037,17 +207903,30 @@ isModalOpen, onModalOpenChange, defaultActiveTab, onAccountDeleted, enableMemory
207037
207903
  const isLocalLLMAvailable = (_a = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.isAvailable) !== null && _a !== void 0 ? _a : hookData.isAvailable;
207038
207904
  const localLLMState = (_b = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.state) !== null && _b !== void 0 ? _b : hookData.state;
207039
207905
  const ollamaStatus = (_c = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.ollamaStatus) !== null && _c !== void 0 ? _c : hookData.ollamaStatus;
207040
- const startDownload = (_d = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.onStartDownload) !== null && _d !== void 0 ? _d : hookData.startDownload;
207041
- const cancelDownload = (_e = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.onCancelDownload) !== null && _e !== void 0 ? _e : hookData.cancelDownload;
207042
- const installOllama = (_f = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.onInstallOllama) !== null && _f !== void 0 ? _f : hookData.installOllama;
207906
+ const systemMemory = (_d = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.systemMemory) !== null && _d !== void 0 ? _d : hookData.systemMemory;
207907
+ const startDownload = (_e = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.onStartDownload) !== null && _e !== void 0 ? _e : hookData.startDownload;
207908
+ const cancelDownload = (_f = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.onCancelDownload) !== null && _f !== void 0 ? _f : hookData.cancelDownload;
207909
+ const installOllama = (_g = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.onInstallOllama) !== null && _g !== void 0 ? _g : hookData.installOllama;
207910
+ const stopManager = (_h = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.onStopManager) !== null && _h !== void 0 ? _h : hookData.stopManager;
207043
207911
  const installFoundry = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.onInstallFoundry;
207044
- const checkStatus = (_g = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.onCheckStatus) !== null && _g !== void 0 ? _g : hookData.checkStatus;
207045
- const resetState = (_h = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.onResetState) !== null && _h !== void 0 ? _h : hookData.resetState;
207912
+ const checkStatus = (_j = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.onCheckStatus) !== null && _j !== void 0 ? _j : hookData.checkStatus;
207913
+ const resetState = (_k = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.onResetState) !== null && _k !== void 0 ? _k : hookData.resetState;
207046
207914
  const isUsingFoundry = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.isUsingFoundry;
207047
207915
  const foundryModels = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.foundryModels;
207048
207916
  const selectedFoundryModel = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.selectedFoundryModel;
207049
207917
  const foundryStatus = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.foundryStatus;
207050
207918
  const onSelectFoundryModel = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.onSelectFoundryModel;
207919
+ // System Control (GhostOS) state from props (if provided) or from hook (fallback)
207920
+ const ghostOsData = useGhostOs();
207921
+ const isSystemControlAvailable = (_l = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.isAvailable) !== null && _l !== void 0 ? _l : ghostOsData.isAvailable;
207922
+ const systemControlState = (_m = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.state) !== null && _m !== void 0 ? _m : ghostOsData.state;
207923
+ const systemControlStatus = (_o = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.status) !== null && _o !== void 0 ? _o : ghostOsData.status;
207924
+ const accessibilityPermission = (_p = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.accessibilityPermission) !== null && _p !== void 0 ? _p : ghostOsData.accessibilityPermission;
207925
+ const installGhostOs = (_q = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.onInstall) !== null && _q !== void 0 ? _q : ghostOsData.install;
207926
+ const stopGhostOs = (_r = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.onStop) !== null && _r !== void 0 ? _r : ghostOsData.stop;
207927
+ const checkGhostOsStatus = (_s = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.onCheckStatus) !== null && _s !== void 0 ? _s : ghostOsData.checkStatus;
207928
+ const resetGhostOsState = (_t = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.onResetState) !== null && _t !== void 0 ? _t : ghostOsData.resetState;
207929
+ const requestAccessibilityPermission = (_u = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.onRequestAccessibilityPermission) !== null && _u !== void 0 ? _u : ghostOsData.requestAccessibilityPermission;
207051
207930
  // Support controlled modal state
207052
207931
  const [internalIsUserProfileOpen, setInternalIsUserProfileOpen] = useState(false);
207053
207932
  const isUserProfileOpen = isModalOpen !== null && isModalOpen !== void 0 ? isModalOpen : internalIsUserProfileOpen;
@@ -207206,6 +208085,7 @@ isModalOpen, onModalOpenChange, defaultActiveTab, onAccountDeleted, enableMemory
207206
208085
  isAvailable: isLocalLLMAvailable,
207207
208086
  state: localLLMState,
207208
208087
  ollamaStatus,
208088
+ systemMemory,
207209
208089
  isUsingFoundry,
207210
208090
  foundryModels,
207211
208091
  selectedFoundryModel,
@@ -207213,11 +208093,29 @@ isModalOpen, onModalOpenChange, defaultActiveTab, onAccountDeleted, enableMemory
207213
208093
  onStartDownload: startDownload,
207214
208094
  onCancelDownload: cancelDownload,
207215
208095
  onInstallOllama: installOllama,
208096
+ onStopManager: stopManager,
207216
208097
  onInstallFoundry: installFoundry,
207217
208098
  onCheckStatus: checkStatus,
207218
208099
  onResetState: resetState,
207219
208100
  onSelectFoundryModel,
207220
208101
  },
208102
+ systemControlProps: {
208103
+ isAvailable: isSystemControlAvailable,
208104
+ state: systemControlState,
208105
+ status: systemControlStatus,
208106
+ requiredSizeGb: systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.requiredSizeGb,
208107
+ // Local-model status/memory + downloader, so the "Upgrade" action can
208108
+ // check what's installed, keep the RAM/VRAM check, and pull a model.
208109
+ ollamaStatus,
208110
+ systemMemory,
208111
+ accessibilityPermission,
208112
+ onInstall: installGhostOs,
208113
+ onStop: stopGhostOs,
208114
+ onCheckStatus: checkGhostOsStatus,
208115
+ onResetState: resetGhostOsState,
208116
+ onRequestAccessibilityPermission: requestAccessibilityPermission,
208117
+ onDownloadModel: startDownload,
208118
+ },
207221
208119
  onAccountDeleted,
207222
208120
  enableMemoryTab,
207223
208121
  };
@@ -210441,8 +211339,8 @@ const CourseContentLayout = ({ courseId, isPlatformAdmin, currentTenant, tabHref
210441
211339
  }, children: children }) })] })] })] }) }));
210442
211340
  };
210443
211341
 
210444
- const CustomSwitch = React.forwardRef(({ className, checked, onCheckedChange, ...props }, ref) => (jsx(Root$9, { checked: checked, onCheckedChange: onCheckedChange, className: cn('peer focus-visible:ring-ring focus-visible:ring-offset-background inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-blue-500 data-[state=unchecked]:bg-gray-200', className), ...props, ref: ref, children: jsx(Thumb, { className: cn('pointer-events-none block h-5 w-5 rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0') }) })));
210445
- CustomSwitch.displayName = Root$9.displayName;
211342
+ const CustomSwitch = React.forwardRef(({ className, checked, onCheckedChange, ...props }, ref) => (jsx(Root$a, { checked: checked, onCheckedChange: onCheckedChange, className: cn('peer focus-visible:ring-ring focus-visible:ring-offset-background inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-blue-500 data-[state=unchecked]:bg-gray-200', className), ...props, ref: ref, children: jsx(Thumb, { className: cn('pointer-events-none block h-5 w-5 rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0') }) })));
211343
+ CustomSwitch.displayName = Root$a.displayName;
210446
211344
 
210447
211345
  /**
210448
211346
  * Label contract for the Settings tab and its two sub-modals.