@iblai/iblai-js 1.20.6 → 1.20.7

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';
@@ -100340,150 +100340,6 @@ var dist = Gravatar;
100340
100340
 
100341
100341
  var Gravatar$1 = /*@__PURE__*/getDefaultExportFromCjs(dist);
100342
100342
 
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
100343
  var DISMISSABLE_LAYER_NAME = "DismissableLayer";
100488
100344
  var CONTEXT_UPDATE = "dismissableLayer.update";
100489
100345
  var POINTER_DOWN_OUTSIDE = "dismissableLayer.pointerDownOutside";
@@ -100704,7 +100560,7 @@ var Arrow$1 = React.forwardRef((props, forwardedRef) => {
100704
100560
  );
100705
100561
  });
100706
100562
  Arrow$1.displayName = NAME$4;
100707
- var Root$8 = Arrow$1;
100563
+ var Root$9 = Arrow$1;
100708
100564
 
100709
100565
  var POPPER_NAME = "Popper";
100710
100566
  var [createPopperContext, createPopperScope] = createContextScope$1(POPPER_NAME);
@@ -100912,7 +100768,7 @@ var PopperArrow = React.forwardRef(function PopperArrow2(props, forwardedRef) {
100912
100768
  visibility: contentContext.shouldHideArrow ? "hidden" : void 0
100913
100769
  },
100914
100770
  children: /* @__PURE__ */ jsx(
100915
- Root$8,
100771
+ Root$9,
100916
100772
  {
100917
100773
  ...arrowProps,
100918
100774
  ref: forwardedRef,
@@ -101009,7 +100865,7 @@ var VisuallyHidden = React.forwardRef(
101009
100865
  }
101010
100866
  );
101011
100867
  VisuallyHidden.displayName = NAME$3;
101012
- var Root$7 = VisuallyHidden;
100868
+ var Root$8 = VisuallyHidden;
101013
100869
 
101014
100870
  var [createTooltipContext, createTooltipScope] = createContextScope$1("Tooltip", [
101015
100871
  createPopperScope
@@ -101342,7 +101198,7 @@ var TooltipContentImpl = React.forwardRef(
101342
101198
  },
101343
101199
  children: [
101344
101200
  /* @__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 }) })
101201
+ /* @__PURE__ */ jsx(VisuallyHiddenContentContextProvider, { scope: __scopeTooltip, isInside: true, children: /* @__PURE__ */ jsx(Root$8, { id: context.contentId, role: "tooltip", children: ariaLabel || children }) })
101346
101202
  ]
101347
101203
  }
101348
101204
  )
@@ -101499,6 +101355,150 @@ function TooltipContent({ className, sideOffset = 0, children, ...props }) {
101499
101355
  return (jsx(Portal$1, { children: jsxs(Content2$2, { "data-slot": "tooltip-content", sideOffset: sideOffset, className: cn("bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance", className), ...props, children: [children, jsx(Arrow2, { className: "bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" })] }) }));
101500
101356
  }
101501
101357
 
101358
+ var SWITCH_NAME = "Switch";
101359
+ var [createSwitchContext, createSwitchScope] = createContextScope$1(SWITCH_NAME);
101360
+ var [SwitchProvider, useSwitchContext] = createSwitchContext(SWITCH_NAME);
101361
+ var Switch$1 = React.forwardRef(
101362
+ (props, forwardedRef) => {
101363
+ const {
101364
+ __scopeSwitch,
101365
+ name,
101366
+ checked: checkedProp,
101367
+ defaultChecked,
101368
+ required,
101369
+ disabled,
101370
+ value = "on",
101371
+ onCheckedChange,
101372
+ form,
101373
+ ...switchProps
101374
+ } = props;
101375
+ const [button, setButton] = React.useState(null);
101376
+ const composedRefs = useComposedRefs$1(forwardedRef, (node) => setButton(node));
101377
+ const hasConsumerStoppedPropagationRef = React.useRef(false);
101378
+ const isFormControl = button ? form || !!button.closest("form") : true;
101379
+ const [checked, setChecked] = useControllableState$1({
101380
+ prop: checkedProp,
101381
+ defaultProp: defaultChecked ?? false,
101382
+ onChange: onCheckedChange,
101383
+ caller: SWITCH_NAME
101384
+ });
101385
+ return /* @__PURE__ */ jsxs(SwitchProvider, { scope: __scopeSwitch, checked, disabled, children: [
101386
+ /* @__PURE__ */ jsx(
101387
+ Primitive$2.button,
101388
+ {
101389
+ type: "button",
101390
+ role: "switch",
101391
+ "aria-checked": checked,
101392
+ "aria-required": required,
101393
+ "data-state": getState$2(checked),
101394
+ "data-disabled": disabled ? "" : void 0,
101395
+ disabled,
101396
+ value,
101397
+ ...switchProps,
101398
+ ref: composedRefs,
101399
+ onClick: composeEventHandlers$1(props.onClick, (event) => {
101400
+ setChecked((prevChecked) => !prevChecked);
101401
+ if (isFormControl) {
101402
+ hasConsumerStoppedPropagationRef.current = event.isPropagationStopped();
101403
+ if (!hasConsumerStoppedPropagationRef.current) event.stopPropagation();
101404
+ }
101405
+ })
101406
+ }
101407
+ ),
101408
+ isFormControl && /* @__PURE__ */ jsx(
101409
+ SwitchBubbleInput,
101410
+ {
101411
+ control: button,
101412
+ bubbles: !hasConsumerStoppedPropagationRef.current,
101413
+ name,
101414
+ value,
101415
+ checked,
101416
+ required,
101417
+ disabled,
101418
+ form,
101419
+ style: { transform: "translateX(-100%)" }
101420
+ }
101421
+ )
101422
+ ] });
101423
+ }
101424
+ );
101425
+ Switch$1.displayName = SWITCH_NAME;
101426
+ var THUMB_NAME$1 = "SwitchThumb";
101427
+ var SwitchThumb = React.forwardRef(
101428
+ (props, forwardedRef) => {
101429
+ const { __scopeSwitch, ...thumbProps } = props;
101430
+ const context = useSwitchContext(THUMB_NAME$1, __scopeSwitch);
101431
+ return /* @__PURE__ */ jsx(
101432
+ Primitive$2.span,
101433
+ {
101434
+ "data-state": getState$2(context.checked),
101435
+ "data-disabled": context.disabled ? "" : void 0,
101436
+ ...thumbProps,
101437
+ ref: forwardedRef
101438
+ }
101439
+ );
101440
+ }
101441
+ );
101442
+ SwitchThumb.displayName = THUMB_NAME$1;
101443
+ var BUBBLE_INPUT_NAME = "SwitchBubbleInput";
101444
+ var SwitchBubbleInput = React.forwardRef(
101445
+ ({
101446
+ __scopeSwitch,
101447
+ control,
101448
+ checked,
101449
+ bubbles = true,
101450
+ ...props
101451
+ }, forwardedRef) => {
101452
+ const ref = React.useRef(null);
101453
+ const composedRefs = useComposedRefs$1(ref, forwardedRef);
101454
+ const prevChecked = usePrevious$1(checked);
101455
+ const controlSize = useSize$1(control);
101456
+ React.useEffect(() => {
101457
+ const input = ref.current;
101458
+ if (!input) return;
101459
+ const inputProto = window.HTMLInputElement.prototype;
101460
+ const descriptor = Object.getOwnPropertyDescriptor(
101461
+ inputProto,
101462
+ "checked"
101463
+ );
101464
+ const setChecked = descriptor.set;
101465
+ if (prevChecked !== checked && setChecked) {
101466
+ const event = new Event("click", { bubbles });
101467
+ setChecked.call(input, checked);
101468
+ input.dispatchEvent(event);
101469
+ }
101470
+ }, [prevChecked, checked, bubbles]);
101471
+ return /* @__PURE__ */ jsx(
101472
+ "input",
101473
+ {
101474
+ type: "checkbox",
101475
+ "aria-hidden": true,
101476
+ defaultChecked: checked,
101477
+ ...props,
101478
+ tabIndex: -1,
101479
+ ref: composedRefs,
101480
+ style: {
101481
+ ...props.style,
101482
+ ...controlSize,
101483
+ position: "absolute",
101484
+ pointerEvents: "none",
101485
+ opacity: 0,
101486
+ margin: 0
101487
+ }
101488
+ }
101489
+ );
101490
+ }
101491
+ );
101492
+ SwitchBubbleInput.displayName = BUBBLE_INPUT_NAME;
101493
+ function getState$2(checked) {
101494
+ return checked ? "checked" : "unchecked";
101495
+ }
101496
+ var Root$7 = Switch$1;
101497
+ var Thumb = SwitchThumb;
101498
+
101499
+ const Switch = React.forwardRef(({ className, ...props }, ref) => (jsx(Root$7, { 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") }) })));
101500
+ Switch.displayName = Root$7.displayName;
101501
+
101502
101502
  // packages/react/context/src/createContext.tsx
101503
101503
  function createContextScope(scopeName, createContextScopeDeps = []) {
101504
101504
  let defaultContexts = [];
@@ -101792,7 +101792,491 @@ var Indicator$1 = ProgressIndicator;
101792
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
101793
  Progress.displayName = Root$6.displayName;
101794
101794
 
101795
+ var SLOTTABLE_IDENTIFIER = Symbol("radix.slottable");
101796
+ // @__NO_SIDE_EFFECTS__
101797
+ function createSlottable(ownerName) {
101798
+ const Slottable2 = ({ children }) => {
101799
+ return /* @__PURE__ */ jsx(Fragment$1, { children });
101800
+ };
101801
+ Slottable2.displayName = `${ownerName}.Slottable`;
101802
+ Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
101803
+ return Slottable2;
101804
+ }
101805
+
101806
+ var ROOT_NAME = "AlertDialog";
101807
+ var [createAlertDialogContext, createAlertDialogScope] = createContextScope$1(ROOT_NAME, [
101808
+ createDialogScope
101809
+ ]);
101810
+ var useDialogScope = createDialogScope();
101811
+ var AlertDialog$1 = (props) => {
101812
+ const { __scopeAlertDialog, ...alertDialogProps } = props;
101813
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101814
+ return /* @__PURE__ */ jsx(DialogPrimitive.Root, { ...dialogScope, ...alertDialogProps, modal: true });
101815
+ };
101816
+ AlertDialog$1.displayName = ROOT_NAME;
101817
+ var TRIGGER_NAME$2 = "AlertDialogTrigger";
101818
+ var AlertDialogTrigger = React.forwardRef(
101819
+ (props, forwardedRef) => {
101820
+ const { __scopeAlertDialog, ...triggerProps } = props;
101821
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101822
+ return /* @__PURE__ */ jsx(DialogPrimitive.Trigger, { ...dialogScope, ...triggerProps, ref: forwardedRef });
101823
+ }
101824
+ );
101825
+ AlertDialogTrigger.displayName = TRIGGER_NAME$2;
101826
+ var PORTAL_NAME$1 = "AlertDialogPortal";
101827
+ var AlertDialogPortal$1 = (props) => {
101828
+ const { __scopeAlertDialog, ...portalProps } = props;
101829
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101830
+ return /* @__PURE__ */ jsx(DialogPrimitive.Portal, { ...dialogScope, ...portalProps });
101831
+ };
101832
+ AlertDialogPortal$1.displayName = PORTAL_NAME$1;
101833
+ var OVERLAY_NAME = "AlertDialogOverlay";
101834
+ var AlertDialogOverlay$1 = React.forwardRef(
101835
+ (props, forwardedRef) => {
101836
+ const { __scopeAlertDialog, ...overlayProps } = props;
101837
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101838
+ return /* @__PURE__ */ jsx(DialogPrimitive.Overlay, { ...dialogScope, ...overlayProps, ref: forwardedRef });
101839
+ }
101840
+ );
101841
+ AlertDialogOverlay$1.displayName = OVERLAY_NAME;
101842
+ var CONTENT_NAME$2 = "AlertDialogContent";
101843
+ var [AlertDialogContentProvider, useAlertDialogContentContext] = createAlertDialogContext(CONTENT_NAME$2);
101844
+ var Slottable = createSlottable("AlertDialogContent");
101845
+ var AlertDialogContent$1 = React.forwardRef(
101846
+ (props, forwardedRef) => {
101847
+ const { __scopeAlertDialog, children, ...contentProps } = props;
101848
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101849
+ const contentRef = React.useRef(null);
101850
+ const composedRefs = useComposedRefs$1(forwardedRef, contentRef);
101851
+ const cancelRef = React.useRef(null);
101852
+ return /* @__PURE__ */ jsx(
101853
+ DialogPrimitive.WarningProvider,
101854
+ {
101855
+ contentName: CONTENT_NAME$2,
101856
+ titleName: TITLE_NAME,
101857
+ docsSlug: "alert-dialog",
101858
+ children: /* @__PURE__ */ jsx(AlertDialogContentProvider, { scope: __scopeAlertDialog, cancelRef, children: /* @__PURE__ */ jsxs(
101859
+ DialogPrimitive.Content,
101860
+ {
101861
+ role: "alertdialog",
101862
+ ...dialogScope,
101863
+ ...contentProps,
101864
+ ref: composedRefs,
101865
+ onOpenAutoFocus: composeEventHandlers$2(contentProps.onOpenAutoFocus, (event) => {
101866
+ event.preventDefault();
101867
+ cancelRef.current?.focus({ preventScroll: true });
101868
+ }),
101869
+ onPointerDownOutside: (event) => event.preventDefault(),
101870
+ onInteractOutside: (event) => event.preventDefault(),
101871
+ children: [
101872
+ /* @__PURE__ */ jsx(Slottable, { children }),
101873
+ /* @__PURE__ */ jsx(DescriptionWarning, { contentRef })
101874
+ ]
101875
+ }
101876
+ ) })
101877
+ }
101878
+ );
101879
+ }
101880
+ );
101881
+ AlertDialogContent$1.displayName = CONTENT_NAME$2;
101882
+ var TITLE_NAME = "AlertDialogTitle";
101883
+ var AlertDialogTitle$1 = React.forwardRef(
101884
+ (props, forwardedRef) => {
101885
+ const { __scopeAlertDialog, ...titleProps } = props;
101886
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101887
+ return /* @__PURE__ */ jsx(DialogPrimitive.Title, { ...dialogScope, ...titleProps, ref: forwardedRef });
101888
+ }
101889
+ );
101890
+ AlertDialogTitle$1.displayName = TITLE_NAME;
101891
+ var DESCRIPTION_NAME = "AlertDialogDescription";
101892
+ var AlertDialogDescription$1 = React.forwardRef((props, forwardedRef) => {
101893
+ const { __scopeAlertDialog, ...descriptionProps } = props;
101894
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101895
+ return /* @__PURE__ */ jsx(DialogPrimitive.Description, { ...dialogScope, ...descriptionProps, ref: forwardedRef });
101896
+ });
101897
+ AlertDialogDescription$1.displayName = DESCRIPTION_NAME;
101898
+ var ACTION_NAME = "AlertDialogAction";
101899
+ var AlertDialogAction$1 = React.forwardRef(
101900
+ (props, forwardedRef) => {
101901
+ const { __scopeAlertDialog, ...actionProps } = props;
101902
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101903
+ return /* @__PURE__ */ jsx(DialogPrimitive.Close, { ...dialogScope, ...actionProps, ref: forwardedRef });
101904
+ }
101905
+ );
101906
+ AlertDialogAction$1.displayName = ACTION_NAME;
101907
+ var CANCEL_NAME = "AlertDialogCancel";
101908
+ var AlertDialogCancel$1 = React.forwardRef(
101909
+ (props, forwardedRef) => {
101910
+ const { __scopeAlertDialog, ...cancelProps } = props;
101911
+ const { cancelRef } = useAlertDialogContentContext(CANCEL_NAME, __scopeAlertDialog);
101912
+ const dialogScope = useDialogScope(__scopeAlertDialog);
101913
+ const ref = useComposedRefs$1(forwardedRef, cancelRef);
101914
+ return /* @__PURE__ */ jsx(DialogPrimitive.Close, { ...dialogScope, ...cancelProps, ref });
101915
+ }
101916
+ );
101917
+ AlertDialogCancel$1.displayName = CANCEL_NAME;
101918
+ var DescriptionWarning = ({ contentRef }) => {
101919
+ const MESSAGE = `\`${CONTENT_NAME$2}\` requires a description for the component to be accessible for screen reader users.
101920
+
101921
+ 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.
101922
+
101923
+ 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.
101924
+
101925
+ For more information, see https://radix-ui.com/primitives/docs/components/alert-dialog`;
101926
+ React.useEffect(() => {
101927
+ const hasDescription = document.getElementById(
101928
+ contentRef.current?.getAttribute("aria-describedby")
101929
+ );
101930
+ if (!hasDescription) console.warn(MESSAGE);
101931
+ }, [MESSAGE, contentRef]);
101932
+ return null;
101933
+ };
101934
+ var Root2$3 = AlertDialog$1;
101935
+ var Portal2 = AlertDialogPortal$1;
101936
+ var Overlay2 = AlertDialogOverlay$1;
101937
+ var Content2$1 = AlertDialogContent$1;
101938
+ var Action = AlertDialogAction$1;
101939
+ var Cancel = AlertDialogCancel$1;
101940
+ var Title2 = AlertDialogTitle$1;
101941
+ var Description2 = AlertDialogDescription$1;
101942
+
101943
+ const AlertDialog = Root2$3;
101944
+ const AlertDialogPortal = Portal2;
101945
+ 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 })));
101946
+ AlertDialogOverlay.displayName = Overlay2.displayName;
101947
+ 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 })] })));
101948
+ AlertDialogContent.displayName = Content2$1.displayName;
101949
+ const AlertDialogHeader = ({ className, ...props }) => (jsx("div", { className: cn('flex flex-col space-y-2 text-center sm:text-left', className), ...props }));
101950
+ AlertDialogHeader.displayName = 'AlertDialogHeader';
101951
+ const AlertDialogFooter = ({ className, ...props }) => (jsx("div", { className: cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className), ...props }));
101952
+ AlertDialogFooter.displayName = 'AlertDialogFooter';
101953
+ const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => (jsx(Title2, { ref: ref, className: cn('text-lg font-semibold', className), ...props })));
101954
+ AlertDialogTitle.displayName = Title2.displayName;
101955
+ const AlertDialogDescription = React.forwardRef(({ className, ...props }, ref) => (jsx(Description2, { ref: ref, className: cn('text-muted-foreground text-sm', className), ...props })));
101956
+ AlertDialogDescription.displayName = Description2.displayName;
101957
+ const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => (jsx(Action, { ref: ref, className: cn(buttonVariants(), className), ...props })));
101958
+ AlertDialogAction.displayName = Action.displayName;
101959
+ const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => (jsx(Cancel, { ref: ref, className: cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className), ...props })));
101960
+ AlertDialogCancel.displayName = Cancel.displayName;
101961
+
101962
+ // Tauri types for model download functionality
101963
+ /**
101964
+ * Initial state for model download
101965
+ */
101966
+ const initialModelDownloadState = {
101967
+ status: 'idle',
101968
+ progress: 0,
101969
+ message: '',
101970
+ logs: [],
101971
+ lastUpdated: new Date().toISOString(),
101972
+ };
101973
+ /**
101974
+ * Check if the app is running inside Tauri
101975
+ * In Tauri 2.0, the global is __TAURI_INTERNALS__ (with withGlobalTauri: true)
101976
+ * We also check for __TAURI__ for backwards compatibility
101977
+ */
101978
+ const isTauriApp$1 = () => {
101979
+ if (typeof window === 'undefined')
101980
+ return false;
101981
+ return '__TAURI_INTERNALS__' in window || '__TAURI__' in window;
101982
+ };
101983
+ /**
101984
+ * Tauri event names
101985
+ */
101986
+ const TAURI_EVENTS = {
101987
+ DOWNLOAD_PROGRESS: 'model:download-progress',
101988
+ INSTALLATION_LOG: 'model:installation-log',
101989
+ DISK_SPACE_ERROR: 'model:disk-space-error',
101990
+ OLLAMA_STATUS: 'model:ollama-status',
101991
+ };
101992
+ /**
101993
+ * Tauri command names
101994
+ */
101995
+ const TAURI_COMMANDS = {
101996
+ INSTALL_OLLAMA: 'install_ollama',
101997
+ STOP_OLLAMA: 'stop_ollama',
101998
+ CHECK_OLLAMA_STATUS: 'check_ollama_status',
101999
+ CHECK_DISK_SPACE: 'check_disk_space_for_model',
102000
+ GET_SYSTEM_MEMORY: 'get_system_memory',
102001
+ DOWNLOAD_MODEL: 'download_model',
102002
+ CANCEL_DOWNLOAD: 'cancel_model_download',
102003
+ CHECK_NETWORK_STATUS: 'check_network_status',
102004
+ GET_OS_TYPE: 'get_os_type',
102005
+ CHECK_FOUNDRY_STATUS: 'check_foundry_local_status',
102006
+ START_FOUNDRY_SERVICE: 'start_foundry_local_service',
102007
+ LOAD_FOUNDRY_MODEL: 'load_foundry_local_model',
102008
+ SET_SELECTED_FOUNDRY_MODEL: 'set_selected_foundry_model',
102009
+ GET_SELECTED_FOUNDRY_MODEL: 'get_selected_foundry_model',
102010
+ INSTALL_FOUNDRY: 'install_foundry',
102011
+ DOWNLOAD_FOUNDRY_MODEL: 'download_foundry_model_cmd',
102012
+ GET_RECOMMENDED_FOUNDRY_MODELS: 'get_recommended_foundry_models',
102013
+ LOG_STDOUT: 'log_stdout',
102014
+ };
102015
+ /**
102016
+ * Initial state for the System Control install flow.
102017
+ */
102018
+ const initialGhostOsInstallState = {
102019
+ status: 'idle',
102020
+ progress: 0,
102021
+ message: '',
102022
+ logs: [],
102023
+ lastUpdated: new Date().toISOString(),
102024
+ };
102025
+ /**
102026
+ * Tauri event names emitted by the host during GhostOS install/setup.
102027
+ */
102028
+ const GHOST_OS_TAURI_EVENTS = {
102029
+ INSTALL_PROGRESS: 'ghost-os:install-progress',
102030
+ INSTALLATION_LOG: 'ghost-os:installation-log',
102031
+ STATUS: 'ghost-os:status',
102032
+ };
102033
+ /**
102034
+ * Tauri command names the host implements for GhostOS install/setup. The web
102035
+ * layer invokes these; the native side performs the clone + setup.
102036
+ */
102037
+ const GHOST_OS_TAURI_COMMANDS = {
102038
+ /** Install and set up GhostOS (clone repo + run setup). */
102039
+ INSTALL_GHOST_OS: 'install_ghost_os',
102040
+ /** Stop the running GhostOS manager. */
102041
+ STOP_GHOST_OS: 'stop_ghost_os',
102042
+ /** Report current {@link GhostOsStatus}. */
102043
+ CHECK_GHOST_OS_STATUS: 'check_ghost_os_status',
102044
+ };
102045
+ /**
102046
+ * Tauri command strings exposed by `tauri-plugin-macos-permissions`
102047
+ * (https://github.com/ayangweb/tauri-plugin-macos-permissions). GhostOS needs
102048
+ * macOS Accessibility permission to control the device, so the System Control
102049
+ * card surfaces it. These are invoked directly via `useTauri().invoke(...)` —
102050
+ * no extra npm dependency. The host must register the plugin in its Tauri app
102051
+ * (`tauri_plugin_macos_permissions::init()`). Each `CHECK_*` returns a boolean;
102052
+ * each `REQUEST_*` prompts / opens the relevant System Settings pane.
102053
+ */
102054
+ const MACOS_PERMISSIONS_COMMANDS = {
102055
+ CHECK_ACCESSIBILITY: 'plugin:macos-permissions|check_accessibility_permission',
102056
+ REQUEST_ACCESSIBILITY: 'plugin:macos-permissions|request_accessibility_permission',
102057
+ CHECK_SCREEN_RECORDING: 'plugin:macos-permissions|check_screen_recording_permission',
102058
+ REQUEST_SCREEN_RECORDING: 'plugin:macos-permissions|request_screen_recording_permission',
102059
+ CHECK_INPUT_MONITORING: 'plugin:macos-permissions|check_input_monitoring_permission',
102060
+ REQUEST_INPUT_MONITORING: 'plugin:macos-permissions|request_input_monitoring_permission',
102061
+ CHECK_FULL_DISK_ACCESS: 'plugin:macos-permissions|check_full_disk_access_permission',
102062
+ REQUEST_FULL_DISK_ACCESS: 'plugin:macos-permissions|request_full_disk_access_permission',
102063
+ };
102064
+
102065
+ /**
102066
+ * Dynamically import Tauri APIs to avoid SSR issues
102067
+ */
102068
+ const getTauriAPIs = async () => {
102069
+ try {
102070
+ const { invoke } = await import('@tauri-apps/api/core');
102071
+ const { listen } = await import('@tauri-apps/api/event');
102072
+ return { invoke, listen };
102073
+ }
102074
+ catch (error) {
102075
+ console.error('Failed to load Tauri APIs:', error);
102076
+ return null;
102077
+ }
102078
+ };
102079
+ /**
102080
+ * Hook to access Tauri APIs with SSR safety
102081
+ *
102082
+ * @returns Object with isAvailable flag and Tauri invoke/listen functions
102083
+ */
102084
+ function useTauri() {
102085
+ const [isAvailable, setIsAvailable] = useState(false);
102086
+ const [apis, setApis] = useState(null);
102087
+ useEffect(() => {
102088
+ // Check if we're in a Tauri environment
102089
+ const inTauri = isTauriApp$1();
102090
+ if (inTauri) {
102091
+ getTauriAPIs().then((result) => {
102092
+ setApis(result);
102093
+ setIsAvailable(!!result);
102094
+ });
102095
+ }
102096
+ }, []);
102097
+ /**
102098
+ * Invoke a Tauri command
102099
+ */
102100
+ const invoke = useCallback(async (command, args) => {
102101
+ if (!(apis === null || apis === void 0 ? void 0 : apis.invoke)) {
102102
+ throw new Error('Tauri is not available');
102103
+ }
102104
+ return apis.invoke(command, args);
102105
+ }, [apis]);
102106
+ /**
102107
+ * Listen to a Tauri event
102108
+ * Returns an unlisten function that should be called on cleanup
102109
+ */
102110
+ const listen = useCallback(async (event, handler) => {
102111
+ // The synchronous global (`__TAURI_INTERNALS__`) exposes `invoke` but
102112
+ // frequently NOT `event.listen`, so `apis.listen` can be undefined even
102113
+ // though Tauri is fully available. Fall back to the real event API via
102114
+ // dynamic import so download progress/completion events are actually
102115
+ // delivered — otherwise the UI never leaves the "downloading" state.
102116
+ let listenFn = apis === null || apis === void 0 ? void 0 : apis.listen;
102117
+ if (!listenFn) {
102118
+ const { listen: importedListen } = await import('@tauri-apps/api/event');
102119
+ listenFn = importedListen;
102120
+ }
102121
+ return listenFn(event, (e) => handler(e.payload));
102122
+ }, [apis]);
102123
+ return {
102124
+ isAvailable,
102125
+ invoke,
102126
+ listen,
102127
+ };
102128
+ }
102129
+
101795
102130
  const LOCAL_LLM_ENABLED_KEY = 'ibl_local_llm_enabled';
102131
+ const LOCAL_LLM_MODEL_KEY = 'ibl_local_llm_model';
102132
+ const LOCAL_LLM_TOOL_SUPPORT_KEY = 'ibl_local_llm_tool_support';
102133
+ /**
102134
+ * Curated catalog of small, on-device-friendly models from the Ollama library
102135
+ * (https://ollama.com/library). The model manager itself (Ollama) is not a
102136
+ * model and is intentionally absent — enabling Local Models installs it.
102137
+ */
102138
+ // `tool_support` reflects whether the model advertises Tools / function calling
102139
+ // on its Ollama library page (ollama.com/library/<model>), used by the host to
102140
+ // route to the MCP/tool proxy on :8000 vs plain Ollama on :11434. Verified Jun
102141
+ // 2026 from the per-model capability badges; adjust as Ollama support changes.
102142
+ // Only tool / function-calling capable models are listed (the local manager is
102143
+ // for tool-using chat). `tool_support` is kept on the type/contract — persisted
102144
+ // on select and read by the host to route to the MCP/tool proxy (:8000).
102145
+ const LOCAL_MODELS = [
102146
+ { name: 'Phi-4 Mini', provider: 'Microsoft', id: 'phi4-mini:latest', size: '2.5 GB', tool_support: true },
102147
+ { name: 'Llama 3.2', provider: 'Meta', id: 'llama3.2', size: '2.0 GB', tool_support: true },
102148
+ { name: 'Qwen 3', provider: 'Alibaba', id: 'qwen3', size: '5.2 GB', tool_support: true },
102149
+ { name: 'Mistral', provider: 'Mistral AI', id: 'mistral', size: '4.4 GB', tool_support: true },
102150
+ { name: 'DeepSeek-R1', provider: 'DeepSeek', id: 'deepseek-r1', size: '5.2 GB', tool_support: true },
102151
+ { name: 'Granite 4.1', provider: 'IBM', id: 'granite4.1:8b', size: '5.3 GB', tool_support: true },
102152
+ { name: 'GPT-OSS 20B', provider: 'OpenAI', id: 'gpt-oss:20b', size: '14 GB', tool_support: true },
102153
+ // Larger models. Sizes use "GB" so they parse for the System Control size gate
102154
+ // (only models above the configured gate can use System Control).
102155
+ { name: 'Qwen 3.6', provider: 'Alibaba', id: 'qwen3.6:latest', size: '24 GB', tool_support: true },
102156
+ { name: 'Gemma 4 31B', provider: 'Google', id: 'gemma4:31b', size: '20 GB', tool_support: true },
102157
+ { name: 'Nemotron 3 33B', provider: 'NVIDIA', id: 'nemotron3:33b', size: '28 GB', tool_support: true },
102158
+ { name: 'Nemotron 3 Super 120B', provider: 'NVIDIA', id: 'nemotron-3-super:120b', size: '87 GB', tool_support: true },
102159
+ ];
102160
+ /** The model wired to the existing single-model download flow (default selection). */
102161
+ const PRIMARY_MODEL_ID = 'phi4-mini:latest';
102162
+ /**
102163
+ * Whether a catalog model id (e.g. "llama3.2", "phi3:mini") is present among the
102164
+ * installed Ollama tags reported by the backend (e.g. "llama3.2:latest"). Matches
102165
+ * an exact tag or the same base model with any tag suffix, so a model pulled as
102166
+ * `<id>:latest` still counts as installed.
102167
+ */
102168
+ function isModelInstalled(modelId, installedTags) {
102169
+ if (!installedTags)
102170
+ return false;
102171
+ return installedTags.some((tag) => tag === modelId || tag.startsWith(`${modelId}:`));
102172
+ }
102173
+ /**
102174
+ * Whether a model id is an Ollama cloud-hosted model — either the plain ":cloud"
102175
+ * tag (e.g. "glm-5.2:cloud") or a sized cloud tag (e.g. "gemma4:31b-cloud").
102176
+ * Cloud models run on Ollama's servers, so they are available by default — never
102177
+ * downloaded, no local memory required, always "ready".
102178
+ */
102179
+ function isCloudModelId(modelId) {
102180
+ return (modelId.endsWith(':cloud') ||
102181
+ (modelId.includes(':') && modelId.endsWith('-cloud')));
102182
+ }
102183
+ /**
102184
+ * Fraction of the machine's memory above which a model is treated as "too large"
102185
+ * and a confirmation is required before downloading. TESTING VALUE — set low (1%)
102186
+ * so the warning is easy to trigger; raise toward real capacity (e.g. 0.8) later.
102187
+ */
102188
+ const MODEL_SIZE_WARN_FRACTION = 0.01;
102189
+ /** Multipliers for the size units used in the catalog (binary / 1024-based). */
102190
+ const SIZE_UNIT_BYTES = {
102191
+ B: 1,
102192
+ KB: 1024,
102193
+ MB: 1024 ** 2,
102194
+ GB: 1024 ** 3,
102195
+ TB: 1024 ** 4,
102196
+ };
102197
+ /**
102198
+ * Parse a human catalog size like "2.2 GB" into bytes, or null if unrecognized.
102199
+ */
102200
+ function parseModelSizeBytes(size) {
102201
+ var _a;
102202
+ const match = /([\d.]+)\s*(TB|GB|MB|KB|B)/i.exec(size);
102203
+ if (!match)
102204
+ return null;
102205
+ const value = parseFloat(match[1]);
102206
+ if (Number.isNaN(value))
102207
+ return null;
102208
+ return value * ((_a = SIZE_UNIT_BYTES[match[2].toUpperCase()]) !== null && _a !== void 0 ? _a : 1);
102209
+ }
102210
+ /** Format a byte count as a short, human-readable size (e.g. "16.0 GB"). */
102211
+ function formatBytes(bytes) {
102212
+ if (bytes <= 0)
102213
+ return '0 GB';
102214
+ const gb = bytes / 1024 ** 3;
102215
+ if (gb >= 1)
102216
+ return `${gb.toFixed(1)} GB`;
102217
+ return `${Math.round(bytes / 1024 ** 2)} MB`;
102218
+ }
102219
+ /**
102220
+ * The memory a model can use on this machine: the larger of system RAM and GPU
102221
+ * VRAM (a model runs in one or the other). Returns 0 when memory is unknown.
102222
+ */
102223
+ function usableMemoryBytes(systemMemory) {
102224
+ if (!systemMemory)
102225
+ return 0;
102226
+ return Math.max(systemMemory.ram_total, systemMemory.vram_total);
102227
+ }
102228
+ /**
102229
+ * Whether `model` is large enough relative to this machine's memory to warrant a
102230
+ * "might not run" confirmation. False when memory or the model size is unknown,
102231
+ * so the download simply proceeds rather than warning on incomplete data.
102232
+ */
102233
+ function modelExceedsCapacity(model, systemMemory) {
102234
+ const capacity = usableMemoryBytes(systemMemory);
102235
+ const modelBytes = parseModelSizeBytes(model.size);
102236
+ if (modelBytes == null)
102237
+ return false;
102238
+ return modelBytes > capacity * MODEL_SIZE_WARN_FRACTION;
102239
+ }
102240
+ /**
102241
+ * Default minimum model size (GB) that can use the System Control Manager
102242
+ * (GhostOS MCP/tools). Smaller models don't support tools/MCP well, so the
102243
+ * manager is gated to models above this size. Per-app overridable via the
102244
+ * System Control card's `requiredSizeGb` prop (see SystemControlTabProps).
102245
+ */
102246
+ const DEFAULT_SYSTEM_CONTROL_REQUIRED_SIZE_GB = 12;
102247
+ /**
102248
+ * Whether the given model (by Ollama id) is large enough to use the System
102249
+ * Control Manager — i.e. its catalog size exceeds `gb` (default
102250
+ * {@link DEFAULT_SYSTEM_CONTROL_REQUIRED_SIZE_GB}). Unknown ids or unparseable
102251
+ * sizes return false (gated off).
102252
+ */
102253
+ function modelSupportsSystemControl(modelId, gb = DEFAULT_SYSTEM_CONTROL_REQUIRED_SIZE_GB) {
102254
+ if (!modelId)
102255
+ return false;
102256
+ const model = LOCAL_MODELS.find((m) => m.id === modelId);
102257
+ if (!model)
102258
+ return false;
102259
+ const bytes = parseModelSizeBytes(model.size);
102260
+ if (bytes == null)
102261
+ return false;
102262
+ return bytes > gb * 1024 ** 3;
102263
+ }
102264
+ /**
102265
+ * The smallest catalog model that can use the System Control Manager (size
102266
+ * exceeds `gb`), or null if none qualify. Used by the "Upgrade" action to pick
102267
+ * the lightest capable model. Ties / unparseable sizes sort last.
102268
+ */
102269
+ function smallestSystemControlModel(gb = DEFAULT_SYSTEM_CONTROL_REQUIRED_SIZE_GB) {
102270
+ const usable = LOCAL_MODELS.filter((m) => modelSupportsSystemControl(m.id, gb));
102271
+ if (usable.length === 0)
102272
+ return null;
102273
+ return usable.reduce((smallest, m) => {
102274
+ var _a, _b;
102275
+ const a = (_a = parseModelSizeBytes(m.size)) !== null && _a !== void 0 ? _a : Infinity;
102276
+ const b = (_b = parseModelSizeBytes(smallest.size)) !== null && _b !== void 0 ? _b : Infinity;
102277
+ return a < b ? m : smallest;
102278
+ });
102279
+ }
101796
102280
  /**
101797
102281
  * Check if local LLM is enabled from localStorage
101798
102282
  */
@@ -101809,13 +102293,64 @@ function setLocalLLMEnabled(enabled) {
101809
102293
  return;
101810
102294
  localStorage.setItem(LOCAL_LLM_ENABLED_KEY, String(enabled));
101811
102295
  }
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;
102296
+ /**
102297
+ * Get the model the user has chosen to chat with (Ollama pull id), or null.
102298
+ */
102299
+ function getLocalLLMModel() {
102300
+ if (typeof window === 'undefined')
102301
+ return null;
102302
+ return localStorage.getItem(LOCAL_LLM_MODEL_KEY);
102303
+ }
102304
+ /**
102305
+ * Set the model the user has chosen to chat with (Ollama pull id).
102306
+ */
102307
+ function setLocalLLMModel(modelId) {
102308
+ if (typeof window === 'undefined')
102309
+ return;
102310
+ localStorage.setItem(LOCAL_LLM_MODEL_KEY, modelId);
102311
+ }
102312
+ /**
102313
+ * Persist whether the selected model supports tools (set when a model is chosen).
102314
+ */
102315
+ function setLocalLLMToolSupport(toolSupport) {
102316
+ if (typeof window === 'undefined')
102317
+ return;
102318
+ localStorage.setItem(LOCAL_LLM_TOOL_SUPPORT_KEY, String(toolSupport));
102319
+ }
102320
+ function LocalLLMTab({ isAvailable, state, ollamaStatus, systemMemory, isUsingFoundry = false, foundryModels = [], selectedFoundryModel = null, foundryStatus, onStartDownload, onCancelDownload, onInstallOllama, onStopManager, onInstallFoundry, onCheckStatus, onResetState, onSelectFoundryModel, }) {
102321
+ var _a, _b, _c, _d, _e, _f, _g;
102322
+ // Direct Tauri access so the model-size check can read system memory fresh at
102323
+ // download-click time (the threaded `systemMemory` prop is only a fast path).
102324
+ const { invoke } = useTauri();
101814
102325
  const isDownloading = state.status === 'downloading';
101815
- const isModelReady = state.status === 'completed' || (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed);
101816
102326
  const isManagerInstalling = state.managerInstalling || (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installing);
101817
102327
  // User preference state - independent of whether model is installed
101818
102328
  const [isEnabled, setIsEnabled] = useState(() => isLocalLLMEnabled());
102329
+ // Which model the shared download flow currently targets. Prefer the model
102330
+ // recorded in the persisted download state, so the correct row shows progress
102331
+ // even after the dialog is closed and reopened mid-download; fall back to a
102332
+ // local value for immediate feedback the instant a Download button is clicked.
102333
+ const [localActiveModelId, setLocalActiveModelId] = useState(PRIMARY_MODEL_ID);
102334
+ const activeModelId = state.status === 'downloading' && state.activeModel ? state.activeModel : localActiveModelId;
102335
+ // Which model the user has chosen to chat with (exclusive selection).
102336
+ const [selectedModelId, setSelectedModelId] = useState(() => getLocalLLMModel() || PRIMARY_MODEL_ID);
102337
+ // A model the user asked to download that is large relative to this machine's
102338
+ // memory. Set when we need the user to confirm a "might not run" warning before
102339
+ // starting the download; null when no confirmation is pending.
102340
+ const [pendingModel, setPendingModel] = useState(null);
102341
+ // "Enable Local Models" === run the model manager (Ollama). The toggle is a
102342
+ // user preference that is always operable: turning it on installs (if needed)
102343
+ // and starts the manager; turning it off stops it. The card + model table are
102344
+ // shown whenever the toggle is on (or mid-install), independent of install
102345
+ // state, so the user always gets feedback for their choice.
102346
+ const managerRunning = (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.running) === true;
102347
+ const localModelsEnabled = isEnabled || isManagerInstalling;
102348
+ // A model can be chosen for chat only once it is downloaded/ready. The backend
102349
+ // reports every installed model tag, so any downloaded model qualifies.
102350
+ const selectedModelReady = isCloudModelId(selectedModelId) ||
102351
+ isModelInstalled(selectedModelId, ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed_models) ||
102352
+ (selectedModelId === PRIMARY_MODEL_ID &&
102353
+ (state.status === 'completed' || (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed) === true));
101819
102354
  // Track previous model installation state to detect when it first becomes installed
101820
102355
  const [, setWasModelInstalled] = useState(() => {
101821
102356
  return (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed) === true;
@@ -101834,21 +102369,35 @@ function LocalLLMTab({ isAvailable, state, ollamaStatus, isUsingFoundry = false,
101834
102369
  const isNowInstalled = (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed) === true;
101835
102370
  setWasModelInstalled(isNowInstalled);
101836
102371
  }, [ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed]);
101837
- // Sync localStorage when user preference changes
102372
+ // Sync the app-facing "local LLM enabled" flag. It means "local models are
102373
+ // usable" (manager + a model ready), which is what the rest of the app reads.
101838
102374
  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);
102375
+ if (isUsingFoundry) {
102376
+ if (isEnabled && isFullyReady) {
102377
+ setLocalLLMEnabled(true);
102378
+ }
102379
+ else if (!isEnabled) {
102380
+ setLocalLLMEnabled(false);
102381
+ }
101847
102382
  }
101848
- // If user enabled but system not ready, don't save yet
101849
- }, [isEnabled, isFullyReady]);
102383
+ else {
102384
+ // Ollama path: the app-facing flag mirrors the user's toggle choice so the
102385
+ // rest of the app uses local models exactly when the user asked for it.
102386
+ setLocalLLMEnabled(isEnabled);
102387
+ }
102388
+ }, [isEnabled, isFullyReady, isUsingFoundry]);
102389
+ // While Local Models is enabled but the manager isn't running yet (it may still
102390
+ // be starting, or an earlier status check ran before Ollama was up and answered),
102391
+ // keep re-checking so the card flips to "Running" as soon as Ollama answers —
102392
+ // instead of being stuck showing a stale "stopped". Stops once running/disabled.
102393
+ useEffect(() => {
102394
+ if (isUsingFoundry || !isEnabled || managerRunning)
102395
+ return;
102396
+ const id = setInterval(() => onCheckStatus(), 2500);
102397
+ return () => clearInterval(id);
102398
+ }, [isUsingFoundry, isEnabled, managerRunning, onCheckStatus]);
101850
102399
  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." }) }));
102400
+ 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
102401
  }
101853
102402
  // Check if we should show Foundry installation UI
101854
102403
  const shouldShowFoundryInstall = foundryStatus && foundryStatus.is_supported && !foundryStatus.has_models && !isUsingFoundry;
@@ -101895,56 +102444,328 @@ function LocalLLMTab({ isAvailable, state, ollamaStatus, isUsingFoundry = false,
101895
102444
  }, 100);
101896
102445
  }
101897
102446
  }, [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
102447
  const handleToggle = (checked) => {
102448
+ // Always reflect the user's intent — the toggle is operable in both directions.
102449
+ setIsEnabled(checked);
102450
+ // Foundry readiness is driven separately (download/load a model), so for the
102451
+ // Foundry path the toggle is just a preference.
102452
+ if (isUsingFoundry)
102453
+ return;
102454
+ // Ollama path: "Enable Local Models" === run the model manager.
101903
102455
  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
102456
+ // Ensure the manager is installed AND running (install_ollama does both;
102457
+ // it also starts it if it's installed but stopped).
102458
+ if (!isManagerInstalling) {
101911
102459
  onInstallOllama();
101912
102460
  }
101913
- else if (needsModelDownload) {
101914
- // Need to download the model
101915
- onStartDownload();
101916
- }
101917
102461
  }
101918
102462
  else {
101919
- // User wants to disable - always allow this
101920
- setIsEnabled(false);
102463
+ // Disabling stops the model manager.
102464
+ onStopManager === null || onStopManager === void 0 ? void 0 : onStopManager();
102465
+ }
102466
+ };
102467
+ const startDownloadNow = (modelId) => {
102468
+ setLocalActiveModelId(modelId);
102469
+ onStartDownload(modelId);
102470
+ };
102471
+ const handleDownloadModel = async (modelId) => {
102472
+ // Always read system memory FIRST — this is the call that hits the backend
102473
+ // `get_system_memory`. Doing it before the catalog lookup guarantees it runs
102474
+ // on every Download click (falling back to the value fetched on mount).
102475
+ const model = LOCAL_MODELS.find((m) => modelId.startsWith(m.id));
102476
+ if (!model) {
102477
+ await invoke(TAURI_COMMANDS.LOG_STDOUT, { s: `[Local Models] -> Download: bad download request, model ${model}` });
102478
+ console.log(`[Local Models] -> Download: bad download request, model ${model}`);
102479
+ return;
102480
+ }
102481
+ let mem = null;
102482
+ try {
102483
+ mem = await invoke(TAURI_COMMANDS.GET_SYSTEM_MEMORY);
101921
102484
  }
102485
+ catch (e) {
102486
+ await invoke(TAURI_COMMANDS.LOG_STDOUT, { s: `[Local Models] -> Download: couldn't detect system memory ${modelId} ${e}` });
102487
+ console.log(`[Local Models] -> Download: couldn't detect system memory {e}`);
102488
+ return;
102489
+ }
102490
+ const exceeds = modelExceedsCapacity(model, mem);
102491
+ console.warn('[Local Models] -> Download: download click', {
102492
+ modelId,
102493
+ systemMemory: mem,
102494
+ capacityBytes: usableMemoryBytes(mem),
102495
+ modelBytes: parseModelSizeBytes(model.size),
102496
+ thresholdBytes: usableMemoryBytes(mem) * MODEL_SIZE_WARN_FRACTION,
102497
+ exceeds,
102498
+ });
102499
+ if (exceeds) {
102500
+ setPendingModel(model);
102501
+ return;
102502
+ }
102503
+ startDownloadNow(modelId);
102504
+ };
102505
+ const confirmPendingDownload = () => {
102506
+ if (pendingModel) {
102507
+ startDownloadNow(pendingModel.id);
102508
+ }
102509
+ setPendingModel(null);
101922
102510
  };
101923
- // The switch should be checked if user preference is enabled AND system is ready
101924
- const switchChecked = isEnabled && isFullyReady;
102511
+ const handleSelectModel = (modelId) => {
102512
+ var _a, _b;
102513
+ setSelectedModelId(modelId);
102514
+ setLocalLLMModel(modelId);
102515
+ // Persist the model's tool-calling support so the host can route chat to the
102516
+ // MCP/tool proxy (:8000) vs plain Ollama (:11434).
102517
+ 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);
102518
+ };
102519
+ // For Ollama the switch reflects the user's toggle preference (always
102520
+ // operable, so it can be turned off). Foundry additionally requires a
102521
+ // downloaded model to be considered "on".
102522
+ const switchChecked = isUsingFoundry ? isEnabled && isFullyReady : isEnabled;
101925
102523
  // 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
102524
+ // For Foundry: disabled if no downloaded models, or while downloading/checking.
102525
+ // For Ollama: disabled only during a transient operation (installing/starting,
102526
+ // a model download, or a status check) — when idle it is always operable so
102527
+ // the user can turn local models off. (The "can't disable" bug was the
102528
+ // checked state being forced on, not this; see switchChecked.)
101933
102529
  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))) }))] }))] }) }))] }) }));
102530
+ ? !hasDownloadedFoundryModel || isDownloading || state.status === 'checking'
102531
+ : isDownloading || isManagerInstalling || state.status === 'checking';
102532
+ 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) => {
102533
+ const isPrimary = model.id === PRIMARY_MODEL_ID;
102534
+ const isActive = activeModelId === model.id;
102535
+ // Cloud-hosted models are always available — they run on
102536
+ // Ollama's cloud, so there's nothing to download.
102537
+ const isCloud = isCloudModelId(model.id);
102538
+ // A model is ready once Ollama reports its tag as installed.
102539
+ // Fall back to (a) this row's own download having just
102540
+ // completed — so it flips to "Ready" before the status
102541
+ // refresh lands — and (b) the primary-model status flag.
102542
+ // Cloud models are ready by default.
102543
+ const isInstalled = isCloud ||
102544
+ isModelInstalled(model.id, ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed_models) ||
102545
+ (isActive && state.status === 'completed') ||
102546
+ (isPrimary && (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed) === true);
102547
+ // An installed model must never render a download/cancel
102548
+ // state — guards against a stale persisted "downloading"
102549
+ // status making an already-ready model look like it's
102550
+ // still downloading (and wrongly cancellable) on reopen.
102551
+ const rowDownloading = isActive && isDownloading && !isInstalled;
102552
+ const rowChecking = isActive && state.status === 'checking';
102553
+ const rowError = isActive && state.status === 'error';
102554
+ const rowCancelled = isActive && state.status === 'cancelled';
102555
+ const canDownload = !!(ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed) &&
102556
+ !isInstalled &&
102557
+ !rowDownloading &&
102558
+ !rowChecking;
102559
+ const isSelected = selectedModelId === model.id;
102560
+ 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
102561
+ ? 'h-4 w-4 cursor-pointer accent-blue-500'
102562
+ : '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));
102563
+ }) })] }), 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) ||
102564
+ 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'
102565
+ ? 'text-red-600 dark:text-red-400'
102566
+ : log.level === 'warn'
102567
+ ? 'text-yellow-600 dark:text-yellow-400'
102568
+ : 'text-gray-700 dark:text-gray-300'}`, children: log.message }, idx))) }))] }))] }) }))] }), jsx(AlertDialog, { open: !!pendingModel, onOpenChange: (open) => {
102569
+ if (!open)
102570
+ setPendingModel(null);
102571
+ }, 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" })] })] }) })] }));
102572
+ }
102573
+
102574
+ function LocalModelsContent(props) {
102575
+ const [isCollapsed, setIsCollapsed] = useState(true);
102576
+ if (!props.isAvailable) {
102577
+ return null;
102578
+ }
102579
+ 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 }) }))] }));
102580
+ }
102581
+
102582
+ const SYSTEM_CONTROL_ENABLED_KEY = 'ibl_system_control_enabled';
102583
+ /**
102584
+ * Whether the Computer Assistant is enabled, read from localStorage. This is a
102585
+ * user preference (mirrors {@link isLocalLLMEnabled}); it reflects the toggle the
102586
+ * user last chose, independent of whether the helper is installed yet.
102587
+ */
102588
+ function isSystemControlEnabled() {
102589
+ if (typeof window === 'undefined')
102590
+ return false;
102591
+ return localStorage.getItem(SYSTEM_CONTROL_ENABLED_KEY) === 'true';
102592
+ }
102593
+ /**
102594
+ * Persist the Computer Assistant enabled preference to localStorage.
102595
+ */
102596
+ function setSystemControlEnabled(enabled) {
102597
+ if (typeof window === 'undefined')
102598
+ return;
102599
+ localStorage.setItem(SYSTEM_CONTROL_ENABLED_KEY, String(enabled));
102600
+ }
102601
+ /** Green "done" pill. */
102602
+ function DonePill({ children }) {
102603
+ 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] }));
102604
+ }
102605
+ /** Blue "in progress" pill. */
102606
+ function WorkingPill({ children }) {
102607
+ 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] }));
102608
+ }
102609
+ /** A single numbered step row. */
102610
+ function Step$1({ number, done, title, description, action, }) {
102611
+ 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
102612
+ ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300'
102613
+ : '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 })] }));
102614
+ }
102615
+ /**
102616
+ * Body of the "Computer Assistant" card: a plain-language on/off with two simple
102617
+ * steps — set up, then allow control — so non-technical users can follow along.
102618
+ * Mirrors the Local Models tab so the two cards behave the same.
102619
+ */
102620
+ function SystemControlTab({ isAvailable, state, status, requiredSizeGb = DEFAULT_SYSTEM_CONTROL_REQUIRED_SIZE_GB, ollamaStatus, systemMemory, accessibilityPermission, onInstall, onStop, onCheckStatus, onResetState, onRequestAccessibilityPermission, onDownloadModel, }) {
102621
+ const isInstalling = state.status === 'installing' || (status === null || status === void 0 ? void 0 : status.installing) === true;
102622
+ // User preference — independent of whether the helper is installed. Always
102623
+ // operable so the user can turn it off again.
102624
+ const [isEnabled, setIsEnabled] = useState(() => isSystemControlEnabled());
102625
+ // The currently-selected local model (persisted by the Local Models card).
102626
+ // Polled so a selection change in the sibling card is reflected here without a
102627
+ // remount. The assistant only works well with larger models, so it's gated to
102628
+ // a selected model above the size threshold.
102629
+ const [selectedModel, setSelectedModel] = useState(() => getLocalLLMModel());
102630
+ // Whether "Local Models" is turned on. The assistant runs on a local model, so
102631
+ // it's gated off until Local Models is enabled. Polled alongside the model so a
102632
+ // toggle in the sibling card is reflected without a remount.
102633
+ const [localModelsEnabled, setLocalModelsEnabled] = useState(() => isLocalLLMEnabled());
102634
+ useEffect(() => {
102635
+ const id = setInterval(() => {
102636
+ setSelectedModel(getLocalLLMModel());
102637
+ setLocalModelsEnabled(isLocalLLMEnabled());
102638
+ }, 1500);
102639
+ return () => clearInterval(id);
102640
+ }, []);
102641
+ const localModelsOff = !localModelsEnabled;
102642
+ const modelTooSmall = !modelSupportsSystemControl(selectedModel, requiredSizeGb);
102643
+ // "Upgrade" target: the smallest catalog model that clears the size gate.
102644
+ const upgradeTarget = smallestSystemControlModel(requiredSizeGb);
102645
+ // Set while an upgrade is downloading a model, before we auto-enable.
102646
+ const [pendingEnableModelId, setPendingEnableModelId] = useState(null);
102647
+ // Model awaiting a RAM/VRAM "may be too large" confirmation before download.
102648
+ const [pendingCapacityModel, setPendingCapacityModel] = useState(null);
102649
+ const isUpgrading = pendingEnableModelId !== null;
102650
+ const managerRunning = (status === null || status === void 0 ? void 0 : status.running) === true;
102651
+ // Steps are shown whenever the toggle is on (or mid-setup), so the user always
102652
+ // gets feedback for their choice.
102653
+ const managerEnabled = isEnabled || isInstalling;
102654
+ // Permission is known (macOS + plugin wired) and granted.
102655
+ const permissionKnown = typeof accessibilityPermission === 'boolean';
102656
+ const permissionGranted = accessibilityPermission === true;
102657
+ const allSet = managerRunning && (!permissionKnown || permissionGranted);
102658
+ // Keep the app-facing preference flag in sync with the toggle.
102659
+ useEffect(() => {
102660
+ setSystemControlEnabled(isEnabled);
102661
+ }, [isEnabled]);
102662
+ // While enabled but not yet running, keep polling so the step flips to "Ready"
102663
+ // as soon as the host reports the helper is up.
102664
+ useEffect(() => {
102665
+ if (!isEnabled || managerRunning)
102666
+ return;
102667
+ const id = setInterval(() => onCheckStatus(), 2500);
102668
+ return () => clearInterval(id);
102669
+ }, [isEnabled, managerRunning, onCheckStatus]);
102670
+ // While permission isn't granted yet, keep re-checking (the grant happens in
102671
+ // System Settings) so the step flips to "Allowed" on its own.
102672
+ useEffect(() => {
102673
+ if (accessibilityPermission !== false)
102674
+ return;
102675
+ const id = setInterval(() => onCheckStatus(), 2500);
102676
+ return () => clearInterval(id);
102677
+ }, [accessibilityPermission, onCheckStatus]);
102678
+ // After an upgrade kicks off a download, turn the assistant on once the model
102679
+ // finishes downloading (it shows up in the installed list).
102680
+ useEffect(() => {
102681
+ if (!pendingEnableModelId)
102682
+ return;
102683
+ if (isModelInstalled(pendingEnableModelId, ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed_models)) {
102684
+ setPendingEnableModelId(null);
102685
+ setIsEnabled(true);
102686
+ onInstall();
102687
+ }
102688
+ }, [pendingEnableModelId, ollamaStatus, onInstall]);
102689
+ if (!isAvailable) {
102690
+ return (jsx("div", { className: "text-sm text-gray-500 dark:text-gray-400", children: "The Computer Assistant is only available in the desktop app." }));
102691
+ }
102692
+ const handleToggle = (checked) => {
102693
+ // Guard: needs Local Models on and a large-enough model.
102694
+ if (checked && (localModelsOff || modelTooSmall))
102695
+ return;
102696
+ setIsEnabled(checked);
102697
+ if (checked) {
102698
+ if (!isInstalling)
102699
+ onInstall();
102700
+ }
102701
+ else {
102702
+ onStop === null || onStop === void 0 ? void 0 : onStop();
102703
+ }
102704
+ };
102705
+ // Upgrade = switch to the smallest usable model: select it, then (if it isn't
102706
+ // already downloaded) download it and auto-enable the assistant once it's
102707
+ // ready. The RAM/VRAM check is preserved via the confirm below.
102708
+ const runUpgrade = (model) => {
102709
+ setLocalLLMModel(model.id);
102710
+ setLocalLLMToolSupport(model.tool_support);
102711
+ setLocalLLMEnabled(true);
102712
+ setSelectedModel(model.id); // recompute the size gate immediately
102713
+ if (isModelInstalled(model.id, ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed_models)) {
102714
+ // Already downloaded → enable now.
102715
+ setIsEnabled(true);
102716
+ if (!isInstalling)
102717
+ onInstall();
102718
+ }
102719
+ else {
102720
+ // Not downloaded → download, then auto-enable when it lands (effect above).
102721
+ setPendingEnableModelId(model.id);
102722
+ onDownloadModel === null || onDownloadModel === void 0 ? void 0 : onDownloadModel(model.id);
102723
+ }
102724
+ };
102725
+ const handleUpgrade = () => {
102726
+ if (!upgradeTarget)
102727
+ return;
102728
+ // Keep the RAM/VRAM check: confirm before pulling a model that may not fit.
102729
+ if (modelExceedsCapacity(upgradeTarget, systemMemory)) {
102730
+ setPendingCapacityModel(upgradeTarget);
102731
+ return;
102732
+ }
102733
+ runUpgrade(upgradeTarget);
102734
+ };
102735
+ const switchChecked = isEnabled;
102736
+ const switchDisabled = isInstalling ||
102737
+ state.status === 'checking' ||
102738
+ ((localModelsOff || modelTooSmall) && !isEnabled);
102739
+ 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
102740
+ ? `Upgrade to ${upgradeTarget.name} (${upgradeTarget.size}) to use it.`
102741
+ : '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: () => {
102742
+ onResetState();
102743
+ onInstall();
102744
+ }, 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) => {
102745
+ if (!open)
102746
+ setPendingCapacityModel(null);
102747
+ }, 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: () => {
102748
+ const model = pendingCapacityModel;
102749
+ setPendingCapacityModel(null);
102750
+ if (model)
102751
+ runUpgrade(model);
102752
+ }, children: "Download anyway" })] })] }) })] }));
102753
+ }
102754
+
102755
+ /**
102756
+ * Collapsible "System Control" card for the UserProfile → Advanced section.
102757
+ * Mirrors {@link LocalModelsContent}: a purely presentational, titled,
102758
+ * collapsible container whose body is the {@link SystemControlTab}. Availability
102759
+ * and handlers are threaded in (see UserProfileDropdown, which falls back to
102760
+ * {@link useGhostOs} when the host doesn't supply them). Renders nothing outside
102761
+ * the desktop app.
102762
+ */
102763
+ function SystemControlContent(props) {
102764
+ const [isCollapsed, setIsCollapsed] = useState(true);
102765
+ if (!props.isAvailable) {
102766
+ return null;
102767
+ }
102768
+ 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
102769
  }
101949
102770
 
101950
102771
  // src/primitive.tsx
@@ -125048,7 +125869,7 @@ function ChatPrivacyTab({ org, username }) {
125048
125869
  const renderLucideIcon = (Icon) => function RenderedIcon(props) {
125049
125870
  return jsx(Icon, { ...props });
125050
125871
  };
125051
- function Profile({ tenant, username, tenants, onClose, customization = {}, isAdmin = false, targetTab = 'basic', onAccountDeleted, enableMemoryTab = false, localLLMProps, }) {
125872
+ function Profile({ tenant, username, tenants, onClose, customization = {}, isAdmin = false, targetTab = 'basic', onAccountDeleted, enableMemoryTab = false, localLLMProps, systemControlProps, }) {
125052
125873
  var _a, _b, _c, _d, _e, _f, _g, _h;
125053
125874
  console.log('[Profile] localLLMProps received:', {
125054
125875
  isAvailable: localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.isAvailable,
@@ -125093,8 +125914,10 @@ function Profile({ tenant, username, tenants, onClose, customization = {}, isAdm
125093
125914
  : []),
125094
125915
  { id: 'security', label: 'Security', renderIcon: renderLucideIcon(Shield) },
125095
125916
  ];
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)
125917
+ // Add Advanced tab only when in Tauri app (Local Models or System Control is
125918
+ // provided and available)
125919
+ const isAdvancedAvailable = (localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.isAvailable) === true || (systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.isAvailable) === true;
125920
+ const TABS = isAdvancedAvailable
125098
125921
  ? [...baseTabs, { id: 'advanced', label: 'Advanced', renderIcon: renderLucideIcon(Settings$1) }]
125099
125922
  : baseTabs;
125100
125923
  const RoleIcon = renderLucideIcon(User);
@@ -125237,7 +126060,7 @@ function Profile({ tenant, username, tenants, onClose, customization = {}, isAdm
125237
126060
  var _a;
125238
126061
  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
126062
  },
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: () => {
126063
+ })] })] }) })), 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
126064
  if (activeTab === 'basic') {
125242
126065
  basicForm.handleSubmit();
125243
126066
  }
@@ -125332,11 +126155,11 @@ var TabsList$1 = React.forwardRef(
125332
126155
  }
125333
126156
  );
125334
126157
  TabsList$1.displayName = TAB_LIST_NAME;
125335
- var TRIGGER_NAME$2 = "TabsTrigger";
126158
+ var TRIGGER_NAME$1 = "TabsTrigger";
125336
126159
  var TabsTrigger$1 = React.forwardRef(
125337
126160
  (props, forwardedRef) => {
125338
126161
  const { __scopeTabs, value, disabled = false, ...triggerProps } = props;
125339
- const context = useTabsContext(TRIGGER_NAME$2, __scopeTabs);
126162
+ const context = useTabsContext(TRIGGER_NAME$1, __scopeTabs);
125340
126163
  const rovingFocusGroupScope = useRovingFocusGroupScope$1(__scopeTabs);
125341
126164
  const triggerId = makeTriggerId(context.baseId, value);
125342
126165
  const contentId = makeContentId(context.baseId, value);
@@ -125383,12 +126206,12 @@ var TabsTrigger$1 = React.forwardRef(
125383
126206
  );
125384
126207
  }
125385
126208
  );
125386
- TabsTrigger$1.displayName = TRIGGER_NAME$2;
125387
- var CONTENT_NAME$2 = "TabsContent";
126209
+ TabsTrigger$1.displayName = TRIGGER_NAME$1;
126210
+ var CONTENT_NAME$1 = "TabsContent";
125388
126211
  var TabsContent$1 = React.forwardRef(
125389
126212
  (props, forwardedRef) => {
125390
126213
  const { __scopeTabs, value, forceMount, children, ...contentProps } = props;
125391
- const context = useTabsContext(CONTENT_NAME$2, __scopeTabs);
126214
+ const context = useTabsContext(CONTENT_NAME$1, __scopeTabs);
125392
126215
  const triggerId = makeTriggerId(context.baseId, value);
125393
126216
  const contentId = makeContentId(context.baseId, value);
125394
126217
  const isSelected = value === context.value;
@@ -125418,19 +126241,19 @@ var TabsContent$1 = React.forwardRef(
125418
126241
  ) });
125419
126242
  }
125420
126243
  );
125421
- TabsContent$1.displayName = CONTENT_NAME$2;
126244
+ TabsContent$1.displayName = CONTENT_NAME$1;
125422
126245
  function makeTriggerId(baseId, value) {
125423
126246
  return `${baseId}-trigger-${value}`;
125424
126247
  }
125425
126248
  function makeContentId(baseId, value) {
125426
126249
  return `${baseId}-content-${value}`;
125427
126250
  }
125428
- var Root2$3 = Tabs$1;
126251
+ var Root2$2 = Tabs$1;
125429
126252
  var List = TabsList$1;
125430
126253
  var Trigger$1 = TabsTrigger$1;
125431
126254
  var Content = TabsContent$1;
125432
126255
 
125433
- const Tabs = Root2$3;
126256
+ const Tabs = Root2$2;
125434
126257
  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
126258
  TabsList.displayName = List.displayName;
125436
126259
  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 })));
@@ -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$7, { 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$7.displayName;
210446
211344
 
210447
211345
  /**
210448
211346
  * Label contract for the Settings tab and its two sub-modals.