@iblai/iblai-js 1.20.5 → 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.
- package/dist/web-containers/source/index.esm.js +1589 -659
- package/dist/web-containers/source/next/index.esm.js +1441 -543
- package/package.json +5 -5
|
@@ -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,
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
|
|
101813
|
-
|
|
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
|
|
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
|
-
|
|
101840
|
-
|
|
101841
|
-
|
|
101842
|
-
|
|
101843
|
-
|
|
101844
|
-
|
|
101845
|
-
|
|
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
|
-
|
|
101849
|
-
|
|
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: "
|
|
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
|
-
//
|
|
101905
|
-
if
|
|
101906
|
-
|
|
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
|
-
//
|
|
101920
|
-
|
|
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
|
-
|
|
101924
|
-
|
|
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
|
-
//
|
|
101928
|
-
//
|
|
101929
|
-
//
|
|
101930
|
-
//
|
|
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
|
-
?
|
|
101935
|
-
|
|
101936
|
-
|
|
101937
|
-
|
|
101938
|
-
|
|
101939
|
-
|
|
101940
|
-
|
|
101941
|
-
|
|
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
|
-
|
|
101944
|
-
|
|
101945
|
-
|
|
101946
|
-
|
|
101947
|
-
|
|
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 (
|
|
125097
|
-
|
|
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(
|
|
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$
|
|
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$
|
|
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$
|
|
125387
|
-
var CONTENT_NAME$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
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
|
|
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
|
|
126740
|
-
var PORTAL_NAME
|
|
126741
|
-
var [PortalProvider, usePortalContext] = createPopoverContext(PORTAL_NAME
|
|
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
|
|
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
|
|
126750
|
-
var CONTENT_NAME
|
|
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
|
|
127576
|
+
const portalContext = usePortalContext(CONTENT_NAME, props.__scopePopover);
|
|
126754
127577
|
const { forceMount = portalContext.forceMount, ...contentProps } = props;
|
|
126755
|
-
const context = usePopoverContext(CONTENT_NAME
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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$
|
|
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
|
|
127760
|
+
var Content2 = PopoverContent$1;
|
|
126938
127761
|
|
|
126939
|
-
const Popover = Root2$
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
206853
|
-
|
|
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
|
-
|
|
206860
|
-
|
|
206861
|
-
status
|
|
206862
|
-
|
|
206863
|
-
|
|
206864
|
-
|
|
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
|
|
207041
|
-
const
|
|
207042
|
-
const
|
|
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 = (
|
|
207045
|
-
const 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$
|
|
210445
|
-
CustomSwitch.displayName = Root$
|
|
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.
|