@iblai/iblai-js 2.2.7 → 2.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
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, createContext, useContext, useMemo, useCallback, useReducer, useRef, useImperativeHandle, isValidElement, Children, PureComponent, cloneElement, Component as Component$1, useId as useId$2, useDebugValue, lazy, Suspense } from 'react';
3
+ import React__default, { useState, useEffect, forwardRef, createElement as createElement$3, useLayoutEffect, createContext, useContext, useMemo, useReducer, useRef, useImperativeHandle, useCallback, isValidElement, Children, PureComponent, cloneElement, Component as Component$1, useId as useId$2, useDebugValue, lazy, Suspense } from 'react';
4
4
  import * as dataLayer from '@iblai/data-layer';
5
- 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, useGetLearnerDetailsQuery, useGetUserEnrolledCoursesQuery, useGetUserEnrolledProgramsQuery, useGetUserCatalogPathwaysQuery, useGetUsersAsAssertionsQuery, useGetUserEarnedSkillsQuery, useGetUserReportedSkillsQuery, useGetUserDesiredSkillsQuery, useCreateGlobalMemoryMutation, useGetMemsearchStatusQuery, useGetUserMemorySettingsQuery, useUpdateUserMemorySettingsMutation, useGetGlobalMemoriesQuery, useDeleteGlobalMemoryMutation, 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, useGetRbacResourceDiscoveryQuery, useGetRbacResourceListQuery, useGetRbacPoliciesQuery, useCreateRbacPolicyMutation, useUpdateRbacPolicyMutation, useDeleteRbacPolicyMutation, useGetRbacPolicyDetailsQuery, useGetWatchedGroupsQuery, useCreateWatchedGroupMutation, useUpdateWatchedGroupMutation, useDeleteWatchedGroupMutation, useAddWatchedUserMutation, useRemoveWatchedUserMutation, useAddWatcherMutation, useUpdateWatcherMutation, useDeleteWatcherMutation, useGetWatchedUsersQuery, useGetWatchersQuery, WATCHER_NOTIFICATION_EVENTS, WATCHER_NOTIFICATION_EVENT_LABELS, useGetApplicationFormsQuery, useGetApplicationFormDetailQuery, useCreateApplicationFormMutation, useUpdateApplicationFormMutation, useGetApplicationAdminDetailQuery, useInviteApplicationCandidateMutation, useLazyGetApplicationAttachmentFileQuery, useCreateApplicationWaiverMutation, useDeleteApplicationWaiverMutation, useSubmitApplicationOverrideMutation, useUpdateApplicationPlacementMutation, useDeleteApplicationCourseAssignmentMutation, useCreateApplicationNoteMutation, useUpdateApplicationNoteMutation, useDeleteApplicationNoteMutation, useTransitionApplicationMutation, useLinkApplicationCandidateMutation, useCreateApplicationPlacementsMutation, useGetCustomMentorsQuery, useCreateApplicationCourseAssignmentsMutation, useGetApplicationBlocksQuery, useDeleteApplicationBlockMutation, useGetApplicationsPipelineStatsQuery, useGetApplicationsPipelineQuery, 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, 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, useStarMentorMutation, useUnstarMentorMutation, useCreateUserProjectMutation, useDeleteUserProjectMutation, useGetUserProjectDetailsQuery, useEditTrainingDocumentMutation, useAddTrainingDocumentMutation, useLazyGetCredentialsQuery, useGetMentorSettingsQuery, useEditMentorMutation, useUploadLightLogoMutation, useUploadDarkLogoMutation, useUpdatePlatformInfoMutation, LOGO_ENDPOINTS, useCreateEvalDatasetMutation, useAddEvalDatasetItemsMutation, useUploadEvalDatasetCsvMutation, useDeleteEvalDatasetItemMutation, useListEvalDatasetItemsQuery, evalTotalPages, useListEvalDatasetsQuery, useGetUserProjectsQuery, useCreateSessionIdMutation, useGetMentorCategoriesQuery, useGetTenantChatPrivacyConfigQuery as useGetTenantChatPrivacyConfigQuery$1, useGetCallConfigurationsQuery, useCreateCallConfigurationMutation, useUpdateCallConfigurationMutation, chatPrivacyApiSlice, 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, useGetMentorPublicSettingsQuery, useGetChatHistoryFilterQuery, useGetChatHistoryQuery, useGetMentorSummariesQuery, useGetConversationMemoriesQuery, useGetSupportTicketsQuery, useGetSupportTicketMessagesQuery, useCreateSupportTicketMessageMutation, usePatchSupportTicketMutation, useCloseSupportTicketMutation, useCreateLtiMentorMutation, useUpdateLtiMentorMutation, useGetLtiMentorsQuery, useCreateLtiKeyMutation, useUpdateLtiKeyMutation, useDeleteLtiKeyMutation, useGetLtiKeysQuery, useCreateLtiToolMutation, useUpdateLtiToolMutation, useGetLtiToolsQuery, useGetPlatformInfoQuery, useCreatePromptMutation, useGetPromptsSearchQuery, useUpdatePromptMutation, useCreateRedirectTokenMutation, useGetShareableLinkQuery, useCreateShareableLinkMutation, useUpdateShareableLinkMutation, useCreateDisclaimerMutation, useUpdateDisclaimerMutation, useGetDisclaimersQuery, useUpdateRbacMentorAccessMutation, useGetRbacMentorAccessListQuery, useStartEvalRunMutation, useEvaluateEvalRunMutation, useGetEvalRunQuery, useDeleteEvalScoreMutation, useListEvalRunJudgesQuery, useCreateEvalScoreMutation, useDeleteEvalRunMutation, useExportEvalRunMutation, useListEvalRunsQuery, useLazyGetEvalRunQuery, evaluationsApiSlice, EVALUATIONS_TAGS, useGetPersonnalizedMentorsQuery, useCreateCallCredentialsMutation, useGetGuidedPromptsQuery, useUpdateChatSessionSharedMutation, useUpdateMessageFeedbackMutation, useLazyGetPromptsSearchQuery, useLazyGetGuidedPromptsQuery, useGetPublicMentorsQuery, useGetPeriodicAgentsQuery, useGetPeriodicAgentLogsListQuery, useCreatePeriodicAgentMutation, useDeletePeriodicAgentMutation, useDeletePromptMutation, useDeleteTrainingDocumentMutation, useGetTrainingDocumentRetrainScheduleQuery, useCreateTrainingDocumentRetrainScheduleMutation, useCreateMemoryCategoryMutation, useUpdateMemoryCategoryMutation, useDeleteMemoryCategoryMutation, useUpdateArtifactMutation, useLazyGetArtifactVersionQuery, useLazyListArtifactVersionsQuery, useSetCurrentVersionMutation, useListArtifactVersionsQuery, useLazyGetArtifactQuery, useLazyListArtifactsQuery, useEditSessionMutation } from '@iblai/data-layer';
6
- import { getInitials, useTenantMetadata, WithPermissions, useStripeUpgrade, openExternalUrl, 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, pickWritableFields, ANONYMOUS_USERNAME as ANONYMOUS_USERNAME$3, 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';
5
+ 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, useGetLearnerDetailsQuery, useGetUserEnrolledCoursesQuery, useGetUserEnrolledProgramsQuery, useGetUserCatalogPathwaysQuery, useGetUsersAsAssertionsQuery, useGetUserEarnedSkillsQuery, useGetUserReportedSkillsQuery, useGetUserDesiredSkillsQuery, useCreateGlobalMemoryMutation, useGetMemsearchStatusQuery, useGetUserMemorySettingsQuery, useUpdateUserMemorySettingsMutation, useGetGlobalMemoriesQuery, useDeleteGlobalMemoryMutation, 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, useGetRbacResourceDiscoveryQuery, useGetRbacResourceListQuery, useGetRbacPoliciesQuery, useCreateRbacPolicyMutation, useUpdateRbacPolicyMutation, useDeleteRbacPolicyMutation, useGetRbacPolicyDetailsQuery, useGetWatchedGroupsQuery, useCreateWatchedGroupMutation, useUpdateWatchedGroupMutation, useDeleteWatchedGroupMutation, useAddWatchedUserMutation, useRemoveWatchedUserMutation, useAddWatcherMutation, useUpdateWatcherMutation, useDeleteWatcherMutation, useGetWatchedUsersQuery, useGetWatchersQuery, WATCHER_NOTIFICATION_EVENTS, WATCHER_NOTIFICATION_EVENT_LABELS, useGetApplicationFormsQuery, useGetApplicationFormDetailQuery, useCreateApplicationFormMutation, useUpdateApplicationFormMutation, useGetApplicationAdminDetailQuery, useInviteApplicationCandidateMutation, useLazyGetApplicationAttachmentFileQuery, useCreateApplicationWaiverMutation, useDeleteApplicationWaiverMutation, useSubmitApplicationOverrideMutation, useUpdateApplicationPlacementMutation, useDeleteApplicationCourseAssignmentMutation, useCreateApplicationNoteMutation, useUpdateApplicationNoteMutation, useDeleteApplicationNoteMutation, useTransitionApplicationMutation, useLinkApplicationCandidateMutation, useCreateApplicationPlacementsMutation, useGetCustomMentorsQuery, useCreateApplicationCourseAssignmentsMutation, useGetApplicationBlocksQuery, useDeleteApplicationBlockMutation, useGetApplicationsPipelineStatsQuery, useGetApplicationsPipelineQuery, 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, 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, useStarMentorMutation, useUnstarMentorMutation, useCreateUserProjectMutation, useDeleteUserProjectMutation, useGetUserProjectDetailsQuery, useEditTrainingDocumentMutation, useAddTrainingDocumentMutation, useLazyGetCredentialsQuery, useGetMentorSettingsQuery, useEditMentorMutation, useUploadLightLogoMutation, useUploadDarkLogoMutation, useUpdatePlatformInfoMutation, LOGO_ENDPOINTS, useCreateEvalDatasetMutation, useAddEvalDatasetItemsMutation, useUploadEvalDatasetCsvMutation, useDeleteEvalDatasetItemMutation, useListEvalDatasetItemsQuery, evalTotalPages, useListEvalDatasetsQuery, useGetUserProjectsQuery, useCreateSessionIdMutation, useGetMentorCategoriesQuery, useGetTenantChatPrivacyConfigQuery as useGetTenantChatPrivacyConfigQuery$1, useGetCallConfigurationsQuery, useCreateCallConfigurationMutation, useUpdateCallConfigurationMutation, chatPrivacyApiSlice, 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, useGetMentorGraderConfigurationQuery, useListGraderCriteriaQuery, useCreateMentorGraderConfigurationMutation, useUpdateMentorGraderConfigurationMutation, useCreateGraderCriterionMutation, useUpdateGraderCriterionMutation, useDeleteGraderCriterionMutation, useGetMentorPublicSettingsQuery, useGetChatHistoryFilterQuery, useGetChatHistoryQuery, useGetMentorSummariesQuery, useGetConversationMemoriesQuery, useGetSupportTicketsQuery, useGetSupportTicketMessagesQuery, useCreateSupportTicketMessageMutation, usePatchSupportTicketMutation, useCloseSupportTicketMutation, useCreateLtiMentorMutation, useUpdateLtiMentorMutation, useGetLtiMentorsQuery, useCreateLtiKeyMutation, useUpdateLtiKeyMutation, useDeleteLtiKeyMutation, useGetLtiKeysQuery, useCreateLtiToolMutation, useUpdateLtiToolMutation, useGetLtiToolsQuery, useGetPlatformInfoQuery, useCreatePromptMutation, useGetPromptsSearchQuery, useUpdatePromptMutation, useCreateRedirectTokenMutation, useGetShareableLinkQuery, useCreateShareableLinkMutation, useUpdateShareableLinkMutation, useCreateDisclaimerMutation, useUpdateDisclaimerMutation, useGetDisclaimersQuery, useUpdateRbacMentorAccessMutation, useGetRbacMentorAccessListQuery, useStartEvalRunMutation, useEvaluateEvalRunMutation, useGetEvalRunQuery, useDeleteEvalScoreMutation, useListEvalRunJudgesQuery, useCreateEvalScoreMutation, useDeleteEvalRunMutation, useExportEvalRunMutation, useListEvalRunsQuery, useLazyGetEvalRunQuery, evaluationsApiSlice, EVALUATIONS_TAGS, useGetPersonnalizedMentorsQuery, useCreateCallCredentialsMutation, useGetGuidedPromptsQuery, useUpdateChatSessionSharedMutation, useUpdateMessageFeedbackMutation, useLazyGetPromptsSearchQuery, useLazyGetGuidedPromptsQuery, useGetPublicMentorsQuery, useGetPeriodicAgentsQuery, useGetPeriodicAgentLogsListQuery, useCreatePeriodicAgentMutation, useDeletePeriodicAgentMutation, useDeletePromptMutation, useDeleteTrainingDocumentMutation, useGetTrainingDocumentRetrainScheduleQuery, useCreateTrainingDocumentRetrainScheduleMutation, useCreateMemoryCategoryMutation, useUpdateMemoryCategoryMutation, useDeleteMemoryCategoryMutation, useUpdateArtifactMutation, useLazyGetArtifactVersionQuery, useLazyListArtifactVersionsQuery, useSetCurrentVersionMutation, useListArtifactVersionsQuery, useLazyGetArtifactQuery, useLazyListArtifactsQuery, useEditSessionMutation } from '@iblai/data-layer';
6
+ import { getInitials, useTenantMetadata, WithPermissions, useStripeUpgrade, openExternalUrl, CHAT_AREA_SIZE, isAlphaNumeric32, checkRbacPermission, selectNumberOfActiveChatMessages, useUsername, selectStreaming, isLoggedIn, TOOLS, hasRemoteAiConfig, 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, pickWritableFields, ANONYMOUS_USERNAME as ANONYMOUS_USERNAME$3, 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';
7
7
  import { useDispatch, useSelector } from 'react-redux';
8
8
  import { toast } from 'sonner';
9
9
  import { useSearchParams, useParams, useRouter, usePathname } from 'next/navigation';
@@ -98069,6 +98069,118 @@ var gradebookGradebookTab$3 = {
98069
98069
  previousPage: "Previous",
98070
98070
  nextPage: "Next"
98071
98071
  };
98072
+ var graderTabLabels$3 = {
98073
+ header: {
98074
+ title: "Grader",
98075
+ description: "Set up how your agent grades work against a rubric you define."
98076
+ },
98077
+ capability: {
98078
+ title: "Grading",
98079
+ description: "Let your agent grade work. When this is on, the agent scores what people submit against the rubric you set up below and shares the result as feedback — great for essays, projects, and practice exercises.",
98080
+ offHint: "Turn it on to write grading instructions and build your rubric. Anything you set up here is kept safe if you later turn it off."
98081
+ },
98082
+ subTabs: {
98083
+ setup: "Grading setup",
98084
+ rubric: "Rubric"
98085
+ },
98086
+ denied: {
98087
+ title: "No access to grading settings",
98088
+ description: "You don't have permission to view this agent's grading setup. Ask an administrator for access."
98089
+ },
98090
+ warnings: {
98091
+ noConfig: "Grading is on but not set up yet — the agent will fail when asked to grade. Save your grading instructions in Grading setup to finish.",
98092
+ noCriteria: "Grading is on but the rubric is empty — the agent will fail when asked to grade. Add at least one criterion in the Rubric tab."
98093
+ },
98094
+ config: {
98095
+ sectionTitle: "Grading setup",
98096
+ gradingMode: {
98097
+ label: "What gets graded",
98098
+ options: {
98099
+ submission: "A submission",
98100
+ conversation: "The conversation"
98101
+ },
98102
+ help: {
98103
+ submission: "The agent grades a piece of work the person hands in, like an essay or answer.",
98104
+ conversation: "The agent grades how the whole conversation went."
98105
+ }
98106
+ },
98107
+ feedbackMode: {
98108
+ label: "Feedback shared with the person",
98109
+ options: {
98110
+ overall: "Overall feedback only",
98111
+ perCriteria: "Feedback per criterion",
98112
+ both: "Overall + per criterion"
98113
+ },
98114
+ help: "The overall score is always calculated — this only controls how much detail is shared back."
98115
+ },
98116
+ instructions: {
98117
+ label: "Grading instructions",
98118
+ placeholder: "Describe what's being graded and anything the grader should know beyond the rubric. E.g. 'You are grading a 500-word persuasive essay on climate policy written by a high-school student. Be encouraging but honest.'",
98119
+ help: "Required. This is the context the agent uses every time it grades."
98120
+ },
98121
+ saveButton: "Save",
98122
+ savingButton: "Saving…"
98123
+ },
98124
+ criteria: {
98125
+ sectionTitle: "Rubric",
98126
+ description: "Each row is one thing the agent looks for and how many points it is worth. The overall score is points earned out of the total.",
98127
+ needsConfigHint: "Save your grading setup first, then add rubric criteria here.",
98128
+ emptyState: "No criteria yet. Add your first one — for example \"Clarity\", 10 points.",
98129
+ addButton: "Add criterion",
98130
+ columns: {
98131
+ name: "Name",
98132
+ criteria: "Criteria",
98133
+ points: "Points"
98134
+ },
98135
+ actionsAria: "Actions for {name}",
98136
+ edit: "Edit",
98137
+ "delete": "Delete",
98138
+ totalPoints: "Total possible points: {total}",
98139
+ scoreHint: "Overall score = points earned ÷ {total}.",
98140
+ lastCriterionHint: "A rubric needs at least one criterion — add another before removing this one.",
98141
+ modal: {
98142
+ addTitle: "Add criterion",
98143
+ editTitle: "Edit criterion",
98144
+ name: {
98145
+ label: "Name",
98146
+ placeholder: "e.g. Clarity",
98147
+ required: "Name is required"
98148
+ },
98149
+ criteria: {
98150
+ label: "Criteria",
98151
+ placeholder: "e.g. The argument is easy to follow and each paragraph makes one clear point.",
98152
+ required: "Criteria is required"
98153
+ },
98154
+ points: {
98155
+ label: "Points",
98156
+ placeholder: "10",
98157
+ positive: "Points must be a positive number"
98158
+ },
98159
+ save: "Save",
98160
+ saving: "Saving…",
98161
+ cancel: "Cancel"
98162
+ },
98163
+ deleteModal: {
98164
+ title: "Delete criterion",
98165
+ confirmationPrefix: "Are you sure you want to remove",
98166
+ confirmationSuffix: " from the rubric?",
98167
+ cancel: "Cancel",
98168
+ "delete": "Delete",
98169
+ deleting: "Deleting…"
98170
+ }
98171
+ },
98172
+ toasts: {
98173
+ toggleOn: "Grading turned on",
98174
+ toggleOff: "Grading turned off — your rubric is kept for next time",
98175
+ toggleError: "Couldn't update grading",
98176
+ configSaved: "Grading setup saved",
98177
+ configError: "Couldn't save grading setup",
98178
+ criterionAdded: "Criterion added",
98179
+ criterionUpdated: "Criterion updated",
98180
+ criterionDeleted: "Criterion deleted",
98181
+ criterionError: "Couldn't update the rubric"
98182
+ }
98183
+ };
98072
98184
  var careerExperienceDialog$3 = {
98073
98185
  pleaseSelectACompany: "Please select a company",
98074
98186
  endDateCannotBeBeforeStartDate: "End date cannot be before start date",
@@ -98222,6 +98334,9 @@ var chatInputFormInsideButtons$3 = {
98222
98334
  studyMode: "Study Mode",
98223
98335
  deepResearch: "Deep Research",
98224
98336
  memory: "Memory",
98337
+ cowork: "Cowork",
98338
+ coworkNeedsBackend: "Enable a large local model or sign in to a workspace to use Cowork.",
98339
+ coworkModelTooSmall: "Your local model is too small for Cowork. Pick a model of at least 14GB in Local Models.",
98225
98340
  moreOptions: "More options"
98226
98341
  };
98227
98342
  var chatInputFormMemoryMenu$3 = {
@@ -102633,6 +102748,7 @@ var en = {
102633
102748
  careerEducationDialog: careerEducationDialog$3,
102634
102749
  careerEducationTab: careerEducationTab$3,
102635
102750
  gradebookGradebookTab: gradebookGradebookTab$3,
102751
+ graderTabLabels: graderTabLabels$3,
102636
102752
  careerExperienceDialog: careerExperienceDialog$3,
102637
102753
  careerExperienceTab: careerExperienceTab$3,
102638
102754
  careerInstitutionDialog: careerInstitutionDialog$3,
@@ -104074,6 +104190,118 @@ var gradebookGradebookTab$2 = {
104074
104190
  previousPage: "Précédent",
104075
104191
  nextPage: "Suivant"
104076
104192
  };
104193
+ var graderTabLabels$2 = {
104194
+ header: {
104195
+ title: "Évaluateur",
104196
+ description: "Configurez la façon dont votre agent note le travail selon une grille que vous définissez."
104197
+ },
104198
+ capability: {
104199
+ title: "Notation",
104200
+ description: "Laissez votre agent noter le travail. Lorsque cette option est activée, l'agent évalue ce que les personnes soumettent selon la grille que vous configurez ci-dessous et partage le résultat sous forme de retour — idéal pour les dissertations, projets et exercices d'entraînement.",
104201
+ offHint: "Activez-la pour rédiger des consignes de notation et construire votre grille. Tout ce que vous configurez ici est conservé si vous la désactivez plus tard."
104202
+ },
104203
+ subTabs: {
104204
+ setup: "Configuration de la notation",
104205
+ rubric: "Grille d'évaluation"
104206
+ },
104207
+ denied: {
104208
+ title: "Pas d'accès aux paramètres de notation",
104209
+ description: "Vous n'avez pas la permission de voir la configuration de notation de cet agent. Demandez l'accès à un administrateur."
104210
+ },
104211
+ warnings: {
104212
+ noConfig: "La notation est activée mais pas encore configurée — l'agent échouera lorsqu'on lui demandera de noter. Enregistrez vos consignes dans Configuration de la notation pour terminer.",
104213
+ noCriteria: "La notation est activée mais la grille est vide — l'agent échouera lorsqu'on lui demandera de noter. Ajoutez au moins un critère dans l'onglet Grille d'évaluation."
104214
+ },
104215
+ config: {
104216
+ sectionTitle: "Configuration de la notation",
104217
+ gradingMode: {
104218
+ label: "Ce qui est noté",
104219
+ options: {
104220
+ submission: "Un travail remis",
104221
+ conversation: "La conversation"
104222
+ },
104223
+ help: {
104224
+ submission: "L'agent note un travail remis par la personne, comme une dissertation ou une réponse.",
104225
+ conversation: "L'agent note le déroulement de toute la conversation."
104226
+ }
104227
+ },
104228
+ feedbackMode: {
104229
+ label: "Retour partagé avec la personne",
104230
+ options: {
104231
+ overall: "Retour global uniquement",
104232
+ perCriteria: "Retour par critère",
104233
+ both: "Global + par critère"
104234
+ },
104235
+ help: "La note globale est toujours calculée — ceci contrôle seulement le niveau de détail partagé."
104236
+ },
104237
+ instructions: {
104238
+ label: "Consignes de notation",
104239
+ placeholder: "Décrivez ce qui est noté et tout ce que l'évaluateur doit savoir au-delà de la grille. Par ex. 'Vous notez une dissertation persuasive de 500 mots sur la politique climatique écrite par un lycéen. Soyez encourageant mais honnête.'",
104240
+ help: "Obligatoire. C'est le contexte que l'agent utilise à chaque notation."
104241
+ },
104242
+ saveButton: "Enregistrer",
104243
+ savingButton: "Enregistrement…"
104244
+ },
104245
+ criteria: {
104246
+ sectionTitle: "Grille d'évaluation",
104247
+ description: "Chaque ligne est un élément que l'agent recherche et le nombre de points qu'il vaut. La note globale correspond aux points obtenus sur le total.",
104248
+ needsConfigHint: "Enregistrez d'abord votre configuration de notation, puis ajoutez des critères ici.",
104249
+ emptyState: "Aucun critère pour l'instant. Ajoutez le premier — par exemple \"Clarté\", 10 points.",
104250
+ addButton: "Ajouter un critère",
104251
+ columns: {
104252
+ name: "Nom",
104253
+ criteria: "Critères",
104254
+ points: "Points"
104255
+ },
104256
+ actionsAria: "Actions pour {name}",
104257
+ edit: "Modifier",
104258
+ "delete": "Supprimer",
104259
+ totalPoints: "Total de points possibles : {total}",
104260
+ scoreHint: "Note globale = points obtenus ÷ {total}.",
104261
+ lastCriterionHint: "Une grille doit contenir au moins un critère — ajoutez-en un autre avant de supprimer celui-ci.",
104262
+ modal: {
104263
+ addTitle: "Ajouter un critère",
104264
+ editTitle: "Modifier le critère",
104265
+ name: {
104266
+ label: "Nom",
104267
+ placeholder: "par ex. Clarté",
104268
+ required: "Le nom est obligatoire"
104269
+ },
104270
+ criteria: {
104271
+ label: "Critères",
104272
+ placeholder: "par ex. L'argumentation est facile à suivre et chaque paragraphe avance une idée claire.",
104273
+ required: "Les critères sont obligatoires"
104274
+ },
104275
+ points: {
104276
+ label: "Points",
104277
+ placeholder: "10",
104278
+ positive: "Les points doivent être un nombre positif"
104279
+ },
104280
+ save: "Enregistrer",
104281
+ saving: "Enregistrement…",
104282
+ cancel: "Annuler"
104283
+ },
104284
+ deleteModal: {
104285
+ title: "Supprimer le critère",
104286
+ confirmationPrefix: "Voulez-vous vraiment supprimer",
104287
+ confirmationSuffix: " de la grille ?",
104288
+ cancel: "Annuler",
104289
+ "delete": "Supprimer",
104290
+ deleting: "Suppression…"
104291
+ }
104292
+ },
104293
+ toasts: {
104294
+ toggleOn: "Notation activée",
104295
+ toggleOff: "Notation désactivée — votre grille est conservée pour la prochaine fois",
104296
+ toggleError: "Impossible de mettre à jour la notation",
104297
+ configSaved: "Configuration de notation enregistrée",
104298
+ configError: "Impossible d'enregistrer la configuration de notation",
104299
+ criterionAdded: "Critère ajouté",
104300
+ criterionUpdated: "Critère mis à jour",
104301
+ criterionDeleted: "Critère supprimé",
104302
+ criterionError: "Impossible de mettre à jour la grille"
104303
+ }
104304
+ };
104077
104305
  var careerExperienceDialog$2 = {
104078
104306
  pleaseSelectACompany: "Veuillez sélectionner une entreprise",
104079
104307
  endDateCannotBeBeforeStartDate: "La date de fin ne peut pas être antérieure à la date de début",
@@ -104227,6 +104455,9 @@ var chatInputFormInsideButtons$2 = {
104227
104455
  studyMode: "Mode Étude",
104228
104456
  deepResearch: "Recherche Approfondie",
104229
104457
  memory: "Mémoire",
104458
+ cowork: "Cowork",
104459
+ coworkNeedsBackend: "Activez un grand modèle local ou connectez-vous à un espace de travail pour utiliser Cowork.",
104460
+ coworkModelTooSmall: "Votre modèle local est trop petit pour Cowork. Choisissez un modèle d’au moins 14 Go dans Modèles Locaux.",
104230
104461
  moreOptions: "Plus d'options"
104231
104462
  };
104232
104463
  var chatInputFormMemoryMenu$2 = {
@@ -108638,6 +108869,7 @@ var fr = {
108638
108869
  careerEducationDialog: careerEducationDialog$2,
108639
108870
  careerEducationTab: careerEducationTab$2,
108640
108871
  gradebookGradebookTab: gradebookGradebookTab$2,
108872
+ graderTabLabels: graderTabLabels$2,
108641
108873
  careerExperienceDialog: careerExperienceDialog$2,
108642
108874
  careerExperienceTab: careerExperienceTab$2,
108643
108875
  careerInstitutionDialog: careerInstitutionDialog$2,
@@ -110079,6 +110311,118 @@ var gradebookGradebookTab$1 = {
110079
110311
  previousPage: "Anterior",
110080
110312
  nextPage: "Siguiente"
110081
110313
  };
110314
+ var graderTabLabels$1 = {
110315
+ header: {
110316
+ title: "Calificador",
110317
+ description: "Configura cómo tu agente califica el trabajo según una rúbrica que tú defines."
110318
+ },
110319
+ capability: {
110320
+ title: "Calificación",
110321
+ description: "Permite que tu agente califique trabajos. Cuando está activado, el agente puntúa lo que las personas envían según la rúbrica que configures abajo y comparte el resultado como retroalimentación — ideal para ensayos, proyectos y ejercicios de práctica.",
110322
+ offHint: "Actívalo para escribir instrucciones de calificación y crear tu rúbrica. Todo lo que configures aquí se conserva si luego lo desactivas."
110323
+ },
110324
+ subTabs: {
110325
+ setup: "Configuración de calificación",
110326
+ rubric: "Rúbrica"
110327
+ },
110328
+ denied: {
110329
+ title: "Sin acceso a la configuración de calificación",
110330
+ description: "No tienes permiso para ver la configuración de calificación de este agente. Pide acceso a un administrador."
110331
+ },
110332
+ warnings: {
110333
+ noConfig: "La calificación está activada pero aún no está configurada — el agente fallará cuando se le pida calificar. Guarda tus instrucciones en Configuración de calificación para terminar.",
110334
+ noCriteria: "La calificación está activada pero la rúbrica está vacía — el agente fallará cuando se le pida calificar. Agrega al menos un criterio en la pestaña Rúbrica."
110335
+ },
110336
+ config: {
110337
+ sectionTitle: "Configuración de calificación",
110338
+ gradingMode: {
110339
+ label: "Qué se califica",
110340
+ options: {
110341
+ submission: "Una entrega",
110342
+ conversation: "La conversación"
110343
+ },
110344
+ help: {
110345
+ submission: "El agente califica un trabajo que la persona entrega, como un ensayo o una respuesta.",
110346
+ conversation: "El agente califica cómo fue toda la conversación."
110347
+ }
110348
+ },
110349
+ feedbackMode: {
110350
+ label: "Retroalimentación compartida con la persona",
110351
+ options: {
110352
+ overall: "Solo retroalimentación general",
110353
+ perCriteria: "Retroalimentación por criterio",
110354
+ both: "General + por criterio"
110355
+ },
110356
+ help: "La puntuación general siempre se calcula — esto solo controla cuánto detalle se comparte."
110357
+ },
110358
+ instructions: {
110359
+ label: "Instrucciones de calificación",
110360
+ placeholder: "Describe qué se está calificando y todo lo que el calificador deba saber más allá de la rúbrica. P. ej. 'Estás calificando un ensayo persuasivo de 500 palabras sobre política climática escrito por un estudiante de secundaria. Sé alentador pero honesto.'",
110361
+ help: "Obligatorio. Este es el contexto que el agente usa cada vez que califica."
110362
+ },
110363
+ saveButton: "Guardar",
110364
+ savingButton: "Guardando…"
110365
+ },
110366
+ criteria: {
110367
+ sectionTitle: "Rúbrica",
110368
+ description: "Cada fila es algo que el agente evalúa y cuántos puntos vale. La puntuación general son los puntos obtenidos sobre el total.",
110369
+ needsConfigHint: "Primero guarda tu configuración de calificación y luego agrega criterios aquí.",
110370
+ emptyState: "Aún no hay criterios. Agrega el primero — por ejemplo \"Claridad\", 10 puntos.",
110371
+ addButton: "Agregar criterio",
110372
+ columns: {
110373
+ name: "Nombre",
110374
+ criteria: "Criterios",
110375
+ points: "Puntos"
110376
+ },
110377
+ actionsAria: "Acciones para {name}",
110378
+ edit: "Editar",
110379
+ "delete": "Eliminar",
110380
+ totalPoints: "Total de puntos posibles: {total}",
110381
+ scoreHint: "Puntuación general = puntos obtenidos ÷ {total}.",
110382
+ lastCriterionHint: "Una rúbrica necesita al menos un criterio — agrega otro antes de eliminar este.",
110383
+ modal: {
110384
+ addTitle: "Agregar criterio",
110385
+ editTitle: "Editar criterio",
110386
+ name: {
110387
+ label: "Nombre",
110388
+ placeholder: "p. ej. Claridad",
110389
+ required: "El nombre es obligatorio"
110390
+ },
110391
+ criteria: {
110392
+ label: "Criterios",
110393
+ placeholder: "p. ej. El argumento es fácil de seguir y cada párrafo plantea una idea clara.",
110394
+ required: "Los criterios son obligatorios"
110395
+ },
110396
+ points: {
110397
+ label: "Puntos",
110398
+ placeholder: "10",
110399
+ positive: "Los puntos deben ser un número positivo"
110400
+ },
110401
+ save: "Guardar",
110402
+ saving: "Guardando…",
110403
+ cancel: "Cancelar"
110404
+ },
110405
+ deleteModal: {
110406
+ title: "Eliminar criterio",
110407
+ confirmationPrefix: "¿Seguro que quieres eliminar",
110408
+ confirmationSuffix: " de la rúbrica?",
110409
+ cancel: "Cancelar",
110410
+ "delete": "Eliminar",
110411
+ deleting: "Eliminando…"
110412
+ }
110413
+ },
110414
+ toasts: {
110415
+ toggleOn: "Calificación activada",
110416
+ toggleOff: "Calificación desactivada — tu rúbrica se conserva para la próxima vez",
110417
+ toggleError: "No se pudo actualizar la calificación",
110418
+ configSaved: "Configuración de calificación guardada",
110419
+ configError: "No se pudo guardar la configuración de calificación",
110420
+ criterionAdded: "Criterio agregado",
110421
+ criterionUpdated: "Criterio actualizado",
110422
+ criterionDeleted: "Criterio eliminado",
110423
+ criterionError: "No se pudo actualizar la rúbrica"
110424
+ }
110425
+ };
110082
110426
  var careerExperienceDialog$1 = {
110083
110427
  pleaseSelectACompany: "Por favor, selecciona una empresa",
110084
110428
  endDateCannotBeBeforeStartDate: "La fecha de fin no puede ser anterior a la fecha de inicio",
@@ -110232,6 +110576,9 @@ var chatInputFormInsideButtons$1 = {
110232
110576
  studyMode: "Modo Estudio",
110233
110577
  deepResearch: "Investigación Profunda",
110234
110578
  memory: "Memoria",
110579
+ cowork: "Cowork",
110580
+ coworkNeedsBackend: "Activa un modelo local grande o inicia sesión en un espacio de trabajo para usar Cowork.",
110581
+ coworkModelTooSmall: "Tu modelo local es demasiado pequeño para Cowork. Elige un modelo de al menos 14 GB en Modelos Locales.",
110235
110582
  moreOptions: "Más opciones"
110236
110583
  };
110237
110584
  var chatInputFormMemoryMenu$1 = {
@@ -114643,6 +114990,7 @@ var es = {
114643
114990
  careerEducationDialog: careerEducationDialog$1,
114644
114991
  careerEducationTab: careerEducationTab$1,
114645
114992
  gradebookGradebookTab: gradebookGradebookTab$1,
114993
+ graderTabLabels: graderTabLabels$1,
114646
114994
  careerExperienceDialog: careerExperienceDialog$1,
114647
114995
  careerExperienceTab: careerExperienceTab$1,
114648
114996
  careerInstitutionDialog: careerInstitutionDialog$1,
@@ -116084,6 +116432,118 @@ var gradebookGradebookTab = {
116084
116432
  previousPage: "上一页",
116085
116433
  nextPage: "下一页"
116086
116434
  };
116435
+ var graderTabLabels = {
116436
+ header: {
116437
+ title: "评分器",
116438
+ description: "设置您的智能体如何按照您定义的评分标准来评分。"
116439
+ },
116440
+ capability: {
116441
+ title: "评分",
116442
+ description: "让您的智能体为作业评分。开启后,智能体会根据您在下方设置的评分标准为提交的内容打分,并以反馈的形式分享结果 — 非常适合作文、项目和练习。",
116443
+ offHint: "开启后即可编写评分说明并创建评分标准。即使之后关闭,您在此设置的内容也会被保留。"
116444
+ },
116445
+ subTabs: {
116446
+ setup: "评分设置",
116447
+ rubric: "评分标准"
116448
+ },
116449
+ denied: {
116450
+ title: "无权访问评分设置",
116451
+ description: "您没有权限查看此智能体的评分设置。请向管理员申请访问权限。"
116452
+ },
116453
+ warnings: {
116454
+ noConfig: "评分已开启但尚未设置 — 智能体在被要求评分时会失败。请在评分设置中保存评分说明以完成设置。",
116455
+ noCriteria: "评分已开启但评分标准为空 — 智能体在被要求评分时会失败。请在评分标准页签中添加至少一条标准。"
116456
+ },
116457
+ config: {
116458
+ sectionTitle: "评分设置",
116459
+ gradingMode: {
116460
+ label: "评分对象",
116461
+ options: {
116462
+ submission: "一次提交",
116463
+ conversation: "整个对话"
116464
+ },
116465
+ help: {
116466
+ submission: "智能体为提交的作业评分,例如一篇作文或一个答案。",
116467
+ conversation: "智能体为整个对话的表现评分。"
116468
+ }
116469
+ },
116470
+ feedbackMode: {
116471
+ label: "与用户分享的反馈",
116472
+ options: {
116473
+ overall: "仅总体反馈",
116474
+ perCriteria: "按标准逐项反馈",
116475
+ both: "总体 + 逐项"
116476
+ },
116477
+ help: "总分始终会被计算 — 此设置仅控制分享反馈的详细程度。"
116478
+ },
116479
+ instructions: {
116480
+ label: "评分说明",
116481
+ placeholder: "描述评分的内容以及评分标准之外评分者需要了解的信息。例如:'你正在为一名高中生撰写的 500 字气候政策议论文评分。要鼓励但诚实。'",
116482
+ help: "必填。这是智能体每次评分时使用的上下文。"
116483
+ },
116484
+ saveButton: "保存",
116485
+ savingButton: "保存中…"
116486
+ },
116487
+ criteria: {
116488
+ sectionTitle: "评分标准",
116489
+ description: "每一行是智能体考察的一个方面及其分值。总分为获得的分数除以总分值。",
116490
+ needsConfigHint: "请先保存评分设置,然后在此添加评分标准。",
116491
+ emptyState: "还没有标准。请添加第一条 — 例如\"清晰度\",10 分。",
116492
+ addButton: "添加标准",
116493
+ columns: {
116494
+ name: "名称",
116495
+ criteria: "标准",
116496
+ points: "分数"
116497
+ },
116498
+ actionsAria: "{name} 的操作",
116499
+ edit: "编辑",
116500
+ "delete": "删除",
116501
+ totalPoints: "总分值:{total}",
116502
+ scoreHint: "总分 = 获得的分数 ÷ {total}。",
116503
+ lastCriterionHint: "评分标准至少需要一条 — 请先添加另一条再删除这一条。",
116504
+ modal: {
116505
+ addTitle: "添加标准",
116506
+ editTitle: "编辑标准",
116507
+ name: {
116508
+ label: "名称",
116509
+ placeholder: "例如:清晰度",
116510
+ required: "名称为必填项"
116511
+ },
116512
+ criteria: {
116513
+ label: "标准",
116514
+ placeholder: "例如:论点易于理解,每段都有一个明确的观点。",
116515
+ required: "标准为必填项"
116516
+ },
116517
+ points: {
116518
+ label: "分数",
116519
+ placeholder: "10",
116520
+ positive: "分数必须为正数"
116521
+ },
116522
+ save: "保存",
116523
+ saving: "保存中…",
116524
+ cancel: "取消"
116525
+ },
116526
+ deleteModal: {
116527
+ title: "删除标准",
116528
+ confirmationPrefix: "确定要从评分标准中删除",
116529
+ confirmationSuffix: " 吗?",
116530
+ cancel: "取消",
116531
+ "delete": "删除",
116532
+ deleting: "删除中…"
116533
+ }
116534
+ },
116535
+ toasts: {
116536
+ toggleOn: "评分已开启",
116537
+ toggleOff: "评分已关闭 — 您的评分标准已保留,下次可继续使用",
116538
+ toggleError: "无法更新评分",
116539
+ configSaved: "评分设置已保存",
116540
+ configError: "无法保存评分设置",
116541
+ criterionAdded: "标准已添加",
116542
+ criterionUpdated: "标准已更新",
116543
+ criterionDeleted: "标准已删除",
116544
+ criterionError: "无法更新评分标准"
116545
+ }
116546
+ };
116087
116547
  var careerExperienceDialog = {
116088
116548
  pleaseSelectACompany: "请选择一家公司",
116089
116549
  endDateCannotBeBeforeStartDate: "结束日期不能早于开始日期",
@@ -116237,6 +116697,9 @@ var chatInputFormInsideButtons = {
116237
116697
  studyMode: "学习模式",
116238
116698
  deepResearch: "深度研究",
116239
116699
  memory: "记忆",
116700
+ cowork: "Cowork",
116701
+ coworkNeedsBackend: "启用大型本地模型或登录工作区即可使用 Cowork。",
116702
+ coworkModelTooSmall: "您的本地模型太小,无法用于 Cowork。请在“本地模型”中选择至少 14GB 的模型。",
116240
116703
  moreOptions: "更多选项"
116241
116704
  };
116242
116705
  var chatInputFormMemoryMenu = {
@@ -120648,6 +121111,7 @@ var zh = {
120648
121111
  careerEducationDialog: careerEducationDialog,
120649
121112
  careerEducationTab: careerEducationTab,
120650
121113
  gradebookGradebookTab: gradebookGradebookTab,
121114
+ graderTabLabels: graderTabLabels,
120651
121115
  careerExperienceDialog: careerExperienceDialog,
120652
121116
  careerExperienceTab: careerExperienceTab,
120653
121117
  careerInstitutionDialog: careerInstitutionDialog,
@@ -127006,344 +127470,8 @@ var Indicator$2 = ProgressIndicator;
127006
127470
  const Progress = React.forwardRef(({ className, value, ...props }, ref) => (jsx(Root$9, { ref: ref, className: cn('bg-secondary relative h-4 w-full overflow-hidden rounded-full', className), ...props, children: jsx(Indicator$2, { className: "bg-primary h-full w-full flex-1 transition-all", style: { transform: `translateX(-${100 - (value || 0)}%)` } }) })));
127007
127471
  Progress.displayName = Root$9.displayName;
127008
127472
 
127009
- var SLOTTABLE_IDENTIFIER = Symbol("radix.slottable");
127010
- // @__NO_SIDE_EFFECTS__
127011
- function createSlottable(ownerName) {
127012
- const Slottable2 = ({ children }) => {
127013
- return /* @__PURE__ */ jsx(Fragment$1, { children });
127014
- };
127015
- Slottable2.displayName = `${ownerName}.Slottable`;
127016
- Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
127017
- return Slottable2;
127018
- }
127019
-
127020
- var ROOT_NAME = "AlertDialog";
127021
- var [createAlertDialogContext] = createContextScope$1(ROOT_NAME, [
127022
- createDialogScope
127023
- ]);
127024
- var useDialogScope = createDialogScope();
127025
- var AlertDialog$1 = (props) => {
127026
- const { __scopeAlertDialog, ...alertDialogProps } = props;
127027
- const dialogScope = useDialogScope(__scopeAlertDialog);
127028
- return /* @__PURE__ */ jsx(DialogPrimitive.Root, { ...dialogScope, ...alertDialogProps, modal: true });
127029
- };
127030
- AlertDialog$1.displayName = ROOT_NAME;
127031
- var TRIGGER_NAME$3 = "AlertDialogTrigger";
127032
- var AlertDialogTrigger = React.forwardRef(
127033
- (props, forwardedRef) => {
127034
- const { __scopeAlertDialog, ...triggerProps } = props;
127035
- const dialogScope = useDialogScope(__scopeAlertDialog);
127036
- return /* @__PURE__ */ jsx(DialogPrimitive.Trigger, { ...dialogScope, ...triggerProps, ref: forwardedRef });
127037
- }
127038
- );
127039
- AlertDialogTrigger.displayName = TRIGGER_NAME$3;
127040
- var PORTAL_NAME$2 = "AlertDialogPortal";
127041
- var AlertDialogPortal$1 = (props) => {
127042
- const { __scopeAlertDialog, ...portalProps } = props;
127043
- const dialogScope = useDialogScope(__scopeAlertDialog);
127044
- return /* @__PURE__ */ jsx(DialogPrimitive.Portal, { ...dialogScope, ...portalProps });
127045
- };
127046
- AlertDialogPortal$1.displayName = PORTAL_NAME$2;
127047
- var OVERLAY_NAME = "AlertDialogOverlay";
127048
- var AlertDialogOverlay$1 = React.forwardRef(
127049
- (props, forwardedRef) => {
127050
- const { __scopeAlertDialog, ...overlayProps } = props;
127051
- const dialogScope = useDialogScope(__scopeAlertDialog);
127052
- return /* @__PURE__ */ jsx(DialogPrimitive.Overlay, { ...dialogScope, ...overlayProps, ref: forwardedRef });
127053
- }
127054
- );
127055
- AlertDialogOverlay$1.displayName = OVERLAY_NAME;
127056
- var CONTENT_NAME$4 = "AlertDialogContent";
127057
- var [AlertDialogContentProvider, useAlertDialogContentContext] = createAlertDialogContext(CONTENT_NAME$4);
127058
- var Slottable = createSlottable("AlertDialogContent");
127059
- var AlertDialogContent$1 = React.forwardRef(
127060
- (props, forwardedRef) => {
127061
- const { __scopeAlertDialog, children, ...contentProps } = props;
127062
- const dialogScope = useDialogScope(__scopeAlertDialog);
127063
- const contentRef = React.useRef(null);
127064
- const composedRefs = useComposedRefs$1(forwardedRef, contentRef);
127065
- const cancelRef = React.useRef(null);
127066
- return /* @__PURE__ */ jsx(
127067
- DialogPrimitive.WarningProvider,
127068
- {
127069
- contentName: CONTENT_NAME$4,
127070
- titleName: TITLE_NAME,
127071
- docsSlug: "alert-dialog",
127072
- children: /* @__PURE__ */ jsx(AlertDialogContentProvider, { scope: __scopeAlertDialog, cancelRef, children: /* @__PURE__ */ jsxs(
127073
- DialogPrimitive.Content,
127074
- {
127075
- role: "alertdialog",
127076
- ...dialogScope,
127077
- ...contentProps,
127078
- ref: composedRefs,
127079
- onOpenAutoFocus: composeEventHandlers$2(contentProps.onOpenAutoFocus, (event) => {
127080
- event.preventDefault();
127081
- cancelRef.current?.focus({ preventScroll: true });
127082
- }),
127083
- onPointerDownOutside: (event) => event.preventDefault(),
127084
- onInteractOutside: (event) => event.preventDefault(),
127085
- children: [
127086
- /* @__PURE__ */ jsx(Slottable, { children }),
127087
- /* @__PURE__ */ jsx(DescriptionWarning, { contentRef })
127088
- ]
127089
- }
127090
- ) })
127091
- }
127092
- );
127093
- }
127094
- );
127095
- AlertDialogContent$1.displayName = CONTENT_NAME$4;
127096
- var TITLE_NAME = "AlertDialogTitle";
127097
- var AlertDialogTitle$1 = React.forwardRef(
127098
- (props, forwardedRef) => {
127099
- const { __scopeAlertDialog, ...titleProps } = props;
127100
- const dialogScope = useDialogScope(__scopeAlertDialog);
127101
- return /* @__PURE__ */ jsx(DialogPrimitive.Title, { ...dialogScope, ...titleProps, ref: forwardedRef });
127102
- }
127103
- );
127104
- AlertDialogTitle$1.displayName = TITLE_NAME;
127105
- var DESCRIPTION_NAME = "AlertDialogDescription";
127106
- var AlertDialogDescription$1 = React.forwardRef((props, forwardedRef) => {
127107
- const { __scopeAlertDialog, ...descriptionProps } = props;
127108
- const dialogScope = useDialogScope(__scopeAlertDialog);
127109
- return /* @__PURE__ */ jsx(DialogPrimitive.Description, { ...dialogScope, ...descriptionProps, ref: forwardedRef });
127110
- });
127111
- AlertDialogDescription$1.displayName = DESCRIPTION_NAME;
127112
- var ACTION_NAME = "AlertDialogAction";
127113
- var AlertDialogAction$1 = React.forwardRef(
127114
- (props, forwardedRef) => {
127115
- const { __scopeAlertDialog, ...actionProps } = props;
127116
- const dialogScope = useDialogScope(__scopeAlertDialog);
127117
- return /* @__PURE__ */ jsx(DialogPrimitive.Close, { ...dialogScope, ...actionProps, ref: forwardedRef });
127118
- }
127119
- );
127120
- AlertDialogAction$1.displayName = ACTION_NAME;
127121
- var CANCEL_NAME = "AlertDialogCancel";
127122
- var AlertDialogCancel$1 = React.forwardRef(
127123
- (props, forwardedRef) => {
127124
- const { __scopeAlertDialog, ...cancelProps } = props;
127125
- const { cancelRef } = useAlertDialogContentContext(CANCEL_NAME, __scopeAlertDialog);
127126
- const dialogScope = useDialogScope(__scopeAlertDialog);
127127
- const ref = useComposedRefs$1(forwardedRef, cancelRef);
127128
- return /* @__PURE__ */ jsx(DialogPrimitive.Close, { ...dialogScope, ...cancelProps, ref });
127129
- }
127130
- );
127131
- AlertDialogCancel$1.displayName = CANCEL_NAME;
127132
- var DescriptionWarning = ({ contentRef }) => {
127133
- const MESSAGE = `\`${CONTENT_NAME$4}\` requires a description for the component to be accessible for screen reader users.
127134
-
127135
- You can add a description to the \`${CONTENT_NAME$4}\` by passing a \`${DESCRIPTION_NAME}\` component as a child, which also benefits sighted users by adding visible context to the dialog.
127136
-
127137
- 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$4}\`. 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.
127138
-
127139
- For more information, see https://radix-ui.com/primitives/docs/components/alert-dialog`;
127140
- React.useEffect(() => {
127141
- const hasDescription = document.getElementById(
127142
- contentRef.current?.getAttribute("aria-describedby")
127143
- );
127144
- if (!hasDescription) console.warn(MESSAGE);
127145
- }, [MESSAGE, contentRef]);
127146
- return null;
127147
- };
127148
- var Root2$4 = AlertDialog$1;
127149
- var Portal2 = AlertDialogPortal$1;
127150
- var Overlay2 = AlertDialogOverlay$1;
127151
- var Content2$2 = AlertDialogContent$1;
127152
- var Action = AlertDialogAction$1;
127153
- var Cancel = AlertDialogCancel$1;
127154
- var Title2 = AlertDialogTitle$1;
127155
- var Description2 = AlertDialogDescription$1;
127156
-
127157
- const AlertDialog = Root2$4;
127158
- const AlertDialogPortal = Portal2;
127159
- 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 })));
127160
- AlertDialogOverlay.displayName = Overlay2.displayName;
127161
- const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => (jsxs(AlertDialogPortal, { children: [jsx(AlertDialogOverlay, {}), jsx(Content2$2, { 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 })] })));
127162
- AlertDialogContent.displayName = Content2$2.displayName;
127163
- const AlertDialogHeader = ({ className, ...props }) => (jsx("div", { className: cn('flex flex-col space-y-2 text-center sm:text-left', className), ...props }));
127164
- AlertDialogHeader.displayName = 'AlertDialogHeader';
127165
- const AlertDialogFooter = ({ className, ...props }) => (jsx("div", { className: cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className), ...props }));
127166
- AlertDialogFooter.displayName = 'AlertDialogFooter';
127167
- const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => (jsx(Title2, { ref: ref, className: cn('text-lg font-semibold', className), ...props })));
127168
- AlertDialogTitle.displayName = Title2.displayName;
127169
- const AlertDialogDescription = React.forwardRef(({ className, ...props }, ref) => (jsx(Description2, { ref: ref, className: cn('text-muted-foreground text-sm', className), ...props })));
127170
- AlertDialogDescription.displayName = Description2.displayName;
127171
- const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => (jsx(Action, { ref: ref, className: cn(buttonVariants(), className), ...props })));
127172
- AlertDialogAction.displayName = Action.displayName;
127173
- const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => (jsx(Cancel, { ref: ref, className: cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className), ...props })));
127174
- AlertDialogCancel.displayName = Cancel.displayName;
127175
-
127176
- // Tauri types for model download functionality
127177
- /**
127178
- * Initial state for model download
127179
- */
127180
- const initialModelDownloadState = {
127181
- status: 'idle',
127182
- progress: 0,
127183
- message: '',
127184
- logs: [],
127185
- lastUpdated: new Date().toISOString(),
127186
- };
127187
- /**
127188
- * Check if the app is running inside Tauri
127189
- * In Tauri 2.0, the global is __TAURI_INTERNALS__ (with withGlobalTauri: true)
127190
- * We also check for __TAURI__ for backwards compatibility
127191
- */
127192
- const isTauriApp$1 = () => {
127193
- if (typeof window === 'undefined')
127194
- return false;
127195
- return '__TAURI_INTERNALS__' in window || '__TAURI__' in window;
127196
- };
127197
- /**
127198
- * Tauri event names
127199
- */
127200
- const TAURI_EVENTS = {
127201
- DOWNLOAD_PROGRESS: 'model:download-progress',
127202
- INSTALLATION_LOG: 'model:installation-log',
127203
- DISK_SPACE_ERROR: 'model:disk-space-error',
127204
- OLLAMA_STATUS: 'model:ollama-status',
127205
- };
127206
- /**
127207
- * Tauri command names
127208
- */
127209
- const TAURI_COMMANDS = {
127210
- INSTALL_OLLAMA: 'install_ollama',
127211
- STOP_OLLAMA: 'stop_ollama',
127212
- CHECK_OLLAMA_STATUS: 'check_ollama_status',
127213
- CHECK_DISK_SPACE: 'check_disk_space_for_model',
127214
- GET_SYSTEM_MEMORY: 'get_system_memory',
127215
- DOWNLOAD_MODEL: 'download_model',
127216
- CANCEL_DOWNLOAD: 'cancel_model_download',
127217
- CHECK_NETWORK_STATUS: 'check_network_status',
127218
- GET_OS_TYPE: 'get_os_type',
127219
- CHECK_FOUNDRY_STATUS: 'check_foundry_local_status',
127220
- START_FOUNDRY_SERVICE: 'start_foundry_local_service',
127221
- LOAD_FOUNDRY_MODEL: 'load_foundry_local_model',
127222
- SET_SELECTED_FOUNDRY_MODEL: 'set_selected_foundry_model',
127223
- GET_SELECTED_FOUNDRY_MODEL: 'get_selected_foundry_model',
127224
- INSTALL_FOUNDRY: 'install_foundry',
127225
- DOWNLOAD_FOUNDRY_MODEL: 'download_foundry_model_cmd',
127226
- GET_RECOMMENDED_FOUNDRY_MODELS: 'get_recommended_foundry_models',
127227
- LOG_STDOUT: 'log_stdout',
127228
- };
127229
- /**
127230
- * Initial state for the System Control install flow.
127231
- */
127232
- const initialGhostOsInstallState = {
127233
- status: 'idle',
127234
- progress: 0,
127235
- message: '',
127236
- logs: [],
127237
- lastUpdated: new Date().toISOString(),
127238
- };
127239
- /**
127240
- * Tauri event names emitted by the host during GhostOS install/setup.
127241
- */
127242
- const GHOST_OS_TAURI_EVENTS = {
127243
- INSTALL_PROGRESS: 'ghost-os:install-progress',
127244
- INSTALLATION_LOG: 'ghost-os:installation-log',
127245
- STATUS: 'ghost-os:status',
127246
- };
127247
- /**
127248
- * Tauri command names the host implements for GhostOS install/setup. The web
127249
- * layer invokes these; the native side performs the clone + setup.
127250
- */
127251
- const GHOST_OS_TAURI_COMMANDS = {
127252
- /** Install and set up GhostOS (clone repo + run setup). */
127253
- INSTALL_GHOST_OS: 'install_ghost_os',
127254
- /** Stop the running GhostOS manager. */
127255
- STOP_GHOST_OS: 'stop_ghost_os',
127256
- /** Report current {@link GhostOsStatus}. */
127257
- CHECK_GHOST_OS_STATUS: 'check_ghost_os_status',
127258
- };
127259
- /**
127260
- * Tauri command strings exposed by `tauri-plugin-macos-permissions`
127261
- * (https://github.com/ayangweb/tauri-plugin-macos-permissions). GhostOS needs
127262
- * macOS Accessibility permission to control the device, so the System Control
127263
- * card surfaces it. These are invoked directly via `useTauri().invoke(...)` —
127264
- * no extra npm dependency. The host must register the plugin in its Tauri app
127265
- * (`tauri_plugin_macos_permissions::init()`). Each `CHECK_*` returns a boolean;
127266
- * each `REQUEST_*` prompts / opens the relevant System Settings pane.
127267
- */
127268
- const MACOS_PERMISSIONS_COMMANDS = {
127269
- CHECK_ACCESSIBILITY: 'plugin:macos-permissions|check_accessibility_permission',
127270
- REQUEST_ACCESSIBILITY: 'plugin:macos-permissions|request_accessibility_permission',
127271
- CHECK_SCREEN_RECORDING: 'plugin:macos-permissions|check_screen_recording_permission',
127272
- REQUEST_SCREEN_RECORDING: 'plugin:macos-permissions|request_screen_recording_permission',
127273
- CHECK_INPUT_MONITORING: 'plugin:macos-permissions|check_input_monitoring_permission',
127274
- REQUEST_INPUT_MONITORING: 'plugin:macos-permissions|request_input_monitoring_permission',
127275
- CHECK_FULL_DISK_ACCESS: 'plugin:macos-permissions|check_full_disk_access_permission',
127276
- REQUEST_FULL_DISK_ACCESS: 'plugin:macos-permissions|request_full_disk_access_permission',
127277
- };
127278
-
127279
- /**
127280
- * Dynamically import Tauri APIs to avoid SSR issues
127281
- */
127282
- const getTauriAPIs = async () => {
127283
- try {
127284
- const { invoke } = await import('@tauri-apps/api/core');
127285
- const { listen } = await import('@tauri-apps/api/event');
127286
- return { invoke, listen };
127287
- }
127288
- catch (error) {
127289
- console.error('Failed to load Tauri APIs:', error);
127290
- return null;
127291
- }
127292
- };
127293
- /**
127294
- * Hook to access Tauri APIs with SSR safety
127295
- *
127296
- * @returns Object with isAvailable flag and Tauri invoke/listen functions
127297
- */
127298
- function useTauri() {
127299
- const [isAvailable, setIsAvailable] = useState(false);
127300
- const [apis, setApis] = useState(null);
127301
- useEffect(() => {
127302
- // Check if we're in a Tauri environment
127303
- const inTauri = isTauriApp$1();
127304
- if (inTauri) {
127305
- getTauriAPIs().then((result) => {
127306
- setApis(result);
127307
- setIsAvailable(!!result);
127308
- });
127309
- }
127310
- }, []);
127311
- /**
127312
- * Invoke a Tauri command
127313
- */
127314
- const invoke = useCallback(async (command, args) => {
127315
- if (!(apis === null || apis === void 0 ? void 0 : apis.invoke)) {
127316
- throw new Error('Tauri is not available');
127317
- }
127318
- return apis.invoke(command, args);
127319
- }, [apis]);
127320
- /**
127321
- * Listen to a Tauri event
127322
- * Returns an unlisten function that should be called on cleanup
127323
- */
127324
- const listen = useCallback(async (event, handler) => {
127325
- // The synchronous global (`__TAURI_INTERNALS__`) exposes `invoke` but
127326
- // frequently NOT `event.listen`, so `apis.listen` can be undefined even
127327
- // though Tauri is fully available. Fall back to the real event API via
127328
- // dynamic import so download progress/completion events are actually
127329
- // delivered — otherwise the UI never leaves the "downloading" state.
127330
- let listenFn = apis === null || apis === void 0 ? void 0 : apis.listen;
127331
- if (!listenFn) {
127332
- const { listen: importedListen } = await import('@tauri-apps/api/event');
127333
- listenFn = importedListen;
127334
- }
127335
- return listenFn(event, (e) => handler(e.payload));
127336
- }, [apis]);
127337
- return {
127338
- isAvailable,
127339
- invoke,
127340
- listen,
127341
- };
127342
- }
127343
-
127344
127473
  const LOCAL_LLM_ENABLED_KEY = 'ibl_local_llm_enabled';
127345
127474
  const LOCAL_LLM_MODEL_KEY = 'ibl_local_llm_model';
127346
- const LOCAL_LLM_TOOL_SUPPORT_KEY = 'ibl_local_llm_tool_support';
127347
127475
  /**
127348
127476
  * Curated catalog of small, on-device-friendly models from the Ollama library
127349
127477
  * (https://ollama.com/library). The model manager itself (Ollama) is not a
@@ -127376,8 +127504,8 @@ const LOCAL_MODELS = [
127376
127504
  },
127377
127505
  { name: 'Granite 4.1', provider: 'IBM', id: 'granite4.1:8b', size: '5.3 GB', tool_support: true },
127378
127506
  { name: 'GPT-OSS 20B', provider: 'OpenAI', id: 'gpt-oss:20b', size: '14 GB', tool_support: true },
127379
- // Larger models. Sizes use "GB" so they parse for the System Control size gate
127380
- // (only models above the configured gate can use System Control).
127507
+ // Larger models. Sizes use "GB" so they parse for the Cowork size gate
127508
+ // (only models above the configured gate can use Cowork).
127381
127509
  {
127382
127510
  name: 'Qwen 3.6',
127383
127511
  provider: 'Alibaba',
@@ -127401,34 +127529,6 @@ const LOCAL_MODELS = [
127401
127529
  tool_support: true,
127402
127530
  },
127403
127531
  ];
127404
- /** The model wired to the existing single-model download flow (default selection). */
127405
- const PRIMARY_MODEL_ID = 'phi4-mini:latest';
127406
- /**
127407
- * Whether a catalog model id (e.g. "llama3.2", "phi3:mini") is present among the
127408
- * installed Ollama tags reported by the backend (e.g. "llama3.2:latest"). Matches
127409
- * an exact tag or the same base model with any tag suffix, so a model pulled as
127410
- * `<id>:latest` still counts as installed.
127411
- */
127412
- function isModelInstalled(modelId, installedTags) {
127413
- if (!installedTags)
127414
- return false;
127415
- return installedTags.some((tag) => tag === modelId || tag.startsWith(`${modelId}:`));
127416
- }
127417
- /**
127418
- * Whether a model id is an Ollama cloud-hosted model — either the plain ":cloud"
127419
- * tag (e.g. "glm-5.2:cloud") or a sized cloud tag (e.g. "gemma4:31b-cloud").
127420
- * Cloud models run on Ollama's servers, so they are available by default — never
127421
- * downloaded, no local memory required, always "ready".
127422
- */
127423
- function isCloudModelId(modelId) {
127424
- return modelId.endsWith(':cloud') || (modelId.includes(':') && modelId.endsWith('-cloud'));
127425
- }
127426
- /**
127427
- * Fraction of the machine's memory above which a model is treated as "too large"
127428
- * and a confirmation is required before downloading. TESTING VALUE — set low (1%)
127429
- * so the warning is easy to trigger; raise toward real capacity (e.g. 0.8) later.
127430
- */
127431
- const MODEL_SIZE_WARN_FRACTION = 0.01;
127432
127532
  /** Multipliers for the size units used in the catalog (binary / 1024-based). */
127433
127533
  const SIZE_UNIT_BYTES = {
127434
127534
  B: 1,
@@ -127450,54 +127550,19 @@ function parseModelSizeBytes(size) {
127450
127550
  return null;
127451
127551
  return value * ((_a = SIZE_UNIT_BYTES[match[2].toUpperCase()]) !== null && _a !== void 0 ? _a : 1);
127452
127552
  }
127453
- /** Format a byte count as a short, human-readable size (e.g. "16.0 GB"). */
127454
- function formatBytes(bytes) {
127455
- if (bytes <= 0)
127456
- return '0 GB';
127457
- const gb = bytes / 1024 ** 3;
127458
- if (gb >= 1)
127459
- return `${gb.toFixed(1)} GB`;
127460
- return `${Math.round(bytes / 1024 ** 2)} MB`;
127461
- }
127462
- /**
127463
- * The memory a model can use on this machine: the larger of system RAM and GPU
127464
- * VRAM (a model runs in one or the other). Returns 0 when memory is unknown.
127465
- */
127466
- function usableMemoryBytes(systemMemory) {
127467
- if (!systemMemory)
127468
- return 0;
127469
- return Math.max(systemMemory.ram_total, systemMemory.vram_total);
127470
- }
127471
- /**
127472
- * Whether `model` is large enough relative to this machine's memory to warrant a
127473
- * "might not run" confirmation. False when memory or the model size is unknown,
127474
- * so the download simply proceeds rather than warning on incomplete data.
127475
- */
127476
- function modelExceedsCapacity(model, systemMemory) {
127477
- const capacity = usableMemoryBytes(systemMemory);
127478
- const modelBytes = parseModelSizeBytes(model.size);
127479
- if (modelBytes == null)
127480
- return false;
127481
- // Unknown memory (capacity 0) → don't warn; the download simply proceeds
127482
- // rather than blocking on incomplete data.
127483
- if (capacity <= 0)
127484
- return false;
127485
- return modelBytes > capacity * MODEL_SIZE_WARN_FRACTION;
127486
- }
127487
127553
  /**
127488
- * Default minimum model size (GB) that can use the System Control Manager
127554
+ * Default minimum model size (GB) that can use the Cowork
127489
127555
  * (GhostOS MCP/tools). Smaller models don't support tools/MCP well, so the
127490
- * manager is gated to models above this size. Per-app overridable via the
127491
- * System Control card's `requiredSizeGb` prop (see SystemControlTabProps).
127556
+ * manager is gated to models above this size.
127492
127557
  */
127493
- const DEFAULT_SYSTEM_CONTROL_REQUIRED_SIZE_GB = 12;
127558
+ const DEFAULT_COWORK_REQUIRED_SIZE_GB = 12;
127494
127559
  /**
127495
127560
  * Whether the given model (by Ollama id) is large enough to use the System
127496
127561
  * Control Manager — i.e. its catalog size exceeds `gb` (default
127497
- * {@link DEFAULT_SYSTEM_CONTROL_REQUIRED_SIZE_GB}). Unknown ids or unparseable
127562
+ * {@link DEFAULT_COWORK_REQUIRED_SIZE_GB}). Unknown ids or unparseable
127498
127563
  * sizes return false (gated off).
127499
127564
  */
127500
- function modelSupportsSystemControl(modelId, gb = DEFAULT_SYSTEM_CONTROL_REQUIRED_SIZE_GB) {
127565
+ function modelSupportsCowork(modelId, gb = DEFAULT_COWORK_REQUIRED_SIZE_GB) {
127501
127566
  if (!modelId)
127502
127567
  return false;
127503
127568
  const model = LOCAL_MODELS.find((m) => m.id === modelId);
@@ -127508,22 +127573,6 @@ function modelSupportsSystemControl(modelId, gb = DEFAULT_SYSTEM_CONTROL_REQUIRE
127508
127573
  return false;
127509
127574
  return bytes > gb * 1024 ** 3;
127510
127575
  }
127511
- /**
127512
- * The smallest catalog model that can use the System Control Manager (size
127513
- * exceeds `gb`), or null if none qualify. Used by the "Upgrade" action to pick
127514
- * the lightest capable model. Ties / unparseable sizes sort last.
127515
- */
127516
- function smallestSystemControlModel(gb = DEFAULT_SYSTEM_CONTROL_REQUIRED_SIZE_GB) {
127517
- const usable = LOCAL_MODELS.filter((m) => modelSupportsSystemControl(m.id, gb));
127518
- if (usable.length === 0)
127519
- return null;
127520
- return usable.reduce((smallest, m) => {
127521
- var _a, _b;
127522
- const a = (_a = parseModelSizeBytes(m.size)) !== null && _a !== void 0 ? _a : Infinity;
127523
- const b = (_b = parseModelSizeBytes(smallest.size)) !== null && _b !== void 0 ? _b : Infinity;
127524
- return a < b ? m : smallest;
127525
- });
127526
- }
127527
127576
  /**
127528
127577
  * Check if local LLM is enabled from localStorage
127529
127578
  */
@@ -127548,57 +127597,21 @@ function getLocalLLMModel() {
127548
127597
  return null;
127549
127598
  return localStorage.getItem(LOCAL_LLM_MODEL_KEY);
127550
127599
  }
127551
- /**
127552
- * Set the model the user has chosen to chat with (Ollama pull id).
127553
- */
127554
- function setLocalLLMModel(modelId) {
127555
- if (typeof window === 'undefined')
127556
- return;
127557
- localStorage.setItem(LOCAL_LLM_MODEL_KEY, modelId);
127558
- }
127559
- /**
127560
- * Persist whether the selected model supports tools (set when a model is chosen).
127561
- */
127562
- function setLocalLLMToolSupport(toolSupport) {
127563
- if (typeof window === 'undefined')
127564
- return;
127565
- localStorage.setItem(LOCAL_LLM_TOOL_SUPPORT_KEY, String(toolSupport));
127566
- }
127567
- function LocalLLMTab({ isAvailable, state, ollamaStatus, systemMemory, isUsingFoundry = false, foundryModels = [], selectedFoundryModel = null, foundryStatus, onStartDownload, onCancelDownload, onInstallOllama, onStopManager, onInstallFoundry, onCheckStatus, onResetState, onSelectFoundryModel, }) {
127568
- var _a, _b, _c, _d, _e, _f, _g;
127600
+ function LocalLLMTab({ isAvailable, state, ollamaStatus, isUsingFoundry = false, foundryModels = [], selectedFoundryModel = null, foundryStatus, onInstallOllama, onStopManager, onInstallFoundry, onCheckStatus, onSelectFoundryModel, }) {
127601
+ var _a, _b, _c, _d, _e;
127569
127602
  const t = useT();
127570
- // Direct Tauri access so the model-size check can read system memory fresh at
127571
- // download-click time (the threaded `systemMemory` prop is only a fast path).
127572
- const { invoke } = useTauri();
127573
127603
  const isDownloading = state.status === 'downloading';
127574
127604
  const isManagerInstalling = state.managerInstalling || (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installing);
127575
127605
  // User preference state - independent of whether model is installed
127576
127606
  const [isEnabled, setIsEnabled] = useState(() => isLocalLLMEnabled());
127577
- // Which model the shared download flow currently targets. Prefer the model
127578
- // recorded in the persisted download state, so the correct row shows progress
127579
- // even after the dialog is closed and reopened mid-download; fall back to a
127580
- // local value for immediate feedback the instant a Download button is clicked.
127581
- const [localActiveModelId, setLocalActiveModelId] = useState(PRIMARY_MODEL_ID);
127582
- const activeModelId = state.status === 'downloading' && state.activeModel ? state.activeModel : localActiveModelId;
127583
- // Which model the user has chosen to chat with (exclusive selection).
127584
- const [selectedModelId, setSelectedModelId] = useState(() => getLocalLLMModel() || PRIMARY_MODEL_ID);
127585
- // A model the user asked to download that is large relative to this machine's
127586
- // memory. Set when we need the user to confirm a "might not run" warning before
127587
- // starting the download; null when no confirmation is pending.
127588
- const [pendingModel, setPendingModel] = useState(null);
127589
127607
  // "Enable Local Models" === run the model manager (Ollama). The toggle is a
127590
127608
  // user preference that is always operable: turning it on installs (if needed)
127591
- // and starts the manager; turning it off stops it. The card + model table are
127592
- // shown whenever the toggle is on (or mid-install), independent of install
127593
- // state, so the user always gets feedback for their choice.
127609
+ // and starts the manager; turning it off stops it. Only the manager status
127610
+ // card is shown here (whenever the toggle is on or mid-install) the per-model
127611
+ // list lives in Agent settings LLM tab, where local models are picked and
127612
+ // downloaded alongside cloud models.
127594
127613
  const managerRunning = (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.running) === true;
127595
127614
  const localModelsEnabled = isEnabled || isManagerInstalling;
127596
- // A model can be chosen for chat only once it is downloaded/ready. The backend
127597
- // reports every installed model tag, so any downloaded model qualifies.
127598
- const selectedModelReady = isCloudModelId(selectedModelId) ||
127599
- isModelInstalled(selectedModelId, ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed_models) ||
127600
- (selectedModelId === PRIMARY_MODEL_ID &&
127601
- (state.status === 'completed' || (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed) === true));
127602
127615
  // Track previous model installation state to detect when it first becomes installed
127603
127616
  const [, setWasModelInstalled] = useState(() => {
127604
127617
  return (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed) === true;
@@ -127712,62 +127725,6 @@ function LocalLLMTab({ isAvailable, state, ollamaStatus, systemMemory, isUsingFo
127712
127725
  onStopManager === null || onStopManager === void 0 ? void 0 : onStopManager();
127713
127726
  }
127714
127727
  };
127715
- const startDownloadNow = (modelId) => {
127716
- setLocalActiveModelId(modelId);
127717
- onStartDownload(modelId);
127718
- };
127719
- const handleDownloadModel = async (modelId) => {
127720
- // Always read system memory FIRST — this is the call that hits the backend
127721
- // `get_system_memory`. Doing it before the catalog lookup guarantees it runs
127722
- // on every Download click (falling back to the value fetched on mount).
127723
- const model = LOCAL_MODELS.find((m) => modelId.startsWith(m.id));
127724
- if (!model) {
127725
- await invoke(TAURI_COMMANDS.LOG_STDOUT, {
127726
- s: `[Local Models] -> Download: bad download request, model ${model}`,
127727
- });
127728
- console.log(`[Local Models] -> Download: bad download request, model ${model}`);
127729
- return;
127730
- }
127731
- let mem = null;
127732
- try {
127733
- mem = await invoke(TAURI_COMMANDS.GET_SYSTEM_MEMORY);
127734
- }
127735
- catch (e) {
127736
- await invoke(TAURI_COMMANDS.LOG_STDOUT, {
127737
- s: `[Local Models] -> Download: couldn't detect system memory ${modelId} ${e}`,
127738
- });
127739
- console.log(`[Local Models] -> Download: couldn't detect system memory {e}`);
127740
- return;
127741
- }
127742
- const exceeds = modelExceedsCapacity(model, mem);
127743
- console.warn('[Local Models] -> Download: download click', {
127744
- modelId,
127745
- systemMemory: mem,
127746
- capacityBytes: usableMemoryBytes(mem),
127747
- modelBytes: parseModelSizeBytes(model.size),
127748
- thresholdBytes: usableMemoryBytes(mem) * MODEL_SIZE_WARN_FRACTION,
127749
- exceeds,
127750
- });
127751
- if (exceeds) {
127752
- setPendingModel(model);
127753
- return;
127754
- }
127755
- startDownloadNow(modelId);
127756
- };
127757
- const confirmPendingDownload = () => {
127758
- if (pendingModel) {
127759
- startDownloadNow(pendingModel.id);
127760
- }
127761
- setPendingModel(null);
127762
- };
127763
- const handleSelectModel = (modelId) => {
127764
- var _a, _b;
127765
- setSelectedModelId(modelId);
127766
- setLocalLLMModel(modelId);
127767
- // Persist the model's tool-calling support so the host can route chat to the
127768
- // MCP/tool proxy (:8000) vs plain Ollama (:11434).
127769
- 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);
127770
- };
127771
127728
  // For Ollama the switch reflects the user's toggle preference (always
127772
127729
  // operable, so it can be turned off). Foundry additionally requires a
127773
127730
  // downloaded model to be considered "on".
@@ -127781,54 +127738,16 @@ function LocalLLMTab({ isAvailable, state, ollamaStatus, systemMemory, isUsingFo
127781
127738
  const switchDisabled = isUsingFoundry
127782
127739
  ? !hasDownloadedFoundryModel || isDownloading || state.status === 'checking'
127783
127740
  : isDownloading || isManagerInstalling || state.status === 'checking';
127784
- 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: t('localLlmLocalLlmTab.enableLocalLlm') }), jsx(TooltipProvider, { children: jsxs(Tooltip$1, { children: [jsx(TooltipTrigger, { "aria-label": t('localLlmLocalLlmTab.moreInfoAriaLabel'), 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: t('localLlmLocalLlmTab.tooltipWorkOffline') }), (foundryStatus === null || foundryStatus === void 0 ? void 0 : foundryStatus.is_supported) && (jsx("p", { className: "text-xs text-gray-300", children: t('localLlmLocalLlmTab.tooltipFoundrySupported') }))] }) })] }) })] }), 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": t('localLlmLocalLlmTab.startingModelManagerAriaLabel') })), jsx(Switch, { checked: switchChecked, onCheckedChange: handleToggle, disabled: switchDisabled, "aria-label": t('localLlmLocalLlmTab.switchAriaLabel', {
127785
- state: switchChecked
127786
- ? t('localLlmLocalLlmTab.switchStateEnabled')
127787
- : t('localLlmLocalLlmTab.switchStateDisabled'),
127788
- }), 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: t('localLlmLocalLlmTab.preferredBadge') })] }) }), 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: t('localLlmLocalLlmTab.settingUpOfflineAi') }), jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: t('localLlmLocalLlmTab.foundryInstallDescription') })] })] }), 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 || t('localLlmLocalLlmTab.preparingDownload') })] }), 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 || t('localLlmLocalLlmTab.downloadFailed') })] }), 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" }), t('localLlmLocalLlmTab.tryAgain')] }) })] })) : (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: t('localLlmLocalLlmTab.startingDownload') })] }) })), jsxs("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: [jsx("p", { className: "font-medium mb-1", children: t('localLlmLocalLlmTab.downloadSize') }), jsx("p", { children: t('localLlmLocalLlmTab.oneTimeSetup') })] })] }) })) : /* 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: t('localLlmLocalLlmTab.noteLabel') }), ' ', t('localLlmLocalLlmTab.foundryAlternativeNote')] })] }) })), 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: t('localLlmLocalLlmTab.modelManagerName') }), jsx("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: t('localLlmLocalLlmTab.modelManagerDescription') })] })] }), 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" }), t('localLlmLocalLlmTab.running')] }), jsx(Button$1, { onClick: onCheckStatus, variant: "ghost", size: "sm", "aria-label": t('localLlmLocalLlmTab.refreshManagerStatusAriaLabel'), 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" }), t('localLlmLocalLlmTab.starting')] }), jsx(Button$1, { onClick: onCheckStatus, variant: "ghost", size: "sm", "aria-label": t('localLlmLocalLlmTab.refreshManagerStatusAriaLabel'), 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: t('localLlmLocalLlmTab.tableHeaderUse') }), jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: t('localLlmLocalLlmTab.tableHeaderModel') }), jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: t('localLlmLocalLlmTab.tableHeaderStatus') }), jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: t('localLlmLocalLlmTab.tableHeaderAction') })] }) }), jsx("tbody", { className: "bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-800", children: LOCAL_MODELS.map((model) => {
127789
- const isPrimary = model.id === PRIMARY_MODEL_ID;
127790
- const isActive = activeModelId === model.id;
127791
- // Cloud-hosted models are always available — they run on
127792
- // Ollama's cloud, so there's nothing to download.
127793
- const isCloud = isCloudModelId(model.id);
127794
- // A model is ready once Ollama reports its tag as installed.
127795
- // Fall back to (a) this row's own download having just
127796
- // completed — so it flips to "Ready" before the status
127797
- // refresh lands — and (b) the primary-model status flag.
127798
- // Cloud models are ready by default.
127799
- const isInstalled = isCloud ||
127800
- isModelInstalled(model.id, ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed_models) ||
127801
- (isActive && state.status === 'completed') ||
127802
- (isPrimary && (ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.model_installed) === true);
127803
- // An installed model must never render a download/cancel
127804
- // state — guards against a stale persisted "downloading"
127805
- // status making an already-ready model look like it's
127806
- // still downloading (and wrongly cancellable) on reopen.
127807
- const rowDownloading = isActive && isDownloading && !isInstalled;
127808
- const rowChecking = isActive && state.status === 'checking';
127809
- const rowError = isActive && state.status === 'error';
127810
- const rowCancelled = isActive && state.status === 'cancelled';
127811
- const canDownload = !!(ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed) &&
127812
- !isInstalled &&
127813
- !rowDownloading &&
127814
- !rowChecking;
127815
- const isSelected = selectedModelId === model.id;
127816
- 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": t('localLlmLocalLlmTab.useModelForChatAriaLabel', {
127817
- name: model.name,
127818
- }), title: isInstalled
127819
- ? undefined
127820
- : t('localLlmLocalLlmTab.downloadToChatTitle'), className: isInstalled
127821
- ? 'h-4 w-4 cursor-pointer accent-blue-500'
127822
- : '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" }), t('localLlmLocalLlmTab.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" }), t('localLlmLocalLlmTab.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" }), t('localLlmLocalLlmTab.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" }), t('localLlmLocalLlmTab.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" }), t('localLlmLocalLlmTab.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: t('localLlmLocalLlmTab.notDownloaded') })) }) }), 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" }), t('localLlmLocalLlmTab.cancel')] })), canDownload && (jsxs(Button$1, { onClick: () => handleDownloadModel(model.id), size: "sm", variant: "outline", children: [jsx(Download, { className: "h-4 w-4 mr-2" }), t('localLlmLocalLlmTab.download')] })), rowError && (jsx(Button$1, { onClick: onResetState, variant: "ghost", size: "sm", children: t('localLlmLocalLlmTab.reset') })), isInstalled && !isCloud && (jsx(Button$1, { onClick: onCheckStatus, variant: "ghost", size: "sm", children: jsx(RefreshCw, { className: "h-4 w-4" }) }))] }) })] }, model.id));
127823
- }) })] }), 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: [t('localLlmLocalLlmTab.chattingWith'), ' ', 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: t('localLlmLocalLlmTab.noModelSelectedHint') })) })] }))] })) : (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: t('localLlmLocalLlmTab.offlineAiReady') }), 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" }), t('localLlmLocalLlmTab.preferredBadge')] })] }), jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: t('localLlmLocalLlmTab.foundryReadyDescription') })] })] }) }), 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: t('localLlmLocalLlmTab.selectModelLabel') }), 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: t('localLlmLocalLlmTab.selectModelPlaceholder') }) }), 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: t('localLlmLocalLlmTab.notDownloaded') })), 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: t('localLlmLocalLlmTab.downloaded') }))] })] }) }, model.id))) })] }), jsxs("div", { className: "flex flex-col gap-1", children: [jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: t('localLlmLocalLlmTab.modelsAvailable', { count: foundryModels.length }) }), 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: [t('localLlmLocalLlmTab.usingModelPrefix'), ' ', ((_e = foundryModels.find((m) => m.id === selectedFoundryModel)) === null || _e === void 0 ? void 0 : _e.name) ||
127824
- 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'
127825
- ? 'text-red-600 dark:text-red-400'
127826
- : log.level === 'warn'
127827
- ? 'text-yellow-600 dark:text-yellow-400'
127828
- : 'text-gray-700 dark:text-gray-300'}`, children: log.message }, idx))) }))] }))] }) }))] }), jsx(AlertDialog, { open: !!pendingModel, onOpenChange: (open) => {
127829
- if (!open)
127830
- setPendingModel(null);
127831
- }, 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" })] })] }) })] }));
127741
+ return (jsx("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: t('localLlmLocalLlmTab.enableLocalLlm') }), jsx(TooltipProvider, { children: jsxs(Tooltip$1, { children: [jsx(TooltipTrigger, { "aria-label": t('localLlmLocalLlmTab.moreInfoAriaLabel'), 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: t('localLlmLocalLlmTab.tooltipWorkOffline') }), (foundryStatus === null || foundryStatus === void 0 ? void 0 : foundryStatus.is_supported) && (jsx("p", { className: "text-xs text-gray-300", children: t('localLlmLocalLlmTab.tooltipFoundrySupported') }))] }) })] }) })] }), 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": t('localLlmLocalLlmTab.startingModelManagerAriaLabel') })), jsx(Switch, { checked: switchChecked, onCheckedChange: handleToggle, disabled: switchDisabled, "aria-label": t('localLlmLocalLlmTab.switchAriaLabel', {
127742
+ state: switchChecked
127743
+ ? t('localLlmLocalLlmTab.switchStateEnabled')
127744
+ : t('localLlmLocalLlmTab.switchStateDisabled'),
127745
+ }), 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: t('localLlmLocalLlmTab.preferredBadge') })] }) }), 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: t('localLlmLocalLlmTab.settingUpOfflineAi') }), jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: t('localLlmLocalLlmTab.foundryInstallDescription') })] })] }), 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 || t('localLlmLocalLlmTab.preparingDownload') })] }), 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 || t('localLlmLocalLlmTab.downloadFailed') })] }), 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" }), t('localLlmLocalLlmTab.tryAgain')] }) })] })) : (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: t('localLlmLocalLlmTab.startingDownload') })] }) })), jsxs("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: [jsx("p", { className: "font-medium mb-1", children: t('localLlmLocalLlmTab.downloadSize') }), jsx("p", { children: t('localLlmLocalLlmTab.oneTimeSetup') })] })] }) })) : /* 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: t('localLlmLocalLlmTab.noteLabel') }), ' ', t('localLlmLocalLlmTab.foundryAlternativeNote')] })] }) })), 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: t('localLlmLocalLlmTab.modelManagerName') }), jsx("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: t('localLlmLocalLlmTab.modelManagerDescription') })] })] }), 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" }), t('localLlmLocalLlmTab.running')] }), jsx(Button$1, { onClick: onCheckStatus, variant: "ghost", size: "sm", "aria-label": t('localLlmLocalLlmTab.refreshManagerStatusAriaLabel'), 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" }), t('localLlmLocalLlmTab.starting')] }), jsx(Button$1, { onClick: onCheckStatus, variant: "ghost", size: "sm", "aria-label": t('localLlmLocalLlmTab.refreshManagerStatusAriaLabel'), 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" }) }))] }))] })) : (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: t('localLlmLocalLlmTab.offlineAiReady') }), 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" }), t('localLlmLocalLlmTab.preferredBadge')] })] }), jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: t('localLlmLocalLlmTab.foundryReadyDescription') })] })] }) }), 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: t('localLlmLocalLlmTab.selectModelLabel') }), 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: t('localLlmLocalLlmTab.selectModelPlaceholder') }) }), 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: t('localLlmLocalLlmTab.notDownloaded') })), 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: t('localLlmLocalLlmTab.downloaded') }))] })] }) }, model.id))) })] }), jsxs("div", { className: "flex flex-col gap-1", children: [jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: t('localLlmLocalLlmTab.modelsAvailable', { count: foundryModels.length }) }), 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: [t('localLlmLocalLlmTab.usingModelPrefix'), ' ', ((_c = foundryModels.find((m) => m.id === selectedFoundryModel)) === null || _c === void 0 ? void 0 : _c.name) ||
127746
+ selectedFoundryModel, ((_d = foundryModels.find((m) => m.id === selectedFoundryModel)) === null || _d === void 0 ? void 0 : _d.device) && (jsxs("span", { className: "ml-1 text-gray-500 dark:text-gray-400", children: ["(", (_e = foundryModels.find((m) => m.id === selectedFoundryModel)) === null || _e === void 0 ? void 0 : _e.device, ")"] }))] })] }))] })] })), state.status === 'downloading' && (jsxs("div", { className: "mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg", children: [jsxs("div", { className: "flex items-center gap-3", children: [jsx("div", { className: "animate-spin h-5 w-5 border-2 border-blue-600 dark:border-blue-400 border-t-transparent rounded-full" }), jsxs("div", { className: "flex-1", children: [jsx("p", { className: "text-sm font-medium text-blue-900 dark:text-blue-100", children: state.message }), state.progress > 0 && (jsx("div", { className: "mt-2 w-full bg-blue-200 dark:bg-blue-800 rounded-full h-2", children: jsx("div", { className: "bg-blue-600 dark:bg-blue-400 h-2 rounded-full transition-all duration-300", style: { width: `${state.progress}%` } }) }))] })] }), state.logs.length > 0 && (jsx("div", { className: "mt-3 max-h-32 overflow-y-auto bg-white dark:bg-gray-800 rounded border border-blue-200 dark:border-blue-700 p-2", children: state.logs.slice(-10).map((log, idx) => (jsx("div", { className: `text-xs font-mono ${log.level === 'error'
127747
+ ? 'text-red-600 dark:text-red-400'
127748
+ : log.level === 'warn'
127749
+ ? 'text-yellow-600 dark:text-yellow-400'
127750
+ : 'text-gray-700 dark:text-gray-300'}`, children: log.message }, idx))) }))] }))] }) }))] }) }));
127832
127751
  }
127833
127752
 
127834
127753
  function LocalModelsContent(props) {
@@ -127839,195 +127758,6 @@ function LocalModelsContent(props) {
127839
127758
  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$1, { 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 }) }))] }));
127840
127759
  }
127841
127760
 
127842
- const SYSTEM_CONTROL_ENABLED_KEY = 'ibl_system_control_enabled';
127843
- /**
127844
- * Whether the Computer Assistant is enabled, read from localStorage. This is a
127845
- * user preference (mirrors {@link isLocalLLMEnabled}); it reflects the toggle the
127846
- * user last chose, independent of whether the helper is installed yet.
127847
- */
127848
- function isSystemControlEnabled() {
127849
- if (typeof window === 'undefined')
127850
- return false;
127851
- return localStorage.getItem(SYSTEM_CONTROL_ENABLED_KEY) === 'true';
127852
- }
127853
- /**
127854
- * Persist the Computer Assistant enabled preference to localStorage.
127855
- */
127856
- function setSystemControlEnabled(enabled) {
127857
- if (typeof window === 'undefined')
127858
- return;
127859
- localStorage.setItem(SYSTEM_CONTROL_ENABLED_KEY, String(enabled));
127860
- }
127861
- /** Green "done" pill. */
127862
- function DonePill({ children }) {
127863
- 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] }));
127864
- }
127865
- /** Blue "in progress" pill. */
127866
- function WorkingPill({ children }) {
127867
- 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] }));
127868
- }
127869
- /** A single numbered step row. */
127870
- function Step$2({ number, done, title, description, action, }) {
127871
- 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
127872
- ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300'
127873
- : '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 })] }));
127874
- }
127875
- /**
127876
- * Body of the "Computer Assistant" card: a plain-language on/off with two simple
127877
- * steps — set up, then allow control — so non-technical users can follow along.
127878
- * Mirrors the Local Models tab so the two cards behave the same.
127879
- */
127880
- function SystemControlTab({ isAvailable, state, status, requiredSizeGb = DEFAULT_SYSTEM_CONTROL_REQUIRED_SIZE_GB, ollamaStatus, systemMemory, accessibilityPermission, onInstall, onStop, onCheckStatus, onResetState, onRequestAccessibilityPermission, onDownloadModel, }) {
127881
- const isInstalling = state.status === 'installing' || (status === null || status === void 0 ? void 0 : status.installing) === true;
127882
- // User preference — independent of whether the helper is installed. Always
127883
- // operable so the user can turn it off again.
127884
- const [isEnabled, setIsEnabled] = useState(() => isSystemControlEnabled());
127885
- // The currently-selected local model (persisted by the Local Models card).
127886
- // Polled so a selection change in the sibling card is reflected here without a
127887
- // remount. The assistant only works well with larger models, so it's gated to
127888
- // a selected model above the size threshold.
127889
- const [selectedModel, setSelectedModel] = useState(() => getLocalLLMModel());
127890
- // Whether "Local Models" is turned on. The assistant runs on a local model, so
127891
- // it's gated off until Local Models is enabled. Polled alongside the model so a
127892
- // toggle in the sibling card is reflected without a remount.
127893
- const [localModelsEnabled, setLocalModelsEnabled] = useState(() => isLocalLLMEnabled());
127894
- useEffect(() => {
127895
- const id = setInterval(() => {
127896
- setSelectedModel(getLocalLLMModel());
127897
- setLocalModelsEnabled(isLocalLLMEnabled());
127898
- }, 1500);
127899
- return () => clearInterval(id);
127900
- }, []);
127901
- const localModelsOff = !localModelsEnabled;
127902
- const modelTooSmall = !modelSupportsSystemControl(selectedModel, requiredSizeGb);
127903
- // "Upgrade" target: the smallest catalog model that clears the size gate.
127904
- const upgradeTarget = smallestSystemControlModel(requiredSizeGb);
127905
- // Set while an upgrade is downloading a model, before we auto-enable.
127906
- const [pendingEnableModelId, setPendingEnableModelId] = useState(null);
127907
- // Model awaiting a RAM/VRAM "may be too large" confirmation before download.
127908
- const [pendingCapacityModel, setPendingCapacityModel] = useState(null);
127909
- const isUpgrading = pendingEnableModelId !== null;
127910
- const managerRunning = (status === null || status === void 0 ? void 0 : status.running) === true;
127911
- // Steps are shown whenever the toggle is on (or mid-setup), so the user always
127912
- // gets feedback for their choice.
127913
- const managerEnabled = isEnabled || isInstalling;
127914
- // Permission is known (macOS + plugin wired) and granted.
127915
- const permissionKnown = typeof accessibilityPermission === 'boolean';
127916
- const permissionGranted = accessibilityPermission === true;
127917
- const allSet = managerRunning && (!permissionKnown || permissionGranted);
127918
- // Keep the app-facing preference flag in sync with the toggle.
127919
- useEffect(() => {
127920
- setSystemControlEnabled(isEnabled);
127921
- }, [isEnabled]);
127922
- // While enabled but not yet running, keep polling so the step flips to "Ready"
127923
- // as soon as the host reports the helper is up.
127924
- useEffect(() => {
127925
- if (!isEnabled || managerRunning)
127926
- return;
127927
- const id = setInterval(() => onCheckStatus(), 2500);
127928
- return () => clearInterval(id);
127929
- }, [isEnabled, managerRunning, onCheckStatus]);
127930
- // While permission isn't granted yet, keep re-checking (the grant happens in
127931
- // System Settings) so the step flips to "Allowed" on its own.
127932
- useEffect(() => {
127933
- if (accessibilityPermission !== false)
127934
- return;
127935
- const id = setInterval(() => onCheckStatus(), 2500);
127936
- return () => clearInterval(id);
127937
- }, [accessibilityPermission, onCheckStatus]);
127938
- // After an upgrade kicks off a download, turn the assistant on once the model
127939
- // finishes downloading (it shows up in the installed list).
127940
- useEffect(() => {
127941
- if (!pendingEnableModelId)
127942
- return;
127943
- if (isModelInstalled(pendingEnableModelId, ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed_models)) {
127944
- setPendingEnableModelId(null);
127945
- setIsEnabled(true);
127946
- onInstall();
127947
- }
127948
- }, [pendingEnableModelId, ollamaStatus, onInstall]);
127949
- if (!isAvailable) {
127950
- return (jsx("div", { className: "text-sm text-gray-500 dark:text-gray-400", children: "The Computer Assistant is only available in the desktop app." }));
127951
- }
127952
- const handleToggle = (checked) => {
127953
- // Guard: needs Local Models on and a large-enough model.
127954
- if (checked && (localModelsOff || modelTooSmall))
127955
- return;
127956
- setIsEnabled(checked);
127957
- if (checked) {
127958
- if (!isInstalling)
127959
- onInstall();
127960
- }
127961
- else {
127962
- onStop === null || onStop === void 0 ? void 0 : onStop();
127963
- }
127964
- };
127965
- // Upgrade = switch to the smallest usable model: select it, then (if it isn't
127966
- // already downloaded) download it and auto-enable the assistant once it's
127967
- // ready. The RAM/VRAM check is preserved via the confirm below.
127968
- const runUpgrade = (model) => {
127969
- setLocalLLMModel(model.id);
127970
- setLocalLLMToolSupport(model.tool_support);
127971
- setLocalLLMEnabled(true);
127972
- setSelectedModel(model.id); // recompute the size gate immediately
127973
- if (isModelInstalled(model.id, ollamaStatus === null || ollamaStatus === void 0 ? void 0 : ollamaStatus.installed_models)) {
127974
- // Already downloaded → enable now.
127975
- setIsEnabled(true);
127976
- if (!isInstalling)
127977
- onInstall();
127978
- }
127979
- else {
127980
- // Not downloaded → download, then auto-enable when it lands (effect above).
127981
- setPendingEnableModelId(model.id);
127982
- onDownloadModel === null || onDownloadModel === void 0 ? void 0 : onDownloadModel(model.id);
127983
- }
127984
- };
127985
- const handleUpgrade = () => {
127986
- if (!upgradeTarget)
127987
- return;
127988
- // Keep the RAM/VRAM check: confirm before pulling a model that may not fit.
127989
- if (modelExceedsCapacity(upgradeTarget, systemMemory)) {
127990
- setPendingCapacityModel(upgradeTarget);
127991
- return;
127992
- }
127993
- runUpgrade(upgradeTarget);
127994
- };
127995
- const switchChecked = isEnabled;
127996
- const switchDisabled = isInstalling ||
127997
- state.status === 'checking' ||
127998
- ((localModelsOff || modelTooSmall) && !isEnabled);
127999
- 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
128000
- ? `Upgrade to ${upgradeTarget.name} (${upgradeTarget.size}) to use it.`
128001
- : '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$2, { 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$2, { 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: () => {
128002
- onResetState();
128003
- onInstall();
128004
- }, 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) => {
128005
- if (!open)
128006
- setPendingCapacityModel(null);
128007
- }, 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: () => {
128008
- const model = pendingCapacityModel;
128009
- setPendingCapacityModel(null);
128010
- if (model)
128011
- runUpgrade(model);
128012
- }, children: "Download anyway" })] })] }) })] }));
128013
- }
128014
-
128015
- /**
128016
- * Collapsible "System Control" card for the UserProfile → Advanced section.
128017
- * Mirrors {@link LocalModelsContent}: a purely presentational, titled,
128018
- * collapsible container whose body is the {@link SystemControlTab}. Availability
128019
- * and handlers are threaded in (see UserProfileDropdown, which falls back to
128020
- * {@link useGhostOs} when the host doesn't supply them). Renders nothing outside
128021
- * the desktop app.
128022
- */
128023
- function SystemControlContent(props) {
128024
- const [isCollapsed, setIsCollapsed] = useState(true);
128025
- if (!props.isAvailable) {
128026
- return null;
128027
- }
128028
- 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$1, { 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 }) }))] }));
128029
- }
128030
-
128031
127761
  // src/primitive.tsx
128032
127762
  var NODES = [
128033
127763
  "a",
@@ -176051,6 +175781,26 @@ function ChatPrivacyTab({ org, username }) {
176051
175781
  }) })] }));
176052
175782
  }
176053
175783
 
175784
+ const COWORK_ENABLED_KEY = 'ibl_cowork_enabled';
175785
+ /**
175786
+ * Whether Cowork is enabled, read from localStorage. This is a user preference
175787
+ * (mirrors isLocalLLMEnabled); it reflects the toggle the user last chose,
175788
+ * independent of whether the assistant is installed yet.
175789
+ */
175790
+ function isCoworkEnabled() {
175791
+ if (typeof window === 'undefined')
175792
+ return false;
175793
+ return localStorage.getItem(COWORK_ENABLED_KEY) === 'true';
175794
+ }
175795
+ /**
175796
+ * Persist the Cowork enabled preference to localStorage.
175797
+ */
175798
+ function setCoworkEnabled(enabled) {
175799
+ if (typeof window === 'undefined')
175800
+ return;
175801
+ localStorage.setItem(COWORK_ENABLED_KEY, String(enabled));
175802
+ }
175803
+
176054
175804
  const renderLucideIcon = (Icon) => function RenderedIcon(props) {
176055
175805
  return jsx(Icon, { ...props });
176056
175806
  };
@@ -176077,7 +175827,7 @@ const TAB_DESC_PREVIEW_KEYS = {
176077
175827
  memory: 'descMemoryPreview',
176078
175828
  privacy: 'descPrivacyPreview',
176079
175829
  };
176080
- function Profile({ tenant, username, tenants, onClose, customization = {}, isAdmin = false, targetTab = 'basic', onAccountDeleted, enableMemoryTab = false, localLLMProps, systemControlProps, currentSPA = '', }) {
175830
+ function Profile({ tenant, username, tenants, onClose, customization = {}, isAdmin = false, targetTab = 'basic', onAccountDeleted, enableMemoryTab = false, localLLMProps, currentSPA = '', }) {
176081
175831
  var _a, _b, _c, _d, _e, _f, _g, _h;
176082
175832
  const t = useT();
176083
175833
  const { data: memsearchStatus, isError: isMemsearchStatusError } = useGetMemsearchStatusQuery({ org: tenant, userId: username }, { skip: !enableMemoryTab });
@@ -176154,9 +175904,8 @@ function Profile({ tenant, username, tenants, onClose, customization = {}, isAdm
176154
175904
  ]
176155
175905
  : []),
176156
175906
  ];
176157
- // Add Advanced tab only when in Tauri app (Local Models or System Control is
176158
- // provided and available)
176159
- const isAdvancedAvailable = (localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.isAvailable) === true || (systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.isAvailable) === true;
175907
+ // Add Advanced tab only when in Tauri app (Local Models is provided and available)
175908
+ const isAdvancedAvailable = (localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.isAvailable) === true;
176160
175909
  const TABS = isAdvancedAvailable
176161
175910
  ? [
176162
175911
  ...baseTabs,
@@ -176322,7 +176071,7 @@ function Profile({ tenant, username, tenants, onClose, customization = {}, isAdm
176322
176071
  var _a;
176323
176072
  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: t('profileIndex.placeholderX'), value: field.state.value, onChange: (e) => handleSocialLinkChange(e.target.value, field.handleChange, X_URL_PREFIX), disabled: socialForm.state.isSubmitting || isReadOnly })] }), 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 })] }));
176324
176073
  },
176325
- })] })] }) })), activeTab === 'education' && (jsxs(Tabs, { value: educationSubTab, onValueChange: setEducationSubTab, className: "space-y-4", children: [jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsxs(TabsList, { children: [jsx(TabsTrigger, { value: "education", children: t('profileIndex.tabEducation') }), jsx(TabsTrigger, { value: "credentials", children: t('gradebookGradebookTab.typeCredentials') }), jsx(TabsTrigger, { value: "skills", children: t('gradebookGradebookTab.typeSkills') })] }), headerAddButton] }), jsx(TabsContent, { value: "education", children: jsx(EducationTab, { org: tenant, username: username }) }), jsx(TabsContent, { value: "credentials", children: jsx(CredentialsList, { org: tenant, username: username }) }), jsx(TabsContent, { value: "skills", children: jsx(SkillsList, { org: tenant, username: username }) })] })), activeTab === 'gradebook' && jsx(GradebookTab, { org: tenant, username: username }), activeTab === 'experience' && (jsxs(Tabs, { value: experienceSubTab, onValueChange: setExperienceSubTab, className: "space-y-4", children: [jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsxs(TabsList, { children: [jsx(TabsTrigger, { value: "experience", children: t('profileIndex.tabExperience') }), jsx(TabsTrigger, { value: "resume", children: t('profileIndex.tabResume') })] }), headerAddButton] }), jsx(TabsContent, { value: "experience", children: jsx(ExperienceTab, { org: tenant, username: username }) }), jsx(TabsContent, { value: "resume", children: 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 }), !isReadOnly && activeTab === 'security' && (jsx(Security, { app: currentSPA || '', email: (userMetadata === null || userMetadata === void 0 ? void 0 : userMetadata.email) || '', platformKey: tenant, 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 }))] }))] }), !isReadOnly && ['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: t('profileIndex.buttonCancel') }), jsx(Button$1, { onClick: () => {
176074
+ })] })] }) })), activeTab === 'education' && (jsxs(Tabs, { value: educationSubTab, onValueChange: setEducationSubTab, className: "space-y-4", children: [jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsxs(TabsList, { children: [jsx(TabsTrigger, { value: "education", children: t('profileIndex.tabEducation') }), jsx(TabsTrigger, { value: "credentials", children: t('gradebookGradebookTab.typeCredentials') }), jsx(TabsTrigger, { value: "skills", children: t('gradebookGradebookTab.typeSkills') })] }), headerAddButton] }), jsx(TabsContent, { value: "education", children: jsx(EducationTab, { org: tenant, username: username }) }), jsx(TabsContent, { value: "credentials", children: jsx(CredentialsList, { org: tenant, username: username }) }), jsx(TabsContent, { value: "skills", children: jsx(SkillsList, { org: tenant, username: username }) })] })), activeTab === 'gradebook' && jsx(GradebookTab, { org: tenant, username: username }), activeTab === 'experience' && (jsxs(Tabs, { value: experienceSubTab, onValueChange: setExperienceSubTab, className: "space-y-4", children: [jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsxs(TabsList, { children: [jsx(TabsTrigger, { value: "experience", children: t('profileIndex.tabExperience') }), jsx(TabsTrigger, { value: "resume", children: t('profileIndex.tabResume') })] }), headerAddButton] }), jsx(TabsContent, { value: "experience", children: jsx(ExperienceTab, { org: tenant, username: username }) }), jsx(TabsContent, { value: "resume", children: 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 }), !isReadOnly && activeTab === 'security' && (jsx(Security, { app: currentSPA || '', email: (userMetadata === null || userMetadata === void 0 ? void 0 : userMetadata.email) || '', platformKey: tenant, onAccountDeleted: onAccountDeleted })), activeTab === 'advanced' && localLLMProps && (jsx("div", { className: "space-y-6", children: 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 }) }))] }), !isReadOnly && ['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: t('profileIndex.buttonCancel') }), jsx(Button$1, { onClick: () => {
176326
176075
  if (activeTab === 'basic') {
176327
176076
  basicForm.handleSubmit();
176328
176077
  }
@@ -177801,11 +177550,11 @@ var PopoverAnchor$1 = React.forwardRef(
177801
177550
  }
177802
177551
  );
177803
177552
  PopoverAnchor$1.displayName = ANCHOR_NAME$1;
177804
- var TRIGGER_NAME$2 = "PopoverTrigger";
177553
+ var TRIGGER_NAME$3 = "PopoverTrigger";
177805
177554
  var PopoverTrigger$1 = React.forwardRef(
177806
177555
  (props, forwardedRef) => {
177807
177556
  const { __scopePopover, ...triggerProps } = props;
177808
- const context = usePopoverContext(TRIGGER_NAME$2, __scopePopover);
177557
+ const context = usePopoverContext(TRIGGER_NAME$3, __scopePopover);
177809
177558
  const popperScope = usePopperScope$1(__scopePopover);
177810
177559
  const composedTriggerRef = useComposedRefs$1(forwardedRef, context.triggerRef);
177811
177560
  const trigger = /* @__PURE__ */ jsx(
@@ -177824,31 +177573,31 @@ var PopoverTrigger$1 = React.forwardRef(
177824
177573
  return context.hasCustomAnchor ? trigger : /* @__PURE__ */ jsx(Anchor$3, { asChild: true, ...popperScope, children: trigger });
177825
177574
  }
177826
177575
  );
177827
- PopoverTrigger$1.displayName = TRIGGER_NAME$2;
177828
- var PORTAL_NAME$1 = "PopoverPortal";
177829
- var [PortalProvider$1, usePortalContext$1] = createPopoverContext(PORTAL_NAME$1, {
177576
+ PopoverTrigger$1.displayName = TRIGGER_NAME$3;
177577
+ var PORTAL_NAME$2 = "PopoverPortal";
177578
+ var [PortalProvider$1, usePortalContext$1] = createPopoverContext(PORTAL_NAME$2, {
177830
177579
  forceMount: void 0
177831
177580
  });
177832
177581
  var PopoverPortal = (props) => {
177833
177582
  const { __scopePopover, forceMount, children, container } = props;
177834
- const context = usePopoverContext(PORTAL_NAME$1, __scopePopover);
177583
+ const context = usePopoverContext(PORTAL_NAME$2, __scopePopover);
177835
177584
  return /* @__PURE__ */ jsx(PortalProvider$1, { scope: __scopePopover, forceMount, children: /* @__PURE__ */ jsx(Presence$3, { present: forceMount || context.open, children: /* @__PURE__ */ jsx(Portal$7, { asChild: true, container, children }) }) });
177836
177585
  };
177837
- PopoverPortal.displayName = PORTAL_NAME$1;
177838
- var CONTENT_NAME$3 = "PopoverContent";
177586
+ PopoverPortal.displayName = PORTAL_NAME$2;
177587
+ var CONTENT_NAME$4 = "PopoverContent";
177839
177588
  var PopoverContent$1 = React.forwardRef(
177840
177589
  (props, forwardedRef) => {
177841
- const portalContext = usePortalContext$1(CONTENT_NAME$3, props.__scopePopover);
177590
+ const portalContext = usePortalContext$1(CONTENT_NAME$4, props.__scopePopover);
177842
177591
  const { forceMount = portalContext.forceMount, ...contentProps } = props;
177843
- const context = usePopoverContext(CONTENT_NAME$3, props.__scopePopover);
177592
+ const context = usePopoverContext(CONTENT_NAME$4, props.__scopePopover);
177844
177593
  return /* @__PURE__ */ jsx(Presence$3, { present: forceMount || context.open, children: context.modal ? /* @__PURE__ */ jsx(PopoverContentModal, { ...contentProps, ref: forwardedRef }) : /* @__PURE__ */ jsx(PopoverContentNonModal, { ...contentProps, ref: forwardedRef }) });
177845
177594
  }
177846
177595
  );
177847
- PopoverContent$1.displayName = CONTENT_NAME$3;
177596
+ PopoverContent$1.displayName = CONTENT_NAME$4;
177848
177597
  var Slot = createSlot$1("PopoverContent.RemoveScroll");
177849
177598
  var PopoverContentModal = React.forwardRef(
177850
177599
  (props, forwardedRef) => {
177851
- const context = usePopoverContext(CONTENT_NAME$3, props.__scopePopover);
177600
+ const context = usePopoverContext(CONTENT_NAME$4, props.__scopePopover);
177852
177601
  const contentRef = React.useRef(null);
177853
177602
  const composedRefs = useComposedRefs$1(forwardedRef, contentRef);
177854
177603
  const isRightClickOutsideRef = React.useRef(false);
@@ -177888,7 +177637,7 @@ var PopoverContentModal = React.forwardRef(
177888
177637
  );
177889
177638
  var PopoverContentNonModal = React.forwardRef(
177890
177639
  (props, forwardedRef) => {
177891
- const context = usePopoverContext(CONTENT_NAME$3, props.__scopePopover);
177640
+ const context = usePopoverContext(CONTENT_NAME$4, props.__scopePopover);
177892
177641
  const hasInteractedOutsideRef = React.useRef(false);
177893
177642
  const hasPointerDownOutsideRef = React.useRef(false);
177894
177643
  return /* @__PURE__ */ jsx(
@@ -177940,7 +177689,7 @@ var PopoverContentImpl = React.forwardRef(
177940
177689
  onInteractOutside,
177941
177690
  ...contentProps
177942
177691
  } = props;
177943
- const context = usePopoverContext(CONTENT_NAME$3, __scopePopover);
177692
+ const context = usePopoverContext(CONTENT_NAME$4, __scopePopover);
177944
177693
  const popperScope = usePopperScope$1(__scopePopover);
177945
177694
  useFocusGuards();
177946
177695
  return /* @__PURE__ */ jsx(
@@ -178018,26 +177767,26 @@ PopoverArrow.displayName = ARROW_NAME$2;
178018
177767
  function getState$2(open) {
178019
177768
  return open ? "open" : "closed";
178020
177769
  }
178021
- var Root2$3 = Popover$1;
177770
+ var Root2$4 = Popover$1;
178022
177771
  var Anchor2 = PopoverAnchor$1;
178023
177772
  var Trigger$1 = PopoverTrigger$1;
178024
177773
  var Portal$1 = PopoverPortal;
178025
- var Content2$1 = PopoverContent$1;
177774
+ var Content2$2 = PopoverContent$1;
178026
177775
 
178027
- const Popover = Root2$3;
177776
+ const Popover = Root2$4;
178028
177777
  const PopoverTrigger = Trigger$1;
178029
177778
  const PopoverAnchor = Anchor2;
178030
177779
  const PopoverContent = React.forwardRef(({ className, align = 'center', sideOffset = 4, portalled = true, container, ...props }, ref) => {
178031
177780
  var _a;
178032
177781
  const portalContainer = useFloatingPortalContainer();
178033
177782
  const resolvedContainer = (_a = container !== null && container !== void 0 ? container : portalContainer) !== null && _a !== void 0 ? _a : undefined;
178034
- const content = (jsx(Content2$1, { ref: ref, "data-iblai-dialog-interaction-layer": true, align: align, sideOffset: sideOffset, className: cn('bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-none', className), ...props }));
177783
+ const content = (jsx(Content2$2, { 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 }));
178035
177784
  if (!portalled) {
178036
177785
  return content;
178037
177786
  }
178038
177787
  return jsx(Portal$1, { container: resolvedContainer, children: content });
178039
177788
  });
178040
- PopoverContent.displayName = Content2$1.displayName;
177789
+ PopoverContent.displayName = Content2$2.displayName;
178041
177790
 
178042
177791
  function UsersTab({ tenant, onInviteClick, currentSpa, showGradebookTab = false, }) {
178043
177792
  const t = useT();
@@ -179919,11 +179668,11 @@ var Collapsible$1 = React.forwardRef(
179919
179668
  }
179920
179669
  );
179921
179670
  Collapsible$1.displayName = COLLAPSIBLE_NAME;
179922
- var TRIGGER_NAME$1 = "CollapsibleTrigger";
179671
+ var TRIGGER_NAME$2 = "CollapsibleTrigger";
179923
179672
  var CollapsibleTrigger$1 = React.forwardRef(
179924
179673
  (props, forwardedRef) => {
179925
179674
  const { __scopeCollapsible, ...triggerProps } = props;
179926
- const context = useCollapsibleContext(TRIGGER_NAME$1, __scopeCollapsible);
179675
+ const context = useCollapsibleContext(TRIGGER_NAME$2, __scopeCollapsible);
179927
179676
  return /* @__PURE__ */ jsx(
179928
179677
  Primitive$2.button,
179929
179678
  {
@@ -179940,19 +179689,19 @@ var CollapsibleTrigger$1 = React.forwardRef(
179940
179689
  );
179941
179690
  }
179942
179691
  );
179943
- CollapsibleTrigger$1.displayName = TRIGGER_NAME$1;
179944
- var CONTENT_NAME$2 = "CollapsibleContent";
179692
+ CollapsibleTrigger$1.displayName = TRIGGER_NAME$2;
179693
+ var CONTENT_NAME$3 = "CollapsibleContent";
179945
179694
  var CollapsibleContent$1 = React.forwardRef(
179946
179695
  (props, forwardedRef) => {
179947
179696
  const { forceMount, ...contentProps } = props;
179948
- const context = useCollapsibleContext(CONTENT_NAME$2, props.__scopeCollapsible);
179697
+ const context = useCollapsibleContext(CONTENT_NAME$3, props.__scopeCollapsible);
179949
179698
  return /* @__PURE__ */ jsx(Presence$2, { present: forceMount || context.open, children: ({ present }) => /* @__PURE__ */ jsx(CollapsibleContentImpl, { ...contentProps, ref: forwardedRef, present }) });
179950
179699
  }
179951
179700
  );
179952
- CollapsibleContent$1.displayName = CONTENT_NAME$2;
179701
+ CollapsibleContent$1.displayName = CONTENT_NAME$3;
179953
179702
  var CollapsibleContentImpl = React.forwardRef((props, forwardedRef) => {
179954
179703
  const { __scopeCollapsible, present, children, ...contentProps } = props;
179955
- const context = useCollapsibleContext(CONTENT_NAME$2, __scopeCollapsible);
179704
+ const context = useCollapsibleContext(CONTENT_NAME$3, __scopeCollapsible);
179956
179705
  const [isPresent, setIsPresent] = React.useState(present);
179957
179706
  const ref = React.useRef(null);
179958
179707
  const composedRefs = useComposedRefs$1(forwardedRef, ref);
@@ -203188,7 +202937,7 @@ var joinTextblockForward = () => ({ state, dispatch }) => {
203188
202937
  };
203189
202938
 
203190
202939
  // src/utilities/isMacOS.ts
203191
- function isMacOS() {
202940
+ function isMacOS$1() {
203192
202941
  return typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
203193
202942
  }
203194
202943
 
@@ -203214,7 +202963,7 @@ function normalizeKeyName(name) {
203214
202963
  } else if (/^s(hift)?$/i.test(mod)) {
203215
202964
  shift = true;
203216
202965
  } else if (/^mod$/i.test(mod)) {
203217
- if (isiOS() || isMacOS()) {
202966
+ if (isiOS() || isMacOS$1()) {
203218
202967
  meta = true;
203219
202968
  } else {
203220
202969
  ctrl = true;
@@ -206191,7 +205940,7 @@ var Keymap = Extension.create({
206191
205940
  "Ctrl-a": () => this.editor.commands.selectTextblockStart(),
206192
205941
  "Ctrl-e": () => this.editor.commands.selectTextblockEnd()
206193
205942
  };
206194
- if (isiOS() || isMacOS()) {
205943
+ if (isiOS() || isMacOS$1()) {
206195
205944
  return macKeymap;
206196
205945
  }
206197
205946
  return pcKeymap;
@@ -216720,17 +216469,184 @@ var RadioGroupIndicator = React.forwardRef(
216720
216469
  }
216721
216470
  );
216722
216471
  RadioGroupIndicator.displayName = INDICATOR_NAME2;
216723
- var Root2$2 = RadioGroup$1;
216472
+ var Root2$3 = RadioGroup$1;
216724
216473
  var Item2 = RadioGroupItem$1;
216725
216474
  var Indicator = RadioGroupIndicator;
216726
216475
 
216727
216476
  function RadioGroup({ className, ...props }) {
216728
- return (jsx(Root2$2, { "data-slot": "radio-group", className: cn('grid gap-3', className), ...props }));
216477
+ return (jsx(Root2$3, { "data-slot": "radio-group", className: cn('grid gap-3', className), ...props }));
216729
216478
  }
216730
216479
  function RadioGroupItem({ className, ...props }) {
216731
216480
  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" }) }) }));
216732
216481
  }
216733
216482
 
216483
+ var SLOTTABLE_IDENTIFIER = Symbol("radix.slottable");
216484
+ // @__NO_SIDE_EFFECTS__
216485
+ function createSlottable(ownerName) {
216486
+ const Slottable2 = ({ children }) => {
216487
+ return /* @__PURE__ */ jsx(Fragment$1, { children });
216488
+ };
216489
+ Slottable2.displayName = `${ownerName}.Slottable`;
216490
+ Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
216491
+ return Slottable2;
216492
+ }
216493
+
216494
+ var ROOT_NAME = "AlertDialog";
216495
+ var [createAlertDialogContext] = createContextScope$1(ROOT_NAME, [
216496
+ createDialogScope
216497
+ ]);
216498
+ var useDialogScope = createDialogScope();
216499
+ var AlertDialog$1 = (props) => {
216500
+ const { __scopeAlertDialog, ...alertDialogProps } = props;
216501
+ const dialogScope = useDialogScope(__scopeAlertDialog);
216502
+ return /* @__PURE__ */ jsx(DialogPrimitive.Root, { ...dialogScope, ...alertDialogProps, modal: true });
216503
+ };
216504
+ AlertDialog$1.displayName = ROOT_NAME;
216505
+ var TRIGGER_NAME$1 = "AlertDialogTrigger";
216506
+ var AlertDialogTrigger = React.forwardRef(
216507
+ (props, forwardedRef) => {
216508
+ const { __scopeAlertDialog, ...triggerProps } = props;
216509
+ const dialogScope = useDialogScope(__scopeAlertDialog);
216510
+ return /* @__PURE__ */ jsx(DialogPrimitive.Trigger, { ...dialogScope, ...triggerProps, ref: forwardedRef });
216511
+ }
216512
+ );
216513
+ AlertDialogTrigger.displayName = TRIGGER_NAME$1;
216514
+ var PORTAL_NAME$1 = "AlertDialogPortal";
216515
+ var AlertDialogPortal$1 = (props) => {
216516
+ const { __scopeAlertDialog, ...portalProps } = props;
216517
+ const dialogScope = useDialogScope(__scopeAlertDialog);
216518
+ return /* @__PURE__ */ jsx(DialogPrimitive.Portal, { ...dialogScope, ...portalProps });
216519
+ };
216520
+ AlertDialogPortal$1.displayName = PORTAL_NAME$1;
216521
+ var OVERLAY_NAME = "AlertDialogOverlay";
216522
+ var AlertDialogOverlay$1 = React.forwardRef(
216523
+ (props, forwardedRef) => {
216524
+ const { __scopeAlertDialog, ...overlayProps } = props;
216525
+ const dialogScope = useDialogScope(__scopeAlertDialog);
216526
+ return /* @__PURE__ */ jsx(DialogPrimitive.Overlay, { ...dialogScope, ...overlayProps, ref: forwardedRef });
216527
+ }
216528
+ );
216529
+ AlertDialogOverlay$1.displayName = OVERLAY_NAME;
216530
+ var CONTENT_NAME$2 = "AlertDialogContent";
216531
+ var [AlertDialogContentProvider, useAlertDialogContentContext] = createAlertDialogContext(CONTENT_NAME$2);
216532
+ var Slottable = createSlottable("AlertDialogContent");
216533
+ var AlertDialogContent$1 = React.forwardRef(
216534
+ (props, forwardedRef) => {
216535
+ const { __scopeAlertDialog, children, ...contentProps } = props;
216536
+ const dialogScope = useDialogScope(__scopeAlertDialog);
216537
+ const contentRef = React.useRef(null);
216538
+ const composedRefs = useComposedRefs$1(forwardedRef, contentRef);
216539
+ const cancelRef = React.useRef(null);
216540
+ return /* @__PURE__ */ jsx(
216541
+ DialogPrimitive.WarningProvider,
216542
+ {
216543
+ contentName: CONTENT_NAME$2,
216544
+ titleName: TITLE_NAME,
216545
+ docsSlug: "alert-dialog",
216546
+ children: /* @__PURE__ */ jsx(AlertDialogContentProvider, { scope: __scopeAlertDialog, cancelRef, children: /* @__PURE__ */ jsxs(
216547
+ DialogPrimitive.Content,
216548
+ {
216549
+ role: "alertdialog",
216550
+ ...dialogScope,
216551
+ ...contentProps,
216552
+ ref: composedRefs,
216553
+ onOpenAutoFocus: composeEventHandlers$2(contentProps.onOpenAutoFocus, (event) => {
216554
+ event.preventDefault();
216555
+ cancelRef.current?.focus({ preventScroll: true });
216556
+ }),
216557
+ onPointerDownOutside: (event) => event.preventDefault(),
216558
+ onInteractOutside: (event) => event.preventDefault(),
216559
+ children: [
216560
+ /* @__PURE__ */ jsx(Slottable, { children }),
216561
+ /* @__PURE__ */ jsx(DescriptionWarning, { contentRef })
216562
+ ]
216563
+ }
216564
+ ) })
216565
+ }
216566
+ );
216567
+ }
216568
+ );
216569
+ AlertDialogContent$1.displayName = CONTENT_NAME$2;
216570
+ var TITLE_NAME = "AlertDialogTitle";
216571
+ var AlertDialogTitle$1 = React.forwardRef(
216572
+ (props, forwardedRef) => {
216573
+ const { __scopeAlertDialog, ...titleProps } = props;
216574
+ const dialogScope = useDialogScope(__scopeAlertDialog);
216575
+ return /* @__PURE__ */ jsx(DialogPrimitive.Title, { ...dialogScope, ...titleProps, ref: forwardedRef });
216576
+ }
216577
+ );
216578
+ AlertDialogTitle$1.displayName = TITLE_NAME;
216579
+ var DESCRIPTION_NAME = "AlertDialogDescription";
216580
+ var AlertDialogDescription$1 = React.forwardRef((props, forwardedRef) => {
216581
+ const { __scopeAlertDialog, ...descriptionProps } = props;
216582
+ const dialogScope = useDialogScope(__scopeAlertDialog);
216583
+ return /* @__PURE__ */ jsx(DialogPrimitive.Description, { ...dialogScope, ...descriptionProps, ref: forwardedRef });
216584
+ });
216585
+ AlertDialogDescription$1.displayName = DESCRIPTION_NAME;
216586
+ var ACTION_NAME = "AlertDialogAction";
216587
+ var AlertDialogAction$1 = React.forwardRef(
216588
+ (props, forwardedRef) => {
216589
+ const { __scopeAlertDialog, ...actionProps } = props;
216590
+ const dialogScope = useDialogScope(__scopeAlertDialog);
216591
+ return /* @__PURE__ */ jsx(DialogPrimitive.Close, { ...dialogScope, ...actionProps, ref: forwardedRef });
216592
+ }
216593
+ );
216594
+ AlertDialogAction$1.displayName = ACTION_NAME;
216595
+ var CANCEL_NAME = "AlertDialogCancel";
216596
+ var AlertDialogCancel$1 = React.forwardRef(
216597
+ (props, forwardedRef) => {
216598
+ const { __scopeAlertDialog, ...cancelProps } = props;
216599
+ const { cancelRef } = useAlertDialogContentContext(CANCEL_NAME, __scopeAlertDialog);
216600
+ const dialogScope = useDialogScope(__scopeAlertDialog);
216601
+ const ref = useComposedRefs$1(forwardedRef, cancelRef);
216602
+ return /* @__PURE__ */ jsx(DialogPrimitive.Close, { ...dialogScope, ...cancelProps, ref });
216603
+ }
216604
+ );
216605
+ AlertDialogCancel$1.displayName = CANCEL_NAME;
216606
+ var DescriptionWarning = ({ contentRef }) => {
216607
+ const MESSAGE = `\`${CONTENT_NAME$2}\` requires a description for the component to be accessible for screen reader users.
216608
+
216609
+ 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.
216610
+
216611
+ 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.
216612
+
216613
+ For more information, see https://radix-ui.com/primitives/docs/components/alert-dialog`;
216614
+ React.useEffect(() => {
216615
+ const hasDescription = document.getElementById(
216616
+ contentRef.current?.getAttribute("aria-describedby")
216617
+ );
216618
+ if (!hasDescription) console.warn(MESSAGE);
216619
+ }, [MESSAGE, contentRef]);
216620
+ return null;
216621
+ };
216622
+ var Root2$2 = AlertDialog$1;
216623
+ var Portal2 = AlertDialogPortal$1;
216624
+ var Overlay2 = AlertDialogOverlay$1;
216625
+ var Content2$1 = AlertDialogContent$1;
216626
+ var Action = AlertDialogAction$1;
216627
+ var Cancel = AlertDialogCancel$1;
216628
+ var Title2 = AlertDialogTitle$1;
216629
+ var Description2 = AlertDialogDescription$1;
216630
+
216631
+ const AlertDialog = Root2$2;
216632
+ const AlertDialogPortal = Portal2;
216633
+ 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 })));
216634
+ AlertDialogOverlay.displayName = Overlay2.displayName;
216635
+ 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 })] })));
216636
+ AlertDialogContent.displayName = Content2$1.displayName;
216637
+ const AlertDialogHeader = ({ className, ...props }) => (jsx("div", { className: cn('flex flex-col space-y-2 text-center sm:text-left', className), ...props }));
216638
+ AlertDialogHeader.displayName = 'AlertDialogHeader';
216639
+ const AlertDialogFooter = ({ className, ...props }) => (jsx("div", { className: cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className), ...props }));
216640
+ AlertDialogFooter.displayName = 'AlertDialogFooter';
216641
+ const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => (jsx(Title2, { ref: ref, className: cn('text-lg font-semibold', className), ...props })));
216642
+ AlertDialogTitle.displayName = Title2.displayName;
216643
+ const AlertDialogDescription = React.forwardRef(({ className, ...props }, ref) => (jsx(Description2, { ref: ref, className: cn('text-muted-foreground text-sm', className), ...props })));
216644
+ AlertDialogDescription.displayName = Description2.displayName;
216645
+ const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => (jsx(Action, { ref: ref, className: cn(buttonVariants(), className), ...props })));
216646
+ AlertDialogAction.displayName = Action.displayName;
216647
+ const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => (jsx(Cancel, { ref: ref, className: cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className), ...props })));
216648
+ AlertDialogCancel.displayName = Cancel.displayName;
216649
+
216734
216650
  const THEMES = { light: '', dark: '.dark' };
216735
216651
  const ChartContext = React.createContext(null);
216736
216652
  function useChart() {
@@ -253569,8 +253485,501 @@ function MemoryButton({ mentorId, tenantKey, username }) {
253569
253485
  } }))] }) }), jsx(PopoverContent, { className: "w-96 rounded-lg border border-gray-200 bg-white p-0 shadow-xl", children: jsx(MemoryMenu, { onClose: () => setIsOpen(false), mentorId: mentorId, tenantKey: tenantKey, username: username }) })] }));
253570
253486
  }
253571
253487
 
253488
+ // Tauri types for model download functionality
253489
+ /**
253490
+ * Initial state for model download
253491
+ */
253492
+ const initialModelDownloadState = {
253493
+ status: 'idle',
253494
+ progress: 0,
253495
+ message: '',
253496
+ logs: [],
253497
+ lastUpdated: new Date().toISOString(),
253498
+ };
253499
+ /**
253500
+ * Check if the app is running inside Tauri
253501
+ * In Tauri 2.0, the global is __TAURI_INTERNALS__ (with withGlobalTauri: true)
253502
+ * We also check for __TAURI__ for backwards compatibility
253503
+ */
253504
+ const isTauriApp$1 = () => {
253505
+ if (typeof window === 'undefined')
253506
+ return false;
253507
+ return '__TAURI_INTERNALS__' in window || '__TAURI__' in window;
253508
+ };
253509
+ /**
253510
+ * Tauri event names
253511
+ */
253512
+ const TAURI_EVENTS = {
253513
+ DOWNLOAD_PROGRESS: 'model:download-progress',
253514
+ INSTALLATION_LOG: 'model:installation-log',
253515
+ DISK_SPACE_ERROR: 'model:disk-space-error',
253516
+ OLLAMA_STATUS: 'model:ollama-status',
253517
+ };
253518
+ /**
253519
+ * Tauri command names
253520
+ */
253521
+ const TAURI_COMMANDS = {
253522
+ INSTALL_OLLAMA: 'install_ollama',
253523
+ STOP_OLLAMA: 'stop_ollama',
253524
+ CHECK_OLLAMA_STATUS: 'check_ollama_status',
253525
+ CHECK_DISK_SPACE: 'check_disk_space_for_model',
253526
+ GET_SYSTEM_MEMORY: 'get_system_memory',
253527
+ DOWNLOAD_MODEL: 'download_model',
253528
+ CANCEL_DOWNLOAD: 'cancel_model_download',
253529
+ CHECK_NETWORK_STATUS: 'check_network_status',
253530
+ GET_OS_TYPE: 'get_os_type',
253531
+ CHECK_FOUNDRY_STATUS: 'check_foundry_local_status',
253532
+ START_FOUNDRY_SERVICE: 'start_foundry_local_service',
253533
+ LOAD_FOUNDRY_MODEL: 'load_foundry_local_model',
253534
+ SET_SELECTED_FOUNDRY_MODEL: 'set_selected_foundry_model',
253535
+ GET_SELECTED_FOUNDRY_MODEL: 'get_selected_foundry_model',
253536
+ INSTALL_FOUNDRY: 'install_foundry',
253537
+ DOWNLOAD_FOUNDRY_MODEL: 'download_foundry_model_cmd',
253538
+ GET_RECOMMENDED_FOUNDRY_MODELS: 'get_recommended_foundry_models',
253539
+ LOG_STDOUT: 'log_stdout',
253540
+ };
253541
+ /**
253542
+ * Initial state for the Cowork install flow.
253543
+ */
253544
+ const initialGhostOsInstallState = {
253545
+ status: 'idle',
253546
+ progress: 0,
253547
+ message: '',
253548
+ logs: [],
253549
+ lastUpdated: new Date().toISOString(),
253550
+ };
253551
+ /**
253552
+ * Tauri event names emitted by the host during GhostOS install/setup.
253553
+ */
253554
+ const GHOST_OS_TAURI_EVENTS = {
253555
+ INSTALL_PROGRESS: 'ghost-os:install-progress',
253556
+ INSTALLATION_LOG: 'ghost-os:installation-log',
253557
+ STATUS: 'ghost-os:status',
253558
+ };
253559
+ /**
253560
+ * Tauri command names the host implements for GhostOS install/setup. The web
253561
+ * layer invokes these; the native side performs the clone + setup.
253562
+ */
253563
+ const GHOST_OS_TAURI_COMMANDS = {
253564
+ /** Install and set up GhostOS (clone repo + run setup). */
253565
+ INSTALL_GHOST_OS: 'install_ghost_os',
253566
+ /** Stop the running GhostOS manager. */
253567
+ STOP_GHOST_OS: 'stop_ghost_os',
253568
+ /** Report current {@link GhostOsStatus}. */
253569
+ CHECK_GHOST_OS_STATUS: 'check_ghost_os_status',
253570
+ };
253571
+ /**
253572
+ * Tauri command strings exposed by `tauri-plugin-macos-permissions`
253573
+ * (https://github.com/ayangweb/tauri-plugin-macos-permissions). GhostOS needs
253574
+ * macOS Accessibility permission to control the device, so the Cowork
253575
+ * card surfaces it. These are invoked directly via `useTauri().invoke(...)` —
253576
+ * no extra npm dependency. The host must register the plugin in its Tauri app
253577
+ * (`tauri_plugin_macos_permissions::init()`). Each `CHECK_*` returns a boolean;
253578
+ * each `REQUEST_*` prompts / opens the relevant System Settings pane.
253579
+ */
253580
+ const MACOS_PERMISSIONS_COMMANDS = {
253581
+ CHECK_ACCESSIBILITY: 'plugin:macos-permissions|check_accessibility_permission',
253582
+ REQUEST_ACCESSIBILITY: 'plugin:macos-permissions|request_accessibility_permission',
253583
+ CHECK_SCREEN_RECORDING: 'plugin:macos-permissions|check_screen_recording_permission',
253584
+ REQUEST_SCREEN_RECORDING: 'plugin:macos-permissions|request_screen_recording_permission',
253585
+ CHECK_INPUT_MONITORING: 'plugin:macos-permissions|check_input_monitoring_permission',
253586
+ REQUEST_INPUT_MONITORING: 'plugin:macos-permissions|request_input_monitoring_permission',
253587
+ CHECK_FULL_DISK_ACCESS: 'plugin:macos-permissions|check_full_disk_access_permission',
253588
+ REQUEST_FULL_DISK_ACCESS: 'plugin:macos-permissions|request_full_disk_access_permission',
253589
+ };
253590
+
253591
+ /**
253592
+ * Dynamically import Tauri APIs to avoid SSR issues
253593
+ */
253594
+ const getTauriAPIs = async () => {
253595
+ try {
253596
+ const { invoke } = await import('@tauri-apps/api/core');
253597
+ const { listen } = await import('@tauri-apps/api/event');
253598
+ return { invoke, listen };
253599
+ }
253600
+ catch (error) {
253601
+ console.error('Failed to load Tauri APIs:', error);
253602
+ return null;
253603
+ }
253604
+ };
253605
+ /**
253606
+ * Hook to access Tauri APIs with SSR safety
253607
+ *
253608
+ * @returns Object with isAvailable flag and Tauri invoke/listen functions
253609
+ */
253610
+ function useTauri() {
253611
+ const [isAvailable, setIsAvailable] = useState(false);
253612
+ const [apis, setApis] = useState(null);
253613
+ useEffect(() => {
253614
+ // Check if we're in a Tauri environment
253615
+ const inTauri = isTauriApp$1();
253616
+ if (inTauri) {
253617
+ getTauriAPIs().then((result) => {
253618
+ setApis(result);
253619
+ setIsAvailable(!!result);
253620
+ });
253621
+ }
253622
+ }, []);
253623
+ /**
253624
+ * Invoke a Tauri command
253625
+ */
253626
+ const invoke = useCallback(async (command, args) => {
253627
+ if (!(apis === null || apis === void 0 ? void 0 : apis.invoke)) {
253628
+ throw new Error('Tauri is not available');
253629
+ }
253630
+ return apis.invoke(command, args);
253631
+ }, [apis]);
253632
+ /**
253633
+ * Listen to a Tauri event
253634
+ * Returns an unlisten function that should be called on cleanup
253635
+ */
253636
+ const listen = useCallback(async (event, handler) => {
253637
+ // The synchronous global (`__TAURI_INTERNALS__`) exposes `invoke` but
253638
+ // frequently NOT `event.listen`, so `apis.listen` can be undefined even
253639
+ // though Tauri is fully available. Fall back to the real event API via
253640
+ // dynamic import so download progress/completion events are actually
253641
+ // delivered — otherwise the UI never leaves the "downloading" state.
253642
+ let listenFn = apis === null || apis === void 0 ? void 0 : apis.listen;
253643
+ if (!listenFn) {
253644
+ const { listen: importedListen } = await import('@tauri-apps/api/event');
253645
+ listenFn = importedListen;
253646
+ }
253647
+ return listenFn(event, (e) => handler(e.payload));
253648
+ }, [apis]);
253649
+ return {
253650
+ isAvailable,
253651
+ invoke,
253652
+ listen,
253653
+ };
253654
+ }
253655
+
253656
+ const IS_SERVER = typeof window === 'undefined';
253657
+ /**
253658
+ * A simplified useLocalStorage hook for persisting state to localStorage
253659
+ * with SSR safety.
253660
+ */
253661
+ function useLocalStorage(key, initialValue) {
253662
+ // Get the initial value
253663
+ const getInitialValue = useCallback(() => {
253664
+ const initial = initialValue instanceof Function ? initialValue() : initialValue;
253665
+ if (IS_SERVER) {
253666
+ return initial;
253667
+ }
253668
+ try {
253669
+ const item = window.localStorage.getItem(key);
253670
+ if (item === null) {
253671
+ return initial;
253672
+ }
253673
+ return JSON.parse(item);
253674
+ }
253675
+ catch (error) {
253676
+ console.warn(`Error reading localStorage key "${key}":`, error);
253677
+ return initial;
253678
+ }
253679
+ }, [key, initialValue]);
253680
+ const [storedValue, setStoredValue] = useState(getInitialValue);
253681
+ // Update stored value when key changes
253682
+ useEffect(() => {
253683
+ setStoredValue(getInitialValue());
253684
+ }, [key, getInitialValue]);
253685
+ // Persist to localStorage
253686
+ const setValue = useCallback((value) => {
253687
+ if (IS_SERVER) {
253688
+ console.warn(`Tried setting localStorage key "${key}" even though environment is not a client`);
253689
+ return;
253690
+ }
253691
+ try {
253692
+ const valueToStore = value instanceof Function ? value(storedValue) : value;
253693
+ setStoredValue(valueToStore);
253694
+ window.localStorage.setItem(key, JSON.stringify(valueToStore));
253695
+ }
253696
+ catch (error) {
253697
+ console.warn(`Error setting localStorage key "${key}":`, error);
253698
+ }
253699
+ }, [key, storedValue]);
253700
+ // Remove from localStorage
253701
+ const removeValue = useCallback(() => {
253702
+ if (IS_SERVER) {
253703
+ console.warn(`Tried removing localStorage key "${key}" even though environment is not a client`);
253704
+ return;
253705
+ }
253706
+ try {
253707
+ const initial = initialValue instanceof Function ? initialValue() : initialValue;
253708
+ window.localStorage.removeItem(key);
253709
+ setStoredValue(initial);
253710
+ }
253711
+ catch (error) {
253712
+ console.warn(`Error removing localStorage key "${key}":`, error);
253713
+ }
253714
+ }, [key, initialValue]);
253715
+ return [storedValue, setValue, removeValue];
253716
+ }
253717
+
253718
+ const LOCAL_STORAGE_KEY$1 = 'ghost_os_install_state';
253719
+ /**
253720
+ * Manage installing & setting up GhostOS (https://github.com/ghostwright/ghost-os)
253721
+ * via the Tauri host. This is the self-contained backing hook for the System
253722
+ * Control card and deliberately mirrors `useModelDownload`:
253723
+ *
253724
+ * - checks GhostOS install/run status through the host
253725
+ * - installs/sets up GhostOS with real-time progress + logs
253726
+ * - persists state across app restarts
253727
+ *
253728
+ * Outside the desktop (Tauri) app every action is a no-op and `isAvailable` is
253729
+ * false, so the card hides itself.
253730
+ */
253731
+ function useGhostOs() {
253732
+ const { isAvailable, invoke, listen } = useTauri();
253733
+ const [state, setState] = useLocalStorage(LOCAL_STORAGE_KEY$1, initialGhostOsInstallState);
253734
+ const [status, setStatus] = useState(null);
253735
+ // macOS Accessibility permission, which GhostOS needs to control the device.
253736
+ // null = unknown/not-yet-checked or unsupported (non-macOS / plugin absent).
253737
+ const [accessibilityPermission, setAccessibilityPermission] = useState(null);
253738
+ const unlistenRefs = useRef([]);
253739
+ const hasCheckedStatus = useRef(false);
253740
+ /**
253741
+ * Refresh the macOS Accessibility permission via tauri-plugin-macos-permissions.
253742
+ * Independent of the GhostOS status check so a missing host command for one
253743
+ * doesn't suppress the other. Stays null where unsupported.
253744
+ */
253745
+ const refreshAccessibilityPermission = useCallback(async () => {
253746
+ if (!isAvailable)
253747
+ return;
253748
+ try {
253749
+ const granted = await invoke(MACOS_PERMISSIONS_COMMANDS.CHECK_ACCESSIBILITY);
253750
+ setAccessibilityPermission(granted);
253751
+ }
253752
+ catch (error) {
253753
+ // Plugin not registered or non-macOS host — leave permission unknown.
253754
+ console.warn('[useGhostOs] Accessibility permission check unavailable:', error);
253755
+ setAccessibilityPermission(null);
253756
+ }
253757
+ }, [isAvailable, invoke]);
253758
+ /**
253759
+ * Open the Accessibility pane / prompt so the user can grant permission, then
253760
+ * re-check (the grant happens in System Settings, so we refresh after).
253761
+ */
253762
+ const requestAccessibilityPermission = useCallback(async () => {
253763
+ if (!isAvailable)
253764
+ return;
253765
+ try {
253766
+ await invoke(MACOS_PERMISSIONS_COMMANDS.REQUEST_ACCESSIBILITY);
253767
+ }
253768
+ catch (error) {
253769
+ console.error('[useGhostOs] Failed to request accessibility permission:', error);
253770
+ }
253771
+ await refreshAccessibilityPermission();
253772
+ }, [isAvailable, invoke, refreshAccessibilityPermission]);
253773
+ const addLog = useCallback((log) => {
253774
+ setState((prev) => ({
253775
+ ...prev,
253776
+ logs: [...prev.logs.slice(-99), log],
253777
+ lastUpdated: new Date().toISOString(),
253778
+ }));
253779
+ }, [setState]);
253780
+ /**
253781
+ * Query GhostOS status from the host.
253782
+ */
253783
+ const checkStatus = useCallback(async () => {
253784
+ if (!isAvailable)
253785
+ return;
253786
+ // Refresh the accessibility permission alongside install status (independent).
253787
+ refreshAccessibilityPermission();
253788
+ try {
253789
+ setState((prev) => ({ ...prev, status: 'checking' }));
253790
+ const result = await invoke(GHOST_OS_TAURI_COMMANDS.CHECK_GHOST_OS_STATUS);
253791
+ setStatus(result);
253792
+ setState((prev) => ({
253793
+ ...prev,
253794
+ status: result.installed ? 'completed' : 'idle',
253795
+ progress: result.installed ? 100 : prev.progress,
253796
+ lastUpdated: new Date().toISOString(),
253797
+ }));
253798
+ }
253799
+ catch (error) {
253800
+ console.error('[useGhostOs] Failed to check status:', error);
253801
+ setState((prev) => ({ ...prev, status: 'idle' }));
253802
+ }
253803
+ }, [isAvailable, invoke, setState, refreshAccessibilityPermission]);
253804
+ /**
253805
+ * Install and set up GhostOS. The host clones the repo + runs setup and emits
253806
+ * progress/log/status events along the way.
253807
+ */
253808
+ const install = useCallback(async () => {
253809
+ if (!isAvailable)
253810
+ return;
253811
+ try {
253812
+ setState((prev) => ({
253813
+ ...prev,
253814
+ status: 'installing',
253815
+ progress: 0,
253816
+ message: 'Setting up your assistant…',
253817
+ error: undefined,
253818
+ logs: [],
253819
+ }));
253820
+ addLog({
253821
+ timestamp: new Date().toISOString(),
253822
+ level: 'info',
253823
+ message: 'Starting setup…',
253824
+ });
253825
+ const result = await invoke(GHOST_OS_TAURI_COMMANDS.INSTALL_GHOST_OS);
253826
+ addLog({
253827
+ timestamp: new Date().toISOString(),
253828
+ level: 'info',
253829
+ message: typeof result === 'string' && result ? result : 'Setup complete',
253830
+ });
253831
+ setState((prev) => ({
253832
+ ...prev,
253833
+ status: 'completed',
253834
+ progress: 100,
253835
+ message: 'Your assistant is ready',
253836
+ lastUpdated: new Date().toISOString(),
253837
+ }));
253838
+ await checkStatus();
253839
+ }
253840
+ catch (error) {
253841
+ const message = error instanceof Error ? error.message : String(error);
253842
+ setState((prev) => ({
253843
+ ...prev,
253844
+ status: 'error',
253845
+ error: message,
253846
+ message: 'Setup didn’t finish',
253847
+ lastUpdated: new Date().toISOString(),
253848
+ }));
253849
+ addLog({
253850
+ timestamp: new Date().toISOString(),
253851
+ level: 'error',
253852
+ message: `Install failed: ${message}`,
253853
+ });
253854
+ }
253855
+ }, [isAvailable, invoke, addLog, checkStatus, setState]);
253856
+ /**
253857
+ * Stop the running GhostOS manager.
253858
+ */
253859
+ const stop = useCallback(async () => {
253860
+ if (!isAvailable)
253861
+ return;
253862
+ try {
253863
+ await invoke(GHOST_OS_TAURI_COMMANDS.STOP_GHOST_OS);
253864
+ await checkStatus();
253865
+ }
253866
+ catch (error) {
253867
+ console.error('[useGhostOs] Failed to stop GhostOS:', error);
253868
+ }
253869
+ }, [isAvailable, invoke, checkStatus]);
253870
+ /**
253871
+ * Reset the install state (for retrying after an error).
253872
+ */
253873
+ const resetState = useCallback(() => {
253874
+ setState(initialGhostOsInstallState);
253875
+ }, [setState]);
253876
+ // Wire up host event listeners (progress / logs / status).
253877
+ useEffect(() => {
253878
+ if (!isAvailable)
253879
+ return;
253880
+ const setupListeners = async () => {
253881
+ try {
253882
+ const unlistenProgress = await listen(GHOST_OS_TAURI_EVENTS.INSTALL_PROGRESS, (payload) => {
253883
+ const nextStatus = payload.status === 'completed'
253884
+ ? 'completed'
253885
+ : payload.status === 'cancelled'
253886
+ ? 'cancelled'
253887
+ : payload.status === 'error'
253888
+ ? 'error'
253889
+ : 'installing';
253890
+ setState((prev) => ({
253891
+ ...prev,
253892
+ status: nextStatus,
253893
+ progress: payload.percentage,
253894
+ message: payload.message,
253895
+ lastUpdated: new Date().toISOString(),
253896
+ }));
253897
+ });
253898
+ unlistenRefs.current.push(unlistenProgress);
253899
+ const unlistenLogs = await listen(GHOST_OS_TAURI_EVENTS.INSTALLATION_LOG, addLog);
253900
+ unlistenRefs.current.push(unlistenLogs);
253901
+ const unlistenStatus = await listen(GHOST_OS_TAURI_EVENTS.STATUS, setStatus);
253902
+ unlistenRefs.current.push(unlistenStatus);
253903
+ }
253904
+ catch (error) {
253905
+ console.error('[useGhostOs] Failed to setup event listeners:', error);
253906
+ }
253907
+ };
253908
+ setupListeners();
253909
+ return () => {
253910
+ unlistenRefs.current.forEach((unlisten) => unlisten());
253911
+ unlistenRefs.current = [];
253912
+ };
253913
+ }, [isAvailable, listen, setState, addLog]);
253914
+ // Check status once on mount (in the desktop app).
253915
+ useEffect(() => {
253916
+ if (!isAvailable || hasCheckedStatus.current)
253917
+ return;
253918
+ hasCheckedStatus.current = true;
253919
+ checkStatus();
253920
+ }, [isAvailable, checkStatus]);
253921
+ // Only expose the feature as available inside the desktop (Tauri) app.
253922
+ const finalIsAvailable = isAvailable && isTauriApp$1();
253923
+ return {
253924
+ isAvailable: finalIsAvailable,
253925
+ state,
253926
+ status,
253927
+ accessibilityPermission,
253928
+ install,
253929
+ stop,
253930
+ checkStatus,
253931
+ resetState,
253932
+ requestAccessibilityPermission,
253933
+ };
253934
+ }
253935
+
253936
+ // Cowork is macOS-only in prod; the env flag bypasses the OS check so the
253937
+ // toggle can be exercised on Linux/Windows desktop builds during testing.
253938
+ const isMacOS = () => {
253939
+ if (typeof navigator === 'undefined')
253940
+ return false;
253941
+ return /mac/i.test(navigator.userAgent || '');
253942
+ };
253943
+ const allowNonMacOSCowork = () => process.env.NEXT_PUBLIC_ALLOW_NON_MACOS_COMPUTER_USE_TOGGLE === 'true';
253944
+ // 14GB floor per spec; modelSupportsCowork gates size > gb, so a model of
253945
+ // exactly 14GB is also off (no 14.0GB model in the catalog). Only the local-model
253946
+ // backend needs this; the remote AI has no size requirement.
253947
+ const COWORK_MIN_MODEL_GB = 14;
253572
253948
  const InsideButtons = ({ activeOptions, onOptionClick, deepResearch, studyMode, artifactsEnabled, containerWidth, disabled = false, onOpenPromptGallery, embedMode = false, promptsIsEnabled = false, memoryEnabled = false, mentorId, tenantKey, username, }) => {
253573
253949
  const t = useT();
253950
+ // Cowork = the Tauri GhostOS assistant. GhostOS runs the tools on-device;
253951
+ // the "brain" is EITHER a large local model (Local Models / Ollama) OR the
253952
+ // remote AI (DM OpenAI-compatible endpoint), whichever is set up. useGhostOs
253953
+ // handles install/stop; the localStorage pref (isCoworkEnabled) is read
253954
+ // by the chat hook to route the turn. Reads the pref on mount.
253955
+ // Local state is `coworkOn` so it doesn't shadow the imported setCoworkEnabled.
253956
+ const ghostOs = useGhostOs();
253957
+ const [coworkOn, setCoworkOn] = useState(isCoworkEnabled);
253958
+ const toggleCowork = () => {
253959
+ const next = !coworkOn;
253960
+ // Guard only when turning on. Cowork runs on either a large local model
253961
+ // or the remote AI — allow enabling when either backend is ready (the local
253962
+ // model + Cowork combo still works). The chatbox has no inline notice
253963
+ // space, so remind via toast. (RBAC is enforced separately: this button is
253964
+ // `disabled` when the user lacks the mentor `#chat` permission — see
253965
+ // chat-input-form's isChatDisabledByRbac.)
253966
+ if (next) {
253967
+ const localReady = isLocalLLMEnabled() &&
253968
+ modelSupportsCowork(getLocalLLMModel(), COWORK_MIN_MODEL_GB);
253969
+ if (!localReady && !hasRemoteAiConfig()) {
253970
+ toast.warning(isLocalLLMEnabled()
253971
+ ? t('chatInputFormInsideButtons.coworkModelTooSmall')
253972
+ : t('chatInputFormInsideButtons.coworkNeedsBackend'));
253973
+ return;
253974
+ }
253975
+ }
253976
+ setCoworkOn(next);
253977
+ setCoworkEnabled(next);
253978
+ if (next)
253979
+ ghostOs.install();
253980
+ else
253981
+ ghostOs.stop();
253982
+ };
253574
253983
  const allInsideButtons = [
253575
253984
  {
253576
253985
  name: 'Canvas',
@@ -253614,6 +254023,14 @@ const InsideButtons = ({ activeOptions, onOptionClick, deepResearch, studyMode,
253614
254023
  action: /* istanbul ignore next */ () => onOptionClick(TOOLS.MEMORY),
253615
254024
  isEnabled: memoryEnabled && !embedMode && !!username,
253616
254025
  },
254026
+ {
254027
+ name: 'Cowork',
254028
+ label: t('chatInputFormInsideButtons.cowork'),
254029
+ icon: jsx(Monitor, { className: "h-4 w-4" }),
254030
+ isActive: coworkOn,
254031
+ action: toggleCowork,
254032
+ isEnabled: ghostOs.isAvailable && (isMacOS() || allowNonMacOSCowork()),
254033
+ },
253617
254034
  ].filter((item) => item.isEnabled);
253618
254035
  // Get visible inside buttons based on screen size
253619
254036
  // Active buttons are always visible, inactive ones are hidden based on available width
@@ -256892,7 +257309,7 @@ function Account({ tenant, tenants = [], username, onInviteClick, email, mainPla
256892
257309
  }, 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", children: (_d = (_c = TABS.find((t) => t.id === activeTab)) === null || _c === void 0 ? void 0 : _c.label) !== null && _d !== void 0 ? _d : activeTab }), jsxs("p", { className: "text-gray-600 dark:text-gray-400 text-sm", children: [activeTab === 'basic' && t('profileAccount.descBasic'), activeTab === 'social' && t('profileAccount.descSocial'), activeTab === 'security' && t('profileAccount.descSecurity'), activeTab === 'management' && t('profileAccount.descManagement'), activeTab === 'organization' && t('profileAccount.descOrganization'), activeTab === 'monetization' && t('profileAccount.descMonetization'), activeTab === 'datasets' && t('profileAccount.descBenchmarks'), activeTab === 'advanced' && t('profileAccount.descAdvanced')] })] }), activeTab === 'management' && hasManagementPermissions && (jsx(Admin, { authSpaUrl: authURL, onInviteClick: onInviteClick, tenant: tenant, currentSpa: currentSPA, hasUserTabPermission: hasUserTabPermission, hasGroupsTabPermission: hasGroupsTabPermission, hasPoliciesTabPermission: hasPoliciesTabPermission, hasRolesTabPermission: hasRolesTabPermission, hasTeamsTabPermission: hasTeamsTabPermission, hasInviteUserPermission: hasInviteUserPermission, hasCreateTeamPermission: hasCreateTeamPermission, onLoadGroupPermissions: onLoadGroupPermissions, hasAlertsTabPermission: hasAlertsTabPermission, rbacPermissions: rbacPermissions, enableRbac: enableRbac, showGradebookTab: showGradebookTab })), activeTab === 'organization' && (jsx(OrganizationTab, { platformKey: tenant, tenant: tenants.find((ten) => ten.key === tenant), onTenantUpdate: onTenantUpdate, setOrganizationLogoFromOutside: setOrganizationLogo, defaultSupportPhone: defaultSupportPhone, enableSupportPhone: enableSupportPhone })), 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 === 'datasets' && showBenchmarks && (jsx(BenchmarksTab, { tenantKey: tenant, username: username })), activeTab === 'advanced' && (jsx(AdvancedTab, { platformKey: tenant, currentSPA: currentSPA, username: username, authURL: authURL, currentPlatformBaseDomain: currentPlatformBaseDomain }))] })] }), jsx(ToastProvider, {})] }));
256893
257310
  }
256894
257311
 
256895
- function UserProfileModal({ isOpen, onClose, params, email, mainPlatformKey, showMentorAIDisplayCheckbox = false, showLeaderboardDisplayCheckbox = false, showGradebookTab = 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, showBenchmarks = false, localLLMProps, systemControlProps, enableSupportPhone, }) {
257312
+ function UserProfileModal({ isOpen, onClose, params, email, mainPlatformKey, showMentorAIDisplayCheckbox = false, showLeaderboardDisplayCheckbox = false, showGradebookTab = 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, showBenchmarks = false, localLLMProps, enableSupportPhone, }) {
256896
257313
  const t = useT();
256897
257314
  console.log('[UserProfileModal] localLLMProps received:', {
256898
257315
  isAvailable: localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.isAvailable,
@@ -256958,7 +257375,7 @@ function UserProfileModal({ isOpen, onClose, params, email, mainPlatformKey, sho
256958
257375
  showPlatformName: showPlatformName,
256959
257376
  useGravatarPicFallback: useGravatarPicFallback,
256960
257377
  showGradebookTab: showGradebookTab,
256961
- }, isAdmin: params.isAdmin, targetTab: targetTab, onClose: onClose, onAccountDeleted: onAccountDeleted, enableMemoryTab: enableMemoryTab, localLLMProps: localLLMProps, systemControlProps: systemControlProps })), [
257378
+ }, isAdmin: params.isAdmin, targetTab: targetTab, onClose: onClose, onAccountDeleted: onAccountDeleted, enableMemoryTab: enableMemoryTab, localLLMProps: localLLMProps })), [
256962
257379
  'organization',
256963
257380
  'management',
256964
257381
  'integrations',
@@ -256968,69 +257385,7 @@ function UserProfileModal({ isOpen, onClose, params, email, mainPlatformKey, sho
256968
257385
  ].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, showBenchmarks: showBenchmarks, enableSupportPhone: enableSupportPhone, showGradebookTab: showGradebookTab }), isInviteUserDialogOpen && (jsx(InviteUserDialog, { tenant: params.tenantKey, onClose: () => setIsInviteUserDialogOpen(false), isOpen: isInviteUserDialogOpen, enableCatalogInvite: enableCatalogInvite, hasManageUsersPermission: hasManageUsersPermission })), isInvitedUsersDialogOpen && (jsx(InvitedUsersDialog, { onClose: () => setIsInvitedUsersDialogOpen(false), tenant: params.tenantKey }))] }))] }) }) }));
256969
257386
  }
256970
257387
 
256971
- const IS_SERVER = typeof window === 'undefined';
256972
- /**
256973
- * A simplified useLocalStorage hook for persisting state to localStorage
256974
- * with SSR safety.
256975
- */
256976
- function useLocalStorage(key, initialValue) {
256977
- // Get the initial value
256978
- const getInitialValue = useCallback(() => {
256979
- const initial = initialValue instanceof Function ? initialValue() : initialValue;
256980
- if (IS_SERVER) {
256981
- return initial;
256982
- }
256983
- try {
256984
- const item = window.localStorage.getItem(key);
256985
- if (item === null) {
256986
- return initial;
256987
- }
256988
- return JSON.parse(item);
256989
- }
256990
- catch (error) {
256991
- console.warn(`Error reading localStorage key "${key}":`, error);
256992
- return initial;
256993
- }
256994
- }, [key, initialValue]);
256995
- const [storedValue, setStoredValue] = useState(getInitialValue);
256996
- // Update stored value when key changes
256997
- useEffect(() => {
256998
- setStoredValue(getInitialValue());
256999
- }, [key, getInitialValue]);
257000
- // Persist to localStorage
257001
- const setValue = useCallback((value) => {
257002
- if (IS_SERVER) {
257003
- console.warn(`Tried setting localStorage key "${key}" even though environment is not a client`);
257004
- return;
257005
- }
257006
- try {
257007
- const valueToStore = value instanceof Function ? value(storedValue) : value;
257008
- setStoredValue(valueToStore);
257009
- window.localStorage.setItem(key, JSON.stringify(valueToStore));
257010
- }
257011
- catch (error) {
257012
- console.warn(`Error setting localStorage key "${key}":`, error);
257013
- }
257014
- }, [key, storedValue]);
257015
- // Remove from localStorage
257016
- const removeValue = useCallback(() => {
257017
- if (IS_SERVER) {
257018
- console.warn(`Tried removing localStorage key "${key}" even though environment is not a client`);
257019
- return;
257020
- }
257021
- try {
257022
- const initial = initialValue instanceof Function ? initialValue() : initialValue;
257023
- window.localStorage.removeItem(key);
257024
- setStoredValue(initial);
257025
- }
257026
- catch (error) {
257027
- console.warn(`Error removing localStorage key "${key}":`, error);
257028
- }
257029
- }, [key, initialValue]);
257030
- return [storedValue, setValue, removeValue];
257031
- }
257032
-
257033
- const LOCAL_STORAGE_KEY$1 = 'model_download_state';
257388
+ const LOCAL_STORAGE_KEY = 'model_download_state';
257034
257389
  const FIRST_LAUNCH_DISMISSED_KEY = 'model_download_prompt_dismissed';
257035
257390
  // Manager/download status is authoritative from Tauri, never the persisted
257036
257391
  // snapshot. This flips true the first time any hook instance mounts in a page
@@ -257050,7 +257405,7 @@ let didResetPersistedState = false;
257050
257405
  */
257051
257406
  function useModelDownload() {
257052
257407
  const { isAvailable, invoke, listen } = useTauri();
257053
- const [state, setState] = useLocalStorage(LOCAL_STORAGE_KEY$1, initialModelDownloadState);
257408
+ const [state, setState] = useLocalStorage(LOCAL_STORAGE_KEY, initialModelDownloadState);
257054
257409
  const [ollamaStatus, setOllamaStatus] = useState(null);
257055
257410
  const [systemMemory, setSystemMemory] = useState(null);
257056
257411
  const [isFirstLaunchDismissed, setIsFirstLaunchDismissed] = useLocalStorage(FIRST_LAUNCH_DISMISSED_KEY, false);
@@ -257478,224 +257833,6 @@ function useModelDownload() {
257478
257833
  };
257479
257834
  }
257480
257835
 
257481
- const LOCAL_STORAGE_KEY = 'ghost_os_install_state';
257482
- /**
257483
- * Manage installing & setting up GhostOS (https://github.com/ghostwright/ghost-os)
257484
- * via the Tauri host. This is the self-contained backing hook for the System
257485
- * Control card and deliberately mirrors `useModelDownload`:
257486
- *
257487
- * - checks GhostOS install/run status through the host
257488
- * - installs/sets up GhostOS with real-time progress + logs
257489
- * - persists state across app restarts
257490
- *
257491
- * Outside the desktop (Tauri) app every action is a no-op and `isAvailable` is
257492
- * false, so the card hides itself.
257493
- */
257494
- function useGhostOs() {
257495
- const { isAvailable, invoke, listen } = useTauri();
257496
- const [state, setState] = useLocalStorage(LOCAL_STORAGE_KEY, initialGhostOsInstallState);
257497
- const [status, setStatus] = useState(null);
257498
- // macOS Accessibility permission, which GhostOS needs to control the device.
257499
- // null = unknown/not-yet-checked or unsupported (non-macOS / plugin absent).
257500
- const [accessibilityPermission, setAccessibilityPermission] = useState(null);
257501
- const unlistenRefs = useRef([]);
257502
- const hasCheckedStatus = useRef(false);
257503
- /**
257504
- * Refresh the macOS Accessibility permission via tauri-plugin-macos-permissions.
257505
- * Independent of the GhostOS status check so a missing host command for one
257506
- * doesn't suppress the other. Stays null where unsupported.
257507
- */
257508
- const refreshAccessibilityPermission = useCallback(async () => {
257509
- if (!isAvailable)
257510
- return;
257511
- try {
257512
- const granted = await invoke(MACOS_PERMISSIONS_COMMANDS.CHECK_ACCESSIBILITY);
257513
- setAccessibilityPermission(granted);
257514
- }
257515
- catch (error) {
257516
- // Plugin not registered or non-macOS host — leave permission unknown.
257517
- console.warn('[useGhostOs] Accessibility permission check unavailable:', error);
257518
- setAccessibilityPermission(null);
257519
- }
257520
- }, [isAvailable, invoke]);
257521
- /**
257522
- * Open the Accessibility pane / prompt so the user can grant permission, then
257523
- * re-check (the grant happens in System Settings, so we refresh after).
257524
- */
257525
- const requestAccessibilityPermission = useCallback(async () => {
257526
- if (!isAvailable)
257527
- return;
257528
- try {
257529
- await invoke(MACOS_PERMISSIONS_COMMANDS.REQUEST_ACCESSIBILITY);
257530
- }
257531
- catch (error) {
257532
- console.error('[useGhostOs] Failed to request accessibility permission:', error);
257533
- }
257534
- await refreshAccessibilityPermission();
257535
- }, [isAvailable, invoke, refreshAccessibilityPermission]);
257536
- const addLog = useCallback((log) => {
257537
- setState((prev) => ({
257538
- ...prev,
257539
- logs: [...prev.logs.slice(-99), log],
257540
- lastUpdated: new Date().toISOString(),
257541
- }));
257542
- }, [setState]);
257543
- /**
257544
- * Query GhostOS status from the host.
257545
- */
257546
- const checkStatus = useCallback(async () => {
257547
- if (!isAvailable)
257548
- return;
257549
- // Refresh the accessibility permission alongside install status (independent).
257550
- refreshAccessibilityPermission();
257551
- try {
257552
- setState((prev) => ({ ...prev, status: 'checking' }));
257553
- const result = await invoke(GHOST_OS_TAURI_COMMANDS.CHECK_GHOST_OS_STATUS);
257554
- setStatus(result);
257555
- setState((prev) => ({
257556
- ...prev,
257557
- status: result.installed ? 'completed' : 'idle',
257558
- progress: result.installed ? 100 : prev.progress,
257559
- lastUpdated: new Date().toISOString(),
257560
- }));
257561
- }
257562
- catch (error) {
257563
- console.error('[useGhostOs] Failed to check status:', error);
257564
- setState((prev) => ({ ...prev, status: 'idle' }));
257565
- }
257566
- }, [isAvailable, invoke, setState, refreshAccessibilityPermission]);
257567
- /**
257568
- * Install and set up GhostOS. The host clones the repo + runs setup and emits
257569
- * progress/log/status events along the way.
257570
- */
257571
- const install = useCallback(async () => {
257572
- if (!isAvailable)
257573
- return;
257574
- try {
257575
- setState((prev) => ({
257576
- ...prev,
257577
- status: 'installing',
257578
- progress: 0,
257579
- message: 'Setting up your assistant…',
257580
- error: undefined,
257581
- logs: [],
257582
- }));
257583
- addLog({
257584
- timestamp: new Date().toISOString(),
257585
- level: 'info',
257586
- message: 'Starting setup…',
257587
- });
257588
- const result = await invoke(GHOST_OS_TAURI_COMMANDS.INSTALL_GHOST_OS);
257589
- addLog({
257590
- timestamp: new Date().toISOString(),
257591
- level: 'info',
257592
- message: typeof result === 'string' && result ? result : 'Setup complete',
257593
- });
257594
- setState((prev) => ({
257595
- ...prev,
257596
- status: 'completed',
257597
- progress: 100,
257598
- message: 'Your assistant is ready',
257599
- lastUpdated: new Date().toISOString(),
257600
- }));
257601
- await checkStatus();
257602
- }
257603
- catch (error) {
257604
- const message = error instanceof Error ? error.message : String(error);
257605
- setState((prev) => ({
257606
- ...prev,
257607
- status: 'error',
257608
- error: message,
257609
- message: 'Setup didn’t finish',
257610
- lastUpdated: new Date().toISOString(),
257611
- }));
257612
- addLog({
257613
- timestamp: new Date().toISOString(),
257614
- level: 'error',
257615
- message: `Install failed: ${message}`,
257616
- });
257617
- }
257618
- }, [isAvailable, invoke, addLog, checkStatus, setState]);
257619
- /**
257620
- * Stop the running GhostOS manager.
257621
- */
257622
- const stop = useCallback(async () => {
257623
- if (!isAvailable)
257624
- return;
257625
- try {
257626
- await invoke(GHOST_OS_TAURI_COMMANDS.STOP_GHOST_OS);
257627
- await checkStatus();
257628
- }
257629
- catch (error) {
257630
- console.error('[useGhostOs] Failed to stop GhostOS:', error);
257631
- }
257632
- }, [isAvailable, invoke, checkStatus]);
257633
- /**
257634
- * Reset the install state (for retrying after an error).
257635
- */
257636
- const resetState = useCallback(() => {
257637
- setState(initialGhostOsInstallState);
257638
- }, [setState]);
257639
- // Wire up host event listeners (progress / logs / status).
257640
- useEffect(() => {
257641
- if (!isAvailable)
257642
- return;
257643
- const setupListeners = async () => {
257644
- try {
257645
- const unlistenProgress = await listen(GHOST_OS_TAURI_EVENTS.INSTALL_PROGRESS, (payload) => {
257646
- const nextStatus = payload.status === 'completed'
257647
- ? 'completed'
257648
- : payload.status === 'cancelled'
257649
- ? 'cancelled'
257650
- : payload.status === 'error'
257651
- ? 'error'
257652
- : 'installing';
257653
- setState((prev) => ({
257654
- ...prev,
257655
- status: nextStatus,
257656
- progress: payload.percentage,
257657
- message: payload.message,
257658
- lastUpdated: new Date().toISOString(),
257659
- }));
257660
- });
257661
- unlistenRefs.current.push(unlistenProgress);
257662
- const unlistenLogs = await listen(GHOST_OS_TAURI_EVENTS.INSTALLATION_LOG, addLog);
257663
- unlistenRefs.current.push(unlistenLogs);
257664
- const unlistenStatus = await listen(GHOST_OS_TAURI_EVENTS.STATUS, setStatus);
257665
- unlistenRefs.current.push(unlistenStatus);
257666
- }
257667
- catch (error) {
257668
- console.error('[useGhostOs] Failed to setup event listeners:', error);
257669
- }
257670
- };
257671
- setupListeners();
257672
- return () => {
257673
- unlistenRefs.current.forEach((unlisten) => unlisten());
257674
- unlistenRefs.current = [];
257675
- };
257676
- }, [isAvailable, listen, setState, addLog]);
257677
- // Check status once on mount (in the desktop app).
257678
- useEffect(() => {
257679
- if (!isAvailable || hasCheckedStatus.current)
257680
- return;
257681
- hasCheckedStatus.current = true;
257682
- checkStatus();
257683
- }, [isAvailable, checkStatus]);
257684
- // Only expose the feature as available inside the desktop (Tauri) app.
257685
- const finalIsAvailable = isAvailable && isTauriApp$1();
257686
- return {
257687
- isAvailable: finalIsAvailable,
257688
- state,
257689
- status,
257690
- accessibilityPermission,
257691
- install,
257692
- stop,
257693
- checkStatus,
257694
- resetState,
257695
- requestAccessibilityPermission,
257696
- };
257697
- }
257698
-
257699
257836
  function UserProfileDropdown({ email, mainPlatformKey,
257700
257837
  // User data
257701
257838
  username, userIsAdmin = false, userIsStudent = false, userIsVisiting = false,
@@ -257719,11 +257856,9 @@ metadata, metadataLoaded = false,
257719
257856
  currentSPA = '', showMentorAIDisplayCheckbox = false, showLeaderboardDisplayCheckbox = false, showGradebookTab = false, showUsernameField = false, showPlatformName = false, enableCatalogInvite = false, authURL, onTenantUpdate, currentPlatformBaseDomain = '', rbacPermissions = {}, enableRbac = false, onLoadGroupPermissions, defaultSupportPhone = '', enableSupportPhone,
257720
257857
  // Local LLM props
257721
257858
  localLLMProps,
257722
- // System Control props
257723
- systemControlProps,
257724
257859
  // Controlled modal state
257725
257860
  isModalOpen, onModalOpenChange, defaultActiveTab, onAccountDeleted, enableMemoryTab, showBenchmarks = false, }) {
257726
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
257861
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
257727
257862
  const t = useT();
257728
257863
  // Use hooks to fetch user metadata
257729
257864
  const { data: userMetadata } = useGetUserMetadataQuery({
@@ -257751,17 +257886,6 @@ isModalOpen, onModalOpenChange, defaultActiveTab, onAccountDeleted, enableMemory
257751
257886
  const selectedFoundryModel = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.selectedFoundryModel;
257752
257887
  const foundryStatus = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.foundryStatus;
257753
257888
  const onSelectFoundryModel = localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.onSelectFoundryModel;
257754
- // System Control (GhostOS) state from props (if provided) or from hook (fallback)
257755
- const ghostOsData = useGhostOs();
257756
- const isSystemControlAvailable = (_l = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.isAvailable) !== null && _l !== void 0 ? _l : ghostOsData.isAvailable;
257757
- const systemControlState = (_m = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.state) !== null && _m !== void 0 ? _m : ghostOsData.state;
257758
- const systemControlStatus = (_o = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.status) !== null && _o !== void 0 ? _o : ghostOsData.status;
257759
- const accessibilityPermission = (_p = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.accessibilityPermission) !== null && _p !== void 0 ? _p : ghostOsData.accessibilityPermission;
257760
- const installGhostOs = (_q = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.onInstall) !== null && _q !== void 0 ? _q : ghostOsData.install;
257761
- const stopGhostOs = (_r = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.onStop) !== null && _r !== void 0 ? _r : ghostOsData.stop;
257762
- const checkGhostOsStatus = (_s = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.onCheckStatus) !== null && _s !== void 0 ? _s : ghostOsData.checkStatus;
257763
- const resetGhostOsState = (_t = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.onResetState) !== null && _t !== void 0 ? _t : ghostOsData.resetState;
257764
- const requestAccessibilityPermission = (_u = systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.onRequestAccessibilityPermission) !== null && _u !== void 0 ? _u : ghostOsData.requestAccessibilityPermission;
257765
257889
  // Support controlled modal state
257766
257890
  const [internalIsUserProfileOpen, setInternalIsUserProfileOpen] = useState(false);
257767
257891
  const isUserProfileOpen = isModalOpen !== null && isModalOpen !== void 0 ? isModalOpen : internalIsUserProfileOpen;
@@ -257938,23 +258062,6 @@ isModalOpen, onModalOpenChange, defaultActiveTab, onAccountDeleted, enableMemory
257938
258062
  onResetState: resetState,
257939
258063
  onSelectFoundryModel,
257940
258064
  },
257941
- systemControlProps: {
257942
- isAvailable: isSystemControlAvailable,
257943
- state: systemControlState,
257944
- status: systemControlStatus,
257945
- requiredSizeGb: systemControlProps === null || systemControlProps === void 0 ? void 0 : systemControlProps.requiredSizeGb,
257946
- // Local-model status/memory + downloader, so the "Upgrade" action can
257947
- // check what's installed, keep the RAM/VRAM check, and pull a model.
257948
- ollamaStatus,
257949
- systemMemory,
257950
- accessibilityPermission,
257951
- onInstall: installGhostOs,
257952
- onStop: stopGhostOs,
257953
- onCheckStatus: checkGhostOsStatus,
257954
- onResetState: resetGhostOsState,
257955
- onRequestAccessibilityPermission: requestAccessibilityPermission,
257956
- onDownloadModel: startDownload,
257957
- },
257958
258065
  onAccountDeleted,
257959
258066
  enableMemoryTab,
257960
258067
  showBenchmarks,
@@ -266956,6 +267063,547 @@ function AgentScreenShareTab({ labels: labelsOverride, renderPromptContent, tena
266956
267063
  } }) }))] })) })] }));
266957
267064
  }
266958
267065
 
267066
+ const AGENT_GRADER_TAB_LABELS = {
267067
+ header: {
267068
+ title: 'Grader',
267069
+ description: 'Set up how your agent grades work against a rubric you define.',
267070
+ },
267071
+ capability: {
267072
+ title: 'Grading',
267073
+ description: 'Let your agent grade work. When this is on, the agent scores what people submit against the rubric you set up below and shares the result as feedback — great for essays, projects, and practice exercises.',
267074
+ offHint: 'Turn it on to write grading instructions and build your rubric. Anything you set up here is kept safe if you later turn it off.',
267075
+ },
267076
+ subTabs: {
267077
+ setup: 'Grading setup',
267078
+ rubric: 'Rubric',
267079
+ },
267080
+ denied: {
267081
+ title: 'No access to grading settings',
267082
+ description: "You don't have permission to view this agent's grading setup. Ask an administrator for access.",
267083
+ },
267084
+ warnings: {
267085
+ noConfig: 'Grading is on but not set up yet — the agent will fail when asked to grade. Save your grading instructions in Grading setup to finish.',
267086
+ noCriteria: 'Grading is on but the rubric is empty — the agent will fail when asked to grade. Add at least one criterion in the Rubric tab.',
267087
+ },
267088
+ config: {
267089
+ sectionTitle: 'Grading setup',
267090
+ gradingMode: {
267091
+ label: 'What gets graded',
267092
+ options: {
267093
+ submission: 'A submission',
267094
+ conversation: 'The conversation',
267095
+ },
267096
+ help: {
267097
+ submission: 'The agent grades a piece of work the person hands in, like an essay or answer.',
267098
+ conversation: 'The agent grades how the whole conversation went.',
267099
+ },
267100
+ },
267101
+ feedbackMode: {
267102
+ label: 'Feedback shared with the person',
267103
+ options: {
267104
+ overall: 'Overall feedback only',
267105
+ perCriteria: 'Feedback per criterion',
267106
+ both: 'Overall + per criterion',
267107
+ },
267108
+ help: 'The overall score is always calculated — this only controls how much detail is shared back.',
267109
+ },
267110
+ instructions: {
267111
+ label: 'Grading instructions',
267112
+ placeholder: "Describe what's being graded and anything the grader should know beyond the rubric. E.g. 'You are grading a 500-word persuasive essay on climate policy written by a high-school student. Be encouraging but honest.'",
267113
+ help: 'Required. This is the context the agent uses every time it grades.',
267114
+ },
267115
+ saveButton: 'Save',
267116
+ savingButton: 'Saving…',
267117
+ },
267118
+ criteria: {
267119
+ sectionTitle: 'Rubric',
267120
+ description: 'Each row is one thing the agent looks for and how many points it is worth. The overall score is points earned out of the total.',
267121
+ needsConfigHint: 'Save your grading setup first, then add rubric criteria here.',
267122
+ emptyState: 'No criteria yet. Add your first one — for example "Clarity", 10 points.',
267123
+ addButton: 'Add criterion',
267124
+ columns: {
267125
+ name: 'Name',
267126
+ criteria: 'Criteria',
267127
+ points: 'Points',
267128
+ },
267129
+ actionsAria: (name) => `Actions for ${name}`,
267130
+ edit: 'Edit',
267131
+ delete: 'Delete',
267132
+ totalPoints: 'Total possible points: {total}',
267133
+ scoreHint: 'Overall score = points earned ÷ {total}.',
267134
+ lastCriterionHint: 'A rubric needs at least one criterion — add another before removing this one.',
267135
+ modal: {
267136
+ addTitle: 'Add criterion',
267137
+ editTitle: 'Edit criterion',
267138
+ name: {
267139
+ label: 'Name',
267140
+ placeholder: 'e.g. Clarity',
267141
+ required: 'Name is required',
267142
+ },
267143
+ criteria: {
267144
+ // Required — describes what earns the points.
267145
+ label: 'Criteria',
267146
+ placeholder: 'e.g. The argument is easy to follow and each paragraph makes one clear point.',
267147
+ required: 'Criteria is required',
267148
+ },
267149
+ points: {
267150
+ label: 'Points',
267151
+ placeholder: '10',
267152
+ positive: 'Points must be a positive number',
267153
+ },
267154
+ save: 'Save',
267155
+ saving: 'Saving…',
267156
+ cancel: 'Cancel',
267157
+ },
267158
+ deleteModal: {
267159
+ title: 'Delete criterion',
267160
+ confirmationPrefix: 'Are you sure you want to remove',
267161
+ confirmationSuffix: ' from the rubric?',
267162
+ cancel: 'Cancel',
267163
+ delete: 'Delete',
267164
+ deleting: 'Deleting…',
267165
+ },
267166
+ },
267167
+ toasts: {
267168
+ toggleOn: 'Grading turned on',
267169
+ toggleOff: 'Grading turned off — your rubric is kept for next time',
267170
+ toggleError: "Couldn't update grading",
267171
+ configSaved: 'Grading setup saved',
267172
+ configError: "Couldn't save grading setup",
267173
+ criterionAdded: 'Criterion added',
267174
+ criterionUpdated: 'Criterion updated',
267175
+ criterionDeleted: 'Criterion deleted',
267176
+ criterionError: "Couldn't update the rubric",
267177
+ },
267178
+ };
267179
+ function buildGraderTabLabels(t) {
267180
+ return {
267181
+ header: {
267182
+ title: t('graderTabLabels.header.title'),
267183
+ description: t('graderTabLabels.header.description'),
267184
+ },
267185
+ capability: {
267186
+ title: t('graderTabLabels.capability.title'),
267187
+ description: t('graderTabLabels.capability.description'),
267188
+ offHint: t('graderTabLabels.capability.offHint'),
267189
+ },
267190
+ subTabs: {
267191
+ setup: t('graderTabLabels.subTabs.setup'),
267192
+ rubric: t('graderTabLabels.subTabs.rubric'),
267193
+ },
267194
+ denied: {
267195
+ title: t('graderTabLabels.denied.title'),
267196
+ description: t('graderTabLabels.denied.description'),
267197
+ },
267198
+ warnings: {
267199
+ noConfig: t('graderTabLabels.warnings.noConfig'),
267200
+ noCriteria: t('graderTabLabels.warnings.noCriteria'),
267201
+ },
267202
+ config: {
267203
+ sectionTitle: t('graderTabLabels.config.sectionTitle'),
267204
+ gradingMode: {
267205
+ label: t('graderTabLabels.config.gradingMode.label'),
267206
+ options: {
267207
+ submission: t('graderTabLabels.config.gradingMode.options.submission'),
267208
+ conversation: t('graderTabLabels.config.gradingMode.options.conversation'),
267209
+ },
267210
+ help: {
267211
+ submission: t('graderTabLabels.config.gradingMode.help.submission'),
267212
+ conversation: t('graderTabLabels.config.gradingMode.help.conversation'),
267213
+ },
267214
+ },
267215
+ feedbackMode: {
267216
+ label: t('graderTabLabels.config.feedbackMode.label'),
267217
+ options: {
267218
+ overall: t('graderTabLabels.config.feedbackMode.options.overall'),
267219
+ perCriteria: t('graderTabLabels.config.feedbackMode.options.perCriteria'),
267220
+ both: t('graderTabLabels.config.feedbackMode.options.both'),
267221
+ },
267222
+ help: t('graderTabLabels.config.feedbackMode.help'),
267223
+ },
267224
+ instructions: {
267225
+ label: t('graderTabLabels.config.instructions.label'),
267226
+ placeholder: t('graderTabLabels.config.instructions.placeholder'),
267227
+ help: t('graderTabLabels.config.instructions.help'),
267228
+ },
267229
+ saveButton: t('graderTabLabels.config.saveButton'),
267230
+ savingButton: t('graderTabLabels.config.savingButton'),
267231
+ },
267232
+ criteria: {
267233
+ sectionTitle: t('graderTabLabels.criteria.sectionTitle'),
267234
+ description: t('graderTabLabels.criteria.description'),
267235
+ needsConfigHint: t('graderTabLabels.criteria.needsConfigHint'),
267236
+ emptyState: t('graderTabLabels.criteria.emptyState'),
267237
+ addButton: t('graderTabLabels.criteria.addButton'),
267238
+ columns: {
267239
+ name: t('graderTabLabels.criteria.columns.name'),
267240
+ criteria: t('graderTabLabels.criteria.columns.criteria'),
267241
+ points: t('graderTabLabels.criteria.columns.points'),
267242
+ },
267243
+ actionsAria: (name) => t('graderTabLabels.criteria.actionsAria', { name }),
267244
+ edit: t('graderTabLabels.criteria.edit'),
267245
+ delete: t('graderTabLabels.criteria.delete'),
267246
+ totalPoints: t('graderTabLabels.criteria.totalPoints'),
267247
+ scoreHint: t('graderTabLabels.criteria.scoreHint'),
267248
+ lastCriterionHint: t('graderTabLabels.criteria.lastCriterionHint'),
267249
+ modal: {
267250
+ addTitle: t('graderTabLabels.criteria.modal.addTitle'),
267251
+ editTitle: t('graderTabLabels.criteria.modal.editTitle'),
267252
+ name: {
267253
+ label: t('graderTabLabels.criteria.modal.name.label'),
267254
+ placeholder: t('graderTabLabels.criteria.modal.name.placeholder'),
267255
+ required: t('graderTabLabels.criteria.modal.name.required'),
267256
+ },
267257
+ criteria: {
267258
+ label: t('graderTabLabels.criteria.modal.criteria.label'),
267259
+ placeholder: t('graderTabLabels.criteria.modal.criteria.placeholder'),
267260
+ required: t('graderTabLabels.criteria.modal.criteria.required'),
267261
+ },
267262
+ points: {
267263
+ label: t('graderTabLabels.criteria.modal.points.label'),
267264
+ placeholder: t('graderTabLabels.criteria.modal.points.placeholder'),
267265
+ positive: t('graderTabLabels.criteria.modal.points.positive'),
267266
+ },
267267
+ save: t('graderTabLabels.criteria.modal.save'),
267268
+ saving: t('graderTabLabels.criteria.modal.saving'),
267269
+ cancel: t('graderTabLabels.criteria.modal.cancel'),
267270
+ },
267271
+ deleteModal: {
267272
+ title: t('graderTabLabels.criteria.deleteModal.title'),
267273
+ confirmationPrefix: t('graderTabLabels.criteria.deleteModal.confirmationPrefix'),
267274
+ confirmationSuffix: t('graderTabLabels.criteria.deleteModal.confirmationSuffix'),
267275
+ cancel: t('graderTabLabels.criteria.deleteModal.cancel'),
267276
+ delete: t('graderTabLabels.criteria.deleteModal.delete'),
267277
+ deleting: t('graderTabLabels.criteria.deleteModal.deleting'),
267278
+ },
267279
+ },
267280
+ toasts: {
267281
+ toggleOn: t('graderTabLabels.toasts.toggleOn'),
267282
+ toggleOff: t('graderTabLabels.toasts.toggleOff'),
267283
+ toggleError: t('graderTabLabels.toasts.toggleError'),
267284
+ configSaved: t('graderTabLabels.toasts.configSaved'),
267285
+ configError: t('graderTabLabels.toasts.configError'),
267286
+ criterionAdded: t('graderTabLabels.toasts.criterionAdded'),
267287
+ criterionUpdated: t('graderTabLabels.toasts.criterionUpdated'),
267288
+ criterionDeleted: t('graderTabLabels.toasts.criterionDeleted'),
267289
+ criterionError: t('graderTabLabels.toasts.criterionError'),
267290
+ },
267291
+ };
267292
+ }
267293
+ function resolveGraderTabLabels(override, t) {
267294
+ const base = t ? buildGraderTabLabels(t) : AGENT_GRADER_TAB_LABELS;
267295
+ if (!override)
267296
+ return base;
267297
+ return deepMerge(base, override);
267298
+ }
267299
+
267300
+ function CriterionDeleteModal({ isOpen, onClose, criterion, labels, isDeleting, onConfirm, }) {
267301
+ const handleDelete = async () => {
267302
+ if (!criterion)
267303
+ return;
267304
+ const deleted = await onConfirm(criterion.id);
267305
+ if (deleted)
267306
+ onClose();
267307
+ };
267308
+ return (jsx(Dialog, { open: isOpen, onOpenChange: onClose, children: jsxs(DialogContent, { "aria-describedby": "grader-criterion-delete-description", "data-testid": "grader-criterion-delete-modal", children: [jsx(DialogHeader, { children: jsx(DialogTitle, { className: "ibl-dialog-title", children: labels.title }) }), jsx("div", { className: "my-5", id: "grader-criterion-delete-description", children: jsxs("p", { className: "text-sm text-[#646464]", children: [labels.confirmationPrefix, " ", jsx("span", { className: "font-bold italic", children: criterion === null || criterion === void 0 ? void 0 : criterion.name }), labels.confirmationSuffix] }) }), jsxs(DialogFooter, { className: "gap-3", children: [jsx(Button$1, { variant: "outline", onClick: onClose, children: labels.cancel }), jsx(Button$1, { className: "bg-gradient-to-r from-[#2563EB] to-[#93C5FD] text-white hover:opacity-90", onClick: handleDelete, disabled: isDeleting, "data-testid": "grader-criterion-delete-confirm", children: isDeleting ? labels.deleting : labels.delete })] })] }) }));
267309
+ }
267310
+
267311
+ function parsePoints(raw) {
267312
+ const parsed = Number.parseFloat(raw);
267313
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
267314
+ }
267315
+ function CriterionModal({ isOpen, onClose, criterion, labels, isSaving, onSave }) {
267316
+ const isEditing = Boolean(criterion);
267317
+ const [draft, setDraft] = React__default.useState({ name: '', criteria: '', points: '' });
267318
+ const [showErrors, setShowErrors] = React__default.useState(false);
267319
+ // Reset the form each time the modal opens (create) or targets a new
267320
+ // criterion (edit).
267321
+ React__default.useEffect(() => {
267322
+ if (!isOpen)
267323
+ return;
267324
+ setShowErrors(false);
267325
+ setDraft(criterion
267326
+ ? { name: criterion.name, criteria: criterion.criteria, points: String(criterion.points) }
267327
+ : { name: '', criteria: '', points: '' });
267328
+ }, [isOpen, criterion]);
267329
+ const errors = {
267330
+ name: draft.name.trim() ? '' : labels.name.required,
267331
+ criteria: draft.criteria.trim() ? '' : labels.criteria.required,
267332
+ points: parsePoints(draft.points) > 0 ? '' : labels.points.positive,
267333
+ };
267334
+ const isValid = !errors.name && !errors.criteria && !errors.points;
267335
+ const handleSubmit = async () => {
267336
+ if (!isValid) {
267337
+ setShowErrors(true);
267338
+ return;
267339
+ }
267340
+ const saved = await onSave({
267341
+ name: draft.name.trim(),
267342
+ criteria: draft.criteria.trim(),
267343
+ points: parsePoints(draft.points),
267344
+ });
267345
+ if (saved)
267346
+ onClose();
267347
+ };
267348
+ return (jsx(Dialog, { open: isOpen, onOpenChange: onClose, children: jsxs(DialogContent, { "aria-describedby": "grader-criterion-modal-description", "data-testid": "grader-criterion-modal", children: [jsx(DialogHeader, { children: jsx(DialogTitle, { className: "ibl-dialog-title", children: isEditing ? labels.editTitle : labels.addTitle }) }), jsxs("div", { className: "space-y-4", id: "grader-criterion-modal-description", children: [jsxs("div", { className: "space-y-1.5", children: [jsx(Label$1, { htmlFor: "grader-criterion-name", children: labels.name.label }), jsx(Input, { id: "grader-criterion-name", value: draft.name, placeholder: labels.name.placeholder, onChange: (e) => setDraft((prev) => ({ ...prev, name: e.target.value })) }), showErrors && errors.name && jsx("p", { className: "text-xs text-red-500", children: errors.name })] }), jsxs("div", { className: "space-y-1.5", children: [jsx(Label$1, { htmlFor: "grader-criterion-criteria", children: labels.criteria.label }), jsx(Textarea, { id: "grader-criterion-criteria", value: draft.criteria, placeholder: labels.criteria.placeholder, rows: 3, onChange: (e) => setDraft((prev) => ({ ...prev, criteria: e.target.value })) }), showErrors && errors.criteria && (jsx("p", { className: "text-xs text-red-500", children: errors.criteria }))] }), jsxs("div", { className: "space-y-1.5", children: [jsx(Label$1, { htmlFor: "grader-criterion-points", children: labels.points.label }), jsx(Input, { id: "grader-criterion-points", type: "number", min: 0, step: "any", value: draft.points, placeholder: labels.points.placeholder, className: "w-28", onChange: (e) => setDraft((prev) => ({ ...prev, points: e.target.value })) }), showErrors && errors.points && jsx("p", { className: "text-xs text-red-500", children: errors.points })] })] }), jsxs(DialogFooter, { className: "gap-3", children: [jsx(Button$1, { variant: "outline", onClick: onClose, children: labels.cancel }), jsx(Button$1, { className: "bg-gradient-to-r from-[#2563EB] to-[#93C5FD] text-white hover:opacity-90", onClick: handleSubmit, disabled: isSaving, "data-testid": "grader-criterion-modal-save", children: isSaving ? labels.saving : labels.save })] })] }) }));
267349
+ }
267350
+
267351
+ function RubricSection({ labels, criteria, hasConfig, isLoading, isMutating, totalPoints, onAdd, onEdit, onDelete, }) {
267352
+ const [showCreate, setShowCreate] = React__default.useState(false);
267353
+ const [criterionToEdit, setCriterionToEdit] = React__default.useState(null);
267354
+ const [criterionToDelete, setCriterionToDelete] = React__default.useState(null);
267355
+ const isLastCriterion = criteria.length === 1;
267356
+ return (jsxs("div", { className: "space-y-3", "data-testid": "grader-criteria-section", children: [jsxs("div", { className: "flex items-start justify-between gap-4", children: [jsxs("div", { children: [jsx("h4", { className: "text-sm font-medium text-gray-900", children: labels.sectionTitle }), jsx("p", { className: "text-xs text-gray-600", children: labels.description })] }), jsxs(Button$1, { className: "ibl-button-primary flex-shrink-0", onClick: () => setShowCreate(true), disabled: !hasConfig || isMutating, "data-testid": "grader-add-criterion-button", children: [jsx(Plus, { className: "mr-1 h-4 w-4" }), labels.addButton] })] }), jsx("div", { className: "overflow-hidden rounded-md border", children: jsx("div", { className: "overflow-x-auto sm:mx-0", children: jsx("div", { className: "inline-block min-w-full align-middle", children: isLoading ? (jsx("div", { className: "flex w-full items-center justify-center py-10", children: jsx(Spinner, {}) })) : (jsxs(Table$1, { className: "min-w-full", children: [jsx(TableHeader$1, { children: jsxs(TableRow$1, { className: "bg-muted/50 border-b", children: [jsx(TableHead, { className: "p-3 text-left text-sm whitespace-nowrap text-[#646464]", children: labels.columns.name }), jsx(TableHead, { className: "p-3 text-left text-sm whitespace-nowrap text-[#646464]", children: labels.columns.criteria }), jsx(TableHead, { className: "p-3 text-right text-sm whitespace-nowrap text-[#646464]", children: labels.columns.points }), jsx(TableHead, { className: "w-10 p-3" })] }) }), jsx(TableBody, { children: criteria.length === 0 ? (jsx(TableRow$1, { children: jsx(TableCell$1, { colSpan: 4, className: "p-4 text-center text-[#646464]", children: hasConfig ? labels.emptyState : labels.needsConfigHint }) })) : (criteria.map((criterion) => (jsxs(TableRow$1, { className: "border-b last:border-0", "data-testid": `grader-criterion-row-${criterion.id}`, children: [jsx(TableCell$1, { className: "max-w-[160px] truncate p-3 font-medium whitespace-nowrap text-[#646464]", children: criterion.name }), jsx(TableCell$1, { className: "max-w-[280px] truncate p-3 text-[#646464]", title: criterion.criteria, children: criterion.criteria }), jsx(TableCell$1, { className: "p-3 text-right whitespace-nowrap text-[#646464]", children: criterion.points }), jsx(TableCell$1, { className: "p-3 text-right", children: jsxs(DropdownMenu, { children: [jsx(DropdownMenuTrigger, { asChild: true, children: jsx(Button$1, { type: "button", variant: "ghost", size: "icon", "aria-label": labels.actionsAria(criterion.name), children: jsx(Ellipsis, { className: "h-4 w-4" }) }) }), jsxs(DropdownMenuContent, { align: "end", children: [jsxs(DropdownMenuItem, { onClick: () => setCriterionToEdit(criterion), children: [jsx(Pencil, { className: "mr-2 h-4 w-4" }), labels.edit] }), jsxs(DropdownMenuItem, { className: "text-red-600 focus:text-red-700", disabled: isLastCriterion, onClick: () => {
267357
+ // Radix marks the item disabled but a raw click can
267358
+ // still land — the backend refuses to delete the
267359
+ // last criterion, so hard-guard here too.
267360
+ if (isLastCriterion)
267361
+ return;
267362
+ setCriterionToDelete(criterion);
267363
+ }, children: [jsx(Trash2, { className: "mr-2 h-4 w-4" }), labels.delete] })] })] }) })] }, criterion.id)))) })] })) }) }) }), criteria.length > 0 && !isLoading && (jsxs(Fragment$1, { children: [jsxs("div", { className: "flex items-center justify-between pt-1 text-xs", children: [jsx("span", { className: "font-medium text-gray-900", children: labels.totalPoints.replace('{total}', String(totalPoints)) }), jsx("span", { className: "text-gray-500", children: labels.scoreHint.replace('{total}', String(totalPoints)) })] }), isLastCriterion && (
267364
+ // The backend refuses to delete the last criterion — explain why
267365
+ // its Delete menu action is disabled.
267366
+ jsx("p", { className: "text-xs text-gray-500", "data-testid": "grader-last-criterion-hint", children: labels.lastCriterionHint }))] })), jsx(CriterionModal, { isOpen: showCreate, onClose: () => setShowCreate(false), criterion: null, labels: labels.modal, isSaving: isMutating, onSave: onAdd }), jsx(CriterionModal, { isOpen: criterionToEdit !== null, onClose: () => setCriterionToEdit(null), criterion: criterionToEdit, labels: labels.modal, isSaving: isMutating, onSave: (values) => onEdit(criterionToEdit.id, values) }), jsx(CriterionDeleteModal, { isOpen: criterionToDelete !== null, onClose: () => setCriterionToDelete(null), criterion: criterionToDelete, labels: labels.deleteModal, isDeleting: isMutating, onConfirm: onDelete })] }));
267367
+ }
267368
+
267369
+ function SetupSection({ labels, config, isSaving, onSave }) {
267370
+ const [gradingMode, setGradingMode] = React__default.useState('submission');
267371
+ const [feedbackMode, setFeedbackMode] = React__default.useState('both');
267372
+ const [instructions, setInstructions] = React__default.useState('');
267373
+ // Rehydrate the form whenever the server copy changes.
267374
+ React__default.useEffect(() => {
267375
+ var _a, _b, _c;
267376
+ setGradingMode((_a = config === null || config === void 0 ? void 0 : config.grading_mode) !== null && _a !== void 0 ? _a : 'submission');
267377
+ setFeedbackMode((_b = config === null || config === void 0 ? void 0 : config.feedback_mode) !== null && _b !== void 0 ? _b : 'both');
267378
+ setInstructions((_c = config === null || config === void 0 ? void 0 : config.grader_instructions) !== null && _c !== void 0 ? _c : '');
267379
+ }, [config === null || config === void 0 ? void 0 : config.id, config === null || config === void 0 ? void 0 : config.updated_at]); // eslint-disable-line react-hooks/exhaustive-deps
267380
+ const isDirty = config
267381
+ ? gradingMode !== config.grading_mode ||
267382
+ feedbackMode !== config.feedback_mode ||
267383
+ instructions !== config.grader_instructions
267384
+ : instructions.trim().length > 0;
267385
+ const canSave = instructions.trim().length > 0 && isDirty && !isSaving;
267386
+ return (jsxs("div", { className: "space-y-4", "data-testid": "grader-setup-section", children: [jsxs("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2", children: [jsxs("div", { className: "space-y-1.5", children: [jsx(Label$1, { className: "text-xs font-medium text-gray-700", children: labels.gradingMode.label }), jsxs(Select$1, { value: gradingMode, onValueChange: (value) => setGradingMode(value), children: [jsx(SelectTrigger, { className: "h-9", "data-testid": "grader-grading-mode-select", children: jsx(SelectValue, {}) }), jsxs(SelectContent, { children: [jsx(SelectItem, { value: "submission", children: labels.gradingMode.options.submission }), jsx(SelectItem, { value: "conversation", children: labels.gradingMode.options.conversation })] })] }), jsx("p", { className: "text-xs text-gray-500", children: gradingMode === 'submission'
267387
+ ? labels.gradingMode.help.submission
267388
+ : labels.gradingMode.help.conversation })] }), jsxs("div", { className: "space-y-1.5", children: [jsx(Label$1, { className: "text-xs font-medium text-gray-700", children: labels.feedbackMode.label }), jsxs(Select$1, { value: feedbackMode, onValueChange: (value) => setFeedbackMode(value), children: [jsx(SelectTrigger, { className: "h-9", "data-testid": "grader-feedback-mode-select", children: jsx(SelectValue, {}) }), jsxs(SelectContent, { children: [jsx(SelectItem, { value: "overall", children: labels.feedbackMode.options.overall }), jsx(SelectItem, { value: "per_criteria", children: labels.feedbackMode.options.perCriteria }), jsx(SelectItem, { value: "both", children: labels.feedbackMode.options.both })] })] }), jsx("p", { className: "text-xs text-gray-500", children: labels.feedbackMode.help })] })] }), jsxs("div", { className: "space-y-1.5", children: [jsx(Label$1, { className: "text-xs font-medium text-gray-700", children: labels.instructions.label }), jsx(Textarea, { value: instructions, onChange: (e) => setInstructions(e.target.value), placeholder: labels.instructions.placeholder, rows: 4, "data-testid": "grader-instructions-textarea" }), jsx("p", { className: "text-xs text-gray-500", children: labels.instructions.help })] }), jsx("div", { className: "flex items-center justify-end", children: jsx("button", { type: "button", onClick: () => onSave({
267389
+ grading_mode: gradingMode,
267390
+ feedback_mode: feedbackMode,
267391
+ grader_instructions: instructions,
267392
+ }), disabled: !canSave, "data-testid": "grader-save-button", className: "inline-flex items-center gap-2 rounded-md bg-gradient-to-r from-[#2563EB] to-[#93C5FD] px-4 py-2 text-sm font-medium text-white hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50", children: isSaving ? labels.savingButton : labels.saveButton }) })] }));
267393
+ }
267394
+
267395
+ /**
267396
+ * Grading activation is owned entirely by the Grading tool's attachment to
267397
+ * the agent. The backend matches on `Tool.name` — `slug` defaults to a UUID
267398
+ * and is not stable across environments, so detection MUST use the name.
267399
+ */
267400
+ const GRADING_TOOL_NAME = 'Grading';
267401
+ function errorStatus(error) {
267402
+ return error === null || error === void 0 ? void 0 : error.status;
267403
+ }
267404
+ function errorDetail(error) {
267405
+ var _a;
267406
+ const detail = (_a = error === null || error === void 0 ? void 0 : error.data) === null || _a === void 0 ? void 0 : _a.detail;
267407
+ return typeof detail === 'string' ? detail : '';
267408
+ }
267409
+ function useGrader({ tenantKey, mentorId, username, labels }) {
267410
+ var _a;
267411
+ const identityReady = Boolean(tenantKey && mentorId && username);
267412
+ const { data: mentorSettings, isLoading: isSettingsLoading } = useGetMentorSettingsQuery({
267413
+ mentor: mentorId,
267414
+ org: tenantKey,
267415
+ // @ts-expect-error userId is not in the typed args but the API accepts it
267416
+ userId: username,
267417
+ }, { skip: !identityReady });
267418
+ const { data: tools } = useGetToolsQuery({
267419
+ mentor: mentorId,
267420
+ org: tenantKey,
267421
+ // @ts-expect-error userId is not in the typed args but the API accepts it
267422
+ userId: username,
267423
+ }, { skip: !identityReady });
267424
+ const mentorTools = (_a = mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.mentor_tools) !== null && _a !== void 0 ? _a : [];
267425
+ const gradingEnabled = (mentorSettings === null || mentorSettings === void 0 ? void 0 : mentorSettings.can_use_tools) === true &&
267426
+ mentorTools.some((tool) => (tool === null || tool === void 0 ? void 0 : tool.name) === GRADING_TOOL_NAME);
267427
+ // Optimistic local mirror of the tool attachment so the switch responds
267428
+ // instantly; rolled back if the settings PATCH fails.
267429
+ const [enabled, setEnabled] = React__default.useState(gradingEnabled);
267430
+ React__default.useEffect(() => {
267431
+ setEnabled(gradingEnabled);
267432
+ }, [gradingEnabled]);
267433
+ const [editMentor, { isLoading: isToggling }] = useEditMentorMutation();
267434
+ const { data: config, error: configError, isLoading: isConfigLoading, refetch: refetchConfig, } = useGetMentorGraderConfigurationQuery({ org: tenantKey, mentorId }, { skip: !identityReady });
267435
+ const { data: criteria = [], error: criteriaError, isLoading: isCriteriaLoading, refetch: refetchCriteria, } = useListGraderCriteriaQuery({ org: tenantKey, mentorId }, { skip: !identityReady });
267436
+ const [createConfig, { isLoading: isCreatingConfig }] = useCreateMentorGraderConfigurationMutation();
267437
+ const [updateConfig, { isLoading: isUpdatingConfig }] = useUpdateMentorGraderConfigurationMutation();
267438
+ const [createCriterion, { isLoading: isCreatingCriterion }] = useCreateGraderCriterionMutation();
267439
+ const [updateCriterion, { isLoading: isUpdatingCriterion }] = useUpdateGraderCriterionMutation();
267440
+ const [deleteCriterion, { isLoading: isDeletingCriterion }] = useDeleteGraderCriterionMutation();
267441
+ // 404 is the documented first-run state ("Grader config not found"), not an
267442
+ // error — the config row may simply not exist yet.
267443
+ const configNotFound = errorStatus(configError) === 404;
267444
+ const hasConfig = Boolean(config);
267445
+ const isForbidden = errorStatus(configError) === 403 || errorStatus(criteriaError) === 403;
267446
+ const totalPoints = criteria.reduce((sum, criterion) => sum + (Number(criterion.points) || 0), 0);
267447
+ const toggleGrading = async (checked) => {
267448
+ var _a, _b, _c;
267449
+ const gradingSlug = (_b = (_a = mentorTools.find((tool) => (tool === null || tool === void 0 ? void 0 : tool.name) === GRADING_TOOL_NAME)) === null || _a === void 0 ? void 0 : _a.slug) !== null && _b !== void 0 ? _b : (_c = (tools !== null && tools !== void 0 ? tools : []).find((tool) => (tool === null || tool === void 0 ? void 0 : tool.name) === GRADING_TOOL_NAME)) === null || _c === void 0 ? void 0 : _c.slug;
267450
+ if (checked && !gradingSlug) {
267451
+ // The Grading tool isn't in this tenant's catalogue — nothing to attach.
267452
+ toast.error(labels.toasts.toggleError);
267453
+ return;
267454
+ }
267455
+ const currentSlugs = mentorTools
267456
+ .map((tool) => tool === null || tool === void 0 ? void 0 : tool.slug)
267457
+ .filter((slug) => Boolean(slug));
267458
+ const newSlugs = checked
267459
+ ? Array.from(new Set([...currentSlugs, gradingSlug]))
267460
+ : currentSlugs.filter((slug) => slug !== gradingSlug);
267461
+ setEnabled(checked); // optimistic
267462
+ try {
267463
+ // Detaching the tool deliberately leaves the grader config untouched —
267464
+ // there is no DELETE for it, so rubric work survives disable/re-enable.
267465
+ await editMentor({
267466
+ mentor: mentorId,
267467
+ org: tenantKey,
267468
+ formData: {
267469
+ tool_slugs: newSlugs,
267470
+ can_use_tools: newSlugs.length > 0,
267471
+ },
267472
+ // @ts-expect-error userId is not in the typed args but the API accepts it
267473
+ userId: username,
267474
+ }).unwrap();
267475
+ toast.success(checked ? labels.toasts.toggleOn : labels.toasts.toggleOff);
267476
+ if (checked) {
267477
+ // The backend auto-provisions the config row when the tool is
267478
+ // attached through settings — pick it up without a page reload.
267479
+ refetchConfig();
267480
+ refetchCriteria();
267481
+ }
267482
+ }
267483
+ catch (error) {
267484
+ setEnabled(!checked); // roll back
267485
+ console.error('[AgentGraderTab] toggle grading error', error);
267486
+ toast.error(labels.toasts.toggleError);
267487
+ }
267488
+ };
267489
+ // Create vs update is decided from the already-fetched config state
267490
+ // (GET 200 → PATCH, documented 404 first-run → POST), mirroring the
267491
+ // screenshare tab. No reactive fallback: with iblFetchBaseQuery retrying
267492
+ // failed requests, a POST that 400s would burn through its retries before
267493
+ // any fallback ran.
267494
+ const saveConfig = async (formData) => {
267495
+ try {
267496
+ if (hasConfig) {
267497
+ await updateConfig({ org: tenantKey, mentorId, formData }).unwrap();
267498
+ }
267499
+ else {
267500
+ await createConfig({ org: tenantKey, mentorId, formData }).unwrap();
267501
+ }
267502
+ toast.success(labels.toasts.configSaved);
267503
+ return true;
267504
+ }
267505
+ catch (error) {
267506
+ console.error('[AgentGraderTab] save config error', error);
267507
+ toast.error(errorDetail(error) || labels.toasts.configError);
267508
+ if (!hasConfig) {
267509
+ // If the POST hit "already exists" (config created out of band, e.g.
267510
+ // auto-provisioned on tool attach), re-sync so the next save PATCHes.
267511
+ refetchConfig();
267512
+ }
267513
+ return false;
267514
+ }
267515
+ };
267516
+ const addCriterion = async (formData) => {
267517
+ try {
267518
+ await createCriterion({ org: tenantKey, mentorId, formData }).unwrap();
267519
+ toast.success(labels.toasts.criterionAdded);
267520
+ return true;
267521
+ }
267522
+ catch (error) {
267523
+ console.error('[AgentGraderTab] add criterion error', error);
267524
+ toast.error(errorDetail(error) || labels.toasts.criterionError);
267525
+ return false;
267526
+ }
267527
+ };
267528
+ const editCriterion = async (criterionId, formData) => {
267529
+ try {
267530
+ await updateCriterion({ org: tenantKey, mentorId, criterionId, formData }).unwrap();
267531
+ toast.success(labels.toasts.criterionUpdated);
267532
+ return true;
267533
+ }
267534
+ catch (error) {
267535
+ console.error('[AgentGraderTab] update criterion error', error);
267536
+ toast.error(errorDetail(error) || labels.toasts.criterionError);
267537
+ return false;
267538
+ }
267539
+ };
267540
+ const removeCriterion = async (criterionId) => {
267541
+ try {
267542
+ await deleteCriterion({ org: tenantKey, mentorId, criterionId }).unwrap();
267543
+ toast.success(labels.toasts.criterionDeleted);
267544
+ return true;
267545
+ }
267546
+ catch (error) {
267547
+ console.error('[AgentGraderTab] delete criterion error', error);
267548
+ toast.error(errorDetail(error) || labels.toasts.criterionError);
267549
+ return false;
267550
+ }
267551
+ };
267552
+ return {
267553
+ isLoading: isSettingsLoading || isConfigLoading,
267554
+ isForbidden,
267555
+ enabled,
267556
+ isToggling,
267557
+ toggleGrading,
267558
+ config,
267559
+ hasConfig,
267560
+ configNotFound,
267561
+ isSavingConfig: isCreatingConfig || isUpdatingConfig,
267562
+ saveConfig,
267563
+ criteria,
267564
+ isCriteriaLoading,
267565
+ totalPoints,
267566
+ addCriterion,
267567
+ editCriterion,
267568
+ removeCriterion,
267569
+ isMutatingCriteria: isCreatingCriterion || isUpdatingCriterion || isDeletingCriterion,
267570
+ };
267571
+ }
267572
+
267573
+ const GRADER_SUB_TABS = {
267574
+ setup: 'setup',
267575
+ rubric: 'rubric',
267576
+ };
267577
+ /**
267578
+ * Standalone top-level tab for the agent's grading setup: the in-tab toggle
267579
+ * attaches/detaches the "Grading" tool on the agent (the sole activation
267580
+ * switch on the backend), and the gated content splits into two sub-tabs —
267581
+ * Grading setup (configuration form) and Rubric (criteria table with
267582
+ * modal-based add/edit/delete).
267583
+ *
267584
+ * Grading only runs when the tool is attached AND a config exists AND the
267585
+ * rubric has at least one criterion — the tab surfaces a warning whenever
267586
+ * the tool is on but either of the other two is missing.
267587
+ */
267588
+ function AgentGraderTab({ labels: labelsOverride, tenantKey: tenantKeyProp, mentorId: mentorIdProp, username: usernameProp, }) {
267589
+ var _a, _b, _c;
267590
+ const ctx = useAgentSettingsOptional();
267591
+ const tenantKey = (_a = tenantKeyProp !== null && tenantKeyProp !== void 0 ? tenantKeyProp : ctx === null || ctx === void 0 ? void 0 : ctx.tenantKey) !== null && _a !== void 0 ? _a : '';
267592
+ const mentorId = (_b = mentorIdProp !== null && mentorIdProp !== void 0 ? mentorIdProp : ctx === null || ctx === void 0 ? void 0 : ctx.mentorId) !== null && _b !== void 0 ? _b : '';
267593
+ const username = (_c = usernameProp !== null && usernameProp !== void 0 ? usernameProp : ctx === null || ctx === void 0 ? void 0 : ctx.username) !== null && _c !== void 0 ? _c : '';
267594
+ const t = useT();
267595
+ const labels = React__default.useMemo(() => resolveGraderTabLabels(labelsOverride, t), [labelsOverride, t]);
267596
+ const { isLoading, isForbidden, enabled, isToggling, toggleGrading, config, hasConfig, isSavingConfig, saveConfig, criteria, isCriteriaLoading, totalPoints, addCriterion, editCriterion, removeCriterion, isMutatingCriteria, } = useGrader({ tenantKey, mentorId, username, labels });
267597
+ const showNoCriteriaWarning = enabled && hasConfig && !isCriteriaLoading && criteria.length === 0;
267598
+ const showNoConfigWarning = enabled && !isLoading && !hasConfig && !isForbidden;
267599
+ const warningMessage = showNoConfigWarning
267600
+ ? labels.warnings.noConfig
267601
+ : showNoCriteriaWarning
267602
+ ? labels.warnings.noCriteria
267603
+ : null;
267604
+ return (jsxs(Fragment$1, { children: [jsx("div", { className: "flex h-[73px] flex-shrink-0 items-center border-b border-gray-200 bg-white p-4 lg:block", children: jsxs("div", { children: [jsx("h3", { className: "mb-1 text-base font-medium text-gray-900", children: labels.header.title }), jsx("p", { className: "text-xs text-gray-600", children: labels.header.description })] }) }), jsx("div", { className: "flex-1 space-y-4 p-3 lg:p-4", style: { overflowY: 'auto', overflowX: 'hidden' }, "data-testid": "grader-tab-body", children: isLoading ? (jsx("div", { className: "flex items-center justify-center py-12", "data-testid": "grader-tab-loading", children: jsx(Spinner, {}) })) : isForbidden ? (jsxs("div", { className: "flex flex-col items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-10 text-center", "data-testid": "grader-tab-denied", children: [jsx(ShieldAlert, { className: "h-6 w-6 text-gray-400" }), jsx("p", { className: "text-sm font-medium text-gray-900", children: labels.denied.title }), jsx("p", { className: "max-w-sm text-xs text-gray-600", children: labels.denied.description })] })) : (jsxs(CapabilityGate, { title: labels.capability.title, description: labels.capability.description, offHint: labels.capability.offHint, enabled: enabled, onToggle: toggleGrading, disabled: isToggling, ariaLabel: labels.capability.title, toggleTestId: "grader-capability-toggle", children: [warningMessage && (jsxs("div", { className: "mb-4 flex items-start gap-2 rounded-lg border border-amber-200 bg-amber-50 p-3", "data-testid": "grader-misconfigured-warning", children: [jsx(TriangleAlert, { className: "mt-0.5 h-4 w-4 flex-shrink-0 text-amber-600" }), jsx("p", { className: "text-xs leading-relaxed text-amber-800", children: warningMessage })] })), jsxs(Tabs, { defaultValue: GRADER_SUB_TABS.setup, className: "w-full", children: [jsxs(TabsList, { "aria-label": labels.header.title, "data-testid": "grader-sub-tabs", children: [jsx(TabsTrigger, { value: GRADER_SUB_TABS.setup, "data-testid": "grader-sub-tab-setup", children: labels.subTabs.setup }), jsx(TabsTrigger, { value: GRADER_SUB_TABS.rubric, "data-testid": "grader-sub-tab-rubric", children: labels.subTabs.rubric })] }), jsx(TabsContent, { value: GRADER_SUB_TABS.setup, className: "space-y-4", children: jsx(SetupSection, { labels: labels.config, config: config, isSaving: isSavingConfig, onSave: saveConfig }) }), jsx(TabsContent, { value: GRADER_SUB_TABS.rubric, className: "space-y-4", children: jsx(RubricSection, { labels: labels.criteria, criteria: criteria, hasConfig: hasConfig, isLoading: isCriteriaLoading, isMutating: isMutatingCriteria, totalPoints: totalPoints, onAdd: addCriterion, onEdit: editCriterion, onDelete: removeCriterion }) })] })] })) })] }));
267605
+ }
267606
+
266959
267607
  function ApiKeyModal({ isOpen, onClose, apiKey, labels }) {
266960
267608
  const { copy, status } = useCopyToClipboard(800);
266961
267609
  const Icon = status === 'success' ? Check : Copy;
@@ -328941,5 +329589,5 @@ var index_es = /*#__PURE__*/Object.freeze({
328941
329589
  vectorsRatio: vectorsRatio
328942
329590
  });
328943
329591
 
328944
- export { AGENT_ACCESS_TAB_LABELS, AGENT_ANALYTICS_TAB_LABELS, AGENT_API_TAB_LABELS, AGENT_DATASETS_TAB_LABELS, AGENT_DISCLAIMERS_TAB_LABELS, AGENT_EMBED_TAB_LABELS, AGENT_EVALUATION_TAB_LABELS, AGENT_HISTORY_TAB_LABELS, AGENT_HUMAN_SUPPORT_TAB_LABELS, AGENT_LLM_TAB_LABELS, AGENT_LTI_TAB_LABELS, AGENT_MEMORY_TAB_LABELS, AGENT_PICKER_LABELS, AGENT_PRIVACY_TAB_LABELS, AGENT_PROMPTS_TAB_LABELS, AGENT_SAFETY_TAB_LABELS, AGENT_SCREENSHARE_TAB_LABELS, AGENT_SEARCH_LABELS, AGENT_SETTINGS_TAB_LABELS, AGENT_TASKS_TAB_LABELS, AGENT_TOOLS_TAB_LABELS, AGENT_VOICE_TAB_LABELS, Account, AgentAccessTab, AgentAnalyticsTab, AgentApiTab, AgentCard, AgentDatasetsTab, AgentDisclaimersTab, AgentEmbedTab, AgentEmptyState, AgentEvaluationTab, AgentHistoryTab, AgentHumanSupportTab, AgentLLMTab, AgentLtiTab, AgentMemoryTab, AgentPrivacyTab, AgentPromptsTab, AgentSafetyTab, AgentScreenShareTab, AgentSearch, AgentSearchFilters, AgentSearchInput, AgentSettingsProvider, AgentSettingsTab, AgentTasksTab, AgentToolsTab, AgentVoiceTab, AppSidebar, CATALOGS, Chat, ClientErrorPage, ConversationStarters$1 as ConversationStarters, CopyButton, CopyMentorModal, CourseBox, CourseContentLayout, CourseContentTabPage, CreatePathwayModal, CredentialDetailModal, CredentialMiniBox, DEFAULT_ANALYTICS_HUB_TABS, DEFAULT_LOCALE, DeleteMentorModal, EMPTY_PROGRAM_SETTINGS_FORM, EdxIframe, ErrorPage, GreetingMethod, McpTab, MediaBox, MentorSelectionGrid, NavBar, OrganizationTab, PLATFORM_SIDEBAR_FLYOUT_ITEM_COLOR, PLATFORM_SIDEBAR_FLYOUT_TITLE_COLOR, PLATFORM_SIDEBAR_NAV_ACTIVE_BG_OPEN, PLATFORM_SIDEBAR_NAV_MUTED, PathwayDetailModal, PlatformAccountSheet, PlatformSidebar, PlatformSidebarCollapsedLabelFlyout, PlatformSidebarCollapsedNavFlyout, PlatformSidebarFooter, PlatformSidebarNavDivider, PlatformSidebarNavSection, PlatformSidebarSubNavItem, ProfileInfoCards, ProfileTabs, ProgramDetailModal, ProjectsSidebarDropdown, SUPPORTED_LOCALES, SidebarInset, SidebarProvider, SidebarTrigger, SsoLogin, StarButton, UserProfileDropdown, UserProfileModal, WebContainersI18nProvider, formatDateString, getCatalog, getLLMProviderDetails, initializeLocalStorageWithObject, isValidLocale, resolveAgentPickerLabels, resolveAgentSearchLabels, resolveSettingsTabLabels, useAgentSearch, useAgentSearchWithPagination, useAgentSettings, useAgentStar, useGetChatDetails, usePlatformSidebarFooterActions, usePlatformSidebarNavCallback, useSidebar, useT, useTimeTracker, useWebContainersI18n, useWebContainersLocale };
329592
+ export { AGENT_ACCESS_TAB_LABELS, AGENT_ANALYTICS_TAB_LABELS, AGENT_API_TAB_LABELS, AGENT_DATASETS_TAB_LABELS, AGENT_DISCLAIMERS_TAB_LABELS, AGENT_EMBED_TAB_LABELS, AGENT_EVALUATION_TAB_LABELS, AGENT_GRADER_TAB_LABELS, AGENT_HISTORY_TAB_LABELS, AGENT_HUMAN_SUPPORT_TAB_LABELS, AGENT_LLM_TAB_LABELS, AGENT_LTI_TAB_LABELS, AGENT_MEMORY_TAB_LABELS, AGENT_PICKER_LABELS, AGENT_PRIVACY_TAB_LABELS, AGENT_PROMPTS_TAB_LABELS, AGENT_SAFETY_TAB_LABELS, AGENT_SCREENSHARE_TAB_LABELS, AGENT_SEARCH_LABELS, AGENT_SETTINGS_TAB_LABELS, AGENT_TASKS_TAB_LABELS, AGENT_TOOLS_TAB_LABELS, AGENT_VOICE_TAB_LABELS, Account, AgentAccessTab, AgentAnalyticsTab, AgentApiTab, AgentCard, AgentDatasetsTab, AgentDisclaimersTab, AgentEmbedTab, AgentEmptyState, AgentEvaluationTab, AgentGraderTab, AgentHistoryTab, AgentHumanSupportTab, AgentLLMTab, AgentLtiTab, AgentMemoryTab, AgentPrivacyTab, AgentPromptsTab, AgentSafetyTab, AgentScreenShareTab, AgentSearch, AgentSearchFilters, AgentSearchInput, AgentSettingsProvider, AgentSettingsTab, AgentTasksTab, AgentToolsTab, AgentVoiceTab, AppSidebar, CATALOGS, Chat, ClientErrorPage, ConversationStarters$1 as ConversationStarters, CopyButton, CopyMentorModal, CourseBox, CourseContentLayout, CourseContentTabPage, CreatePathwayModal, CredentialDetailModal, CredentialMiniBox, DEFAULT_ANALYTICS_HUB_TABS, DEFAULT_LOCALE, DeleteMentorModal, EMPTY_PROGRAM_SETTINGS_FORM, EdxIframe, ErrorPage, GRADING_TOOL_NAME, GreetingMethod, McpTab, MediaBox, MentorSelectionGrid, NavBar, OrganizationTab, PLATFORM_SIDEBAR_FLYOUT_ITEM_COLOR, PLATFORM_SIDEBAR_FLYOUT_TITLE_COLOR, PLATFORM_SIDEBAR_NAV_ACTIVE_BG_OPEN, PLATFORM_SIDEBAR_NAV_MUTED, PathwayDetailModal, PlatformAccountSheet, PlatformSidebar, PlatformSidebarCollapsedLabelFlyout, PlatformSidebarCollapsedNavFlyout, PlatformSidebarFooter, PlatformSidebarNavDivider, PlatformSidebarNavSection, PlatformSidebarSubNavItem, ProfileInfoCards, ProfileTabs, ProgramDetailModal, ProjectsSidebarDropdown, SUPPORTED_LOCALES, SidebarInset, SidebarProvider, SidebarTrigger, SsoLogin, StarButton, UserProfileDropdown, UserProfileModal, WebContainersI18nProvider, formatDateString, getCatalog, getLLMProviderDetails, initializeLocalStorageWithObject, isValidLocale, resolveAgentPickerLabels, resolveAgentSearchLabels, resolveSettingsTabLabels, useAgentSearch, useAgentSearchWithPagination, useAgentSettings, useAgentStar, useGetChatDetails, usePlatformSidebarFooterActions, usePlatformSidebarNavCallback, useSidebar, useT, useTimeTracker, useWebContainersI18n, useWebContainersLocale };
328945
329593
  //# sourceMappingURL=index.esm.js.map