From 22b4bb45224f572015ddb69ef94b9684bd564642 Mon Sep 17 00:00:00 2001 From: wowalxa2-tech <289241813+wowalxa2-tech@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:50:29 +0800 Subject: [PATCH] Preserve active plugin workspace on refresh --- backend/static/app.js | 81 +++++++++++++++++++++++- backend/static/app.min.js | 2 +- backend/static/frontend-assets.json | 12 ++-- backend/static/index.html | 4 +- frontend-tests/hub-shell.test.mjs | 19 ++++++ frontend-tests/plugin-workspace.test.mjs | 67 ++++++++++++++++++++ 6 files changed, 173 insertions(+), 12 deletions(-) diff --git a/backend/static/app.js b/backend/static/app.js index e46fd68..a94653b 100644 --- a/backend/static/app.js +++ b/backend/static/app.js @@ -933,6 +933,7 @@ const COMMON_PATH_ROOTS = new Set(['tmp', 'var', 'usr', 'etc', 'home', 'users', const SKILL_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/; const MODULE_SDK_VERSION = '1.6.0'; const MODULE_TASK_STORAGE_KEY = 'chatraw_module_tasks_v1'; +const SHELL_PAGE_STORAGE_KEY = 'chatraw_shell_page_v1'; const MODULE_TERMINAL_STATES = new Set(['succeeded', 'failed', 'cancelled']); const PLUGIN_WORKSPACE_PLACEMENTS = Object.freeze([ 'right', @@ -1307,7 +1308,8 @@ function app() { this.initCrossTabStateSync(); // Note: favicon is updated by loadLogo() which is called from loadSettings() // Initialize plugin system - this.initPluginSystem(); + await this.initPluginSystem(); + this.restorePluginWorkspacePage(); await this.initResidentIntegrations(); await this.resumeModuleTasks(); }, @@ -1529,6 +1531,75 @@ function app() { if (this.pluginWorkspace.show) this.closeActivePluginWorkspace(); }, + storedPluginWorkspacePage() { + let page = null; + try { + page = JSON.parse(sessionStorage.getItem(SHELL_PAGE_STORAGE_KEY) || 'null'); + } catch (_error) { + page = null; + } + if ( + !page + || page.kind !== 'plugin-workspace' + || typeof page.pluginId !== 'string' + || !page.pluginId + || typeof page.panelId !== 'string' + || !PLUGIN_WORKSPACE_PANEL_ID_PATTERN.test(page.panelId) + || !PLUGIN_WORKSPACE_PLACEMENTS.includes(page.placement) + || Object.keys(page).some( + key => !['kind', 'pluginId', 'panelId', 'placement'].includes(key) + ) + ) { + this.forgetPluginWorkspacePage(); + return null; + } + return page; + }, + + rememberPluginWorkspacePage(pluginId, panelId, placement) { + try { + sessionStorage.setItem(SHELL_PAGE_STORAGE_KEY, JSON.stringify({ + kind: 'plugin-workspace', + pluginId, + panelId, + placement + })); + } catch (_error) { + // A blocked session store must not prevent the page from opening. + } + }, + + forgetPluginWorkspacePage() { + try { + sessionStorage.removeItem(SHELL_PAGE_STORAGE_KEY); + } catch (_error) { + // The visible page can still close when storage is unavailable. + } + }, + + restorePluginWorkspacePage() { + const page = this.storedPluginWorkspacePage(); + if (!page) return false; + const definition = this.pluginWorkspaceDefinitions[ + this.pluginWorkspaceKey(page.pluginId, page.panelId) + ]; + if (!definition || !definition.placements.includes(page.placement)) { + this.forgetPluginWorkspacePage(); + return false; + } + try { + return this.openPluginWorkspacePanel( + page.panelId, + { placement: page.placement }, + page.pluginId + ); + } catch (error) { + this.forgetPluginWorkspacePage(); + console.warn('[Plugin Workspace restore]', error); + return false; + } + }, + toggleAgent() { if (!this.agentOpen) { this.showSettings = false; @@ -5287,6 +5358,7 @@ function app() { placement ); } + this.rememberPluginWorkspacePage(owner, panelId, placement); return true; } if (this.pluginWorkspace.show) { @@ -5340,6 +5412,7 @@ function app() { }; const returnFocus = this._pluginWorkspaceReturnFocus; this._pluginWorkspaceReturnFocus = null; + this.forgetPluginWorkspacePage(); this.restorePluginWorkspaceFocus(returnFocus); throw error; } finally { @@ -5352,6 +5425,7 @@ function app() { placement ); } + this.rememberPluginWorkspacePage(owner, panelId, placement); return true; }, @@ -5403,6 +5477,7 @@ function app() { placement: null, dispose: null }; + this.forgetPluginWorkspacePage(); const returnFocus = this._pluginWorkspaceReturnFocus; this._pluginWorkspaceReturnFocus = null; if (restoreFocus) { @@ -6590,9 +6665,9 @@ function app() { } }; - // Load enabled plugins - this.loadEnabledPlugins(); this.initPluginRuntimeSync(); + // Loading must finish before init() can restore the saved Workspace. + return this.loadEnabledPlugins(); }, initPluginRuntimeSync() { diff --git a/backend/static/app.min.js b/backend/static/app.min.js index 3a2db85..6bde103 100644 --- a/backend/static/app.min.js +++ b/backend/static/app.min.js @@ -1 +1 @@ -let hljsLoaded=!1,hljsLoading=!1;async function loadHighlightJS(){if(!hljsLoaded&&!hljsLoading){hljsLoading=!0;try{const e=document.createElement("link");e.rel="stylesheet",e.href="/vendor/highlight/github-dark.min.css",document.head.appendChild(e),await loadScript("/vendor/highlight/highlight.min.js"),await Promise.all([loadScript("/vendor/highlight/languages/python.min.js"),loadScript("/vendor/highlight/languages/javascript.min.js"),loadScript("/vendor/highlight/languages/bash.min.js"),loadScript("/vendor/highlight/languages/json.min.js")]),hljsLoaded=!0,document.querySelectorAll("pre code:not(.hljs)").forEach(e=>{hljs.highlightElement(e)})}catch(e){console.warn("Failed to load highlight.js:",e)}hljsLoading=!1}}function loadScript(e){return new Promise((t,s)=>{const i=document.createElement("script");i.src=e,i.onload=t,i.onerror=s,document.head.appendChild(i)})}const i18n={en:{appDocumentTitle:"{brand} Workspace",newChat:"New Chat",clearAllChats:"Clear All",confirmClearAll:"Are you sure you want to delete all chats? This cannot be undone.",allChatsCleared:"All chats cleared",settings:"Settings",delete:"Delete",expand:"Expand",collapse:"Collapse",defaultSubtitle:"Minimalist AI Assistant, Ready to Use",fastResponse:"Fast Response",multiModel:"Multi-Model",knowledgeBase:"Knowledge Base",kbEnabled:"KB Enabled",thinkingMode:"Thinking Mode",thinkingEnabled:"Thinking Enabled",thinkingProcess:"Thinking Process",uploadImage:"Upload Image",uploadDocument:"Upload Document",image:"Image",imageSelected:"Image Selected",documentAttached:"Document Attached",parseUrl:"Parse URL",agentExtensions:"Agent extensions",openAgentExtensions:"Open Agent extensions",closeAgentExtensions:"Close Agent extensions",enterUrl:"Enter URL",parsing:"Parsing...",urlAttached:"URL Attached",parseFailed:"Parse failed",urlPlaceholder:"https://example.com/article",confirm:"OK",ragReferences:"References",relevance:"Relevance",copyMessage:"Copy",copied:"Copied!",inputPlaceholder:"Type a message... (Enter to send, Shift+Enter for new line)",modelConfig:"Models",chatSettings:"Chat",chatSettingsDesc:"Adjust how the AI responds to your inputs.",ragSettings:"RAG",ragSettingsDesc:"Configure retrieval augmented generation parameters.",uiSettings:"Interface",uiSettingsDesc:"Customize the interface appearance.",kbDesc:"Upload documents for RAG context.",modelConfigDesc:"Manage your AI model connections and parameters.",modelType:"Type",chat:"Chat",embedding:"Embedding",reranker:"Reranker",optional:"Optional",contextLength:"Context Length",maxOutput:"Max Output",capabilities:"Capabilities",vision:"Vision",reasoning:"Reasoning",tools:"Tools",saveAndVerify:"Save & Verify",verifying:"Verifying...",verifySuccess:"Connected",verifyFailed:"Connection failed",temperatureHint:"Lower values make output more deterministic, higher values more creative",topPHint:"Nucleus sampling parameter, controls output diversity",streamOutput:"Stream Output",streamHint:"Display AI response in real-time",systemPrompt:"System Prompt",systemPromptHint:"Set instructions for the AI assistant",systemPromptPlaceholder:"You are a helpful assistant...",chunkSize:"Chunk Size",chunkSizeHint:"Number of characters per document chunk",chunkOverlap:"Chunk Overlap",chunkOverlapHint:"Overlap characters between adjacent chunks",topKHint:"Number of relevant documents to retrieve",scoreThreshold:"Score Threshold",scoreThresholdHint:"Documents below this threshold will be filtered",language:"Language",theme:"Theme",light:"Light",uploadLogo:"Upload",logoText:"Logo Text",subtitle:"Subtitle",avatars:"Avatars",userAvatar:"User",aiAvatar:"AI",uploadDocument:"Upload Document",noDocuments:"No documents yet. Upload one above.",cancel:"Cancel",save:"Save",uploadSuccess:"uploaded successfully",uploadFailed:"Upload failed",deleteFailed:"Delete failed",saveFailed:"Save failed",settingsSaved:"Settings saved",sendFailed:"Send failed",stopGeneration:"Stop",createChatFailed:"Failed to create chat",documentDeleted:"Document deleted",logoName:"Logo & Name",uploadUserAvatar:"Upload User Avatar",uploadAIAvatar:"Upload AI Avatar",active:"Active",error:"Error",mitLicense:"MIT License",plugins:"Plugins",pluginMarket:"Plugin Market",pluginMarketDesc:"Browse and install plugins from the official market.",installedPlugins:"Installed",installedPluginsDesc:"Manage your installed plugins.",installLocalPlugin:"Install Local",installLocalPluginDesc:"Upload a plugin zip file to install.",refreshingMarket:"Refreshing plugin market...",networkError:"Please check network connection",retry:"Retry",install:"Install",installed:"Installed",installing:"Installing...",installSuccess:"Plugin installed successfully",installFailed:"Install failed",uninstall:"Uninstall",uninstallSuccess:"Plugin uninstalled",uninstallFailed:"Uninstall failed",confirmUninstall:"Are you sure you want to uninstall this plugin?",pluginDisabled:"Plugin disabled",selectPluginZip:"Click or drag to upload plugin (.zip)",pluginDevGuide:"Plugin Development Guide",noPluginsAvailable:"No plugins available",searchPlugins:"Search plugins",noPluginsInstalled:"No plugins installed yet",noPluginSettings:"This plugin has no settings.",apiKeySettings:"API Key Settings",enterApiKey:"Enter API Key",apiKeySet:"API Key is already set. Clear and enter new key to change.",onlyZipSupported:"Only .zip files are supported",hermesRun:"Hermes Run",hermesRunStatusStarted:"Started",hermesRunStatusRunning:"Running",hermesRunStatusPendingApproval:"Pending approval",hermesRunStatusCompleted:"Completed",hermesRunStatusFailed:"Failed",hermesRunStatusCancelled:"Cancelled",hermesRunEventRunStarted:"Run started",hermesRunEventToolStarted:"Tool started",hermesRunEventToolCompleted:"Tool completed",hermesRunEventReasoning:"Reasoning available",hermesRunEventApprovalRequest:"Approval required",hermesRunEventApprovalResponded:"Approval responded",hermesRunEventRunCompleted:"Run completed",hermesRunEventRunFailed:"Run failed",hermesRunEventRunCancelled:"Run cancelled",hermesRunTool:"Tool",hermesRunPreview:"Preview",hermesRunCommand:"Command",hermesRunDescription:"Description",hermesRunPatterns:"Patterns",hermesRunDuration:"Duration",hermesRunChoice:"Choice",hermesApprovalOnce:"Allow once",hermesApprovalSession:"Allow for session",hermesApprovalDeny:"Deny",hermesApprovalSubmitting:"Submitting...",hermesApprovalError:"Approval failed",close:"Close",online:"Online",signOut:"Sign out",featureCategories:"Feature categories",featureNavigation:"Feature navigation",loadingFeatureCatalog:"Loading feature catalog…",noFeatures:"No features available",openFeature:"Open",featureUnavailable:"Unavailable",featureAvailable:"Available",featureLoading:"Loading",moduleUnavailable:"Module unavailable",pluginMissing:"Plugin missing",pluginDisabledState:"Plugin disabled",pluginVersionMismatch:"Plugin version mismatch",panelNotRegistered:"Panel not registered",mainPlacementRequired:"Main view required",featurePanelNotReady:"The feature panel is not ready.",backHome:"Back to home",currentConversation:"Current conversation",agentSubtitle:"Your intelligent work assistant",conversationHistory:"Conversation history",newConversation:"New conversation",closeAgent:"Close Agent",openAgent:"Open Hermes Agent",expandAgent:"Expand conversation window",restoreAgent:"Restore conversation window",renameConversation:"Rename conversation",deleteConversation:"Delete conversation",noConversationHistory:"No conversation history yet"},zh:{appDocumentTitle:"{brand} 工作空间",newChat:"新对话",clearAllChats:"清空所有",confirmClearAll:"确定要删除所有对话吗?此操作无法撤销。",allChatsCleared:"已清空所有对话",settings:"设置",delete:"删除",expand:"展开",collapse:"收起",defaultSubtitle:"极简AI助手,开箱即用",fastResponse:"极速响应",multiModel:"多模型支持",knowledgeBase:"知识库",kbEnabled:"知识库已开启",thinkingMode:"思考模式",thinkingEnabled:"思考模式已开启",thinkingProcess:"思考过程",uploadImage:"上传图片",uploadDocument:"上传文档",image:"图片",imageSelected:"已选择图片",documentAttached:"已附加文档",parseUrl:"解析网页",agentExtensions:"Agent 扩展",openAgentExtensions:"打开 Agent 扩展",closeAgentExtensions:"关闭 Agent 扩展",enterUrl:"输入网址",parsing:"解析中...",urlAttached:"已附加网页",parseFailed:"解析失败",urlPlaceholder:"https://example.com/article",confirm:"确定",ragReferences:"引用来源",relevance:"相关度",copyMessage:"复制",copied:"已复制",inputPlaceholder:"输入消息... (Enter 发送, Shift+Enter 换行)",modelConfig:"模型配置",modelConfigDesc:"管理您的AI模型连接和参数",chatSettings:"聊天设置",chatSettingsDesc:"调整AI对您输入的响应方式",ragSettings:"RAG设置",ragSettingsDesc:"配置检索增强生成 (RAG) 参数",uiSettings:"界面设置",uiSettingsDesc:"自定义界面外观",kbDesc:"上传用于RAG上下文的文档",modelType:"类型",chat:"聊天",embedding:"嵌入",reranker:"重排",optional:"可选",contextLength:"上下文长度",maxOutput:"最大输出",capabilities:"模型能力",vision:"视觉",reasoning:"推理",tools:"工具",saveAndVerify:"保存并验证",verifying:"验证中...",verifySuccess:"连接成功",verifyFailed:"连接失败",temperatureHint:"较低的值使输出更确定,较高的值更有创意",topPHint:"核采样参数,控制输出的多样性",streamOutput:"流式输出",streamHint:"实时显示AI的回复内容",systemPrompt:"系统提示词",systemPromptHint:"设置AI助手的行为指令",systemPromptPlaceholder:"你是一个有帮助的助手...",chunkSize:"文档块大小",chunkSizeHint:"文档分块的字符数",chunkOverlap:"块重叠",chunkOverlapHint:"相邻块之间的重叠字符数",topKHint:"检索相关文档的数量",scoreThreshold:"相似度阈值",scoreThresholdHint:"低于此阈值的文档将被过滤",language:"语言",theme:"主题",light:"浅色",uploadLogo:"上传",logoText:"Logo文字",subtitle:"副标题",avatars:"头像",userAvatar:"用户",aiAvatar:"AI",uploadDocument:"上传文档",noDocuments:"暂无文档,请在上方上传",cancel:"取消",save:"保存",uploadSuccess:"上传成功",uploadFailed:"上传失败",deleteFailed:"删除失败",saveFailed:"保存失败",settingsSaved:"设置已保存",sendFailed:"发送失败",stopGeneration:"停止",createChatFailed:"创建对话失败",documentDeleted:"文档已删除",logoName:"Logo & 名称",uploadUserAvatar:"上传用户头像",uploadAIAvatar:"上传 AI 头像",active:"活跃",error:"错误",mitLicense:"MIT 协议",plugins:"插件",pluginMarket:"插件市场",pluginMarketDesc:"浏览并安装官方市场的插件",installedPlugins:"已安装",installedPluginsDesc:"管理已安装的插件",installLocalPlugin:"本地安装",installLocalPluginDesc:"上传插件 zip 文件进行安装",refreshingMarket:"正在刷新插件市场...",networkError:"请检查网络连接",retry:"重试",install:"安装",installed:"已安装",installing:"安装中...",installSuccess:"插件安装成功",installFailed:"安装失败",uninstall:"卸载",uninstallSuccess:"插件已卸载",uninstallFailed:"卸载失败",confirmUninstall:"确定要卸载此插件吗?",pluginDisabled:"插件已禁用",selectPluginZip:"点击或拖拽上传插件 (.zip)",pluginDevGuide:"插件开发指南",noPluginsAvailable:"暂无可用插件",searchPlugins:"搜索插件",noPluginsInstalled:"暂未安装任何插件",noPluginSettings:"此插件暂无可配置项",apiKeySettings:"API 密钥设置",enterApiKey:"输入 API Key",apiKeySet:"API Key 已设置。清空后输入新密钥可更改。",onlyZipSupported:"仅支持 .zip 文件",hermesRun:"Hermes Run",hermesRunStatusStarted:"已开始",hermesRunStatusRunning:"运行中",hermesRunStatusPendingApproval:"等待审批",hermesRunStatusCompleted:"已完成",hermesRunStatusFailed:"失败",hermesRunStatusCancelled:"已取消",hermesRunEventRunStarted:"Run 已开始",hermesRunEventToolStarted:"工具已启动",hermesRunEventToolCompleted:"工具已完成",hermesRunEventReasoning:"推理可用",hermesRunEventApprovalRequest:"需要审批",hermesRunEventApprovalResponded:"审批已响应",hermesRunEventRunCompleted:"Run 已完成",hermesRunEventRunFailed:"Run 失败",hermesRunEventRunCancelled:"Run 已取消",hermesRunTool:"工具",hermesRunPreview:"预览",hermesRunCommand:"命令",hermesRunDescription:"说明",hermesRunPatterns:"规则",hermesRunDuration:"耗时",hermesRunChoice:"选择",hermesApprovalOnce:"允许一次",hermesApprovalSession:"本会话允许",hermesApprovalDeny:"拒绝",hermesApprovalSubmitting:"提交中...",hermesApprovalError:"审批失败",close:"关闭",online:"在线",signOut:"退出登录",featureCategories:"功能分类",featureNavigation:"功能导航",loadingFeatureCatalog:"正在读取功能目录…",noFeatures:"暂无功能",openFeature:"打开",featureUnavailable:"暂不可用",featureAvailable:"可用",featureLoading:"加载中",moduleUnavailable:"模块不可用",pluginMissing:"插件未安装",pluginDisabledState:"插件已停用",pluginVersionMismatch:"插件版本不兼容",panelNotRegistered:"功能面板未注册",mainPlacementRequired:"需要主内容区布局",featurePanelNotReady:"功能界面尚未就绪。",backHome:"返回首页",currentConversation:"当前对话",agentSubtitle:"你的智能工作助手",conversationHistory:"历史会话",newConversation:"新建会话",closeAgent:"关闭 Agent",openAgent:"打开 Hermes Agent",expandAgent:"扩展对话窗口",restoreAgent:"还原对话窗口",renameConversation:"重命名会话",deleteConversation:"删除会话",noConversationHistory:"还没有历史会话"}};Object.assign(i18n.en,{send:"Send",rename:"Rename",renameSharedChat:"Rename this conversation",renameFailed:"Rename failed",deleteSharedChat:"Delete this private conversation? This cannot be undone.",deleteSharedDocument:"Delete this shared document? Other users will lose access to it.",requestFailed:"Request failed",unknownError:"Unknown error",authenticationRequired:"Authentication required",settingsNavigation:"Settings navigation",pluginNavigation:"Plugin navigation",preview:"Preview",logoAlt:"Logo",userAvatarAlt:"User avatar",assistantAvatarAlt:"AI assistant avatar",artifactSize:"{size} bytes",removeImage:"Remove image",removeUrl:"Remove URL",removeDocument:"Remove document",removeLogo:"Remove logo",composerSuggestions:"Composer suggestions",apiEndpoint:"API Endpoint",apiKeyOptional:"API Key (Optional)",configuredReplace:"Configured — enter to replace",clearKey:"Clear key",modelId:"Model ID",temperature:"Temperature",topP:"Top P",english:"English",chinese:"中文",users:"Users",userManagement:"User management",userManagementDesc:"Create accounts, control access, and reset passwords. Usernames cannot be changed.",username:"Username",initialPassword:"Initial password",role:"Role",member:"Member",administrator:"Administrator",createUser:"Create user",resetPassword:"Reset password",enable:"Enable",disable:"Disable",enabled:"Enabled",disabled:"Disabled",userCreated:"User created",userUpdated:"User updated",passwordReset:"Password reset; existing sessions revoked",newPasswordFor:"New password for {username}",unableCreateUser:"Unable to create user",unableUpdateUser:"Unable to update user",unableResetPassword:"Unable to reset password",unableChangePassword:"Unable to change password",modules:"Modules",featureSuites:"Feature suites",featureSuitesDesc:"Each product feature joins one backend module with either a companion plugin or a source-built Resident Integration. ChatRaw does not start processes or containers.",composeNetwork:"Compose network",composeNetworkDesc:"Use this network for ChatRaw and module services.",containerAddressRepair:"Container address repair required",addressesUnchanged:"Existing addresses were not changed automatically.",moduleAddress:"Module address",moduleAddressSecret:"The address is sent to ChatRaw once and is never returned by the management API.",pairingCode:"One-time pairing code",connectForReview:"Connect for review",noModules:"No modules are connected.",moduleId:"Module ID",frontendIntegration:"Frontend integration",moduleProtocol:"Module protocol",installedVersion:"Installed version",requires:"Requires",residentSourceBuilt:"Resident Integration is source-built into this ChatRaw deployment.",networkAndConfiguration:"Network and configuration",configurationStatus:"Configuration",lastChecked:"Last checked",dataOwnership:"Data ownership and retention",requestedCapabilities:"Requested host capabilities",none:"None.",recentFault:"Current availability issue",declaredActions:"Declared actions",noActions:"No actions declared.",minimumRole:"minimum role",supportsStream:"stream",supportsCancel:"cancel",supportsApproval:"approval",supportsArtifacts:"artifacts",supportsChatProjection:"chat projection",reviewRequired:"Review is required before this module can be enabled.",requirementsIncomplete:"Approved, but health, configuration, Ready, or frontend integration requirements are incomplete.",approveDeclaration:"Approve declaration",checkHealth:"Check health",refreshManifest:"Refresh manifest",configure:"Configure",enableSuite:"Enable suite",drain:"Drain",purgeModuleData:"Purge module data",disconnect:"Disconnect",moduleConfiguration:"Module configuration",keep:"Keep",replace:"Replace",clear:"Clear",newSecret:"New secret",saveModuleConfiguration:"Save module configuration",moduleOperationFailed:"Module operation failed",modulePaired:"Module paired for review",approveModuleConfirm:"Approve {moduleId} and its declared capabilities?",moduleApproved:"Module manifest approved",moduleActionCompleted:"Module {action} completed",configurationSavedRevision:"Configuration saved at revision {revision}",disconnectModulePrompt:"Type {moduleId} to disconnect. Module data will be preserved.",moduleDisconnected:"Module disconnected; module data was preserved",purgeModulePrompt:"This deletes data owned by the module. Type {confirmation} to continue.",moduleDataPurged:"Module-owned data purged",actionCheck:"check",actionRefresh:"refresh",actionEnable:"enable",actionDrain:"drain",actionDisable:"disable",account:"Account",currentPassword:"Current password",newPassword:"New password",changePassword:"Change password",signOut:"Sign out",loading:"Loading...",pluginInfo:"Plugin info",installPlugin:"Install plugin",closeResidentIntegration:"Close Resident Integration",tasks:"Tasks",moduleTask:"Module task",task:"Task",closeTask:"Close task",moduleTasks:"Module tasks",approvalRequired:"Approval required",taskProgress:"Task progress",approve:"Approve",deny:"Deny",output:"Output",artifacts:"Artifacts",cancelTask:"Cancel task",executionProcess:"Execution process",activityRunning:"Running",activityUpdated:"Updated",activityInterrupted:"Previous run did not finish",waitingForExecutionDetails:"Waiting for execution details",noExecutionDetails:"No execution details were retained",tool:"Tool",viewDetails:"View details",toolInput:"Input",toolResult:"Result",resultInConversation:"This task is displayed in its conversation.",statePending:"Pending",stateQueued:"Queued",statePaired:"Paired",stateReviewRequired:"Review required",stateApproved:"Approved",stateEnabled:"Enabled",stateDisabled:"Disabled",stateDraining:"Draining",stateReady:"Ready",stateHealthy:"Healthy",stateUnhealthy:"Unhealthy",stateConfigured:"Configured",stateUnconfigured:"Not configured",stateRunning:"Running",stateSucceeded:"Succeeded",stateFailed:"Failed",stateCancelled:"Cancelled",statePendingApproval:"Pending approval",stateReachable:"Reachable",stateUnreachable:"Unreachable",stateCompatible:"Compatible",stateIncompatible:"Incompatible",statePresent:"Present",stateMissing:"Missing",stateValid:"Valid",stateInvalid:"Invalid",statePlugin:"Plugin",stateResident:"Resident Integration",stateInstalled:"Installed",stateAvailable:"Available",stateUnknown:"Unknown",invalidRequest:"Invalid request",invalidUsernameType:"Username must be text",invalidUsernameLength:"Username length is invalid",invalidUsernameCharacters:"Username contains unsupported characters",invalidPasswordLength:"Password is too short",invalidPasswordTooLong:"Password is too long",setupUnavailable:"Initial setup is unavailable",usernameInUse:"Username is already in use",invalidCredentials:"Invalid username or password",currentPasswordIncorrect:"Current password is incorrect",invalidRole:"Invalid role",userNotFound:"User not found",lastActiveAdmin:"The last active administrator cannot be disabled",administratorRequired:"Administrator permission is required",selfUserManagementForbidden:"Manage your own password from Account; your role and access cannot be changed here",confirmPassword:"Confirm password",passwordsDoNotMatch:"The passwords do not match",passwordMinimum:"Use at least 12 characters",administratorAccessWarning:"Administrators can manage users, models, plugins, and modules.",usersLoading:"Loading users…",usersEmpty:"No users found.",usersLoadFailed:"Unable to load users. Try again.",createdAt:"Created",updatedAt:"Updated",currentUser:"You",promoteAdministrator:"Promote to administrator",demoteMember:"Change to member",confirmPromoteUser:"Promote {username} to administrator? They will gain all management permissions and must sign in again.",confirmDemoteUser:"Change {username} to member? They will lose management permissions and must sign in again.",confirmDisableUser:"Disable {username}? Their sessions and task capabilities will be revoked; their data will be kept.",userRoleUpdated:"User role updated",resetPasswordTitle:"Reset password",resetPasswordDesc:"Set a new password for {username}. Their existing sessions will be revoked.",savePassword:"Save password",cancel:"Cancel",manageSelfInAccount:"Use Account to change your own password.",unavailable:"Unavailable",risk:"risk",taskScoped:"task-scoped",declarationOnly:"declaration only",revision:"Revision",secretConfigured:"Secret configured",secretNotConfigured:"Secret not configured",notCheckedYet:"Not checked yet",taskFailed:"Task failed",taskInProgress:"Task in progress",taskUpdatesUnavailable:"Task updates unavailable"}),Object.assign(i18n.zh,{send:"发送",rename:"重命名",renameSharedChat:"重命名此会话",renameFailed:"重命名失败",deleteSharedChat:"确定删除此私有会话吗?此操作无法撤销。",deleteSharedDocument:"确定删除此共享文档吗?其他用户将无法再访问。",requestFailed:"请求失败",unknownError:"未知错误",authenticationRequired:"需要登录",settingsNavigation:"设置导航",pluginNavigation:"插件导航",preview:"预览",logoAlt:"Logo",userAvatarAlt:"用户头像",assistantAvatarAlt:"AI 助手头像",artifactSize:"{size} 字节",removeImage:"移除图片",removeUrl:"移除网址",removeDocument:"移除文档",removeLogo:"移除 Logo",composerSuggestions:"输入建议",apiEndpoint:"API 地址",apiKeyOptional:"API 密钥(可选)",configuredReplace:"已配置 — 输入新值可替换",clearKey:"清除密钥",modelId:"模型 ID",temperature:"温度",topP:"Top P",english:"English",chinese:"中文",users:"用户",userManagement:"用户管理",userManagementDesc:"创建账户、控制访问权限并重置密码。用户名不可修改。",username:"用户名",initialPassword:"初始密码",role:"角色",member:"普通用户",administrator:"管理员",createUser:"创建用户",resetPassword:"重置密码",enable:"启用",disable:"停用",enabled:"已启用",disabled:"已停用",userCreated:"用户已创建",userUpdated:"用户状态已更新",passwordReset:"密码已重置,已有会话已撤销",newPasswordFor:"为 {username} 设置新密码",unableCreateUser:"无法创建用户",unableUpdateUser:"无法更新用户",unableResetPassword:"无法重置密码",unableChangePassword:"无法修改密码",modules:"模块",featureSuites:"功能套件",featureSuitesDesc:"每项产品功能由一个后端模块与配套插件或源码内置的 Resident Integration 组成。ChatRaw 不负责启动进程或容器。",composeNetwork:"Compose 网络",composeNetworkDesc:"ChatRaw 与模块服务应使用此网络。",containerAddressRepair:"需要修复容器地址",addressesUnchanged:"现有地址不会被自动修改。",moduleAddress:"模块地址",moduleAddressSecret:"该地址仅向 ChatRaw 提交一次,管理 API 不会返回此地址。",pairingCode:"一次性配对码",connectForReview:"连接并进入审核",noModules:"尚未连接模块。",moduleId:"模块 ID",frontendIntegration:"前端集成",moduleProtocol:"模块协议",installedVersion:"已安装版本",requires:"要求",residentSourceBuilt:"Resident Integration 已随当前 ChatRaw 部署从源码构建。",networkAndConfiguration:"网络与配置",configurationStatus:"配置",lastChecked:"上次检查",dataOwnership:"数据所有权与保留",requestedCapabilities:"申请的主机能力",none:"无。",recentFault:"当前不可用原因",declaredActions:"声明的操作",noActions:"未声明操作。",minimumRole:"最低角色",supportsStream:"流式",supportsCancel:"可取消",supportsApproval:"需审批",supportsArtifacts:"产物",supportsChatProjection:"投影到对话",reviewRequired:"必须先审核此模块,才能启用。",requirementsIncomplete:"已批准,但健康检查、配置、Ready 状态或前端集成要求尚未满足。",approveDeclaration:"批准声明",checkHealth:"检查健康状态",refreshManifest:"刷新清单",configure:"配置",enableSuite:"启用套件",drain:"排空",purgeModuleData:"清除模块数据",disconnect:"断开连接",moduleConfiguration:"模块配置",keep:"保留",replace:"替换",clear:"清除",newSecret:"新密钥",saveModuleConfiguration:"保存模块配置",moduleOperationFailed:"模块操作失败",modulePaired:"模块已连接,等待审核",approveModuleConfirm:"批准 {moduleId} 及其声明的能力吗?",moduleApproved:"模块清单已批准",moduleActionCompleted:"模块{action}操作已完成",configurationSavedRevision:"配置已保存,修订版本 {revision}",disconnectModulePrompt:"输入 {moduleId} 以断开连接。模块数据将保留。",moduleDisconnected:"模块已断开连接,模块数据已保留",purgeModulePrompt:"这会删除模块拥有的数据。输入 {confirmation} 继续。",moduleDataPurged:"模块数据已清除",actionCheck:"检查",actionRefresh:"刷新",actionEnable:"启用",actionDrain:"排空",actionDisable:"停用",account:"账户",currentPassword:"当前密码",newPassword:"新密码",changePassword:"修改密码",signOut:"退出登录",loading:"加载中...",pluginInfo:"插件信息",installPlugin:"安装插件",closeResidentIntegration:"关闭 Resident Integration",tasks:"任务",moduleTask:"模块任务",task:"任务",closeTask:"关闭任务",moduleTasks:"模块任务",approvalRequired:"需要审批",taskProgress:"任务进度",approve:"批准",deny:"拒绝",output:"输出",artifacts:"产物",cancelTask:"取消任务",executionProcess:"执行过程",activityRunning:"运行中",activityUpdated:"已更新",activityInterrupted:"上次执行未完成",waitingForExecutionDetails:"正在等待执行详情",noExecutionDetails:"没有保留执行详情",tool:"工具",viewDetails:"查看详情",toolInput:"输入",toolResult:"结果",resultInConversation:"此任务已在对应对话中展示。",statePending:"等待中",stateQueued:"排队中",statePaired:"已配对",stateReviewRequired:"等待审核",stateApproved:"已批准",stateEnabled:"已启用",stateDisabled:"已停用",stateDraining:"排空中",stateReady:"就绪",stateHealthy:"健康",stateUnhealthy:"异常",stateConfigured:"已配置",stateUnconfigured:"未配置",stateRunning:"运行中",stateSucceeded:"已成功",stateFailed:"失败",stateCancelled:"已取消",statePendingApproval:"等待审批",stateReachable:"可访问",stateUnreachable:"无法访问",stateCompatible:"兼容",stateIncompatible:"不兼容",statePresent:"已存在",stateMissing:"缺失",stateValid:"有效",stateInvalid:"无效",statePlugin:"插件",stateResident:"Resident Integration",stateInstalled:"已安装",stateAvailable:"可用",stateUnknown:"未知",invalidRequest:"请求无效",invalidUsernameType:"用户名必须是文本",invalidUsernameLength:"用户名长度不符合要求",invalidUsernameCharacters:"用户名包含不支持的字符",invalidPasswordLength:"密码长度不足",invalidPasswordTooLong:"密码过长",setupUnavailable:"初始设置当前不可用",usernameInUse:"用户名已被使用",invalidCredentials:"用户名或密码错误",currentPasswordIncorrect:"当前密码错误",invalidRole:"角色无效",userNotFound:"未找到用户",lastActiveAdmin:"不能停用最后一名活跃管理员",administratorRequired:"需要管理员权限",selfUserManagementForbidden:"请在“账户”中修改自己的密码;不能在此变更自己的角色或访问状态",confirmPassword:"确认密码",passwordsDoNotMatch:"两次输入的密码不一致",passwordMinimum:"密码至少 12 个字符",administratorAccessWarning:"管理员可以管理用户、模型、插件和模块。",usersLoading:"正在加载用户…",usersEmpty:"没有找到用户。",usersLoadFailed:"无法加载用户,请重试。",createdAt:"创建时间",updatedAt:"更新时间",currentUser:"当前账户",promoteAdministrator:"提升为管理员",demoteMember:"降为普通用户",confirmPromoteUser:"确定将 {username} 提升为管理员吗?该用户将获得全部管理权限,并需要重新登录。",confirmDemoteUser:"确定将 {username} 降为普通用户吗?该用户将失去管理权限,并需要重新登录。",confirmDisableUser:"确定停用 {username} 吗?其会话和任务能力将立即撤销,但数据会保留。",userRoleUpdated:"用户角色已更新",resetPasswordTitle:"重置密码",resetPasswordDesc:"为 {username} 设置新密码。该用户已有会话将立即失效。",savePassword:"保存密码",cancel:"取消",manageSelfInAccount:"请在“账户”中修改自己的密码。",unavailable:"不可用",risk:"风险",taskScoped:"仅任务范围",declarationOnly:"仅声明",revision:"修订版本",secretConfigured:"密钥已配置",secretNotConfigured:"密钥未配置",notCheckedYet:"尚未检查",taskFailed:"任务失败",taskInProgress:"任务进行中",taskUpdatesUnavailable:"任务更新暂不可用"}),marked.setOptions({highlight:function(e,t){if(hljsLoaded||hljsLoading||loadHighlightJS(),"undefined"!=typeof hljs){if(t&&hljs.getLanguage(t))try{return hljs.highlight(e,{language:t}).value}catch(e){}return hljs.highlightAuto(e).value}return e.replace(//g,">")},breaks:!0,gfm:!0});const SKILL_MANAGER_PLUGIN_ID="skill-manager",COMPOSER_COMPLETION_LIMIT=20,SKILL_CATALOG_CACHE_MS=3e4,MAX_ACTIVE_SKILLS_PER_REQUEST=5,AGENT_CHAT_ENDPOINT="/api/agent/chat",RESERVED_SLASH_COMMANDS=new Set(["plugins","settings","help","clear","compact","api"]),COMMON_PATH_ROOTS=new Set(["tmp","var","usr","etc","home","users","opt","private","volumes","mnt"]),SKILL_NAME_PATTERN=/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/,MODULE_SDK_VERSION="1.6.0",MODULE_TASK_STORAGE_KEY="chatraw_module_tasks_v1",MODULE_TERMINAL_STATES=new Set(["succeeded","failed","cancelled"]),PLUGIN_WORKSPACE_PLACEMENTS=Object.freeze(["right","top","bottom","main"]),PLUGIN_WORKSPACE_PANEL_ID_PATTERN=/^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/,MODULE_ACTIVITY_LIMIT=512;function app(){return{lang:localStorage.getItem("justchat_lang")||"en",showSettings:!1,agentOpen:!1,agentExpanded:!1,agentHistoryOpen:!1,featureCatalog:{categories:[],cards:[]},activeFeatureCategory:"data-hub",featureCatalogLoading:!0,settingsTab:"models",me:null,adminUsers:[],adminUsersLoading:!1,adminUsersError:null,adminCreateBusy:!1,adminUserBusy:{},newUser:{username:"",password:"",confirmPassword:"",role:"member"},resetUserPasswordDialog:{show:!1,user:null,newPassword:"",confirmPassword:""},passwordForm:{current_password:"",new_password:""},modules:[],modulePairForm:{base_url:"",pairing_code:""},deploymentStatus:{mode:"source",module_network:null,warnings:[]},moduleBusy:null,moduleConfig:null,moduleConfigValues:{},moduleSecretActions:{},moduleTaskUi:{show:!1,task:null,events:[],output:"",progress:0,progressMessage:"",approval:null,artifacts:[],error:null},moduleTasks:{},moduleTaskOrder:[],moduleTaskSubscriptions:{},residentIntegrations:[],residentBuiltIntegrationIds:[],residentIntegrationDefinitions:{},residentRegistrationOpen:!1,residentWorkspace:{show:!1,integrationId:null,title:"",error:null,cleanup:null},_residentFocusHandler:null,_residentWorkspaceReturnFocus:null,showSystemPrompt:!1,currentChatId:null,inputMessage:"",isComposing:!1,completionProviders:{},completionMenuOpen:!1,completionQuery:"",completionItems:[],completionActiveIndex:0,completionRange:null,completionRequestId:0,completionDebounceTimer:null,skillCatalogCache:null,skillCatalogLoadedAt:0,useRAG:!1,isGenerating:!1,abortController:null,uploadedImage:null,uploadedImageBase64:"",uploadProgress:{show:!1,filename:"",progress:0,status:"",current:0,total:0},showUrlInput:!1,urlInputValue:"",isParsingUrl:!1,parsedUrl:null,parsedUrlTitle:"",attachedDocument:null,isUploadingDocument:!1,showPlugins:!1,pluginTab:"market",marketPlugins:[],installedPlugins:[],pluginMarketLoading:!1,pluginMarketError:!1,pluginMarketSearch:"",pluginInstalling:null,pluginUploading:!1,showPluginSettings:!1,currentPluginSettings:null,pluginSettingsValues:{},pluginApiKeys:{},pluginApiKeyConfigured:{},pluginApiKeyActions:{},pluginToolbarButtons:[],showExtensionPalette:!1,pluginFullscreenModal:{show:!1,content:"",closable:!0,onClose:null,pluginId:null},pluginWorkspaceDefinitions:{},pluginWorkspace:{show:!1,pluginId:null,panelId:null,title:null,icon:"",collectionId:null,collectionTitle:null,collectionIcon:"",placement:null,dispose:null},_pluginWorkspaceReturnFocus:null,_pluginFullscreenReturnFocus:null,_pluginWorkspaceActivation:null,_pluginWorkspaceTransition:null,_rootWheelHandler:null,pluginHooks:{parse_document:[],parse_url:[],web_search:[],pre_embedding:[],post_retrieval:[],send_intercept:[],before_send:[],route_message:[],after_receive:[],toolbar_button:[],file_preview:[],custom_action:[],custom_settings:[],transform_input:[],transform_output:[]},loadedPluginDeps:{},pluginRuntimeVersions:{},_pluginRuntimeSyncPromise:null,_pluginRuntimeSyncInitialized:!1,_pluginRuntimeChangeChannel:null,_agentChatChangeChannel:null,_settingsChangeChannel:null,chats:[],messages:[],models:[],documents:[],settings:{chat_settings:{temperature:.7,top_p:.9,stream:!0,system_prompt:""},rag_settings:{chunk_size:500,chunk_overlap:50,top_k:3,score_threshold:.5},ui_settings:{logo_data:"",logo_text:"ChatRaw",subtitle:"",theme_mode:"light",user_avatar:"",assistant_avatar:""}},toast:{show:!1,message:"",type:""},t(e,t={}){const s=(i18n[this.lang]||i18n.en)[e]??i18n.en[e]??e;return String(s).replace(/\{(\w+)\}/g,(e,s)=>void 0===t[s]?`{${s}}`:String(t[s]))},setLanguage(e){this.lang="zh"===e?"zh":"en",localStorage.setItem("justchat_lang",this.lang),document.documentElement.lang="zh"===this.lang?"zh-CN":"en",this.updateDocumentTitle()},updateDocumentTitle(){const e=this.settings?.ui_settings?.logo_text||"ChatRaw";document.title=this.t("appDocumentTitle",{brand:e})},roleLabel(e){return this.t("admin"===e?"administrator":"member")},stateLabel(e){const t=String(e??"").trim().toLowerCase().replace(/[\s-]+/g,"_"),s={pending:"statePending",queued:"stateQueued",paired:"statePaired",review_required:"stateReviewRequired",pending_review:"stateReviewRequired",approved:"stateApproved",enabled:"stateEnabled",disabled:"stateDisabled",draining:"stateDraining",ready:"stateReady",healthy:"stateHealthy",unhealthy:"stateUnhealthy",configured:"stateConfigured",unconfigured:"stateUnconfigured",running:"stateRunning",succeeded:"stateSucceeded",completed:"stateSucceeded",failed:"stateFailed",cancelled:"stateCancelled",canceled:"stateCancelled",pending_approval:"statePendingApproval",reachable:"stateReachable",unreachable:"stateUnreachable",compatible:"stateCompatible",incompatible:"stateIncompatible",present:"statePresent",missing:"stateMissing",valid:"stateValid",invalid:"stateInvalid",plugin:"statePlugin",resident:"stateResident",installed:"stateInstalled",available:"stateAvailable",unavailable:"unavailable",not_present:"stateMissing"};return s[t]?this.t(s[t]):e||this.t("stateUnknown")},actionLabel(e){const t={check:"actionCheck",refresh:"actionRefresh",enable:"actionEnable",drain:"actionDrain",disable:"actionDisable"}[e];return t?this.t(t):e},localizeError(e,t="requestFailed"){const s="string"==typeof e?e:e?.code||e?.error?.code||e?.detail?.code,i={invalid_request:"invalidRequest",invalid_username_type:"invalidUsernameType",invalid_username_length:"invalidUsernameLength",invalid_username_characters:"invalidUsernameCharacters",invalid_password_length:"invalidPasswordLength",invalid_password_too_long:"invalidPasswordTooLong",setup_unavailable:"setupUnavailable",username_in_use:"usernameInUse",invalid_credentials:"invalidCredentials",current_password_incorrect:"currentPasswordIncorrect",invalid_role:"invalidRole",user_not_found:"userNotFound",last_active_admin:"lastActiveAdmin",administrator_required:"administratorRequired",self_user_management_forbidden:"selfUserManagementForbidden",task_failed:"taskFailed",module_event_stream_failed:"taskUpdatesUnavailable",module_event_stream_incomplete:"taskUpdatesUnavailable"};return i[s]?this.t(i[s]):this.t(t)},getModelTypeName(e){return{chat:this.t("chat"),embedding:this.t("embedding"),rerank:this.t("reranker")}[e]||e},getChatModel(){return this.models.find(e=>"chat"===e?.type)||null},chatModelSupportsVision(){return Boolean(this.getChatModel()?.capability?.vision)},syncVisionCapabilityState(){!this.chatModelSupportsVision()&&this.uploadedImage&&this.removeUploadedImage()},async init(){this.setLanguage(this.lang),this.installRootWheelGuard(),await this.loadMe(),await this.loadSettings(),await this.loadModels(),await this.loadAgentChats(),await this.loadFeatureCatalog(),await this.loadDocuments(),await this.loadInstalledPlugins(),this.applyTheme(),this.initCrossTabStateSync(),this.initPluginSystem(),await this.initResidentIntegrations(),await this.resumeModuleTasks()},initCrossTabStateSync(){"function"==typeof window.BroadcastChannel&&(this._agentChatChangeChannel||(this._agentChatChangeChannel=new window.BroadcastChannel("chatraw-agent-chats-v1"),this._agentChatChangeChannel.addEventListener("message",async e=>{"agent-chats-changed"===e.data?.type&&(await this.loadAgentChats(),this.currentChatId&&!this.chats.some(e=>e.id===this.currentChatId)&&(this.isGenerating&&this.stopGeneration(),this.currentChatId=null,this.messages=[]))})),this._settingsChangeChannel||(this._settingsChangeChannel=new window.BroadcastChannel("chatraw-settings-v1"),this._settingsChangeChannel.addEventListener("message",async e=>{"settings-changed"===e.data?.type&&(await this.loadSettings(),this.applyTheme())})))},announceAgentChatChange(e,t){this._agentChatChangeChannel?.postMessage({type:"agent-chats-changed",action:e,chat_id:t})},announceSettingsChange(){this._settingsChangeChannel?.postMessage({type:"settings-changed"})},isAdmin(){return"admin"===this.me?.role},async loadMe(){const e=await fetch("/api/me");if(!e.ok)throw location.replace("/login"),new Error(this.t("authenticationRequired"));this.me=await e.json(),this.isAdmin()||(this.settingsTab="account")},async logout(){await fetch("/api/auth/logout",{method:"POST"}),location.replace("/login")},async loadFeatureCatalog(){this.featureCatalogLoading=!0;try{const e=await fetch("/api/module-feature-catalog");if(!e.ok)throw new Error("Feature catalog unavailable");const t=await e.json();this.featureCatalog={categories:Array.isArray(t.categories)?t.categories:[],cards:Array.isArray(t.cards)?t.cards:[]},this.recomputeFeatureCatalogRuntime(),this.featureCatalog.categories.some(e=>e.id===this.activeFeatureCategory)||(this.activeFeatureCategory=this.featureCatalog.categories[0]?.id||"")}catch(e){console.error("Failed to load feature catalog:",e),this.featureCatalog={categories:[],cards:[]}}finally{this.featureCatalogLoading=!1}},get activeFeatureCards(){return this.featureCatalog.cards.filter(e=>e.category_id===this.activeFeatureCategory).sort((e,t)=>e.order-t.order)},featureCatalogCardForPanel(e,t){return this.featureCatalog.cards.find(s=>s.plugin_id===e&&s.panel_id===t)||null},pluginWorkspacePanelIcon(e,t,s=""){return this.featureCatalogCardForPanel(e,t)?.icon||s},featureStateLabel(e){return this.t({loading:"featureLoading",module_unavailable:"moduleUnavailable",plugin_missing:"pluginMissing",plugin_disabled:"pluginDisabledState",plugin_version_mismatch:"pluginVersionMismatch",panel_not_registered:"panelNotRegistered",main_placement_required:"mainPlacementRequired",available:"featureAvailable"}[e]||"featureUnavailable")},recomputeFeatureCatalogRuntime(){this.featureCatalog={...this.featureCatalog,cards:this.featureCatalog.cards.map(e=>{if(!e.service_ready)return{...e,runtime_ready:!1,available:!1};const t=this.pluginWorkspaceDefinitions[this.pluginWorkspaceKey(e.plugin_id,e.panel_id)];return t?t.placements.includes("main")?{...e,runtime_ready:!0,available:!0,state:"available"}:{...e,runtime_ready:!1,available:!1,state:"main_placement_required"}:{...e,runtime_ready:!1,available:!1,state:"panel_not_registered"}})}},async openFeatureCard(e,t=null){if(!e?.available)return;const s=this.pluginWorkspaceKey(e.plugin_id,e.panel_id);if(this.pluginWorkspaceDefinitions[s]||await this.syncPluginRuntimes(),!this.pluginWorkspaceDefinitions[s])return this.recomputeFeatureCatalogRuntime(),void this.showToast(this.t("featurePanelNotReady"),"error");if(!this.pluginWorkspaceDefinitions[s].placements.includes("main"))return this.recomputeFeatureCatalogRuntime(),void this.showToast(this.t("featurePanelNotReady"),"error");this.showSettings=!1,this.isGenerating&&this.stopGeneration(),this.agentOpen=!1,this.agentExpanded=!1,this.agentHistoryOpen=!1,this.showExtensionPalette=!1;const i=this._pluginWorkspaceActivation;this._pluginWorkspaceActivation=!0===navigator.userActivation?.isActive&&t instanceof HTMLElement&&t.isConnected?{pluginId:e.plugin_id,trigger:t}:null;try{this.openPluginWorkspacePanel(e.panel_id,{placement:"main"},e.plugin_id)}finally{this._pluginWorkspaceActivation=i}},returnHome(){this.showSettings=!1,this.pluginWorkspace.show&&this.closeActivePluginWorkspace()},toggleAgent(){this.agentOpen||(this.showSettings=!1,this.showPlugins=!1),this.agentOpen=!this.agentOpen,this.showExtensionPalette=!1,this.agentOpen||(this.agentHistoryOpen=!1,this.agentExpanded=!1),this.agentOpen&&!this.currentChatId&&this.chats.length&&this.selectChat(this.chats[0].id)},toggleAgentExpanded(){this.agentOpen&&(this.agentExpanded=!this.agentExpanded,this.agentHistoryOpen=!1,this.showExtensionPalette=!1)},toggleAgentHistory(){const e=!this.agentHistoryOpen;this.showExtensionPalette=!1,this.showUrlInput=!1,this.agentHistoryOpen=e},async loadAdminUsers(){if(this.isAdmin()){this.adminUsersLoading=!0,this.adminUsersError=null;try{const e=await fetch("/api/admin/users");let t={};try{t=await e.json()}catch(e){t={}}if(!e.ok)throw t;this.adminUsers=Array.isArray(t.users)?t.users:[]}catch(e){this.adminUsers=[],this.adminUsersError=this.localizeError(e,"usersLoadFailed")}finally{this.adminUsersLoading=!1}}},setAdminUserBusy(e,t){const s={...this.adminUserBusy};t?s[e]=!0:delete s[e],this.adminUserBusy=s},isAdminUserBusy(e){return Boolean(this.adminUserBusy[e])},formatUserDate(e){if(!e)return"—";const t=new Date(e);return Number.isNaN(t.getTime())?e:new Intl.DateTimeFormat("zh"===this.lang?"zh-CN":"en",{dateStyle:"medium",timeStyle:"short"}).format(t)},managedUserSnapshot:e=>({id:String(e.id),username:String(e.username),role:e.role,enabled:Boolean(e.enabled)}),async createManagedUser(){if(this.adminCreateBusy)return;if(this.newUser.password.length<12)return void this.showToast(this.t("passwordMinimum"),"error");if(this.newUser.password!==this.newUser.confirmPassword)return void this.showToast(this.t("passwordsDoNotMatch"),"error");if("admin"===this.newUser.role&&!window.confirm(this.t("administratorAccessWarning")))return;const e={username:this.newUser.username,password:this.newUser.password,role:this.newUser.role};this.adminCreateBusy=!0;try{const t=await fetch("/api/admin/users",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});let s={};try{s=await t.json()}catch(e){s={}}if(!t.ok)return void this.showToast(this.localizeError(s,"unableCreateUser"),"error");this.newUser={username:"",password:"",confirmPassword:"",role:"member"},await this.loadAdminUsers(),this.showToast(this.t("userCreated"),"success")}catch(e){this.showToast(this.localizeError(e,"unableCreateUser"),"error")}finally{this.adminCreateBusy=!1}},async setManagedUserEnabled(e,t){const s=this.managedUserSnapshot(e);if(s.id===this.me?.id||this.isAdminUserBusy(s.id))return;if(!t&&!window.confirm(this.t("confirmDisableUser",{username:s.username})))return;const i=t?"enable":"disable";this.setAdminUserBusy(s.id,!0);try{const e=await fetch(`/api/admin/users/${encodeURIComponent(s.id)}/${i}`,{method:"POST"});let t={};try{t=await e.json()}catch(e){t={}}if(!e.ok)return void this.showToast(this.localizeError(t,"unableUpdateUser"),"error");await this.loadAdminUsers(),this.showToast(this.t("userUpdated"),"success")}catch(e){this.showToast(this.localizeError(e,"unableUpdateUser"),"error")}finally{this.setAdminUserBusy(s.id,!1)}},async setManagedUserRole(e,t){const s=this.managedUserSnapshot(e);if(s.id===this.me?.id||s.role===t||this.isAdminUserBusy(s.id))return;const i="admin"===t?"confirmPromoteUser":"confirmDemoteUser";if(window.confirm(this.t(i,{username:s.username}))){this.setAdminUserBusy(s.id,!0);try{const e=await fetch(`/api/admin/users/${encodeURIComponent(s.id)}/role`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({role:t})});let i={};try{i=await e.json()}catch(e){i={}}if(!e.ok)return void this.showToast(this.localizeError(i,"unableUpdateUser"),"error");await this.loadAdminUsers(),this.showToast(this.t("userRoleUpdated"),"success")}catch(e){this.showToast(this.localizeError(e,"unableUpdateUser"),"error")}finally{this.setAdminUserBusy(s.id,!1)}}},resetManagedUserPassword(e){const t=this.managedUserSnapshot(e);t.id===this.me?.id||this.isAdminUserBusy(t.id)||(this.resetUserPasswordDialog={show:!0,user:t,newPassword:"",confirmPassword:""})},closeManagedUserPasswordDialog(){this.resetUserPasswordDialog={show:!1,user:null,newPassword:"",confirmPassword:""}},async submitManagedUserPasswordReset(){const e=this.resetUserPasswordDialog,t=e.user?{...e.user}:null;if(t&&!this.isAdminUserBusy(t.id))if(e.newPassword.length<12)this.showToast(this.t("passwordMinimum"),"error");else if(e.newPassword===e.confirmPassword){this.setAdminUserBusy(t.id,!0);try{const s=await fetch(`/api/admin/users/${encodeURIComponent(t.id)}/reset-password`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({new_password:e.newPassword})});let i={};try{i=await s.json()}catch(e){i={}}if(!s.ok)return void this.showToast(this.localizeError(i,"unableResetPassword"),"error");this.closeManagedUserPasswordDialog(),await this.loadAdminUsers(),this.showToast(this.t("passwordReset"),"success")}catch(e){this.showToast(this.localizeError(e,"unableResetPassword"),"error")}finally{this.setAdminUserBusy(t.id,!1)}}else this.showToast(this.t("passwordsDoNotMatch"),"error")},async changeMyPassword(){const e=await fetch("/api/me/password",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(this.passwordForm)}),t=await e.json();e.ok?location.replace("/login"):this.showToast(this.localizeError(t,"unableChangePassword"),"error")},async moduleApi(e,t={}){const s=await fetch(e,t);let i={};try{i=await s.json()}catch(e){i={}}if(!s.ok){const e=new Error(this.localizeError(i,"moduleOperationFailed"));throw e.code=i.code||i.error?.code||i.detail?.code||"",e}return i},moduleTaskIds(){try{const e=JSON.parse(localStorage.getItem(MODULE_TASK_STORAGE_KEY)||"[]");return Array.isArray(e)?e.filter(e=>"string"==typeof e).slice(0,20):[]}catch(e){return[]}},rememberModuleTask(e){const t=[e,...this.moduleTaskIds().filter(t=>t!==e)].slice(0,20);localStorage.setItem(MODULE_TASK_STORAGE_KEY,JSON.stringify(t))},forgetModuleTask(e){localStorage.setItem(MODULE_TASK_STORAGE_KEY,JSON.stringify(this.moduleTaskIds().filter(t=>t!==e)))},createModuleTaskView(e,t="task_center"){const s=MODULE_TERMINAL_STATES.has(e?.state);return{show:!1,task:e,events:[],output:e?.result?.text||e?.chat_projection||"",progress:MODULE_TERMINAL_STATES.has(e?.state)?1:0,progressMessage:"",approval:null,artifacts:Array.isArray(e?.artifacts)?e.artifacts:[],error:"failed"===e?.state?e.outcome_code||"task_failed":null,inline:!1,activities:{},activityOrder:[],activitiesLoaded:!1,currentRunId:null,timelineExpanded:!s,timelineTouched:!1,lastAppliedEventId:0,presentation:t,attentionPending:!1}},upsertModuleTask(e,{select:t=!1,presentation:s=null}={}){const i=e?.task_id;if(!i)return null;let n=this.moduleTasks[i];return n?(n.task=e,s&&(n.presentation=s),n.artifacts=Array.isArray(e.artifacts)?e.artifacts:n.artifacts,MODULE_TERMINAL_STATES.has(e.state)&&(n.progress=1,n.approval=null,n.error="failed"===e.state?e.outcome_code||"task_failed":null,n.timelineTouched||(n.timelineExpanded=!1))):(n=this.createModuleTaskView(e,s||"task_center"),this.moduleTasks[i]=n,this.moduleTaskOrder=[i,...this.moduleTaskOrder.filter(e=>e!==i)].slice(0,20)),t&&this.selectModuleTask(i),n},moduleTaskActivityList:e=>e?e.activityOrder.map(t=>e.activities[t]).filter(Boolean):[],moduleTaskActivityKey:e=>`${e.run_id}:${e.activity_id}`,upsertModuleTaskActivity(e,t){if(!e||!t)return;if(e.currentRunId&&e.currentRunId!==t.run_id)for(const t of Object.values(e.activities))t.run_id===e.currentRunId&&"started"===t.state&&(t.interrupted=!0);e.currentRunId=t.run_id;const s=this.moduleTaskActivityKey(t);if(!e.activities[s]){if(e.activityOrder.length>=512){const t=e.activityOrder.shift();t&&delete e.activities[t]}e.activityOrder.push(s)}e.activities[s]={...t,interrupted:!1},e.activitiesLoaded=!0},moduleTaskActivityIcon:e=>"plan"===e?.kind?"ri-list-check-3":"tool"===e?.kind?"ri-tools-line":"preparing"===e?.detail?.phase?"ri-loader-4-line":"finalizing"===e?.detail?.phase?"ri-quill-pen-line":"ri-play-circle-line",moduleTaskActivityState(e){if(e?.interrupted)return this.t("activityInterrupted");const t={started:"activityRunning",updated:"activityUpdated",succeeded:"stateSucceeded",failed:"stateFailed",cancelled:"stateCancelled"}[e?.state];return this.t(t||"stateUnknown")},formatModuleTaskDuration:e=>!Number.isFinite(e)||e<0?"":e<1e3?`${Math.round(e)} ms`:`${(e/1e3).toFixed(1)} s`,messageContent:e=>"string"==typeof e?.content?e.content:"",syncConversationTaskMessage(e){const t=e?.task?.task_id;if(!t||"conversation"!==e.presentation)return;const s=`module-task:${t}`,i=this.messages.findIndex(e=>e.id===s);i<0||(this.messages[i]={...this.messages[i],content:e.output})},async toggleModuleTaskTimeline(e){e&&(e.timelineExpanded=!e.timelineExpanded,e.timelineTouched=!0,e.timelineExpanded&&MODULE_TERMINAL_STATES.has(e.task?.state)&&!e.activitiesLoaded&&window.ChatRaw.modules.subscribe(e.task.task_id,{onClose:()=>{e.activitiesLoaded=!0}}))},mergeConversationTaskMessages(e,t){const s=Array.isArray(e)?e.map(e=>({...e})):[],i=new Set(s.map(e=>e.id).filter(Boolean)),n=new Map,a=new Map;for(const e of t||[]){if(!e?.task_id||!e.user_message_id)continue;const t=this.upsertModuleTask(e,{presentation:"conversation"});t.inline=!0,e.assistant_message_id&&n.set(e.assistant_message_id,t),a.has(e.user_message_id)||a.set(e.user_message_id,[]),a.get(e.user_message_id).push(t)}const o=[];for(const e of s){const t=n.get(e.id);if(t&&(e.moduleTask=t),o.push(e),"user"!==e.role)continue;const s=a.get(e.id)||[];for(const e of s.reverse()){const t=e.task.assistant_message_id;t&&i.has(t)||o.push({id:`module-task:${e.task.task_id}`,chat_id:e.task.chat_id,role:"assistant",content:e.output,created_at:e.task.created_at,moduleTask:e})}}return o},attachConversationTask(e,t){const s=this.upsertModuleTask(e,{presentation:"conversation"});if(s.inline=!0,e.chat_id!==this.currentChatId||!e.user_message_id)return s;const i=this.messages.filter(e=>!String(e.id||"").startsWith("module-task:"));return i.some(t=>t.id===e.user_message_id)||i.push({id:e.user_message_id,chat_id:e.chat_id,role:"user",content:String(t),created_at:e.accepted_at||e.created_at}),this.messages=this.mergeConversationTaskMessages(i,[e]),this.$nextTick(()=>this.scrollToBottom()),s},moduleTaskViews(){return this.moduleTaskOrder.map(e=>this.moduleTasks[e]).filter(e=>e&&"task_center"===e.presentation&&(!MODULE_TERMINAL_STATES.has(e.task?.state)||e.attentionPending||e.show))},moduleTaskAttentionViews(){return this.moduleTaskViews().filter(e=>!MODULE_TERMINAL_STATES.has(e.task?.state)||e.attentionPending)},selectModuleTask(e){const t=this.moduleTasks[e];t&&(this.moduleTaskUi.show=!1,t.show=!0,this.moduleTaskUi=t)},openModuleTaskCenter(){const e=this.moduleTaskAttentionViews(),t=this.moduleTaskUi.task?.task_id,s=e.some(e=>e.task.task_id===t)?t:e[0]?.task?.task_id;s&&this.selectModuleTask(s)},closeModuleTask(){const e=this.moduleTaskUi;e.show=!1,MODULE_TERMINAL_STATES.has(e.task?.state)&&(e.attentionPending=!1,this.forgetModuleTask(e.task.task_id))},applyModuleTaskEvent(e,t){const s=this.moduleTasks[e];s&&(t.id<=s.lastAppliedEventId||(s.lastAppliedEventId=t.id,s.events.push(t),s.events.length>200&&s.events.shift(),"task.status"===t.event||"task.terminal"===t.event?(s.task.state=t.data.state,t.data.outcome_code&&(s.task.outcome_code=t.data.outcome_code),"task.terminal"===t.event&&(s.progress=1,"failed"===t.data.state&&(s.error=t.data.outcome_code||"task_failed"),s.approval=null,s.activitiesLoaded=!0,s.timelineTouched||(s.timelineExpanded=!1),"task_center"!==s.presentation||"cancelled"===t.data.state||s.show?"task_center"===s.presentation&&"cancelled"!==t.data.state||this.forgetModuleTask(e):s.attentionPending=!0)):"task.progress"===t.event?(s.progress=t.data.progress,s.progressMessage=this.t("taskInProgress")):"activity.updated"===t.event?this.upsertModuleTaskActivity(s,t.data):"output.delta"===t.event?(s.output+=t.data.text,this.syncConversationTaskMessage(s)):"output.snapshot"===t.event?(s.output=t.data.text,this.syncConversationTaskMessage(s)):"approval.requested"===t.event?s.approval=t.data:"approval.resolved"===t.event&&(s.approval=null)))},async resolveModuleTaskApproval(e){return this.resolveModuleTaskApprovalFor(this.moduleTaskUi,e)},async resolveModuleTaskApprovalFor(e,t){const s=e?.task,i=e?.approval;if(s&&i)try{await window.ChatRaw.modules.respondApproval(s.task_id,i.approval_id,t)}catch(t){e.error={code:t?.code||"invalid_request"}}},async cancelVisibleModuleTask(){const e=this.moduleTaskUi.task?.task_id;if(e)try{const t=await window.ChatRaw.modules.cancelTask(e);this.upsertModuleTask(t)}catch(e){this.moduleTaskUi.error={code:e?.code||"invalid_request"}}},async downloadVisibleModuleArtifact(e){return this.downloadModuleTaskArtifact(this.moduleTaskUi,e)},async downloadModuleTaskArtifact(e,t){const s=e?.task?.task_id;if(s)try{await window.ChatRaw.modules.downloadArtifact(s,t.artifact_ref)}catch(t){e.error={code:t?.code||"invalid_request"}}},async resumeModuleTasks(){if(window.ChatRaw?.modules)for(const e of this.moduleTaskIds())try{const t=await window.ChatRaw.modules.getTask(e);if(MODULE_TERMINAL_STATES.has(t.state)){this.forgetModuleTask(e);continue}this.upsertModuleTask(t,{presentation:"task_center"}),window.ChatRaw.modules.subscribe(e)}catch(t){404===t?.status&&this.forgetModuleTask(e)}},residentText(e){return e&&"object"==typeof e&&(e[this.lang]||e.en||e.zh)||""},residentEntries(e){return this.residentIntegrations.filter(e=>!0===e.feature?.visible).flatMap(t=>(t.entrypoints||[]).filter(t=>t.placement===e||"composer"===e&&"sidebar"===t.placement).map(e=>({...e,integration:t}))).sort((e,t)=>(e.order??100)-(t.order??100))},async refreshResidentIntegrationStatuses(){if(window.ChatRaw?.modules)for(const e of this.residentIntegrations)try{e.feature=await window.ChatRaw.modules.getFeatureStatus(e.module_id)}catch(t){e.feature={visible:!1,available:!1,state:"hidden",reason:{code:t?.code||"module_not_found",message:"This feature is currently unavailable."}}}},registerResidentIntegration(e){if(!this.residentRegistrationOpen||!e||"string"!=typeof e.id||"function"!=typeof e.mount||Object.keys(e).some(e=>!["id","mount"].includes(e))||!this.residentBuiltIntegrationIds.includes(e.id)||this.residentIntegrationDefinitions[e.id])throw new Error("Resident Integration registration rejected");this.residentIntegrationDefinitions[e.id]=Object.freeze({id:e.id,mount:e.mount})},async initResidentIntegrations(){const e=await fetch("/api/resident-integrations",{credentials:"same-origin"});if(!e.ok)throw new Error("Resident Integration catalog unavailable");const t=await e.json();if("string"!=typeof t.bundle_version||!/^[0-9a-f]{64}$/.test(t.bundle_version)||!Array.isArray(t.built_integration_ids)||t.built_integration_ids.some(e=>"string"!=typeof e||!e)||new Set(t.built_integration_ids).size!==t.built_integration_ids.length)throw new Error("Resident Integration catalog unavailable");if(this.residentBuiltIntegrationIds=[...t.built_integration_ids],this.residentIntegrations=Array.isArray(t.integrations)?t.integrations.map(e=>({...e,feature:{visible:!1,available:!1,state:"hidden",reason:{code:"module_not_found",message:"This feature is currently unavailable."}}})):[],!this.residentIntegrations.length)return;const s=this;window.ChatRawResident=Object.freeze({register(e){s.registerResidentIntegration(e)}}),this.residentRegistrationOpen=!0;try{await this.loadCSS("/resident-integrations/resident-integrations.min.css?v="+encodeURIComponent(t.bundle_version)),await this.loadScript("/resident-integrations/resident-integrations.min.js?v="+encodeURIComponent(t.bundle_version))}finally{this.residentRegistrationOpen=!1}for(const e of this.residentBuiltIntegrationIds)if(!this.residentIntegrationDefinitions[e])throw new Error(`Resident Integration ${e} did not register`);await this.refreshResidentIntegrationStatuses(),this._residentFocusHandler=()=>{this.refreshResidentIntegrationStatuses().catch(e=>{console.warn("[Resident Integration refresh]",e)})},window.addEventListener("focus",this._residentFocusHandler)},async openResidentIntegration(e,t=null){await this.refreshResidentIntegrationStatuses();const s=this.residentIntegrations.find(t=>t.id===e.id);if(!s?.feature?.available)return void this.showToast(s?.feature?.reason?.message||"This feature is currently unavailable.","error");this.closeResidentWorkspace(!1),this.showExtensionPalette=!1,this._residentWorkspaceReturnFocus=t instanceof HTMLElement&&t.isConnected?t:null,this.residentWorkspace={show:!0,integrationId:s.id,title:this.residentText(s.name),error:null,cleanup:null},await new Promise(e=>requestAnimationFrame(e));const i=document.getElementById("resident-workspace-mount"),n=this.residentIntegrationDefinitions[s.id];if(!i||!n)return this.residentWorkspace.error="Resident Integration failed to mount",void document.getElementById("resident-workspace-title")?.focus();try{const e=await n.mount({container:i,moduleId:s.module_id,modules:window.ChatRaw.modules,t:e=>this.residentText(e),showToast:(e,t="")=>this.showToast(e,t),getCurrentChatId:()=>this.currentChatId});"function"==typeof e&&(this.residentWorkspace.cleanup=e)}catch(e){i.replaceChildren(),this.residentWorkspace.error=e?.message||"Resident Integration failed to mount"}document.getElementById("resident-workspace-title")?.focus()},closeResidentWorkspace(e=!0){try{this.residentWorkspace.cleanup?.()}catch(e){console.warn("[Resident Integration cleanup]",e)}const t=document.getElementById("resident-workspace-mount");t?.replaceChildren(),this.residentWorkspace={show:!1,integrationId:null,title:"",error:null,cleanup:null};const s=this._residentWorkspaceReturnFocus;this._residentWorkspaceReturnFocus=null,e&&this.restoreExtensionPaletteFocus(s)},async loadModules(){if(this.isAdmin())try{const[e,t]=await Promise.all([this.moduleApi("/api/admin/modules"),this.moduleApi("/api/admin/deployment-status")]);this.modules=e.modules||[],this.deploymentStatus=t}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}},async pairModule(){if(this.isAdmin()&&!this.moduleBusy){this.moduleBusy="pair";try{await this.moduleApi("/api/admin/modules/pair",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(this.modulePairForm)}),this.modulePairForm={base_url:"",pairing_code:""},await this.loadModules(),await this.refreshResidentIntegrationStatuses(),this.showToast(this.t("modulePaired"),"success")}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}finally{this.moduleBusy=null}}},async approveModule(e){if(this.isAdmin()&&!this.moduleBusy&&window.confirm(this.t("approveModuleConfirm",{moduleId:e.module_id}))){this.moduleBusy=e.id;try{await this.moduleApi(`/api/admin/modules/${encodeURIComponent(e.id)}/approve`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({manifest_digest:e.manifest_digest,approved_capabilities:e.requested_host_capabilities||[]})}),await this.loadModules(),await this.refreshResidentIntegrationStatuses(),this.showToast(this.t("moduleApproved"),"success")}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}finally{this.moduleBusy=null}}},async runModuleAction(e,t){if(this.isAdmin()&&!this.moduleBusy){this.moduleBusy=e.id;try{await this.moduleApi(`/api/admin/modules/${encodeURIComponent(e.id)}/${t}`,{method:"POST"}),await this.loadModules(),await this.refreshResidentIntegrationStatuses(),this.showToast(this.t("moduleActionCompleted",{action:this.actionLabel(t)}),"success")}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}finally{this.moduleBusy=null}}},moduleConfigFields(){return Object.entries(this.moduleConfig?.schema?.properties||{}).map(([e,t])=>({name:e,schema:t,secret:!0===t?.["x-chatraw-secret"]}))},async openModuleConfig(e){if(this.isAdmin()){this.moduleBusy=e.id;try{const t=await this.moduleApi(`/api/admin/modules/${encodeURIComponent(e.id)}/config`);this.moduleConfig={...t,moduleId:e.id,moduleName:e.name},this.moduleConfigValues={...t.values||{}},this.moduleSecretActions={};for(const e of Object.entries(t.schema?.properties||{}))!0===e[1]?.["x-chatraw-secret"]?this.moduleSecretActions[e[0]]={action:"keep",value:""}:void 0===this.moduleConfigValues[e[0]]&&void 0!==e[1]?.default&&(this.moduleConfigValues[e[0]]=e[1].default)}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}finally{this.moduleBusy=null}}},async saveModuleConfig(){if(!this.isAdmin()||!this.moduleConfig||this.moduleBusy)return;this.moduleBusy=this.moduleConfig.moduleId;const e=this.moduleConfig.moduleId,t={};for(const[e,s]of Object.entries(this.moduleSecretActions))t[e]="replace"===s.action?{action:"replace",value:s.value}:{action:s.action};try{const s=await this.moduleApi(`/api/admin/modules/${encodeURIComponent(this.moduleConfig.moduleId)}/config`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({revision:this.moduleConfig.revision,values:this.moduleConfigValues,secrets:t})});if(this.moduleConfig=null,s.configured)try{await this.recheckModuleAfterConfig(e)}catch(e){console.warn("[Module configuration recheck]",e?.code||e?.name||"failed")}await this.loadModules(),await this.refreshResidentIntegrationStatuses(),this.showToast(this.t("configurationSavedRevision",{revision:s.revision}),"success")}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}finally{this.moduleBusy=null}},async recheckModuleAfterConfig(e){let t=null;for(let s=0;s<6;s+=1){if(s>0&&await new Promise(e=>setTimeout(e,750)),t=await this.moduleApi(`/api/admin/modules/${encodeURIComponent(e)}/check`,{method:"POST"}),"ready"===t.ready_status)return t;if("incompatible"===t.health_status||"missing"===t.config_status)return t}return t},async disconnectModule(e){if(!this.isAdmin()||this.moduleBusy)return;const t=window.prompt(this.t("disconnectModulePrompt",{moduleId:e.module_id}));if(t===e.module_id){this.moduleBusy=e.id;try{await this.moduleApi(`/api/admin/modules/${encodeURIComponent(e.id)}/disconnect`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({confirmation:t})}),await this.loadModules(),await this.refreshResidentIntegrationStatuses(),this.showToast(this.t("moduleDisconnected"),"success")}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}finally{this.moduleBusy=null}}},async purgeModuleData(e){if(!this.isAdmin()||!e.supports_data_purge||this.moduleBusy)return;const t=`PURGE ${e.module_id}`,s=window.prompt(this.t("purgeModulePrompt",{confirmation:t}));if(s===t){this.moduleBusy=e.id;try{await this.moduleApi(`/api/admin/modules/${encodeURIComponent(e.id)}/purge-data`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({confirmation:s})}),await this.loadModules(),this.showToast(this.t("moduleDataPurged"),"success")}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}finally{this.moduleBusy=null}}},openSettingsPanel(){this.showPlugins=!1,this.settingsTab=this.isAdmin()?"models":"account",this.showSettings=!0,this.isGenerating&&this.stopGeneration(),this.agentOpen=!1,this.agentExpanded=!1,this.agentHistoryOpen=!1,this.showExtensionPalette=!1,this.pluginWorkspace.show&&this.closeActivePluginWorkspace(),this.isAdmin()&&(this.loadAdminUsers(),this.loadModules())},async openPluginsPanel(){this.isAdmin()&&(this.showSettings=!1,this.showPlugins=!0,this.showExtensionPalette=!1,await this.loadInstalledPlugins(),this.loadPluginMarket())},applyTheme(){this.settings.ui_settings.theme_mode="light",document.documentElement.setAttribute("data-theme","light")},async loadSettings(){try{const e=await fetch("/api/settings");if(e.ok){const t=await e.json();t.chat_settings&&(this.settings.chat_settings={...this.settings.chat_settings,...t.chat_settings}),t.rag_settings&&(this.settings.rag_settings={...this.settings.rag_settings,...t.rag_settings}),t.ui_settings&&(this.settings.ui_settings={...this.settings.ui_settings,...t.ui_settings},this.settings.ui_settings.theme_mode="light")}this.loadLogo()}catch(e){console.error("Failed to load settings:",e)}},async loadLogo(){try{const e=await fetch("/api/settings/logo");if(e.ok){const t=await e.json();this.settings.ui_settings.logo_data=t.logo_data||"","string"==typeof t.logo_text&&(this.settings.ui_settings.logo_text=t.logo_text),this.updateFavicon(this.settings.ui_settings.logo_data)}}catch(e){}},async loadModels(){try{const e=await fetch("/api/models");e.ok&&(this.models=await e.json(),this.models.forEach(e=>{e.api_key="",e.api_key_action="preserve",e.capability||(e.capability={vision:!1,reasoning:!1,tools:!1})}),this.syncVisionCapabilityState())}catch(e){console.error("Failed to load models:",e)}},async loadChats(){return this.loadAgentChats()},async loadAgentChats(){try{const e=await fetch("/api/agent/chats");e.ok&&(this.chats=await e.json()||[])}catch(e){console.error("Failed to load chats:",e)}},async loadDocuments(){try{const e=await fetch("/api/documents");e.ok&&(this.documents=await e.json()||[])}catch(e){console.error("Failed to load documents:",e)}},async createNewChat(){this.isGenerating&&this.stopGeneration(),this.showExtensionPalette=!1;try{const e=await fetch("/api/agent/chats",{method:"POST"});if(e.ok){const t=await e.json();this.chats.unshift(t),await this.selectChat(t.id),this.agentHistoryOpen=!1,this.announceAgentChatChange("created",t.id)}}catch(e){this.showToast(this.t("createChatFailed"),"error")}},async ensureCurrentChat(e){if(this.currentChatId)return this.currentChatId;const t=await fetch("/api/agent/chats",{method:"POST",signal:e});if(!t.ok)throw new Error(this.t("createChatFailed"));const s=await t.json();if(!s||"string"!=typeof s.id||!s.id)throw new Error(this.t("createChatFailed"));return this.chats.unshift(s),this.currentChatId=s.id,s.id},async selectChat(e){this.showExtensionPalette=!1,this.currentChatId!==e?(this.isGenerating&&this.stopGeneration(),this.currentChatId=e,this.messages=[],await this.loadMessages(e),this.agentHistoryOpen=!1):this.agentHistoryOpen=!1},async loadMessages(e){try{const t=await fetch(`/api/agent/chats/${e}/messages`);if(!t.ok)throw 404===t.status&&this.currentChatId===e&&(this.currentChatId=null,this.messages=[],await this.loadAgentChats()),new Error(`Agent history request failed (${t.status})`);if(this.currentChatId===e){const s=await t.json()||[];let i=[];if(window.ChatRaw?.modules)try{i=await window.ChatRaw.modules.listTasks({chat_id:e,limit:100})}catch(e){console.error("Failed to load conversation tasks:",e)}this.messages=this.mergeConversationTaskMessages(s,i);for(const e of i)MODULE_TERMINAL_STATES.has(e.state)||this.moduleTaskSubscriptions[e.task_id]||window.ChatRaw.modules.subscribe(e.task_id);this.$nextTick(()=>this.scrollToBottom())}}catch(t){console.error("Failed to load messages:",t),this.currentChatId===e&&(this.messages=[])}},async deleteChat(e){if(confirm(this.t("deleteSharedChat")))try{this.currentChatId===e&&this.isGenerating&&this.stopGeneration();const t=await fetch(`/api/agent/chats/${e}`,{method:"DELETE"});if(!t.ok){const e=await t.json();throw new Error(e.detail||this.t("deleteFailed"))}this.chats=this.chats.filter(t=>t.id!==e),this.currentChatId===e&&(this.currentChatId=null,this.messages=[]),this.announceAgentChatChange("deleted",e)}catch(e){this.showToast(this.t("deleteFailed"),"error")}},async renameChat(e){const t=window.prompt(this.t("renameSharedChat"),e.title);if(!t||t.trim()===e.title)return;const s=await fetch(`/api/agent/chats/${encodeURIComponent(e.id)}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:t.trim()})}),i=await s.json();s.ok?(e.title=i.title,this.announceAgentChatChange("renamed",e.id)):this.showToast(this.localizeError(i,"renameFailed"),"error")},async clearAllChats(){if(confirm(this.t("confirmClearAll"))){this.isGenerating&&this.stopGeneration();try{for(const e of this.chats){if(!(await fetch(`/api/agent/chats/${e.id}`,{method:"DELETE"})).ok)throw new Error(this.t("deleteFailed"))}this.chats=[],this.currentChatId=null,this.messages=[],this.announceAgentChatChange("cleared",""),this.showToast(this.t("allChatsCleared"),"success")}catch(e){this.showToast(this.t("deleteFailed"),"error")}}},stopGeneration(){this.abortController&&(this.markActiveHermesRunCancelled(),this.abortController.abort(),this.abortController=null,this.isGenerating=!1)},getLocalizedText(e,t=""){return e?"string"==typeof e?e:"object"==typeof e?e[this.lang]||e.en||e.zh||Object.values(e).find(Boolean)||t:String(e):t},escapeComposerHtml:e=>String(e||"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"),getComposerSelection(){const e=this.$refs.inputBox,t=(this.inputMessage||"").length;return{start:e?e.selectionStart:t,end:e?e.selectionEnd:t}},setComposerValue(e,t=null,s=null){this.inputMessage=e,this.$nextTick(()=>{const i=this.$refs.inputBox;if(i){if(i.value=e,Number.isInteger(t)){const e=Number.isInteger(s)?s:t;i.setSelectionRange(t,e)}i.dispatchEvent(new Event("input",{bubbles:!0})),this.autoResize(i)}})},replaceComposerRange(e,t,s){const i=this.inputMessage||"",n=Math.max(0,Math.min(e,i.length)),a=Math.max(n,Math.min(t,i.length)),o=i.slice(0,n)+s+i.slice(a),r=n+s.length;return this.setComposerValue(o,r,r),!0},insertComposerText(e){const t=this.getComposerSelection();return this.replaceComposerRange(t.start,t.end,e)},focusComposer(){this.$nextTick(()=>this.$refs.inputBox?.focus())},handleComposerInput(e){this.inputMessage=e.target.value,this.autoResize(e.target),this.syncComposerHighlightScroll(e),this.isComposing||this.queueCompletionRefresh()},syncComposerHighlightScroll(e=null){const t=e?.target||this.$refs.inputBox,s=this.$refs.composerHighlight;t&&s&&(s.scrollTop=t.scrollTop,s.scrollLeft=t.scrollLeft)},deleteComposerSkillToken(e){const t=this.getComposerSelection();if(t.start!==t.end)return!1;const s=this.inputMessage||"",i=t.start,n=this.getComposerSkillTokenRanges(s,{knownOnly:!0});for(const t of n){let n=t.start,a=t.end,o=!1;if("Backspace"===e?(o=i>t.start&&i<=t.end,!o&&i===t.end+1&&/[ \t]/.test(s[t.end]||"")&&(o=!0,a=t.end+1)):"Delete"===e&&(o=i>=t.start&&i{const e=this.$refs.composerSuggestions,t=e?.querySelector(".composer-suggestion-item.active");t?.scrollIntoView({block:"nearest"})})},async acceptHighlightedCompletion(){const e=this.completionItems[this.completionActiveIndex];e&&await this.acceptCompletion(e)},isCursorInFencedCode(e,t){const s=e.slice(0,t).match(/```/g);return Boolean(s&&s.length%2==1)},isCursorInMarkdownLink(e,t){const s=e.slice(Math.max(0,t-160),t),i=s.lastIndexOf("["),n=s.lastIndexOf("]("),a=s.lastIndexOf(")");return Math.max(i,n)>a},detectSlashToken(e,t){if(!Number.isInteger(t))return null;if(this.isCursorInFencedCode(e,t)||this.isCursorInMarkdownLink(e,t))return null;const s=e.slice(0,t),i=s.lastIndexOf("\n")+1,n=s.slice(i),a=n.match(/(^|\s)(\/\S*)$/);if(!a)return null;const o=a[2];if(!/^\/[a-z0-9-]*$/.test(o))return null;const r=o.slice(1);if(COMMON_PATH_ROOTS.has(r.toLowerCase()))return null;return{start:i+n.length-o.length,end:t,query:r,trigger:"/"}},queueCompletionRefresh(e=80){this.completionDebounceTimer&&clearTimeout(this.completionDebounceTimer),this.completionDebounceTimer=setTimeout(()=>{this.completionDebounceTimer=null,this.refreshCompletions()},e)},async refreshCompletions(){const e=this.$refs.inputBox;if(!e||this.isComposing)return;const t=this.detectSlashToken(this.inputMessage||"",e.selectionStart);if(!t)return void this.closeCompletionMenu();const s=++this.completionRequestId;this.completionRange=t,this.completionQuery=t.query;const i={trigger:t.trigger,query:t.query,range:{...t},value:this.inputMessage||"",selection:this.getComposerSelection()},n=this.getActiveCompletionProviders(t.trigger),a=[];for(const e of n){try{const t=await Promise.resolve(e.getItems(i));if(s!==this.completionRequestId)return;for(const s of t||[])if(s&&s.id&&s.label&&(a.push({...s,providerId:e.fullId,pluginId:e.pluginId,source:s.source||e.source||e.pluginId,icon:s.icon||e.icon||"ri-command-line"}),a.length>=20))break}catch(t){console.error(`[Completion ${e.fullId}] Error:`,t)}if(a.length>=20)break}s===this.completionRequestId&&(this.completionItems=a.slice(0,20),this.completionActiveIndex=0,this.completionMenuOpen=this.completionItems.length>0,this.completionMenuOpen&&this.scrollActiveCompletionIntoView())},closeCompletionMenu(){this.completionDebounceTimer&&(clearTimeout(this.completionDebounceTimer),this.completionDebounceTimer=null),this.completionRequestId+=1,this.completionMenuOpen=!1,this.completionItems=[],this.completionActiveIndex=0,this.completionQuery="",this.completionRange=null},getActiveCompletionProviders(e="/"){const t=[],s=this.getSkillCompletionProvider(e);s&&t.push(s);for(const s of Object.values(this.completionProviders)){if(s.trigger!==e)continue;const i=this.installedPlugins.find(e=>e.id===s.pluginId);i&&!1!==i.enabled&&t.push(s)}return t},registerCompletionProvider(e,t=null){const s=t||this._currentLoadingPlugin||e?.pluginId;if(!s||!e?.id||"function"!=typeof e.getItems)return console.warn("[Plugin Input] Completion provider requires plugin id, id, and getItems"),!1;const i=`${s}:${e.id}`;return this.completionProviders[i]={...e,fullId:i,pluginId:s,trigger:e.trigger||"/"},!0},unregisterCompletionProvider(e,t=null){const s=t||this._currentLoadingPlugin;if(!s||!e)return!1;const i=`${s}:${e}`,n=Boolean(this.completionProviders[i]);return delete this.completionProviders[i],this.completionItems.some(e=>e.providerId===i)&&this.closeCompletionMenu(),n},unregisterPluginCompletions(e){let t=!1;for(const s of Object.keys(this.completionProviders))this.completionProviders[s].pluginId===e&&(delete this.completionProviders[s],t=t||this.completionItems.some(e=>e.providerId===s));"skill-manager"===e&&(this.invalidateSkillCatalog(),t=!0),t&&this.closeCompletionMenu()},async acceptCompletion(e){if(!e)return;const t={query:this.completionQuery,range:this.completionRange?{...this.completionRange}:null,value:this.inputMessage||"",selection:this.getComposerSelection()};"function"==typeof e.onSelect&&await e.onSelect(e,t),this.closeCompletionMenu(),this.focusComposer()},isSkillManagerEnabled(){return this.installedPlugins.some(e=>"skill-manager"===e.id&&!0===e.enabled)},invalidateSkillCatalog(){return this.skillCatalogCache=null,this.skillCatalogLoadedAt=0,this.completionItems.some(e=>"__host:skill-manager-skills"===e.providerId)&&this.closeCompletionMenu(),!0},getSkillCompletionProvider(e){return"/"===e&&this.isSkillManagerEnabled()?{fullId:"__host:skill-manager-skills",pluginId:"skill-manager",source:"skills",icon:"ri-sparkling-2-line",trigger:"/",getItems:async e=>this.getSkillCompletionItems(e)}:null},async loadSkillCatalogForCompletion(){if(!this.isSkillManagerEnabled())return[];const e=Date.now();if(this.skillCatalogCache&&e-this.skillCatalogLoadedAt<3e4)return this.skillCatalogCache;try{const t=await fetch("/api/skills");if(!t.ok)return[];const s=await t.json();return this.skillCatalogCache=Array.isArray(s.skills)?s.skills:[],this.skillCatalogLoadedAt=e,this.skillCatalogCache}catch(e){return console.error("Failed to load skill catalog:",e),[]}},skillAliases(e){const t=e?.metadata||{},s=[];for(const e of["alias","aliases"]){const i=t[e];Array.isArray(i)?s.push(...i.filter(e=>"string"==typeof e)):"string"==typeof i&&s.push(...i.split(",").map(e=>e.trim()).filter(Boolean))}return"string"==typeof t.display_name&&s.push(t.display_name),s},rankSkillCompletion(e,t){const s=(t||"").toLowerCase(),i=(e.name||"").toLowerCase();if(!i||RESERVED_SLASH_COMMANDS.has(i)||COMMON_PATH_ROOTS.has(i))return null;if(!s)return 100;if(RESERVED_SLASH_COMMANDS.has(s)||COMMON_PATH_ROOTS.has(s))return null;if(i===s)return 0;if(i.startsWith(s))return 10;if(i.includes(s))return 20;const n=this.skillAliases(e).map(e=>e.toLowerCase());if(n.some(e=>e===s))return 30;if(n.some(e=>e.startsWith(s)))return 35;if(n.some(e=>e.includes(s)))return 40;const a=(e.description||"").toLowerCase(),o=s.split(/[-\s]+/).filter(Boolean);return o.length&&o.every(e=>a.includes(e))?50:null},async getSkillCompletionItems(e){return(await this.loadSkillCatalogForCompletion()).map(t=>({skill:t,rank:this.rankSkillCompletion(t,e.query)})).filter(e=>null!==e.rank).sort((e,t)=>e.rank-t.rank||e.skill.name.localeCompare(t.skill.name)).slice(0,20).map(({skill:e})=>({id:`skill:${e.name}`,label:`/${e.name}`,description:e.description||"",source:"skills",icon:"ri-sparkling-2-line",type:"skill",metadata:{skill:e},onSelect:(t,s)=>this.selectSkillCompletion(e,s.range)}))},selectSkillCompletion(e,t){if(!e?.name||!t)return;const s=this.inputMessage||"";let i=t.start,n=t.end;for(;s[n]&&!/\s/.test(s[n]);)n+=1;const a=s[n]&&/\s/.test(s[n])?"":" ";this.replaceComposerRange(i,n,`/${e.name}${a}`),this.closeCompletionMenu()},isValidComposerSkillName:e=>SKILL_NAME_PATTERN.test(e)&&!RESERVED_SLASH_COMMANDS.has(e)&&!COMMON_PATH_ROOTS.has(e),isKnownComposerSkillName(e){return!(!this.isValidComposerSkillName(e)||!Array.isArray(this.skillCatalogCache))&&this.skillCatalogCache.some(t=>t?.name===e&&!1!==t.enabled)},getComposerSkillTokenRanges(e,t={}){const s=String(e||""),i=s.matchAll(/(^|\s)\/([a-z0-9][a-z0-9-]{0,62})(?=$|\s)/g),n=new Set,a=[];for(const e of i){const i=e[2];if(!this.isValidComposerSkillName(i))continue;if(t.knownOnly&&!this.isKnownComposerSkillName(i))continue;const o=!n.has(i);if(o&&n.size>=5)continue;o&&n.add(i);const r=e.index+e[1].length;a.push({name:i,start:r,end:r+i.length+1,text:s.slice(r,r+i.length+1)})}return a},renderComposerHighlights(e){const t=String(e||"");if(!t)return"";const s=this.getComposerSkillTokenRanges(t,{knownOnly:!0});if(!s.length)return this.escapeComposerHtml(t);let i=0,n="";for(const e of s)n+=this.escapeComposerHtml(t.slice(i,e.start)),n+=`${this.escapeComposerHtml(e.text)}`,i=e.end;return n+=this.escapeComposerHtml(t.slice(i)),n},async extractActiveSkillNamesFromText(e){if(!this.isSkillManagerEnabled())return[];await this.loadSkillCatalogForCompletion();const t=new Set,s=[];for(const i of this.getComposerSkillTokenRanges(e,{knownOnly:!0}))t.has(i.name)||(t.add(i.name),s.push(i.name));return s},async prepareOutgoingMessage(){const e=(this.inputMessage||"").trim();return e?{message:e,activeSkillNames:await this.extractActiveSkillNamesFromText(e)}:{message:e,activeSkillNames:[]}},async sendMessage(){if(this.isGenerating)return;this.isGenerating=!0;const e=new AbortController;this.abortController=e;try{const t=await this.prepareOutgoingMessage();if(e.signal.aborted)return;let s=t.message;const i=[...t.activeSkillNames];if(!s)return;if(await this.ensureCurrentChat(e.signal),e.signal.aborted)return;this.inputMessage="",this.closeCompletionMenu(),this.$nextTick(()=>this.autoResize(this.$refs.inputBox)),this.messages.push({role:"user",content:s}),this.$nextTick(()=>this.scrollToBottom());let n="",a="";this.parsedUrl&&(n=this.parsedUrl.content,a=this.parsedUrl.url),this.attachedDocument&&(n&&(n+="\n\n---\n\n"),n+=this.attachedDocument.content,a=a?a+", "+this.attachedDocument.filename:this.attachedDocument.filename);let o={chat_id:this.currentChatId,message:s,use_rag:this.useRAG,image_base64:this.uploadedImageBase64,web_content:n,web_url:a};const r=await this.callHook("before_send",{...o});if(e.signal.aborted)return;if(r?.success&&r.body){const e=r.body;for(const t of["use_rag"])"boolean"==typeof e[t]&&(o[t]=e[t]);for(const t of["web_content","web_url"])"string"==typeof e[t]&&(o[t]=e[t])}if(o.chat_id=this.currentChatId,o.message=s,i.length>0?o.active_skills=i:delete o.active_skills,e.signal.aborted)return;this.removeUploadedImage(),this.removeParsedUrl(),this.removeAttachedDocument(),this.settings.chat_settings.stream?await this.handleStreamResponse(o,e.signal):await this.handleNormalResponse(o,e.signal),await this.loadChats()}catch(e){if("AbortError"===e.name);else{if(console.error("Failed to send message:",e),this.currentChatId)try{await this.loadMessages(this.currentChatId)}catch(e){console.error("Failed to recover persisted messages:",e)}this.showToast(this.t("sendFailed"),"error")}}finally{this.abortController===e?(this.abortController=null,this.isGenerating=!1):null===this.abortController&&(this.isGenerating=!1)}},createHermesRunState(e=""){return{runId:"",status:"",events:[],pendingApproval:null,approvalSubmitting:!1,approvalError:"",approvalBridgeResolvedKey:"",chatId:e||this.currentChatId||""}},ensureHermesRunState(e){return e.hermesRun||(e.hermesRun=this.createHermesRunState()),e.hermesRun.events||(e.hermesRun.events=[]),!e.hermesRun.chatId&&this.currentChatId&&(e.hermesRun.chatId=this.currentChatId),e.hermesRun},normalizeHermesRunEvent(e){if(!e||"object"!=typeof e||Array.isArray(e))return null;const t="string"==typeof e.type?e.type:"";if(!t)return null;const s={type:t},i=["run_id","status","tool","preview","error","command","description","choice"];for(const t of i)void 0!==e[t]&&null!==e[t]&&(s[t]=String(e[t]));if(Array.isArray(e.pattern_keys)&&(s.pattern_keys=e.pattern_keys.map(e=>String(e)).filter(Boolean).slice(0,20)),Array.isArray(e.choices)&&(s.choices=e.choices.map(e=>String(e)).filter(Boolean)),Number.isFinite(e.duration_ms)&&(s.duration_ms=Math.round(e.duration_ms)),("boolean"==typeof e.resolved||Number.isFinite(e.resolved))&&(s.resolved=e.resolved),e.usage&&"object"==typeof e.usage&&!Array.isArray(e.usage)){s.usage={};for(const[t,i]of Object.entries(e.usage))Number.isFinite(i)&&(s.usage[t]=i)}return s},hermesApprovalRequestKey(e){if(!e)return"";const t=Array.isArray(e.pattern_keys)?e.pattern_keys.join("|"):"";return[e.run_id||"",e.command||"",e.description||"",t].join("\n")},isHermesRunTerminalStatus:e=>["completed","succeeded","failed","cancelled","canceled","stopped"].includes(e||""),markActiveHermesRunCancelled(){for(let e=this.messages.length-1;e>=0;e-=1){const t=this.messages[e],s=t?.hermesRun;if(s&&Array.isArray(s.events)&&s.events.length){if(this.isHermesRunTerminalStatus(s.status))return;return void this.applyHermesRunEvent(t,{type:"run.cancelled",run_id:s.runId||"",status:"cancelled"},e)}}},applyHermesRunEvent(e,t,s=-1){const i=this.normalizeHermesRunEvent(t);if(!i)return;const n=this.ensureHermesRunState(e);"approval.request"===i.type&&(n.pendingApproval&&this.hermesApprovalRequestKey(n.pendingApproval)===this.hermesApprovalRequestKey(i)||n.approvalBridgeResolvedKey===this.hermesApprovalRequestKey(i))||(i._id=`${i.type}-${n.events.length}`,i.run_id&&(n.runId=i.run_id),i.status&&(n.status=i.status),"approval.request"===i.type?(n.status="pending_approval",n.pendingApproval=i,n.approvalSubmitting=!1,n.approvalError=""):"approval.responded"===i.type?(n.pendingApproval=null,n.approvalSubmitting=!1,n.approvalError="",n.approvalBridgeResolvedKey="",n.status=i.status||"running"):"run.completed"===i.type?(n.pendingApproval=null,n.approvalSubmitting=!1,n.approvalError="",n.approvalBridgeResolvedKey="",n.status="completed"):"run.failed"===i.type?(n.pendingApproval=null,n.approvalSubmitting=!1,n.approvalBridgeResolvedKey="",n.status="failed"):"run.cancelled"===i.type?(n.pendingApproval=null,n.approvalSubmitting=!1,n.approvalBridgeResolvedKey="",n.status="cancelled"):"run.started"!==i.type||n.status||(n.status=i.status||"running"),n.events.push(i),this.commitHermesRunMessage(e,s))},commitHermesRunMessage(e,t=-1){e.hermesRun&&(e.hermesRun={...e.hermesRun,events:[...e.hermesRun.events],pendingApproval:e.hermesRun.pendingApproval?{...e.hermesRun.pendingApproval}:null},t>=0&&(this.messages[t]={...e}),this.$nextTick(()=>this.scrollToBottom()))},hermesRunStatusLabel(e){const t=String(e||"").toLowerCase();return{started:this.t("hermesRunStatusStarted"),queued:this.t("hermesRunStatusStarted"),running:this.t("hermesRunStatusRunning"),in_progress:this.t("hermesRunStatusRunning"),pending_approval:this.t("hermesRunStatusPendingApproval"),completed:this.t("hermesRunStatusCompleted"),succeeded:this.t("hermesRunStatusCompleted"),failed:this.t("hermesRunStatusFailed"),cancelled:this.t("hermesRunStatusCancelled"),canceled:this.t("hermesRunStatusCancelled")}[t]||e||this.t("hermesRunStatusRunning")},hermesRunEventTitle(e){return{"run.started":this.t("hermesRunEventRunStarted"),"tool.started":this.t("hermesRunEventToolStarted"),"tool.completed":this.t("hermesRunEventToolCompleted"),"reasoning.available":this.t("hermesRunEventReasoning"),"approval.request":this.t("hermesRunEventApprovalRequest"),"approval.responded":this.t("hermesRunEventApprovalResponded"),"run.completed":this.t("hermesRunEventRunCompleted"),"run.failed":this.t("hermesRunEventRunFailed"),"run.cancelled":this.t("hermesRunEventRunCancelled")}[e?.type]||e?.type||this.t("hermesRun")},hermesRunEventIcon:e=>({"run.started":"ri-play-circle-line","tool.started":"ri-tools-line","tool.completed":"ri-checkbox-circle-line","reasoning.available":"ri-lightbulb-line","approval.request":"ri-shield-keyhole-line","approval.responded":"ri-check-double-line","run.completed":"ri-checkbox-circle-line","run.failed":"ri-error-warning-line","run.cancelled":"ri-stop-circle-line"}[e?.type]||"ri-terminal-box-line"),hermesRunEventClass(e){const t=String(e?.type||"event").replace(/[^a-z0-9_-]+/gi,"-").toLowerCase(),s=String(e?.status||"").replace(/[^a-z0-9_-]+/gi,"-").toLowerCase();return{[`hermes-event-${t}`]:!0,[`status-${s}`]:Boolean(s)}},formatHermesDuration:e=>Number.isFinite(e)?e<1e3?`${e} ms`:`${(e/1e3).toFixed(e<1e4?1:0)} s`:"",hasHermesDuration:e=>Number.isFinite(e?.duration_ms),isHermesPendingApprovalEvent(e,t){const s=e?.hermesRun?.pendingApproval;return Boolean(s&&t&&s._id===t._id)},async submitHermesApproval(e,t,s=-1){const i=s>=0?s:this.messages.indexOf(e);if(i>=0&&this.messages[i]!==e&&this.messages[i]?.hermesRun?.runId!==e?.hermesRun?.runId)return;const n=i>=0?this.messages[i]:e,a=n?.hermesRun,o=a?.runId,r=a?.chatId;if(a&&o&&r&&!a.approvalSubmitting){a.approvalSubmitting=!0,a.approvalError="",this.commitHermesRunMessage(n,i);try{const e=await fetch(`/api/hermes/runs/${encodeURIComponent(o)}/approval`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({chat_id:r,choice:t,resolve_all:!1})});let s={};try{s=await e.json()}catch(e){s={}}if(!e.ok||!1===s.success)throw new Error(s.error||this.t("hermesApprovalError"));a.status=s.status||a.status||"running",a.approvalBridgeResolvedKey=this.hermesApprovalRequestKey(a.pendingApproval),a.pendingApproval=null,a.approvalSubmitting=!1,a.approvalError="",this.commitHermesRunMessage(n,i)}catch(e){a.approvalSubmitting=!1,a.approvalError=this.t("hermesApprovalError"),this.commitHermesRunMessage(n,i)}}},async handleStreamResponse(e,t=this.abortController?.signal){const s=await fetch("/api/agent/chat",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:t});if(!s.ok){await s.text();throw new Error(this.t("requestFailed"))}const i={role:"assistant",content:"",thinking:"",references:[],showThinking:!1,hermesRun:this.createHermesRunState()};this.messages.push(i);const n=this.messages.length-1,a=s.body.getReader(),o=new TextDecoder;let r="";try{for(;;){const{done:e,value:t}=await a.read();if(e)break;let s;for(r+=o.decode(t,{stream:!0});-1!==(s=r.indexOf("\n"));){const e=r.slice(0,s).trim();if(r=r.slice(s+1),e)try{const t=JSON.parse(e);if(t.chat_id&&(this.currentChatId=t.chat_id,i.hermesRun&&(i.hermesRun.chatId=t.chat_id)),t.hermes_run&&this.applyHermesRunEvent(i,t.hermes_run,n),t.thinking){const e=this.messages[n]?.showThinking||!1;i.thinking+=t.thinking,i.showThinking=e,this.messages[n]={...i},this.$nextTick(()=>this.scrollToBottom())}if(t.content){const e=this.messages[n]?.showThinking||!1;i.content+=t.content,i.showThinking=e,this.messages[n]={...i},this.$nextTick(()=>this.scrollToBottom())}if(t.references){const e=this.messages[n]?.showThinking||!1;i.references=t.references,i.showThinking=e,this.messages[n]={...i},this.$nextTick(()=>this.scrollToBottom())}t.error&&(i.content="Error: "+t.error,this.messages[n]={...i})}catch(e){}}}}catch(e){if("AbortError"!==e.name)throw e;try{await a.cancel()}catch(e){}}},async handleNormalResponse(e,t=this.abortController?.signal){const s=await fetch("/api/agent/chat",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:t}),i=await s.json();if(!s.ok)throw new Error(this.t("requestFailed"));!this.currentChatId&&i.chat_id&&(this.currentChatId=i.chat_id),this.messages.push({role:"assistant",content:i.content,thinking:i.thinking||"",references:i.references||[]}),this.$nextTick(()=>this.scrollToBottom())},async handleImageUpload(e){const t=e.target.files[0];if(t)if(this.chatModelSupportsVision()){try{const e=await this.compressImage(t),s=new FormData;s.append("file",e);const i=await fetch("/api/upload/image",{method:"POST",body:s});if(!i.ok){const e=await i.json();throw new Error(e.error)}{const e=await i.json();this.uploadedImage=t.name,this.uploadedImageBase64=e.base64}}catch(e){this.showToast(this.t("uploadFailed"),"error")}e.target.value=""}else e.target.value=""},handleLogoUpload(e){const t=e.target.files[0];if(!t)return;const s=new FileReader;s.onload=e=>{this.settings.ui_settings.logo_data=e.target.result,this.updateFavicon(e.target.result)},s.readAsDataURL(t),e.target.value=""},removeCustomLogo(){this.settings.ui_settings.logo_data="",this.updateFavicon("")},handleLogoImageError(e){const t=e?.currentTarget;if(!t)return;const s=t.getAttribute("src")||"";s.endsWith("/brand-mark.svg")||"brand-mark.svg"===s||(t.src="/brand-mark.svg")},updateFavicon(e){const t=document.getElementById("favicon");t.href=e||"/brand-mark.svg",this.updateDocumentTitle()},handleAvatarUpload(e,t){const s=e.target.files[0];if(!s)return;const i=new FileReader;i.onload=e=>{"user"===t?this.settings.ui_settings.user_avatar=e.target.result:this.settings.ui_settings.assistant_avatar=e.target.result},i.readAsDataURL(s),e.target.value=""},removeUploadedImage(){this.uploadedImage=null,this.uploadedImageBase64=""},async handleDocumentUpload(e){const t=e.target.files[0];if(t){this.isUploadingDocument=!0;try{const e="."+(t.name.split(".").pop()||"").toLowerCase(),s=this.pluginHooks.parse_document||[];let i=!1;for(const n of s){if(n._pluginId){const e=this.installedPlugins.find(e=>e.id===n._pluginId);if(!e||!e.enabled)continue}const s=n.fileTypes||[];if(s.includes(e)||s.includes(e.toLowerCase()))try{const e=n._pluginId?this.getPluginSettings(n._pluginId):{},s=await n.handler(t,e);if(s?.success&&s.content){this.attachedDocument={filename:t.name,content:s.content},this.showToast(this.t("documentAttached")+": "+t.name,"success"),i=!0;break}if(s?.error){this.showToast(this.t("uploadFailed"),"error"),i=!0;break}}catch(e){console.error(`[Plugin ${n._pluginId}] Parse error:`,e)}}if(!i){const e=new FormData;e.append("file",t);const s=await fetch("/api/upload/document",{method:"POST",body:e});if(!s.ok){const e=await s.text();try{const t=JSON.parse(e);throw new Error(t.error||`HTTP ${s.status}`)}catch{throw new Error(`HTTP ${s.status}: ${e.substring(0,100)}`)}}const i=await s.json();i.success?(this.attachedDocument={filename:i.filename,content:i.content},this.showToast(this.t("documentAttached")+": "+i.filename,"success")):this.showToast(this.t("uploadFailed"),"error")}}catch(e){console.error("Document upload error:",e),this.showToast(this.t("uploadFailed"),"error")}finally{this.isUploadingDocument=!1}e.target.value=""}},removeAttachedDocument(){this.attachedDocument=null},getDocumentAcceptTypes(){const e=this.installedPlugins.filter(e=>e.enabled&&e.fileTypes).flatMap(e=>e.fileTypes);return[...new Set([".pdf",".docx",".doc",".txt",".md",...e])].join(",")},toggleUrlInput(){this.showExtensionPalette=!1,this.showUrlInput=!this.showUrlInput,this.showUrlInput&&(this.urlInputValue="",this.$nextTick(()=>{const e=document.querySelector(".url-input-popup input");e&&e.focus()}))},async parseUrl(){const e=this.urlInputValue.trim();if(e){this.isParsingUrl=!0;try{let t=null;const s=this.pluginHooks.parse_url||[];let i=null;for(const e of s)if(e._pluginId){const t=this.installedPlugins.find(t=>t.id===e._pluginId);if(t&&t.enabled){i=t.id;break}}if(i){const s=this.getPluginSettings(i);if("browser"===(s.parser_mode||"browser")){const i=await fetch("/api/fetch-raw-url",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:e})}),n=await i.json();if(n.success&&null!=n.html){const i=await this.callHook("parse_url",e,n.html,s);i?.success&&null!=i.title&&null!=i.content&&(t={success:!0,title:i.title,content:i.content,url:e})}}else{const i=await this.callHook("parse_url",e,null,s);i?.success&&null!=i.title&&null!=i.content&&(t={success:!0,title:i.title,content:i.content,url:e})}}if(!t){const s=await fetch("/api/parse-url",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:e})});t=await s.json()}t.success?(this.parsedUrl={title:t.title,content:t.content,url:t.url},this.parsedUrlTitle=t.title,this.showUrlInput=!1,this.urlInputValue="",this.showToast(this.t("urlAttached")+": "+t.title,"success")):this.showToast(this.t("parseFailed"),"error")}catch(e){this.showToast(this.t("parseFailed"),"error")}finally{this.isParsingUrl=!1}}},removeParsedUrl(){this.parsedUrl=null,this.parsedUrlTitle=""},cancelUrlInput(){this.showUrlInput=!1,this.urlInputValue=""},async saveAndVerifyModel(e,t){e.status="loading",e.verifyMessage="";try{e.name||(e.name=e.model_id||"Unnamed");const t=await fetch("/api/models",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...e,api_key_action:"clear"===e.api_key_action?"clear":e.api_key?"replace":"preserve"})});if(!t.ok)throw new Error("Failed to save");const s=await t.json();s.id&&(e.id=s.id),e.api_key_configured=s.api_key_configured,e.api_key="",e.api_key_action="preserve";const i=await fetch("/api/models/verify",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:e.id,api_url:e.api_url,api_key:e.api_key,model_id:e.model_id,type:e.type})}),n=await i.json();i.ok&&n.success?(e.status="success",e.verifyMessage=this.t("verifySuccess")):(e.status="error",e.verifyMessage=n.error||this.t("verifyFailed"))}catch(t){e.status="error",e.verifyMessage=t.message||this.t("verifyFailed")}},async saveAllSettings(){try{this.settings.ui_settings.theme_mode="light";if(!(await fetch("/api/settings",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(this.settings)})).ok)throw new Error(this.t("saveFailed"));for(const e of this.models)e.name||(e.name=e.model_id||"Unnamed"),await fetch("/api/models",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...e,api_key_action:"clear"===e.api_key_action?"clear":e.api_key?"replace":"preserve"})});this.applyTheme(),this.announceSettingsChange(),this.showSettings=!1,this.showToast(this.t("settingsSaved"),"success"),await this.loadModels()}catch(e){await this.loadSettings(),this.applyTheme(),this.showToast(this.t("saveFailed"),"error")}},async copyMessage(e,t){if(e)try{await navigator.clipboard.writeText(e);const s=t.currentTarget,i=s.querySelector("i"),n=i.className;i.className="ri-check-line",s.classList.add("copied"),setTimeout(()=>{i.className=n,s.classList.remove("copied")},2e3)}catch(t){const s=document.createElement("textarea");s.value=e,s.style.position="fixed",s.style.opacity="0",document.body.appendChild(s),s.select(),document.execCommand("copy"),document.body.removeChild(s),this.showToast(this.t("copied")||"Copied!")}},renderMarkdown(e){return window.ChatRawContentSecurity.renderMarkdown(e,this.lang)},horizontalWheelDelta:e=>e.defaultPrevented||!e.cancelable||e.ctrlKey?0:Math.abs(e.deltaX)>Math.abs(e.deltaY)?e.deltaX:e.shiftKey?e.deltaY:0,findHorizontalWheelScroller(e){let t=e instanceof Element?e:null;for(;t&&t!==document.body;){const e=getComputedStyle(t).overflowX;if(t.scrollWidth>t.clientWidth&&("auto"===e||"scroll"===e))return t;t=t.parentElement}return null},consumeHorizontalWheel(e,t=null){const s=this.horizontalWheelDelta(e);if(!s)return!1;if(e.preventDefault(),e.stopPropagation(),!t)return!0;const i=1===e.deltaMode?16:2===e.deltaMode?t.clientWidth:1,n=t.scrollWidth-t.clientWidth;return t.scrollLeft=Math.max(0,Math.min(n,t.scrollLeft+s*i)),!0},handleMarkdownTableWheel(e){const t=e.target instanceof Element?e.target.closest(".markdown-table-scroll"):null;return!(!t||t.scrollWidth<=t.clientWidth)&&this.consumeHorizontalWheel(e,t)},handleAppWheel(e){if(e.defaultPrevented)return!0;if(this.handleMarkdownTableWheel(e))return!0;return!!this.horizontalWheelDelta(e)&&this.consumeHorizontalWheel(e,this.findHorizontalWheelScroller(e.target))},installRootWheelGuard(){return!this._rootWheelHandler&&(this._rootWheelHandler=e=>this.handleAppWheel(e),document.addEventListener("wheel",this._rootWheelHandler,{passive:!1}),!0)},scrollToBottom(){const e=this.$refs.messagesContainer;e&&(e.scrollTop=e.scrollHeight)},autoResize(e){e.style.height="auto",e.style.height=Math.min(e.scrollHeight,200)+"px",e===this.$refs.inputBox&&this.syncComposerHighlightScroll()},showToast(e,t=""){this.toast.message=e,this.toast.type=t,this.toast.show=!0,setTimeout(()=>{this.toast.show=!1},3e3)},getSortedPluginButtons(e="toolbar"){return[...this.pluginToolbarButtons].filter(t=>{const s=this.installedPlugins?.find(e=>e.id===t.pluginId);return!0===s?.enabled&&"hermes"!==t.pluginId&&t.placement===e}).sort((e,t)=>(e.order||100)-(t.order||100))},get extensionPaletteEntries(){return[...this.residentEntries("composer").map(e=>({key:`resident:${e.integration.id}:${e.id}`,kind:"resident",order:e.order??100,stableId:`${e.integration.id}:${e.id}`,icon:e.icon,label:this.residentText(e.label),status:e.integration.feature?.available?"":this.t("unavailable"),active:!1,loading:!1,disabled:!e.integration.feature?.available,source:e})),...this.getSortedPluginButtons("toolbar").map(e=>({key:`plugin:${e.fullId}`,kind:"plugin",order:e.order??100,stableId:e.fullId,icon:e.icon,label:this.getPluginButtonLabel(e),status:e.status?this.getLocalizedText(e.status,""):"",active:!0===e.active,loading:!0===e.loading,disabled:!0===e.disabled,source:e}))].sort((e,t)=>e.order-t.order||(e.kind===t.kind?0:"resident"===e.kind?-1:1)||e.stableId.localeCompare(t.stableId))},syncExtensionPaletteVisibility(e){if(0!==e)return;const t=document.getElementById("agent-extension-palette"),s=document.activeElement,i=this.showExtensionPalette||s===this.$refs?.extensionPaletteToggle||t?.contains(s);this.showExtensionPalette=!1,i&&this.restoreExtensionPaletteFocus(null)},isVisibleFocusTarget(e){if(!(e instanceof HTMLElement&&e.isConnected))return!1;if(e.disabled||e.hidden)return!1;for(let t=e;t instanceof HTMLElement;t=t.parentElement){const e=window.getComputedStyle(t);if(t.hidden||"none"===e.display||"hidden"===e.visibility)return!1}return!0},agentSurfaceFocusTarget(e=null){return[e,this.$refs?.inputBox,this.$refs?.agentLauncher].find(e=>this.isVisibleFocusTarget(e))||null},focusFirstExtensionEntry(){this.$nextTick(()=>{requestAnimationFrame(()=>{if(!this.showExtensionPalette)return;const e=document.getElementById("agent-extension-palette"),t=document.querySelector("#agent-extension-palette .agent-extension-item:not(:disabled)");(t||e)?.focus()})})},restoreExtensionPaletteFocus(e=this.$refs?.extensionPaletteToggle){this.$nextTick(()=>{requestAnimationFrame(()=>{this.showExtensionPalette||this.agentSurfaceFocusTarget(e)?.focus()})})},closeExtensionPalette(e=!1){const t=this.$refs?.extensionPaletteToggle;this.showExtensionPalette=!1,e&&this.restoreExtensionPaletteFocus(t)},toggleExtensionPalette(){if(0===this.extensionPaletteEntries.length)return void(this.showExtensionPalette=!1);const e=!this.showExtensionPalette;this.showUrlInput=!1,this.completionMenuOpen=!1,this.agentHistoryOpen=!1,this.showExtensionPalette=e,e&&this.focusFirstExtensionEntry()},extensionEntryTitle:e=>e?.status?`${e.label} · ${e.status}`:e?.label||"",get pluginWorkspaceCollections(){const e=new Map;for(const t of Object.values(this.pluginWorkspaceDefinitions)){const s=t.collection;if(!s)continue;const i=this.installedPlugins?.find(e=>e.id===t.pluginId);!1!==i?.enabled&&(e.has(s.id)||e.set(s.id,{id:s.id,title:s.title,icon:s.icon,order:s.order,panels:[]}),e.get(s.id).panels.push({pluginId:t.pluginId,panelId:t.id,title:t.title,icon:this.pluginWorkspacePanelIcon(t.pluginId,t.id,t.icon),order:s.tabOrder}))}return[...e.values()].map(e=>({...e,panels:e.panels.sort((e,t)=>e.order-t.order||`${e.pluginId}:${e.panelId}`.localeCompare(`${t.pluginId}:${t.panelId}`))})).sort((e,t)=>e.order-t.order||e.id.localeCompare(t.id))},getPluginButtonLabel(e){const t=this.lang||"en";return e.label?.[t]||e.label?.en||e.id},getPluginWorkspaceCollection(e){return this.pluginWorkspaceCollections.find(t=>t.id===e)||null},activePluginWorkspaceCollectionPanels(){return this.getPluginWorkspaceCollection(this.pluginWorkspace.collectionId)?.panels||[]},switchPluginWorkspaceCollectionPanel(e){const t=this.getPluginWorkspaceCollection(this.pluginWorkspace.collectionId);if(!t?.panels.some(t=>t.pluginId===e?.pluginId&&t.panelId===e?.panelId))throw new Error("Plugin Workspace collection panel mismatch");return this.openPluginWorkspacePanel(e.panelId,{placement:"main"},e.pluginId)},pluginWorkspaceKey:(e,t)=>`${e}:${t}`,assertPluginWorkspaceOwner(e){if("string"!=typeof e||!e.trim())throw new TypeError("Plugin Workspace requires an explicit pluginId");const t=this.installedPlugins.find(t=>t.id===e);if(!t||!1===t.enabled)throw new Error(`Plugin Workspace owner is not enabled: ${e}`);return e},assertPluginWorkspaceTransitionAvailable(){if(this._pluginWorkspaceTransition)throw new Error("Plugin Workspace lifecycle cannot change during "+this._pluginWorkspaceTransition)},restorePluginWorkspaceFocus(e){this.$nextTick(()=>{requestAnimationFrame(()=>{this.pluginWorkspace.show||this.agentSurfaceFocusTarget(e)?.focus()})})},validatePluginWorkspaceDefinition(e){const t=new Set(["id","title","icon","placements","defaultPlacement","collection","mount"]);if(!e||"object"!=typeof e||Array.isArray(e)||Object.keys(e).some(e=>!t.has(e))||"string"!=typeof e.id||!PLUGIN_WORKSPACE_PANEL_ID_PATTERN.test(e.id)||!e.title||!e.icon||!/^ri-[a-z0-9-]+$/.test(e.icon)||!Array.isArray(e.placements)||0===e.placements.length||new Set(e.placements).size!==e.placements.length||e.placements.some(e=>!PLUGIN_WORKSPACE_PLACEMENTS.includes(e))||!e.placements.includes(e.defaultPlacement)||"function"!=typeof e.mount)throw new TypeError("Invalid Plugin Workspace definition");if("string"!=typeof e.title&&("object"!=typeof e.title||Array.isArray(e.title)||0===Object.keys(e.title).length||!Object.values(e.title).every(e=>"string"==typeof e)||!Object.values(e.title).some(e=>e.trim())))throw new TypeError("Invalid Plugin Workspace title");if(void 0!==e.collection){const t=e.collection,s=new Set(["id","title","icon","order","tabOrder"]);if(!t||"object"!=typeof t||Array.isArray(t)||Object.keys(t).some(e=>!s.has(e))||"string"!=typeof t.id||!PLUGIN_WORKSPACE_PANEL_ID_PATTERN.test(t.id)||!t.title||"string"!=typeof t.icon||!/^ri-[a-z0-9-]+$/.test(t.icon)||!Number.isFinite(t.order)||!Number.isFinite(t.tabOrder)||"main"!==e.defaultPlacement||!e.placements.includes("main"))throw new TypeError("Invalid Plugin Workspace collection definition");if("string"!=typeof t.title&&("object"!=typeof t.title||Array.isArray(t.title)||0===Object.keys(t.title).length||!Object.values(t.title).every(e=>"string"==typeof e)||!Object.values(t.title).some(e=>e.trim())))throw new TypeError("Invalid Plugin Workspace collection title");const i=e=>"string"==typeof e?e:JSON.stringify(Object.fromEntries(Object.entries(e).sort(([e],[t])=>e.localeCompare(t))));if(Object.values(this.pluginWorkspaceDefinitions).find(e=>e.collection?.id===t.id&&(e.collection.icon!==t.icon||e.collection.order!==t.order||i(e.collection.title)!==i(t.title))))throw new Error(`Plugin Workspace collection metadata mismatch: ${t.id}`)}return e},focusPluginWorkspaceTitle(e,t,s){this.$nextTick(()=>{requestAnimationFrame(()=>{this.pluginWorkspace.show&&this.pluginWorkspace.pluginId===e&&this.pluginWorkspace.panelId===t&&this.pluginWorkspace.placement===s&&document.getElementById("plugin-workspace-title")?.focus()})})},registerPluginWorkspacePanel(e,t){this.assertPluginWorkspaceTransitionAvailable();const s=this.assertPluginWorkspaceOwner(t);this.validatePluginWorkspaceDefinition(e);const i=this.pluginWorkspaceKey(s,e.id);if(this.pluginWorkspace.show&&this.pluginWorkspace.pluginId===s&&this.pluginWorkspace.panelId===e.id){const e=this.teardownPluginWorkspace();if(e)throw e}return this.pluginWorkspaceDefinitions[i]=Object.freeze({pluginId:s,id:e.id,title:e.title,icon:e.icon,placements:Object.freeze([...e.placements]),defaultPlacement:e.defaultPlacement,collection:e.collection?Object.freeze({id:e.collection.id,title:e.collection.title,icon:e.collection.icon,order:e.collection.order,tabOrder:e.collection.tabOrder}):null,mount:e.mount}),this.recomputeFeatureCatalogRuntime(),!0},unregisterPluginWorkspacePanel(e,t){this.assertPluginWorkspaceTransitionAvailable();const s=this.assertPluginWorkspaceOwner(t);if("string"!=typeof e||!PLUGIN_WORKSPACE_PANEL_ID_PATTERN.test(e))throw new TypeError("Invalid Plugin Workspace panelId");const i=this.pluginWorkspaceKey(s,e);if(!this.pluginWorkspaceDefinitions[i])return!1;if(this.pluginWorkspace.show&&this.pluginWorkspace.pluginId===s&&this.pluginWorkspace.panelId===e){const e=this.teardownPluginWorkspace();if(e)throw e}return delete this.pluginWorkspaceDefinitions[i],this.recomputeFeatureCatalogRuntime(),!0},openPluginWorkspacePanel(e,t,s){this.assertPluginWorkspaceTransitionAvailable();const i=this.assertPluginWorkspaceOwner(s);if("string"!=typeof e||!PLUGIN_WORKSPACE_PANEL_ID_PATTERN.test(e)||void 0!==t&&(!t||"object"!=typeof t||Array.isArray(t)||Object.keys(t).some(e=>"placement"!==e)))throw new TypeError("Invalid Plugin Workspace open request");const n=this.pluginWorkspaceKey(i,e),a=this.pluginWorkspaceDefinitions[n];if(!a)throw new Error(`Plugin Workspace panel is not registered: ${e}`);const o=void 0!==t&&Object.prototype.hasOwnProperty.call(t,"placement")?t.placement:a.defaultPlacement;if(!a.placements.includes(o))throw new TypeError(`Unsupported Plugin Workspace placement: ${o}`);const r=this._pluginWorkspaceActivation,l=r?.pluginId===i&&!0===navigator.userActivation?.isActive&&r.trigger instanceof HTMLElement&&r.trigger.isConnected,d=this._pluginWorkspaceReturnFocus,u=l?r.returnFocus instanceof HTMLElement&&r.returnFocus.isConnected?r.returnFocus:r.trigger:d?.isConnected?d:null;if(this.showExtensionPalette=!1,this.pluginWorkspace.show&&this.pluginWorkspace.pluginId===i&&this.pluginWorkspace.panelId===e&&this.pluginWorkspace.placement===o)return l&&(this._pluginWorkspaceReturnFocus=u,this.focusPluginWorkspaceTitle(i,e,o)),!0;if(this.pluginWorkspace.show){const e=this.teardownPluginWorkspace(!1);if(e)throw this.restorePluginWorkspaceFocus(u),e}const c=document.getElementById("plugin-workspace-mount");if(!c)throw this.restorePluginWorkspaceFocus(u),new Error("Plugin Workspace mount container is unavailable");this._pluginWorkspaceReturnFocus=u,c.replaceChildren(),this.pluginWorkspace={show:!0,pluginId:i,panelId:e,title:a.title,icon:a.icon,collectionId:a.collection?.id||null,collectionTitle:a.collection?.title||null,collectionIcon:a.collection?.icon||"",placement:o,dispose:null};try{this._pluginWorkspaceTransition="mount";const e=a.mount({container:c,placement:o});if("function"!=typeof e)throw new TypeError("Plugin Workspace mount must synchronously return dispose()");this.pluginWorkspace.dispose=e}catch(e){c.replaceChildren(),this.pluginWorkspace={show:!1,pluginId:null,panelId:null,title:null,icon:"",collectionId:null,collectionTitle:null,collectionIcon:"",placement:null,dispose:null};const t=this._pluginWorkspaceReturnFocus;throw this._pluginWorkspaceReturnFocus=null,this.restorePluginWorkspaceFocus(t),e}finally{this._pluginWorkspaceTransition=null}return l&&this.focusPluginWorkspaceTitle(i,e,o),!0},teardownPluginWorkspace(e=!0){if(this.assertPluginWorkspaceTransitionAvailable(),!this.pluginWorkspace.show)return null;const t=this.pluginWorkspace.dispose;this.pluginWorkspace.dispose=null;let s=null;try{this._pluginWorkspaceTransition="dispose";const e=t?.();if(void 0!==e)throw null===e||"object"!=typeof e&&"function"!=typeof e||"function"!=typeof e.then||Promise.resolve(e).catch(e=>{console.error("[Plugin Workspace async dispose]",e)}),new TypeError("Plugin Workspace dispose must complete synchronously and return undefined")}catch(e){s=e}finally{this._pluginWorkspaceTransition=null}document.getElementById("plugin-workspace-mount")?.replaceChildren(),this.pluginWorkspace={show:!1,pluginId:null,panelId:null,title:null,icon:"",collectionId:null,collectionTitle:null,collectionIcon:"",placement:null,dispose:null};const i=this._pluginWorkspaceReturnFocus;return this._pluginWorkspaceReturnFocus=null,e&&this.restorePluginWorkspaceFocus(i),s},closeActivePluginWorkspace(){const e=this.teardownPluginWorkspace();e&&console.error("[Plugin Workspace dispose]",e)},closePluginWorkspacePanel(e,t){this.assertPluginWorkspaceTransitionAvailable();const s=this.assertPluginWorkspaceOwner(t);if("string"!=typeof e||!PLUGIN_WORKSPACE_PANEL_ID_PATTERN.test(e))throw new TypeError("Invalid Plugin Workspace panelId");if(!this.pluginWorkspace.show)return!1;if(this.pluginWorkspace.pluginId!==s||this.pluginWorkspace.panelId!==e)throw new Error("Plugin Workspace ownership mismatch");const i=this.teardownPluginWorkspace();if(i)throw i;return!0},unregisterPluginWorkspacePanels(e){if(this.pluginWorkspace.pluginId===e){const e=this.teardownPluginWorkspace();e&&console.error("[Plugin Workspace dispose]",e)}const t=`${e}:`;for(const e of Object.keys(this.pluginWorkspaceDefinitions))e.startsWith(t)&&delete this.pluginWorkspaceDefinitions[e];this.recomputeFeatureCatalogRuntime()},async handlePluginButtonClick(e,t=null,s=t){if(e.loading||e.disabled)return!1;try{const i=!0===navigator.userActivation?.isActive&&t instanceof HTMLElement&&t.isConnected?{pluginId:e.pluginId,trigger:t,returnFocus:s instanceof HTMLElement&&s.isConnected?s:t}:null,n=this._pluginWorkspaceActivation;let a;this._pluginWorkspaceActivation=i;try{a=e.onClick?.(e)}finally{this._pluginWorkspaceActivation=n}return await a,!0}catch(t){return console.error(`[Plugin ${e.pluginId}] Button click error:`,t),e.loading=!1,!1}},async handleExtensionEntryClick(e,t=null){const s=this.extensionPaletteEntries.find(t=>t.key===e?.key);if(!s||s.loading||s.disabled)return this.syncExtensionPaletteVisibility(this.extensionPaletteEntries.length),!1;const i=this.$refs.extensionPaletteToggle;if("resident"===s.kind)return this.showExtensionPalette=!1,await this.openResidentIntegration(s.source.integration,i),!0;return!!await this.handlePluginButtonClick(s.source,t,i)&&(this.showExtensionPalette=!1,this.pluginWorkspace.show||this.pluginFullscreenModal.show||this.residentWorkspace.show||this.restoreExtensionPaletteFocus(i),!0)},closePluginFullscreenModal(){if(this.pluginFullscreenModal.onClose)try{this.pluginFullscreenModal.onClose()}catch(e){console.error("[Plugin] Modal onClose error:",e)}this.pluginFullscreenModal.show=!1,this.pluginFullscreenModal.content="",this.pluginFullscreenModal.onClose=null,this.pluginFullscreenModal.pluginId=null;const e=this._pluginFullscreenReturnFocus;this._pluginFullscreenReturnFocus=null,this.restoreExtensionPaletteFocus(e)},pluginHookRegistry:{},initPluginSystem(){this._currentLoadingPlugin=null;const e=this;class t extends Error{constructor(e,t,s){super(e),this.name="ModuleSdkError",this.code=t||"module_request_failed",this.status=s||0}}const s=async(e,s={})=>{const i=await fetch(e,{credentials:"same-origin",...s});let n=null;try{n=await i.json()}catch(e){n=null}if(!i.ok)throw new t(n?.detail||"Module request failed",n?.code||"module_request_failed",i.status);return n},i=async(e,s,i)=>{let n=null;try{n=await e.json()}catch(e){n=null}return new t(n?.detail||s,n?.code||i,e.status)},n=e=>`/api/module-tasks/${encodeURIComponent(e)}`,a=(e,s,i)=>{if("string"!=typeof e||!e||"string"!=typeof s||!s)throw new t("taskId and resourceRef are required","invalid_sdk_argument",0);return`${n(e)}/resources/${encodeURIComponent(s)}?disposition=${encodeURIComponent(i)}`},o=async(e,s)=>{try{return((e,s)=>{const i=e.headers.get("content-disposition")||"",n=(i.split(";",1)[0]||"").trim().toLowerCase(),a=i.match(/filename\*=UTF-8''([^;]+)/i),o=i.match(/filename="([^"]+)"/i);let r=o?.[1];if(a)try{r=decodeURIComponent(a[1])}catch(e){r=null}const l=(e.headers.get("content-type")||"").split(";",1)[0].trim(),d=e.headers.get("content-length"),u=null===d?NaN:Number(d);if(!r||!l||!Number.isSafeInteger(u)||u<0||!["inline","attachment"].includes(n))throw new t("Task resource metadata is invalid",s,502);return{filename:r,mime:l,size:u,disposition:n}})(e,s)}finally{e.body&&await e.body.cancel()}},r=e=>{let s=null,i="message";const n=[];for(const t of e.split(/\r?\n/)){if(!t||t.startsWith(":"))continue;const e=t.indexOf(":"),a=e<0?t:t.slice(0,e);let o=e<0?"":t.slice(e+1);o.startsWith(" ")&&(o=o.slice(1)),"id"===a?s=o:"event"===a?i=o:"data"===a&&n.push(o)}if(!n.length)return null;const a=Number(s);if(!Number.isInteger(a)||a<1)throw new t("Module event stream returned an invalid cursor","invalid_event_cursor",502);return{id:a,event:i,data:JSON.parse(n.join("\n"))}},l=(i,a=null)=>{if("string"!=typeof i||!i)throw new t("taskId is required","invalid_sdk_argument",0);e.moduleTaskSubscriptions[i]?.abort();const o=new AbortController;e.moduleTaskSubscriptions[i]=o;const l="function"==typeof a?{onEvent:a}:a||{};return(async()=>{let a=0,d=0;for(;!o.signal.aborted;)try{const u=await fetch(`${n(i)}/events`,{headers:{"Last-Event-ID":String(a)},credentials:"same-origin",signal:o.signal});if(!u.ok||!u.body){let e=null;try{e=await u.json()}catch(e){}throw new t(e?.detail||"Module event stream failed",e?.code||"module_event_stream_failed",u.status)}const c=u.body.pipeThrough(new TextDecoderStream).getReader();let h="";for(;!o.signal.aborted;){const{value:t,done:u}=await c.read();if(u)break;h+=t;const p=h.split(/\r?\n\r?\n/);h=p.pop()||"";for(const t of p){const u=r(t);if(u&&!(u.id<=a)){if(d=0,a=u.id,e.applyModuleTaskEvent(i,u),"artifact.added"===u.event||"task.terminal"===u.event){const t=await s(n(i));e.upsertModuleTask(t),"task.terminal"===u.event&&t.chat_id&&e.currentChatId===t.chat_id&&(await e.loadMessages(t.chat_id),await e.loadChats())}if(l.onEvent?.(u),"task.terminal"===u.event||"task.status"===u.event&&MODULE_TERMINAL_STATES.has(u.data?.state)){l.onClose?.(),o.abort();break}}}}if(!o.signal.aborted)throw new t("Module event stream ended before a terminal event","module_event_stream_incomplete",0)}catch(t){if(o.signal.aborted)break;if(d+=1,l.onError?.(t),d>=5){const s=e.moduleTasks[i];s&&(s.error={code:t?.code||"module_event_stream_failed"});break}await new Promise(e=>setTimeout(e,Math.min(500*2**(d-1),8e3)))}e.moduleTaskSubscriptions[i]===o&&delete e.moduleTaskSubscriptions[i]})(),()=>o.abort()},d=Object.freeze({version:"1.6.0",getFeatureStatus:e=>s(`/api/module-features/${encodeURIComponent(e)}`),uploadTaskResource:async e=>{if(!(e instanceof Blob&&"string"==typeof e.name&&e.name))throw new t("file is required","invalid_sdk_argument",0);const s=new FormData;s.append("file",e,e.name);const i=await fetch("/api/module-task-resources",{method:"POST",credentials:"same-origin",body:s});let n=null;try{n=await i.json()}catch(e){}if(!i.ok)throw new t(n?.detail||"Task resource upload failed",n?.code||"task_resource_upload_failed",i.status);if(!(e=>{if(!e||"object"!=typeof e||Array.isArray(e))return!1;const t=["resource_id","filename","media_type","size","sha256","expires_at"],s=Object.keys(e).sort();return!(s.length!==t.length||!t.sort().every((e,t)=>s[t]===e))&&"string"==typeof e.resource_id&&e.resource_id.length>0&&"string"==typeof e.filename&&e.filename.length>0&&"string"==typeof e.media_type&&e.media_type.length>0&&Number.isSafeInteger(e.size)&&e.size>=0&&"string"==typeof e.sha256&&/^[0-9a-f]{64}$/.test(e.sha256)&&"string"==typeof e.expires_at&&/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(e.expires_at)&&!Number.isNaN(Date.parse(e.expires_at))})(n))throw new t("Task resource upload response is invalid","task_resource_upload_response_invalid",502);return n},startTask:async(i,n={})=>{if(!i||"string"!=typeof i.module_id||"string"!=typeof i.action_id||!i.input||"object"!=typeof i.input||Array.isArray(i.input))throw new t("module_id, action_id, and object input are required","invalid_sdk_argument",0);if(!n||"object"!=typeof n||Array.isArray(n)||Object.keys(n).some(e=>"presentation"!==e)||!["task_center","embedded","conversation"].includes(n.presentation||"task_center"))throw new t("presentation must be task_center, embedded, or conversation","invalid_sdk_argument",0);const a=n.presentation||"task_center";if(!("conversation"!==a||"string"==typeof i.chat_id&&i.chat_id&&"string"==typeof i.user_message&&i.user_message.trim()))throw new t("conversation presentation requires chat_id and user_message","invalid_sdk_argument",0);const o={module_id:i.module_id,action_id:i.action_id,input:i.input};for(const e of["chat_id","user_message","resource_ids","active_skill_ids"])void 0!==i[e]&&(o[e]=i[e]);const r=i.idempotency_key||(()=>{if("function"==typeof crypto.randomUUID)return crypto.randomUUID();const e=crypto.getRandomValues(new Uint8Array(16));e[6]=15&e[6]|64,e[8]=63&e[8]|128;const t=Array.from(e,e=>e.toString(16).padStart(2,"0")).join("");return[t.slice(0,8),t.slice(8,12),t.slice(12,16),t.slice(16,20),t.slice(20)].join("-")})(),d=await s("/api/module-tasks",{method:"POST",headers:{"Content-Type":"application/json","Idempotency-Key":r},body:JSON.stringify(o)});return"task_center"===a&&e.rememberModuleTask(d.task_id),e.upsertModuleTask(d,{select:"task_center"===a,presentation:a}),"conversation"===a&&e.attachConversationTask(d,i.user_message),"task_center"!==a&&"conversation"!==a||l(d.task_id),d},listTasks:async(e={})=>{if(!e||"object"!=typeof e||Array.isArray(e)||Object.keys(e).some(e=>!["module_id","action_id","state","chat_id","limit"].includes(e)))throw new t("Task filters are invalid","invalid_sdk_argument",0);const i=new URLSearchParams;for(const s of["module_id","action_id","state","chat_id"])if(void 0!==e[s]){if("string"!=typeof e[s]||!e[s])throw new t("Task filters are invalid","invalid_sdk_argument",0);i.set(s,e[s])}if(void 0!==e.limit){if(!Number.isInteger(e.limit)||e.limit<1||e.limit>100)throw new t("Task limit is invalid","invalid_sdk_argument",0);i.set("limit",String(e.limit))}const n=i.size?`?${i}`:"";return(await s(`/api/module-tasks${n}`)).tasks},getTask:e=>s(n(e)),subscribe:l,cancelTask:e=>s(`${n(e)}/cancel`,{method:"POST",headers:{"Content-Type":"application/json"},body:"{}"}),respondApproval:(e,i,a)=>{if(!["approve","deny"].includes(a))throw new t("decision must be approve or deny","invalid_sdk_argument",0);return s(`${n(e)}/approvals/${encodeURIComponent(i)}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({decision:a})})},downloadArtifact:async(e,s)=>{const i=await fetch(`${n(e)}/artifacts/${encodeURIComponent(s)}`,{credentials:"same-origin"});if(!i.ok){let e=null;try{e=await i.json()}catch(e){}throw new t(e?.detail||"Artifact download failed",e?.code||"artifact_download_failed",i.status)}const a=i.headers.get("content-disposition")||"",o=a.match(/filename\*=UTF-8''([^;]+)/i),r=a.match(/filename="([^"]+)"/i),l=o?decodeURIComponent(o[1]):r?.[1]||"artifact",d=URL.createObjectURL(await i.blob()),u=document.createElement("a");return u.href=d,u.download=l,u.click(),setTimeout(()=>URL.revokeObjectURL(d),0),{filename:l}},getTaskResourceView:async(e,s)=>{const n=a(e,s,"inline"),r=await fetch(n,{method:"GET",credentials:"same-origin"});if(!r.ok)throw await i(r,"Task resource view failed","task_resource_view_failed");const l=await o(r,"task_resource_view_failed");if("inline"!==l.disposition)throw new t("Task resource is not available for browser preview","task_resource_preview_unavailable",415);return{url:n,...l}},downloadTaskResource:async(e,t)=>{const s=a(e,t,"attachment"),n=await fetch(s,{method:"GET",credentials:"same-origin"});if(!n.ok)throw await i(n,"Task resource download failed","task_resource_download_failed");const r=await o(n,"task_resource_download_failed"),l=document.createElement("a");return l.href=s,l.download=r.filename,l.click(),{filename:r.filename}}});window.ChatRaw=window.ChatRaw||{},window.ChatRaw.modules=d,window.ChatRawPlugin={hooks:{register:(e,t)=>this.registerHook(e,t),available:()=>Object.keys(this.pluginHooks)},require:e=>this.loadedPluginDeps[e],proxy:{request:e=>this.proxyRequest(e),upload:async(e,t,s,i={},n="file")=>{const a=new FormData;a.append("file",e),a.append("service_id",t),a.append("url",s),a.append("extra_fields",JSON.stringify(i)),a.append("file_field_name",n);try{const e=await fetch("/api/proxy/upload",{method:"POST",body:a});return await e.json()}catch(e){return{success:!1,error:e.message}}}},settings:e=>this.getPluginSettings(e||this._currentLoadingPlugin),input:{getValue:()=>e.inputMessage||"",setValue:t=>(e.setComposerValue(String(t||"")),!0),insertText:t=>e.insertComposerText(String(t||"")),replaceRange:(t,s,i)=>e.replaceComposerRange(t,s,String(i||"")),getSelection:()=>e.getComposerSelection(),focus:()=>(e.focusComposer(),!0),registerCompletionProvider:(t,s=null)=>e.registerCompletionProvider(t,s),unregisterCompletionProvider:(t,s=null)=>e.unregisterCompletionProvider(t,s),invalidateSkillCatalog:()=>e.invalidateSkillCatalog()},utils:{loadScript:t=>e.loadScript(t),loadCSS:t=>e.loadCSS(t),loadHighlightJS:()=>"function"==typeof loadHighlightJS?loadHighlightJS():Promise.resolve(),showToast:(t,s="")=>e.showToast(t,s),getLanguage:()=>e.lang,t:t=>e.t(t),showProgress:(t,s="")=>{e.uploadProgress={show:!0,filename:s,progress:t,status:"processing",current:t,total:100}},hideProgress:()=>{e.uploadProgress={show:!1,filename:"",progress:0,status:"",current:0,total:0}},getCurrentChatId:()=>e.currentChatId,getMessages:()=>[...e.messages],addMessage:(t,s)=>{e.messages.push({role:t,content:s}),e.$nextTick(()=>e.scrollToBottom())}},storage:{get:(t,s=null,i=null)=>{const n=i||e._currentLoadingPlugin;if(!n)return console.warn("[Plugin Storage] No plugin context - pass pluginId as third argument"),s;try{const e=`chatraw_plugin_${n}`,i=JSON.parse(localStorage.getItem(e)||"{}");return t in i?i[t]:s}catch(e){return s}},set:(t,s,i=null)=>{const n=i||e._currentLoadingPlugin;if(!n)return console.warn("[Plugin Storage] No plugin context - pass pluginId as third argument"),!1;try{const e=`chatraw_plugin_${n}`,i=JSON.parse(localStorage.getItem(e)||"{}");i[t]=s;const a=JSON.stringify(i);return a.length>1048576?(console.warn("[Plugin Storage] Storage limit exceeded (1MB)"),!1):(localStorage.setItem(e,a),!0)}catch(e){return console.error("[Plugin Storage] Error:",e),!1}},remove:(t,s=null)=>{const i=s||e._currentLoadingPlugin;if(!i)return!1;try{const e=`chatraw_plugin_${i}`,s=JSON.parse(localStorage.getItem(e)||"{}");return delete s[t],localStorage.setItem(e,JSON.stringify(s)),!0}catch(e){return!1}},clear:(t=null)=>{const s=t||e._currentLoadingPlugin;if(!s)return!1;try{return localStorage.removeItem(`chatraw_plugin_${s}`),!0}catch(e){return!1}},getAll:(t=null)=>{const s=t||e._currentLoadingPlugin;if(!s)return{};try{const e=`chatraw_plugin_${s}`;return JSON.parse(localStorage.getItem(e)||"{}")}catch(e){return{}}}},ui:{registerToolbarButton:(t,s=null)=>{const i=s||e._currentLoadingPlugin||t.pluginId;if(!i)return console.warn("[Plugin UI] Cannot register button: unknown plugin"),!1;if(!t.icon||!/^ri-[a-z0-9-]+$/.test(t.icon))return console.warn(`[Plugin ${i}] Invalid icon format. Must be RemixIcon (ri-xxx-xxx)`),!1;if(!t.id||!t.onClick)return console.warn(`[Plugin ${i}] Button requires id and onClick`),!1;const n=`${i}:${t.id}`,a=e.pluginToolbarButtons.findIndex(e=>e.fullId===n),o={fullId:n,pluginId:i,id:t.id,icon:t.icon,label:t.label||{en:t.id},onClick:t.onClick,order:t.order??100,placement:"toolbar",status:t.status||null,disabled:!0===t.disabled,active:!1,loading:!1};return a>=0?e.pluginToolbarButtons[a]=o:e.pluginToolbarButtons.push(o),!0},unregisterToolbarButton:(t,s=null)=>{const i=s||e._currentLoadingPlugin;if(!i)return!1;const n=`${i}:${t}`,a=e.pluginToolbarButtons.findIndex(e=>e.fullId===n);return a>=0&&(e.pluginToolbarButtons.splice(a,1),!0)},setButtonState:(t,s,i=null)=>{const n=i||e._currentLoadingPlugin;if(!n)return!1;const a=`${n}:${t}`,o=e.pluginToolbarButtons.find(e=>e.fullId===a);return!!o&&(void 0!==s.active&&(o.active=s.active),void 0!==s.loading&&(o.loading=s.loading),void 0!==s.disabled&&(o.disabled=!0===s.disabled),void 0!==s.status&&(o.status=s.status),!0)},openFullscreenModal:(t,s=null)=>{const i=s||e._currentLoadingPlugin||t?.pluginId;if("string"==typeof t&&(t={content:t}),!t?.content)return console.warn("[Plugin UI] Cannot open modal: empty content"),!1;const n=e._pluginWorkspaceActivation;return e._pluginFullscreenReturnFocus=n?.returnFocus instanceof HTMLElement&&n.returnFocus.isConnected?n.returnFocus:null,e.showExtensionPalette=!1,e.pluginFullscreenModal={show:!0,content:t.content,closable:!1!==t.closable,onClose:t.onClose||null,pluginId:i},!0},closeFullscreenModal:()=>{e.closePluginFullscreenModal()},registerWorkspacePanel:(t,s)=>e.registerPluginWorkspacePanel(t,s),unregisterWorkspacePanel:(t,s)=>e.unregisterPluginWorkspacePanel(t,s),openWorkspacePanel:(t,s,i)=>e.openPluginWorkspacePanel(t,s,i),closeWorkspacePanel:(t,s)=>e.closePluginWorkspacePanel(t,s)}},this.loadEnabledPlugins(),this.initPluginRuntimeSync()},initPluginRuntimeSync(){if(this._pluginRuntimeSyncInitialized)return;this._pluginRuntimeSyncInitialized=!0;const e=()=>{"visible"===document.visibilityState&&this.syncPluginRuntimes()};"function"==typeof window.addEventListener&&window.addEventListener("focus",e),"undefined"!=typeof document&&"function"==typeof document.addEventListener&&document.addEventListener("visibilitychange",e),"function"==typeof window.BroadcastChannel&&(this._pluginRuntimeChangeChannel=new window.BroadcastChannel("chatraw-plugin-runtime-v1"),this._pluginRuntimeChangeChannel.addEventListener("message",e=>{"plugins-changed"===e.data?.type&&this.syncPluginRuntimes()}))},announcePluginRuntimeChange(e){this._pluginRuntimeChangeChannel?.postMessage({type:"plugins-changed",plugin_id:e})},async refreshFeatureCatalogAfterPluginChange(e){await this.loadFeatureCatalog(),this.announcePluginRuntimeChange(e)},async syncPluginRuntimes(){return this._pluginRuntimeSyncPromise||(this._pluginRuntimeSyncPromise=(async()=>{const e=await fetch("/api/plugins",{credentials:"same-origin",cache:"no-store"});if(!e.ok)return;const t=await e.json(),s=new Map(t.map(e=>[e.id,e]));for(const e of Object.keys(this.pluginRuntimeVersions)){const t=s.get(e);t&&!1!==t.enabled||this.cleanupPluginRuntime(e)}this.installedPlugins=t;for(const e of t){if(!1===e.enabled)continue;this.pluginRuntimeVersions[e.id]!==String(e.version||"0")&&await this.loadPluginJS(e)}this.recomputeFeatureCatalogRuntime()})().catch(e=>{console.error("[Plugin] Failed to synchronize runtimes:",e)}).finally(()=>{this._pluginRuntimeSyncPromise=null})),this._pluginRuntimeSyncPromise},async loadInstalledPlugins(){try{const e=await fetch("/api/plugins");e.ok&&(this.installedPlugins=await e.json())}catch(e){console.error("Failed to load installed plugins:",e)}},async loadEnabledPlugins(){for(const e of this.installedPlugins)e.enabled&&await this.loadPluginJS(e)},async loadPluginJS(e){try{if(this.cleanupPluginRuntime(e.id),e.dependencies)for(const[t,s]of Object.entries(e.dependencies))if(!this.loadedPluginDeps[t])try{if("string"==typeof s&&s.endsWith(".css"))await this.loadCSS(s),this.loadedPluginDeps[t]=!0;else{if(t.startsWith("hljs-"))continue;await this.loadScript(s),"xlsx"===t&&window.XLSX?this.loadedPluginDeps[t]=window.XLSX:"pdfjs"===t&&window.pdfjsLib?this.loadedPluginDeps[t]=window.pdfjsLib:window[t]?this.loadedPluginDeps[t]=window[t]:this.loadedPluginDeps[t]=!0}}catch(s){console.error(`[Plugin] Failed to load dependency ${t} for ${e.id}:`,s)}this._currentLoadingPlugin=e.id;const t=`/api/plugins/${encodeURIComponent(e.id)}/main.js`,s=`${t}?v=${encodeURIComponent(String(e.version||"0"))}`;await this.loadScript(s,{reload:!0,reloadPrefix:t}),this.pluginRuntimeVersions[e.id]=String(e.version||"0"),this._currentLoadingPlugin=null,console.log(`[Plugin] Loaded: ${e.id}`)}catch(t){this._currentLoadingPlugin=null,console.error(`[Plugin] Failed to load ${e.id}:`,t)}},cleanupPluginRuntime(e){delete this.pluginRuntimeVersions[e],this.unregisterPluginHooks(e),this.unregisterPluginCompletions(e),this.pluginToolbarButtons=this.pluginToolbarButtons.filter(t=>t.pluginId!==e),this.pluginFullscreenModal?.pluginId===e&&this.closePluginFullscreenModal(),this.unregisterPluginWorkspacePanels(e)},loadScript:(e,t={})=>new Promise((s,i)=>{const n=document.querySelector(`script[src="${e}"]`);if(n){if(!t.reload)return void s();n.remove()}if(t.reload&&t.reloadPrefix)for(const e of Array.from(document.querySelectorAll("script"))){(e.getAttribute("src")||"").startsWith(t.reloadPrefix)&&e.remove()}const a=document.createElement("script");a.src=e,a.onload=s,a.onerror=()=>i(new Error(`Failed to load script: ${e}`)),document.head.appendChild(a)}),loadCSS:e=>new Promise((t,s)=>{if(document.querySelector(`link[href="${e}"]`))return void t();const i=document.createElement("link");i.rel="stylesheet",i.href=e,i.onload=t,i.onerror=()=>s(new Error(`Failed to load CSS: ${e}`)),document.head.appendChild(i)}),registerHook(e,t){if(!this.pluginHooks[e])return void console.warn(`[Plugin] Unknown hook: ${e}`);const s=this._currentLoadingPlugin,i={...t,_pluginId:s};this.pluginHooks[e].push(i),this.pluginHooks[e].sort((e,t)=>(t.priority||0)-(e.priority||0)),s&&(this.pluginHookRegistry[s]||(this.pluginHookRegistry[s]=[]),this.pluginHookRegistry[s].push({hookName:e,handler:i}))},unregisterPluginHooks(e){const t=this.pluginHookRegistry[e];if(t){for(const{hookName:e,handler:s}of t){const t=this.pluginHooks[e];if(t){const e=t.indexOf(s);-1!==e&&t.splice(e,1)}}delete this.pluginHookRegistry[e]}},async callHook(e,...t){const s=this.pluginHooks[e]||[];for(const i of s){if(i._pluginId){const e=this.installedPlugins.find(e=>e.id===i._pluginId);if(e&&!e.enabled)continue}try{const e=await i.handler(...t);if(e?.success)return e}catch(t){console.error(`[Hook ${e}] Error:`,t)}}return null},getPluginSettings(e){const t=this.installedPlugins.find(t=>t.id===e);if(!t)return{};const s={};if(t.settings)for(const e of t.settings)s[e.id]=e.default;return{...s,...t.settings_values||{}}},async proxyRequest(e){try{const t={service_id:e.serviceId||e.service_id,url:e.url,method:e.method||"POST",headers:e.headers||{},body:e.body||{}},s=await fetch("/api/proxy/request",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});return await s.json()}catch(e){return{success:!1,error:e.message}}},async loadPluginMarket(){if(this.pluginMarketLoading)return;this.pluginMarketLoading=!0,this.pluginMarketError=!1;let e=null;try{const t=new AbortController;e=setTimeout(()=>t.abort(),5e3);let s=await fetch("/api/plugins/market",{signal:t.signal});if(s.ok||(s=await fetch("https://raw.githubusercontent.com/massif-01/ChatRaw/main/Plugins/Plugin_market/index.json",{signal:t.signal})),clearTimeout(e),e=null,!s.ok)throw new Error("Failed to fetch");const i=await s.json();this.marketPlugins=i.plugins||[]}catch(e){"AbortError"===e.name?console.warn("Plugin market request timed out"):console.error("Failed to load plugin market:",e),this.pluginMarketError=!0}finally{e&&clearTimeout(e),this.pluginMarketLoading=!1}},getMarketPluginIconUrl(e){if(e?.id&&this.isPluginInstalled(e.id))return`/api/plugins/${encodeURIComponent(e.id)}/icon`;return`/api/plugins/market/${encodeURIComponent(e?.folder||e?.id||"")}/icon`},handleMarketPluginIconError(e,t){const s=e?.target;if(s)return!s.dataset.remoteFallbackTried&&t?.folder?(s.dataset.remoteFallbackTried="1",void(s.src=`https://raw.githubusercontent.com/massif-01/ChatRaw/main/Plugins/Plugin_market/${t.folder}/icon.png`)):void(s.src="data:image/svg+xml,")},isPluginInstalled(e){return this.installedPlugins.some(t=>t.id===e)},get filteredMarketPlugins(){const e=(this.pluginMarketSearch||"").trim().toLowerCase();return e?this.marketPlugins.filter(t=>{const s=(t.name?.en||"").toLowerCase(),i=t.name?.zh||"",n=(t.description?.en||"").toLowerCase(),a=t.description?.zh||"";return s.includes(e)||i.includes(e)||n.includes(e)||a.includes(e)}):this.marketPlugins},async installPlugin(e){this.pluginInstalling=e.id;try{const t=`https://raw.githubusercontent.com/massif-01/ChatRaw/main/Plugins/Plugin_market/${e.folder}`,s=await fetch("/api/plugins/install",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({source_url:t})});if((await s.json()).success){this.showToast(this.t("installSuccess"),"success"),await this.loadInstalledPlugins();const t=this.installedPlugins.find(t=>t.id===e.id);t&&await this.loadPluginJS(t),await this.refreshFeatureCatalogAfterPluginChange(e.id)}else this.showToast(this.t("installFailed"),"error")}catch(e){this.showToast(this.t("installFailed"),"error")}finally{this.pluginInstalling=null}},async handlePluginUpload(e){const t=e.target?.files?.[0];t&&await this.uploadPluginFile(t,e.target)},async handlePluginDrop(e){const t=e.dataTransfer?.files?.[0];t&&(e.dataTransfer.clearData(),await this.uploadPluginFile(t))},async uploadPluginFile(e,t){if(e.name.endsWith(".zip")){this.pluginUploading=!0;try{const t=new FormData;t.append("file",e);const s=await fetch("/api/plugins/upload",{method:"POST",body:t}),i=await s.json();if(i.success){this.showToast(this.t("installSuccess"),"success"),await this.loadInstalledPlugins();const e=this.installedPlugins.find(e=>e.id===i.plugin_id);e&&await this.loadPluginJS(e),await this.refreshFeatureCatalogAfterPluginChange(i.plugin_id)}else this.showToast(this.t("installFailed"),"error")}catch(e){this.showToast(this.t("installFailed"),"error")}finally{this.pluginUploading=!1,t&&(t.value="")}}else this.showToast(this.t("onlyZipSupported"),"error")},async togglePlugin(e){const t=!e.enabled;try{(await fetch(`/api/plugins/${encodeURIComponent(e.id)}/toggle`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:t})})).ok&&(e.enabled=t,t?(await this.loadPluginJS(e),this.showToast(this.t("settingsSaved"),"success")):(this.cleanupPluginRuntime(e.id),this.showToast(this.t("pluginDisabled"),"success")),await this.refreshFeatureCatalogAfterPluginChange(e.id))}catch(e){this.showToast(this.t("saveFailed"),"error")}},async openPluginSettings(e){this.currentPluginSettings=e;const t={};if(e.settings)for(const s of e.settings)t[s.id]=s.default;if(Object.assign(t,e.settings_values||{}),this.pluginSettingsValues=t,this.pluginApiKeys={},this.pluginApiKeyConfigured={},this.pluginApiKeyActions={},e.proxy&&e.proxy.length>0)try{const t=await fetch("/api/plugins/api-keys");if(t.ok){const s=(await t.json()).api_keys||{};for(const t of e.proxy)this.pluginApiKeys[t.id]="",this.pluginApiKeyConfigured[t.id]=Boolean(s[t.id]?.configured),this.pluginApiKeyActions[t.id]="preserve"}}catch(e){console.error("Failed to load API keys:",e)}this.showPluginSettings=!0,e.customSettings&&setTimeout(()=>{window.dispatchEvent(new CustomEvent("plugin-settings-open",{detail:{pluginId:e.id,customSettings:!0}}))},50)},async savePluginSettings(){if(this.currentPluginSettings)try{if(!(await fetch(`/api/plugins/${encodeURIComponent(this.currentPluginSettings.id)}/settings`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({settings:this.pluginSettingsValues})})).ok)throw new Error("Failed to save settings");for(const[e,t]of Object.entries(this.pluginApiKeys)){const s="clear"===this.pluginApiKeyActions[e]?"clear":t?"replace":"preserve";"preserve"!==s&&await fetch("/api/plugins/api-key",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({service_id:e,api_key:t,action:s})})}const e=this.installedPlugins.find(e=>e.id===this.currentPluginSettings.id);e&&(e.settings_values={...this.pluginSettingsValues}),this.showPluginSettings=!1,this.showToast(this.t("settingsSaved"),"success")}catch(e){this.showToast(this.t("saveFailed"),"error")}},async uninstallPlugin(e){if(e&&confirm(this.t("confirmUninstall")))try{const t=await fetch(`/api/plugins/${encodeURIComponent(e.id)}`,{method:"DELETE"});if(t.ok)this.cleanupPluginRuntime(e.id),this.showPluginSettings=!1,this.currentPluginSettings=null,this.showToast(this.t("uninstallSuccess"),"success"),await this.loadInstalledPlugins(),await this.refreshFeatureCatalogAfterPluginChange(e.id);else{await t.json().catch(()=>({}));this.showToast(this.t("uninstallFailed"),"error")}}catch(e){this.showToast(this.t("uninstallFailed"),"error")}}}} \ No newline at end of file +let hljsLoaded=!1,hljsLoading=!1;async function loadHighlightJS(){if(!hljsLoaded&&!hljsLoading){hljsLoading=!0;try{const e=document.createElement("link");e.rel="stylesheet",e.href="/vendor/highlight/github-dark.min.css",document.head.appendChild(e),await loadScript("/vendor/highlight/highlight.min.js"),await Promise.all([loadScript("/vendor/highlight/languages/python.min.js"),loadScript("/vendor/highlight/languages/javascript.min.js"),loadScript("/vendor/highlight/languages/bash.min.js"),loadScript("/vendor/highlight/languages/json.min.js")]),hljsLoaded=!0,document.querySelectorAll("pre code:not(.hljs)").forEach(e=>{hljs.highlightElement(e)})}catch(e){console.warn("Failed to load highlight.js:",e)}hljsLoading=!1}}function loadScript(e){return new Promise((t,s)=>{const i=document.createElement("script");i.src=e,i.onload=t,i.onerror=s,document.head.appendChild(i)})}const i18n={en:{appDocumentTitle:"{brand} Workspace",newChat:"New Chat",clearAllChats:"Clear All",confirmClearAll:"Are you sure you want to delete all chats? This cannot be undone.",allChatsCleared:"All chats cleared",settings:"Settings",delete:"Delete",expand:"Expand",collapse:"Collapse",defaultSubtitle:"Minimalist AI Assistant, Ready to Use",fastResponse:"Fast Response",multiModel:"Multi-Model",knowledgeBase:"Knowledge Base",kbEnabled:"KB Enabled",thinkingMode:"Thinking Mode",thinkingEnabled:"Thinking Enabled",thinkingProcess:"Thinking Process",uploadImage:"Upload Image",uploadDocument:"Upload Document",image:"Image",imageSelected:"Image Selected",documentAttached:"Document Attached",parseUrl:"Parse URL",agentExtensions:"Agent extensions",openAgentExtensions:"Open Agent extensions",closeAgentExtensions:"Close Agent extensions",enterUrl:"Enter URL",parsing:"Parsing...",urlAttached:"URL Attached",parseFailed:"Parse failed",urlPlaceholder:"https://example.com/article",confirm:"OK",ragReferences:"References",relevance:"Relevance",copyMessage:"Copy",copied:"Copied!",inputPlaceholder:"Type a message... (Enter to send, Shift+Enter for new line)",modelConfig:"Models",chatSettings:"Chat",chatSettingsDesc:"Adjust how the AI responds to your inputs.",ragSettings:"RAG",ragSettingsDesc:"Configure retrieval augmented generation parameters.",uiSettings:"Interface",uiSettingsDesc:"Customize the interface appearance.",kbDesc:"Upload documents for RAG context.",modelConfigDesc:"Manage your AI model connections and parameters.",modelType:"Type",chat:"Chat",embedding:"Embedding",reranker:"Reranker",optional:"Optional",contextLength:"Context Length",maxOutput:"Max Output",capabilities:"Capabilities",vision:"Vision",reasoning:"Reasoning",tools:"Tools",saveAndVerify:"Save & Verify",verifying:"Verifying...",verifySuccess:"Connected",verifyFailed:"Connection failed",temperatureHint:"Lower values make output more deterministic, higher values more creative",topPHint:"Nucleus sampling parameter, controls output diversity",streamOutput:"Stream Output",streamHint:"Display AI response in real-time",systemPrompt:"System Prompt",systemPromptHint:"Set instructions for the AI assistant",systemPromptPlaceholder:"You are a helpful assistant...",chunkSize:"Chunk Size",chunkSizeHint:"Number of characters per document chunk",chunkOverlap:"Chunk Overlap",chunkOverlapHint:"Overlap characters between adjacent chunks",topKHint:"Number of relevant documents to retrieve",scoreThreshold:"Score Threshold",scoreThresholdHint:"Documents below this threshold will be filtered",language:"Language",theme:"Theme",light:"Light",uploadLogo:"Upload",logoText:"Logo Text",subtitle:"Subtitle",avatars:"Avatars",userAvatar:"User",aiAvatar:"AI",uploadDocument:"Upload Document",noDocuments:"No documents yet. Upload one above.",cancel:"Cancel",save:"Save",uploadSuccess:"uploaded successfully",uploadFailed:"Upload failed",deleteFailed:"Delete failed",saveFailed:"Save failed",settingsSaved:"Settings saved",sendFailed:"Send failed",stopGeneration:"Stop",createChatFailed:"Failed to create chat",documentDeleted:"Document deleted",logoName:"Logo & Name",uploadUserAvatar:"Upload User Avatar",uploadAIAvatar:"Upload AI Avatar",active:"Active",error:"Error",mitLicense:"MIT License",plugins:"Plugins",pluginMarket:"Plugin Market",pluginMarketDesc:"Browse and install plugins from the official market.",installedPlugins:"Installed",installedPluginsDesc:"Manage your installed plugins.",installLocalPlugin:"Install Local",installLocalPluginDesc:"Upload a plugin zip file to install.",refreshingMarket:"Refreshing plugin market...",networkError:"Please check network connection",retry:"Retry",install:"Install",installed:"Installed",installing:"Installing...",installSuccess:"Plugin installed successfully",installFailed:"Install failed",uninstall:"Uninstall",uninstallSuccess:"Plugin uninstalled",uninstallFailed:"Uninstall failed",confirmUninstall:"Are you sure you want to uninstall this plugin?",pluginDisabled:"Plugin disabled",selectPluginZip:"Click or drag to upload plugin (.zip)",pluginDevGuide:"Plugin Development Guide",noPluginsAvailable:"No plugins available",searchPlugins:"Search plugins",noPluginsInstalled:"No plugins installed yet",noPluginSettings:"This plugin has no settings.",apiKeySettings:"API Key Settings",enterApiKey:"Enter API Key",apiKeySet:"API Key is already set. Clear and enter new key to change.",onlyZipSupported:"Only .zip files are supported",hermesRun:"Hermes Run",hermesRunStatusStarted:"Started",hermesRunStatusRunning:"Running",hermesRunStatusPendingApproval:"Pending approval",hermesRunStatusCompleted:"Completed",hermesRunStatusFailed:"Failed",hermesRunStatusCancelled:"Cancelled",hermesRunEventRunStarted:"Run started",hermesRunEventToolStarted:"Tool started",hermesRunEventToolCompleted:"Tool completed",hermesRunEventReasoning:"Reasoning available",hermesRunEventApprovalRequest:"Approval required",hermesRunEventApprovalResponded:"Approval responded",hermesRunEventRunCompleted:"Run completed",hermesRunEventRunFailed:"Run failed",hermesRunEventRunCancelled:"Run cancelled",hermesRunTool:"Tool",hermesRunPreview:"Preview",hermesRunCommand:"Command",hermesRunDescription:"Description",hermesRunPatterns:"Patterns",hermesRunDuration:"Duration",hermesRunChoice:"Choice",hermesApprovalOnce:"Allow once",hermesApprovalSession:"Allow for session",hermesApprovalDeny:"Deny",hermesApprovalSubmitting:"Submitting...",hermesApprovalError:"Approval failed",close:"Close",online:"Online",signOut:"Sign out",featureCategories:"Feature categories",featureNavigation:"Feature navigation",loadingFeatureCatalog:"Loading feature catalog…",noFeatures:"No features available",openFeature:"Open",featureUnavailable:"Unavailable",featureAvailable:"Available",featureLoading:"Loading",moduleUnavailable:"Module unavailable",pluginMissing:"Plugin missing",pluginDisabledState:"Plugin disabled",pluginVersionMismatch:"Plugin version mismatch",panelNotRegistered:"Panel not registered",mainPlacementRequired:"Main view required",featurePanelNotReady:"The feature panel is not ready.",backHome:"Back to home",currentConversation:"Current conversation",agentSubtitle:"Your intelligent work assistant",conversationHistory:"Conversation history",newConversation:"New conversation",closeAgent:"Close Agent",openAgent:"Open Hermes Agent",expandAgent:"Expand conversation window",restoreAgent:"Restore conversation window",renameConversation:"Rename conversation",deleteConversation:"Delete conversation",noConversationHistory:"No conversation history yet"},zh:{appDocumentTitle:"{brand} 工作空间",newChat:"新对话",clearAllChats:"清空所有",confirmClearAll:"确定要删除所有对话吗?此操作无法撤销。",allChatsCleared:"已清空所有对话",settings:"设置",delete:"删除",expand:"展开",collapse:"收起",defaultSubtitle:"极简AI助手,开箱即用",fastResponse:"极速响应",multiModel:"多模型支持",knowledgeBase:"知识库",kbEnabled:"知识库已开启",thinkingMode:"思考模式",thinkingEnabled:"思考模式已开启",thinkingProcess:"思考过程",uploadImage:"上传图片",uploadDocument:"上传文档",image:"图片",imageSelected:"已选择图片",documentAttached:"已附加文档",parseUrl:"解析网页",agentExtensions:"Agent 扩展",openAgentExtensions:"打开 Agent 扩展",closeAgentExtensions:"关闭 Agent 扩展",enterUrl:"输入网址",parsing:"解析中...",urlAttached:"已附加网页",parseFailed:"解析失败",urlPlaceholder:"https://example.com/article",confirm:"确定",ragReferences:"引用来源",relevance:"相关度",copyMessage:"复制",copied:"已复制",inputPlaceholder:"输入消息... (Enter 发送, Shift+Enter 换行)",modelConfig:"模型配置",modelConfigDesc:"管理您的AI模型连接和参数",chatSettings:"聊天设置",chatSettingsDesc:"调整AI对您输入的响应方式",ragSettings:"RAG设置",ragSettingsDesc:"配置检索增强生成 (RAG) 参数",uiSettings:"界面设置",uiSettingsDesc:"自定义界面外观",kbDesc:"上传用于RAG上下文的文档",modelType:"类型",chat:"聊天",embedding:"嵌入",reranker:"重排",optional:"可选",contextLength:"上下文长度",maxOutput:"最大输出",capabilities:"模型能力",vision:"视觉",reasoning:"推理",tools:"工具",saveAndVerify:"保存并验证",verifying:"验证中...",verifySuccess:"连接成功",verifyFailed:"连接失败",temperatureHint:"较低的值使输出更确定,较高的值更有创意",topPHint:"核采样参数,控制输出的多样性",streamOutput:"流式输出",streamHint:"实时显示AI的回复内容",systemPrompt:"系统提示词",systemPromptHint:"设置AI助手的行为指令",systemPromptPlaceholder:"你是一个有帮助的助手...",chunkSize:"文档块大小",chunkSizeHint:"文档分块的字符数",chunkOverlap:"块重叠",chunkOverlapHint:"相邻块之间的重叠字符数",topKHint:"检索相关文档的数量",scoreThreshold:"相似度阈值",scoreThresholdHint:"低于此阈值的文档将被过滤",language:"语言",theme:"主题",light:"浅色",uploadLogo:"上传",logoText:"Logo文字",subtitle:"副标题",avatars:"头像",userAvatar:"用户",aiAvatar:"AI",uploadDocument:"上传文档",noDocuments:"暂无文档,请在上方上传",cancel:"取消",save:"保存",uploadSuccess:"上传成功",uploadFailed:"上传失败",deleteFailed:"删除失败",saveFailed:"保存失败",settingsSaved:"设置已保存",sendFailed:"发送失败",stopGeneration:"停止",createChatFailed:"创建对话失败",documentDeleted:"文档已删除",logoName:"Logo & 名称",uploadUserAvatar:"上传用户头像",uploadAIAvatar:"上传 AI 头像",active:"活跃",error:"错误",mitLicense:"MIT 协议",plugins:"插件",pluginMarket:"插件市场",pluginMarketDesc:"浏览并安装官方市场的插件",installedPlugins:"已安装",installedPluginsDesc:"管理已安装的插件",installLocalPlugin:"本地安装",installLocalPluginDesc:"上传插件 zip 文件进行安装",refreshingMarket:"正在刷新插件市场...",networkError:"请检查网络连接",retry:"重试",install:"安装",installed:"已安装",installing:"安装中...",installSuccess:"插件安装成功",installFailed:"安装失败",uninstall:"卸载",uninstallSuccess:"插件已卸载",uninstallFailed:"卸载失败",confirmUninstall:"确定要卸载此插件吗?",pluginDisabled:"插件已禁用",selectPluginZip:"点击或拖拽上传插件 (.zip)",pluginDevGuide:"插件开发指南",noPluginsAvailable:"暂无可用插件",searchPlugins:"搜索插件",noPluginsInstalled:"暂未安装任何插件",noPluginSettings:"此插件暂无可配置项",apiKeySettings:"API 密钥设置",enterApiKey:"输入 API Key",apiKeySet:"API Key 已设置。清空后输入新密钥可更改。",onlyZipSupported:"仅支持 .zip 文件",hermesRun:"Hermes Run",hermesRunStatusStarted:"已开始",hermesRunStatusRunning:"运行中",hermesRunStatusPendingApproval:"等待审批",hermesRunStatusCompleted:"已完成",hermesRunStatusFailed:"失败",hermesRunStatusCancelled:"已取消",hermesRunEventRunStarted:"Run 已开始",hermesRunEventToolStarted:"工具已启动",hermesRunEventToolCompleted:"工具已完成",hermesRunEventReasoning:"推理可用",hermesRunEventApprovalRequest:"需要审批",hermesRunEventApprovalResponded:"审批已响应",hermesRunEventRunCompleted:"Run 已完成",hermesRunEventRunFailed:"Run 失败",hermesRunEventRunCancelled:"Run 已取消",hermesRunTool:"工具",hermesRunPreview:"预览",hermesRunCommand:"命令",hermesRunDescription:"说明",hermesRunPatterns:"规则",hermesRunDuration:"耗时",hermesRunChoice:"选择",hermesApprovalOnce:"允许一次",hermesApprovalSession:"本会话允许",hermesApprovalDeny:"拒绝",hermesApprovalSubmitting:"提交中...",hermesApprovalError:"审批失败",close:"关闭",online:"在线",signOut:"退出登录",featureCategories:"功能分类",featureNavigation:"功能导航",loadingFeatureCatalog:"正在读取功能目录…",noFeatures:"暂无功能",openFeature:"打开",featureUnavailable:"暂不可用",featureAvailable:"可用",featureLoading:"加载中",moduleUnavailable:"模块不可用",pluginMissing:"插件未安装",pluginDisabledState:"插件已停用",pluginVersionMismatch:"插件版本不兼容",panelNotRegistered:"功能面板未注册",mainPlacementRequired:"需要主内容区布局",featurePanelNotReady:"功能界面尚未就绪。",backHome:"返回首页",currentConversation:"当前对话",agentSubtitle:"你的智能工作助手",conversationHistory:"历史会话",newConversation:"新建会话",closeAgent:"关闭 Agent",openAgent:"打开 Hermes Agent",expandAgent:"扩展对话窗口",restoreAgent:"还原对话窗口",renameConversation:"重命名会话",deleteConversation:"删除会话",noConversationHistory:"还没有历史会话"}};Object.assign(i18n.en,{send:"Send",rename:"Rename",renameSharedChat:"Rename this conversation",renameFailed:"Rename failed",deleteSharedChat:"Delete this private conversation? This cannot be undone.",deleteSharedDocument:"Delete this shared document? Other users will lose access to it.",requestFailed:"Request failed",unknownError:"Unknown error",authenticationRequired:"Authentication required",settingsNavigation:"Settings navigation",pluginNavigation:"Plugin navigation",preview:"Preview",logoAlt:"Logo",userAvatarAlt:"User avatar",assistantAvatarAlt:"AI assistant avatar",artifactSize:"{size} bytes",removeImage:"Remove image",removeUrl:"Remove URL",removeDocument:"Remove document",removeLogo:"Remove logo",composerSuggestions:"Composer suggestions",apiEndpoint:"API Endpoint",apiKeyOptional:"API Key (Optional)",configuredReplace:"Configured — enter to replace",clearKey:"Clear key",modelId:"Model ID",temperature:"Temperature",topP:"Top P",english:"English",chinese:"中文",users:"Users",userManagement:"User management",userManagementDesc:"Create accounts, control access, and reset passwords. Usernames cannot be changed.",username:"Username",initialPassword:"Initial password",role:"Role",member:"Member",administrator:"Administrator",createUser:"Create user",resetPassword:"Reset password",enable:"Enable",disable:"Disable",enabled:"Enabled",disabled:"Disabled",userCreated:"User created",userUpdated:"User updated",passwordReset:"Password reset; existing sessions revoked",newPasswordFor:"New password for {username}",unableCreateUser:"Unable to create user",unableUpdateUser:"Unable to update user",unableResetPassword:"Unable to reset password",unableChangePassword:"Unable to change password",modules:"Modules",featureSuites:"Feature suites",featureSuitesDesc:"Each product feature joins one backend module with either a companion plugin or a source-built Resident Integration. ChatRaw does not start processes or containers.",composeNetwork:"Compose network",composeNetworkDesc:"Use this network for ChatRaw and module services.",containerAddressRepair:"Container address repair required",addressesUnchanged:"Existing addresses were not changed automatically.",moduleAddress:"Module address",moduleAddressSecret:"The address is sent to ChatRaw once and is never returned by the management API.",pairingCode:"One-time pairing code",connectForReview:"Connect for review",noModules:"No modules are connected.",moduleId:"Module ID",frontendIntegration:"Frontend integration",moduleProtocol:"Module protocol",installedVersion:"Installed version",requires:"Requires",residentSourceBuilt:"Resident Integration is source-built into this ChatRaw deployment.",networkAndConfiguration:"Network and configuration",configurationStatus:"Configuration",lastChecked:"Last checked",dataOwnership:"Data ownership and retention",requestedCapabilities:"Requested host capabilities",none:"None.",recentFault:"Current availability issue",declaredActions:"Declared actions",noActions:"No actions declared.",minimumRole:"minimum role",supportsStream:"stream",supportsCancel:"cancel",supportsApproval:"approval",supportsArtifacts:"artifacts",supportsChatProjection:"chat projection",reviewRequired:"Review is required before this module can be enabled.",requirementsIncomplete:"Approved, but health, configuration, Ready, or frontend integration requirements are incomplete.",approveDeclaration:"Approve declaration",checkHealth:"Check health",refreshManifest:"Refresh manifest",configure:"Configure",enableSuite:"Enable suite",drain:"Drain",purgeModuleData:"Purge module data",disconnect:"Disconnect",moduleConfiguration:"Module configuration",keep:"Keep",replace:"Replace",clear:"Clear",newSecret:"New secret",saveModuleConfiguration:"Save module configuration",moduleOperationFailed:"Module operation failed",modulePaired:"Module paired for review",approveModuleConfirm:"Approve {moduleId} and its declared capabilities?",moduleApproved:"Module manifest approved",moduleActionCompleted:"Module {action} completed",configurationSavedRevision:"Configuration saved at revision {revision}",disconnectModulePrompt:"Type {moduleId} to disconnect. Module data will be preserved.",moduleDisconnected:"Module disconnected; module data was preserved",purgeModulePrompt:"This deletes data owned by the module. Type {confirmation} to continue.",moduleDataPurged:"Module-owned data purged",actionCheck:"check",actionRefresh:"refresh",actionEnable:"enable",actionDrain:"drain",actionDisable:"disable",account:"Account",currentPassword:"Current password",newPassword:"New password",changePassword:"Change password",signOut:"Sign out",loading:"Loading...",pluginInfo:"Plugin info",installPlugin:"Install plugin",closeResidentIntegration:"Close Resident Integration",tasks:"Tasks",moduleTask:"Module task",task:"Task",closeTask:"Close task",moduleTasks:"Module tasks",approvalRequired:"Approval required",taskProgress:"Task progress",approve:"Approve",deny:"Deny",output:"Output",artifacts:"Artifacts",cancelTask:"Cancel task",executionProcess:"Execution process",activityRunning:"Running",activityUpdated:"Updated",activityInterrupted:"Previous run did not finish",waitingForExecutionDetails:"Waiting for execution details",noExecutionDetails:"No execution details were retained",tool:"Tool",viewDetails:"View details",toolInput:"Input",toolResult:"Result",resultInConversation:"This task is displayed in its conversation.",statePending:"Pending",stateQueued:"Queued",statePaired:"Paired",stateReviewRequired:"Review required",stateApproved:"Approved",stateEnabled:"Enabled",stateDisabled:"Disabled",stateDraining:"Draining",stateReady:"Ready",stateHealthy:"Healthy",stateUnhealthy:"Unhealthy",stateConfigured:"Configured",stateUnconfigured:"Not configured",stateRunning:"Running",stateSucceeded:"Succeeded",stateFailed:"Failed",stateCancelled:"Cancelled",statePendingApproval:"Pending approval",stateReachable:"Reachable",stateUnreachable:"Unreachable",stateCompatible:"Compatible",stateIncompatible:"Incompatible",statePresent:"Present",stateMissing:"Missing",stateValid:"Valid",stateInvalid:"Invalid",statePlugin:"Plugin",stateResident:"Resident Integration",stateInstalled:"Installed",stateAvailable:"Available",stateUnknown:"Unknown",invalidRequest:"Invalid request",invalidUsernameType:"Username must be text",invalidUsernameLength:"Username length is invalid",invalidUsernameCharacters:"Username contains unsupported characters",invalidPasswordLength:"Password is too short",invalidPasswordTooLong:"Password is too long",setupUnavailable:"Initial setup is unavailable",usernameInUse:"Username is already in use",invalidCredentials:"Invalid username or password",currentPasswordIncorrect:"Current password is incorrect",invalidRole:"Invalid role",userNotFound:"User not found",lastActiveAdmin:"The last active administrator cannot be disabled",administratorRequired:"Administrator permission is required",selfUserManagementForbidden:"Manage your own password from Account; your role and access cannot be changed here",confirmPassword:"Confirm password",passwordsDoNotMatch:"The passwords do not match",passwordMinimum:"Use at least 12 characters",administratorAccessWarning:"Administrators can manage users, models, plugins, and modules.",usersLoading:"Loading users…",usersEmpty:"No users found.",usersLoadFailed:"Unable to load users. Try again.",createdAt:"Created",updatedAt:"Updated",currentUser:"You",promoteAdministrator:"Promote to administrator",demoteMember:"Change to member",confirmPromoteUser:"Promote {username} to administrator? They will gain all management permissions and must sign in again.",confirmDemoteUser:"Change {username} to member? They will lose management permissions and must sign in again.",confirmDisableUser:"Disable {username}? Their sessions and task capabilities will be revoked; their data will be kept.",userRoleUpdated:"User role updated",resetPasswordTitle:"Reset password",resetPasswordDesc:"Set a new password for {username}. Their existing sessions will be revoked.",savePassword:"Save password",cancel:"Cancel",manageSelfInAccount:"Use Account to change your own password.",unavailable:"Unavailable",risk:"risk",taskScoped:"task-scoped",declarationOnly:"declaration only",revision:"Revision",secretConfigured:"Secret configured",secretNotConfigured:"Secret not configured",notCheckedYet:"Not checked yet",taskFailed:"Task failed",taskInProgress:"Task in progress",taskUpdatesUnavailable:"Task updates unavailable"}),Object.assign(i18n.zh,{send:"发送",rename:"重命名",renameSharedChat:"重命名此会话",renameFailed:"重命名失败",deleteSharedChat:"确定删除此私有会话吗?此操作无法撤销。",deleteSharedDocument:"确定删除此共享文档吗?其他用户将无法再访问。",requestFailed:"请求失败",unknownError:"未知错误",authenticationRequired:"需要登录",settingsNavigation:"设置导航",pluginNavigation:"插件导航",preview:"预览",logoAlt:"Logo",userAvatarAlt:"用户头像",assistantAvatarAlt:"AI 助手头像",artifactSize:"{size} 字节",removeImage:"移除图片",removeUrl:"移除网址",removeDocument:"移除文档",removeLogo:"移除 Logo",composerSuggestions:"输入建议",apiEndpoint:"API 地址",apiKeyOptional:"API 密钥(可选)",configuredReplace:"已配置 — 输入新值可替换",clearKey:"清除密钥",modelId:"模型 ID",temperature:"温度",topP:"Top P",english:"English",chinese:"中文",users:"用户",userManagement:"用户管理",userManagementDesc:"创建账户、控制访问权限并重置密码。用户名不可修改。",username:"用户名",initialPassword:"初始密码",role:"角色",member:"普通用户",administrator:"管理员",createUser:"创建用户",resetPassword:"重置密码",enable:"启用",disable:"停用",enabled:"已启用",disabled:"已停用",userCreated:"用户已创建",userUpdated:"用户状态已更新",passwordReset:"密码已重置,已有会话已撤销",newPasswordFor:"为 {username} 设置新密码",unableCreateUser:"无法创建用户",unableUpdateUser:"无法更新用户",unableResetPassword:"无法重置密码",unableChangePassword:"无法修改密码",modules:"模块",featureSuites:"功能套件",featureSuitesDesc:"每项产品功能由一个后端模块与配套插件或源码内置的 Resident Integration 组成。ChatRaw 不负责启动进程或容器。",composeNetwork:"Compose 网络",composeNetworkDesc:"ChatRaw 与模块服务应使用此网络。",containerAddressRepair:"需要修复容器地址",addressesUnchanged:"现有地址不会被自动修改。",moduleAddress:"模块地址",moduleAddressSecret:"该地址仅向 ChatRaw 提交一次,管理 API 不会返回此地址。",pairingCode:"一次性配对码",connectForReview:"连接并进入审核",noModules:"尚未连接模块。",moduleId:"模块 ID",frontendIntegration:"前端集成",moduleProtocol:"模块协议",installedVersion:"已安装版本",requires:"要求",residentSourceBuilt:"Resident Integration 已随当前 ChatRaw 部署从源码构建。",networkAndConfiguration:"网络与配置",configurationStatus:"配置",lastChecked:"上次检查",dataOwnership:"数据所有权与保留",requestedCapabilities:"申请的主机能力",none:"无。",recentFault:"当前不可用原因",declaredActions:"声明的操作",noActions:"未声明操作。",minimumRole:"最低角色",supportsStream:"流式",supportsCancel:"可取消",supportsApproval:"需审批",supportsArtifacts:"产物",supportsChatProjection:"投影到对话",reviewRequired:"必须先审核此模块,才能启用。",requirementsIncomplete:"已批准,但健康检查、配置、Ready 状态或前端集成要求尚未满足。",approveDeclaration:"批准声明",checkHealth:"检查健康状态",refreshManifest:"刷新清单",configure:"配置",enableSuite:"启用套件",drain:"排空",purgeModuleData:"清除模块数据",disconnect:"断开连接",moduleConfiguration:"模块配置",keep:"保留",replace:"替换",clear:"清除",newSecret:"新密钥",saveModuleConfiguration:"保存模块配置",moduleOperationFailed:"模块操作失败",modulePaired:"模块已连接,等待审核",approveModuleConfirm:"批准 {moduleId} 及其声明的能力吗?",moduleApproved:"模块清单已批准",moduleActionCompleted:"模块{action}操作已完成",configurationSavedRevision:"配置已保存,修订版本 {revision}",disconnectModulePrompt:"输入 {moduleId} 以断开连接。模块数据将保留。",moduleDisconnected:"模块已断开连接,模块数据已保留",purgeModulePrompt:"这会删除模块拥有的数据。输入 {confirmation} 继续。",moduleDataPurged:"模块数据已清除",actionCheck:"检查",actionRefresh:"刷新",actionEnable:"启用",actionDrain:"排空",actionDisable:"停用",account:"账户",currentPassword:"当前密码",newPassword:"新密码",changePassword:"修改密码",signOut:"退出登录",loading:"加载中...",pluginInfo:"插件信息",installPlugin:"安装插件",closeResidentIntegration:"关闭 Resident Integration",tasks:"任务",moduleTask:"模块任务",task:"任务",closeTask:"关闭任务",moduleTasks:"模块任务",approvalRequired:"需要审批",taskProgress:"任务进度",approve:"批准",deny:"拒绝",output:"输出",artifacts:"产物",cancelTask:"取消任务",executionProcess:"执行过程",activityRunning:"运行中",activityUpdated:"已更新",activityInterrupted:"上次执行未完成",waitingForExecutionDetails:"正在等待执行详情",noExecutionDetails:"没有保留执行详情",tool:"工具",viewDetails:"查看详情",toolInput:"输入",toolResult:"结果",resultInConversation:"此任务已在对应对话中展示。",statePending:"等待中",stateQueued:"排队中",statePaired:"已配对",stateReviewRequired:"等待审核",stateApproved:"已批准",stateEnabled:"已启用",stateDisabled:"已停用",stateDraining:"排空中",stateReady:"就绪",stateHealthy:"健康",stateUnhealthy:"异常",stateConfigured:"已配置",stateUnconfigured:"未配置",stateRunning:"运行中",stateSucceeded:"已成功",stateFailed:"失败",stateCancelled:"已取消",statePendingApproval:"等待审批",stateReachable:"可访问",stateUnreachable:"无法访问",stateCompatible:"兼容",stateIncompatible:"不兼容",statePresent:"已存在",stateMissing:"缺失",stateValid:"有效",stateInvalid:"无效",statePlugin:"插件",stateResident:"Resident Integration",stateInstalled:"已安装",stateAvailable:"可用",stateUnknown:"未知",invalidRequest:"请求无效",invalidUsernameType:"用户名必须是文本",invalidUsernameLength:"用户名长度不符合要求",invalidUsernameCharacters:"用户名包含不支持的字符",invalidPasswordLength:"密码长度不足",invalidPasswordTooLong:"密码过长",setupUnavailable:"初始设置当前不可用",usernameInUse:"用户名已被使用",invalidCredentials:"用户名或密码错误",currentPasswordIncorrect:"当前密码错误",invalidRole:"角色无效",userNotFound:"未找到用户",lastActiveAdmin:"不能停用最后一名活跃管理员",administratorRequired:"需要管理员权限",selfUserManagementForbidden:"请在“账户”中修改自己的密码;不能在此变更自己的角色或访问状态",confirmPassword:"确认密码",passwordsDoNotMatch:"两次输入的密码不一致",passwordMinimum:"密码至少 12 个字符",administratorAccessWarning:"管理员可以管理用户、模型、插件和模块。",usersLoading:"正在加载用户…",usersEmpty:"没有找到用户。",usersLoadFailed:"无法加载用户,请重试。",createdAt:"创建时间",updatedAt:"更新时间",currentUser:"当前账户",promoteAdministrator:"提升为管理员",demoteMember:"降为普通用户",confirmPromoteUser:"确定将 {username} 提升为管理员吗?该用户将获得全部管理权限,并需要重新登录。",confirmDemoteUser:"确定将 {username} 降为普通用户吗?该用户将失去管理权限,并需要重新登录。",confirmDisableUser:"确定停用 {username} 吗?其会话和任务能力将立即撤销,但数据会保留。",userRoleUpdated:"用户角色已更新",resetPasswordTitle:"重置密码",resetPasswordDesc:"为 {username} 设置新密码。该用户已有会话将立即失效。",savePassword:"保存密码",cancel:"取消",manageSelfInAccount:"请在“账户”中修改自己的密码。",unavailable:"不可用",risk:"风险",taskScoped:"仅任务范围",declarationOnly:"仅声明",revision:"修订版本",secretConfigured:"密钥已配置",secretNotConfigured:"密钥未配置",notCheckedYet:"尚未检查",taskFailed:"任务失败",taskInProgress:"任务进行中",taskUpdatesUnavailable:"任务更新暂不可用"}),marked.setOptions({highlight:function(e,t){if(hljsLoaded||hljsLoading||loadHighlightJS(),"undefined"!=typeof hljs){if(t&&hljs.getLanguage(t))try{return hljs.highlight(e,{language:t}).value}catch(e){}return hljs.highlightAuto(e).value}return e.replace(//g,">")},breaks:!0,gfm:!0});const SKILL_MANAGER_PLUGIN_ID="skill-manager",COMPOSER_COMPLETION_LIMIT=20,SKILL_CATALOG_CACHE_MS=3e4,MAX_ACTIVE_SKILLS_PER_REQUEST=5,AGENT_CHAT_ENDPOINT="/api/agent/chat",RESERVED_SLASH_COMMANDS=new Set(["plugins","settings","help","clear","compact","api"]),COMMON_PATH_ROOTS=new Set(["tmp","var","usr","etc","home","users","opt","private","volumes","mnt"]),SKILL_NAME_PATTERN=/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/,MODULE_SDK_VERSION="1.6.0",MODULE_TASK_STORAGE_KEY="chatraw_module_tasks_v1",SHELL_PAGE_STORAGE_KEY="chatraw_shell_page_v1",MODULE_TERMINAL_STATES=new Set(["succeeded","failed","cancelled"]),PLUGIN_WORKSPACE_PLACEMENTS=Object.freeze(["right","top","bottom","main"]),PLUGIN_WORKSPACE_PANEL_ID_PATTERN=/^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/,MODULE_ACTIVITY_LIMIT=512;function app(){return{lang:localStorage.getItem("justchat_lang")||"en",showSettings:!1,agentOpen:!1,agentExpanded:!1,agentHistoryOpen:!1,featureCatalog:{categories:[],cards:[]},activeFeatureCategory:"data-hub",featureCatalogLoading:!0,settingsTab:"models",me:null,adminUsers:[],adminUsersLoading:!1,adminUsersError:null,adminCreateBusy:!1,adminUserBusy:{},newUser:{username:"",password:"",confirmPassword:"",role:"member"},resetUserPasswordDialog:{show:!1,user:null,newPassword:"",confirmPassword:""},passwordForm:{current_password:"",new_password:""},modules:[],modulePairForm:{base_url:"",pairing_code:""},deploymentStatus:{mode:"source",module_network:null,warnings:[]},moduleBusy:null,moduleConfig:null,moduleConfigValues:{},moduleSecretActions:{},moduleTaskUi:{show:!1,task:null,events:[],output:"",progress:0,progressMessage:"",approval:null,artifacts:[],error:null},moduleTasks:{},moduleTaskOrder:[],moduleTaskSubscriptions:{},residentIntegrations:[],residentBuiltIntegrationIds:[],residentIntegrationDefinitions:{},residentRegistrationOpen:!1,residentWorkspace:{show:!1,integrationId:null,title:"",error:null,cleanup:null},_residentFocusHandler:null,_residentWorkspaceReturnFocus:null,showSystemPrompt:!1,currentChatId:null,inputMessage:"",isComposing:!1,completionProviders:{},completionMenuOpen:!1,completionQuery:"",completionItems:[],completionActiveIndex:0,completionRange:null,completionRequestId:0,completionDebounceTimer:null,skillCatalogCache:null,skillCatalogLoadedAt:0,useRAG:!1,isGenerating:!1,abortController:null,uploadedImage:null,uploadedImageBase64:"",uploadProgress:{show:!1,filename:"",progress:0,status:"",current:0,total:0},showUrlInput:!1,urlInputValue:"",isParsingUrl:!1,parsedUrl:null,parsedUrlTitle:"",attachedDocument:null,isUploadingDocument:!1,showPlugins:!1,pluginTab:"market",marketPlugins:[],installedPlugins:[],pluginMarketLoading:!1,pluginMarketError:!1,pluginMarketSearch:"",pluginInstalling:null,pluginUploading:!1,showPluginSettings:!1,currentPluginSettings:null,pluginSettingsValues:{},pluginApiKeys:{},pluginApiKeyConfigured:{},pluginApiKeyActions:{},pluginToolbarButtons:[],showExtensionPalette:!1,pluginFullscreenModal:{show:!1,content:"",closable:!0,onClose:null,pluginId:null},pluginWorkspaceDefinitions:{},pluginWorkspace:{show:!1,pluginId:null,panelId:null,title:null,icon:"",collectionId:null,collectionTitle:null,collectionIcon:"",placement:null,dispose:null},_pluginWorkspaceReturnFocus:null,_pluginFullscreenReturnFocus:null,_pluginWorkspaceActivation:null,_pluginWorkspaceTransition:null,_rootWheelHandler:null,pluginHooks:{parse_document:[],parse_url:[],web_search:[],pre_embedding:[],post_retrieval:[],send_intercept:[],before_send:[],route_message:[],after_receive:[],toolbar_button:[],file_preview:[],custom_action:[],custom_settings:[],transform_input:[],transform_output:[]},loadedPluginDeps:{},pluginRuntimeVersions:{},_pluginRuntimeSyncPromise:null,_pluginRuntimeSyncInitialized:!1,_pluginRuntimeChangeChannel:null,_agentChatChangeChannel:null,_settingsChangeChannel:null,chats:[],messages:[],models:[],documents:[],settings:{chat_settings:{temperature:.7,top_p:.9,stream:!0,system_prompt:""},rag_settings:{chunk_size:500,chunk_overlap:50,top_k:3,score_threshold:.5},ui_settings:{logo_data:"",logo_text:"ChatRaw",subtitle:"",theme_mode:"light",user_avatar:"",assistant_avatar:""}},toast:{show:!1,message:"",type:""},t(e,t={}){const s=(i18n[this.lang]||i18n.en)[e]??i18n.en[e]??e;return String(s).replace(/\{(\w+)\}/g,(e,s)=>void 0===t[s]?`{${s}}`:String(t[s]))},setLanguage(e){this.lang="zh"===e?"zh":"en",localStorage.setItem("justchat_lang",this.lang),document.documentElement.lang="zh"===this.lang?"zh-CN":"en",this.updateDocumentTitle()},updateDocumentTitle(){const e=this.settings?.ui_settings?.logo_text||"ChatRaw";document.title=this.t("appDocumentTitle",{brand:e})},roleLabel(e){return this.t("admin"===e?"administrator":"member")},stateLabel(e){const t=String(e??"").trim().toLowerCase().replace(/[\s-]+/g,"_"),s={pending:"statePending",queued:"stateQueued",paired:"statePaired",review_required:"stateReviewRequired",pending_review:"stateReviewRequired",approved:"stateApproved",enabled:"stateEnabled",disabled:"stateDisabled",draining:"stateDraining",ready:"stateReady",healthy:"stateHealthy",unhealthy:"stateUnhealthy",configured:"stateConfigured",unconfigured:"stateUnconfigured",running:"stateRunning",succeeded:"stateSucceeded",completed:"stateSucceeded",failed:"stateFailed",cancelled:"stateCancelled",canceled:"stateCancelled",pending_approval:"statePendingApproval",reachable:"stateReachable",unreachable:"stateUnreachable",compatible:"stateCompatible",incompatible:"stateIncompatible",present:"statePresent",missing:"stateMissing",valid:"stateValid",invalid:"stateInvalid",plugin:"statePlugin",resident:"stateResident",installed:"stateInstalled",available:"stateAvailable",unavailable:"unavailable",not_present:"stateMissing"};return s[t]?this.t(s[t]):e||this.t("stateUnknown")},actionLabel(e){const t={check:"actionCheck",refresh:"actionRefresh",enable:"actionEnable",drain:"actionDrain",disable:"actionDisable"}[e];return t?this.t(t):e},localizeError(e,t="requestFailed"){const s="string"==typeof e?e:e?.code||e?.error?.code||e?.detail?.code,i={invalid_request:"invalidRequest",invalid_username_type:"invalidUsernameType",invalid_username_length:"invalidUsernameLength",invalid_username_characters:"invalidUsernameCharacters",invalid_password_length:"invalidPasswordLength",invalid_password_too_long:"invalidPasswordTooLong",setup_unavailable:"setupUnavailable",username_in_use:"usernameInUse",invalid_credentials:"invalidCredentials",current_password_incorrect:"currentPasswordIncorrect",invalid_role:"invalidRole",user_not_found:"userNotFound",last_active_admin:"lastActiveAdmin",administrator_required:"administratorRequired",self_user_management_forbidden:"selfUserManagementForbidden",task_failed:"taskFailed",module_event_stream_failed:"taskUpdatesUnavailable",module_event_stream_incomplete:"taskUpdatesUnavailable"};return i[s]?this.t(i[s]):this.t(t)},getModelTypeName(e){return{chat:this.t("chat"),embedding:this.t("embedding"),rerank:this.t("reranker")}[e]||e},getChatModel(){return this.models.find(e=>"chat"===e?.type)||null},chatModelSupportsVision(){return Boolean(this.getChatModel()?.capability?.vision)},syncVisionCapabilityState(){!this.chatModelSupportsVision()&&this.uploadedImage&&this.removeUploadedImage()},async init(){this.setLanguage(this.lang),this.installRootWheelGuard(),await this.loadMe(),await this.loadSettings(),await this.loadModels(),await this.loadAgentChats(),await this.loadFeatureCatalog(),await this.loadDocuments(),await this.loadInstalledPlugins(),this.applyTheme(),this.initCrossTabStateSync(),await this.initPluginSystem(),this.restorePluginWorkspacePage(),await this.initResidentIntegrations(),await this.resumeModuleTasks()},initCrossTabStateSync(){"function"==typeof window.BroadcastChannel&&(this._agentChatChangeChannel||(this._agentChatChangeChannel=new window.BroadcastChannel("chatraw-agent-chats-v1"),this._agentChatChangeChannel.addEventListener("message",async e=>{"agent-chats-changed"===e.data?.type&&(await this.loadAgentChats(),this.currentChatId&&!this.chats.some(e=>e.id===this.currentChatId)&&(this.isGenerating&&this.stopGeneration(),this.currentChatId=null,this.messages=[]))})),this._settingsChangeChannel||(this._settingsChangeChannel=new window.BroadcastChannel("chatraw-settings-v1"),this._settingsChangeChannel.addEventListener("message",async e=>{"settings-changed"===e.data?.type&&(await this.loadSettings(),this.applyTheme())})))},announceAgentChatChange(e,t){this._agentChatChangeChannel?.postMessage({type:"agent-chats-changed",action:e,chat_id:t})},announceSettingsChange(){this._settingsChangeChannel?.postMessage({type:"settings-changed"})},isAdmin(){return"admin"===this.me?.role},async loadMe(){const e=await fetch("/api/me");if(!e.ok)throw location.replace("/login"),new Error(this.t("authenticationRequired"));this.me=await e.json(),this.isAdmin()||(this.settingsTab="account")},async logout(){await fetch("/api/auth/logout",{method:"POST"}),location.replace("/login")},async loadFeatureCatalog(){this.featureCatalogLoading=!0;try{const e=await fetch("/api/module-feature-catalog");if(!e.ok)throw new Error("Feature catalog unavailable");const t=await e.json();this.featureCatalog={categories:Array.isArray(t.categories)?t.categories:[],cards:Array.isArray(t.cards)?t.cards:[]},this.recomputeFeatureCatalogRuntime(),this.featureCatalog.categories.some(e=>e.id===this.activeFeatureCategory)||(this.activeFeatureCategory=this.featureCatalog.categories[0]?.id||"")}catch(e){console.error("Failed to load feature catalog:",e),this.featureCatalog={categories:[],cards:[]}}finally{this.featureCatalogLoading=!1}},get activeFeatureCards(){return this.featureCatalog.cards.filter(e=>e.category_id===this.activeFeatureCategory).sort((e,t)=>e.order-t.order)},featureCatalogCardForPanel(e,t){return this.featureCatalog.cards.find(s=>s.plugin_id===e&&s.panel_id===t)||null},pluginWorkspacePanelIcon(e,t,s=""){return this.featureCatalogCardForPanel(e,t)?.icon||s},featureStateLabel(e){return this.t({loading:"featureLoading",module_unavailable:"moduleUnavailable",plugin_missing:"pluginMissing",plugin_disabled:"pluginDisabledState",plugin_version_mismatch:"pluginVersionMismatch",panel_not_registered:"panelNotRegistered",main_placement_required:"mainPlacementRequired",available:"featureAvailable"}[e]||"featureUnavailable")},recomputeFeatureCatalogRuntime(){this.featureCatalog={...this.featureCatalog,cards:this.featureCatalog.cards.map(e=>{if(!e.service_ready)return{...e,runtime_ready:!1,available:!1};const t=this.pluginWorkspaceDefinitions[this.pluginWorkspaceKey(e.plugin_id,e.panel_id)];return t?t.placements.includes("main")?{...e,runtime_ready:!0,available:!0,state:"available"}:{...e,runtime_ready:!1,available:!1,state:"main_placement_required"}:{...e,runtime_ready:!1,available:!1,state:"panel_not_registered"}})}},async openFeatureCard(e,t=null){if(!e?.available)return;const s=this.pluginWorkspaceKey(e.plugin_id,e.panel_id);if(this.pluginWorkspaceDefinitions[s]||await this.syncPluginRuntimes(),!this.pluginWorkspaceDefinitions[s])return this.recomputeFeatureCatalogRuntime(),void this.showToast(this.t("featurePanelNotReady"),"error");if(!this.pluginWorkspaceDefinitions[s].placements.includes("main"))return this.recomputeFeatureCatalogRuntime(),void this.showToast(this.t("featurePanelNotReady"),"error");this.showSettings=!1,this.isGenerating&&this.stopGeneration(),this.agentOpen=!1,this.agentExpanded=!1,this.agentHistoryOpen=!1,this.showExtensionPalette=!1;const i=this._pluginWorkspaceActivation;this._pluginWorkspaceActivation=!0===navigator.userActivation?.isActive&&t instanceof HTMLElement&&t.isConnected?{pluginId:e.plugin_id,trigger:t}:null;try{this.openPluginWorkspacePanel(e.panel_id,{placement:"main"},e.plugin_id)}finally{this._pluginWorkspaceActivation=i}},returnHome(){this.showSettings=!1,this.pluginWorkspace.show&&this.closeActivePluginWorkspace()},storedPluginWorkspacePage(){let e=null;try{e=JSON.parse(sessionStorage.getItem(SHELL_PAGE_STORAGE_KEY)||"null")}catch(t){e=null}return e&&"plugin-workspace"===e.kind&&"string"==typeof e.pluginId&&e.pluginId&&"string"==typeof e.panelId&&PLUGIN_WORKSPACE_PANEL_ID_PATTERN.test(e.panelId)&&PLUGIN_WORKSPACE_PLACEMENTS.includes(e.placement)&&!Object.keys(e).some(e=>!["kind","pluginId","panelId","placement"].includes(e))?e:(this.forgetPluginWorkspacePage(),null)},rememberPluginWorkspacePage(e,t,s){try{sessionStorage.setItem(SHELL_PAGE_STORAGE_KEY,JSON.stringify({kind:"plugin-workspace",pluginId:e,panelId:t,placement:s}))}catch(e){}},forgetPluginWorkspacePage(){try{sessionStorage.removeItem(SHELL_PAGE_STORAGE_KEY)}catch(e){}},restorePluginWorkspacePage(){const e=this.storedPluginWorkspacePage();if(!e)return!1;const t=this.pluginWorkspaceDefinitions[this.pluginWorkspaceKey(e.pluginId,e.panelId)];if(!t||!t.placements.includes(e.placement))return this.forgetPluginWorkspacePage(),!1;try{return this.openPluginWorkspacePanel(e.panelId,{placement:e.placement},e.pluginId)}catch(e){return this.forgetPluginWorkspacePage(),console.warn("[Plugin Workspace restore]",e),!1}},toggleAgent(){this.agentOpen||(this.showSettings=!1,this.showPlugins=!1),this.agentOpen=!this.agentOpen,this.showExtensionPalette=!1,this.agentOpen||(this.agentHistoryOpen=!1,this.agentExpanded=!1),this.agentOpen&&!this.currentChatId&&this.chats.length&&this.selectChat(this.chats[0].id)},toggleAgentExpanded(){this.agentOpen&&(this.agentExpanded=!this.agentExpanded,this.agentHistoryOpen=!1,this.showExtensionPalette=!1)},toggleAgentHistory(){const e=!this.agentHistoryOpen;this.showExtensionPalette=!1,this.showUrlInput=!1,this.agentHistoryOpen=e},async loadAdminUsers(){if(this.isAdmin()){this.adminUsersLoading=!0,this.adminUsersError=null;try{const e=await fetch("/api/admin/users");let t={};try{t=await e.json()}catch(e){t={}}if(!e.ok)throw t;this.adminUsers=Array.isArray(t.users)?t.users:[]}catch(e){this.adminUsers=[],this.adminUsersError=this.localizeError(e,"usersLoadFailed")}finally{this.adminUsersLoading=!1}}},setAdminUserBusy(e,t){const s={...this.adminUserBusy};t?s[e]=!0:delete s[e],this.adminUserBusy=s},isAdminUserBusy(e){return Boolean(this.adminUserBusy[e])},formatUserDate(e){if(!e)return"—";const t=new Date(e);return Number.isNaN(t.getTime())?e:new Intl.DateTimeFormat("zh"===this.lang?"zh-CN":"en",{dateStyle:"medium",timeStyle:"short"}).format(t)},managedUserSnapshot:e=>({id:String(e.id),username:String(e.username),role:e.role,enabled:Boolean(e.enabled)}),async createManagedUser(){if(this.adminCreateBusy)return;if(this.newUser.password.length<12)return void this.showToast(this.t("passwordMinimum"),"error");if(this.newUser.password!==this.newUser.confirmPassword)return void this.showToast(this.t("passwordsDoNotMatch"),"error");if("admin"===this.newUser.role&&!window.confirm(this.t("administratorAccessWarning")))return;const e={username:this.newUser.username,password:this.newUser.password,role:this.newUser.role};this.adminCreateBusy=!0;try{const t=await fetch("/api/admin/users",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});let s={};try{s=await t.json()}catch(e){s={}}if(!t.ok)return void this.showToast(this.localizeError(s,"unableCreateUser"),"error");this.newUser={username:"",password:"",confirmPassword:"",role:"member"},await this.loadAdminUsers(),this.showToast(this.t("userCreated"),"success")}catch(e){this.showToast(this.localizeError(e,"unableCreateUser"),"error")}finally{this.adminCreateBusy=!1}},async setManagedUserEnabled(e,t){const s=this.managedUserSnapshot(e);if(s.id===this.me?.id||this.isAdminUserBusy(s.id))return;if(!t&&!window.confirm(this.t("confirmDisableUser",{username:s.username})))return;const i=t?"enable":"disable";this.setAdminUserBusy(s.id,!0);try{const e=await fetch(`/api/admin/users/${encodeURIComponent(s.id)}/${i}`,{method:"POST"});let t={};try{t=await e.json()}catch(e){t={}}if(!e.ok)return void this.showToast(this.localizeError(t,"unableUpdateUser"),"error");await this.loadAdminUsers(),this.showToast(this.t("userUpdated"),"success")}catch(e){this.showToast(this.localizeError(e,"unableUpdateUser"),"error")}finally{this.setAdminUserBusy(s.id,!1)}},async setManagedUserRole(e,t){const s=this.managedUserSnapshot(e);if(s.id===this.me?.id||s.role===t||this.isAdminUserBusy(s.id))return;const i="admin"===t?"confirmPromoteUser":"confirmDemoteUser";if(window.confirm(this.t(i,{username:s.username}))){this.setAdminUserBusy(s.id,!0);try{const e=await fetch(`/api/admin/users/${encodeURIComponent(s.id)}/role`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({role:t})});let i={};try{i=await e.json()}catch(e){i={}}if(!e.ok)return void this.showToast(this.localizeError(i,"unableUpdateUser"),"error");await this.loadAdminUsers(),this.showToast(this.t("userRoleUpdated"),"success")}catch(e){this.showToast(this.localizeError(e,"unableUpdateUser"),"error")}finally{this.setAdminUserBusy(s.id,!1)}}},resetManagedUserPassword(e){const t=this.managedUserSnapshot(e);t.id===this.me?.id||this.isAdminUserBusy(t.id)||(this.resetUserPasswordDialog={show:!0,user:t,newPassword:"",confirmPassword:""})},closeManagedUserPasswordDialog(){this.resetUserPasswordDialog={show:!1,user:null,newPassword:"",confirmPassword:""}},async submitManagedUserPasswordReset(){const e=this.resetUserPasswordDialog,t=e.user?{...e.user}:null;if(t&&!this.isAdminUserBusy(t.id))if(e.newPassword.length<12)this.showToast(this.t("passwordMinimum"),"error");else if(e.newPassword===e.confirmPassword){this.setAdminUserBusy(t.id,!0);try{const s=await fetch(`/api/admin/users/${encodeURIComponent(t.id)}/reset-password`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({new_password:e.newPassword})});let i={};try{i=await s.json()}catch(e){i={}}if(!s.ok)return void this.showToast(this.localizeError(i,"unableResetPassword"),"error");this.closeManagedUserPasswordDialog(),await this.loadAdminUsers(),this.showToast(this.t("passwordReset"),"success")}catch(e){this.showToast(this.localizeError(e,"unableResetPassword"),"error")}finally{this.setAdminUserBusy(t.id,!1)}}else this.showToast(this.t("passwordsDoNotMatch"),"error")},async changeMyPassword(){const e=await fetch("/api/me/password",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(this.passwordForm)}),t=await e.json();e.ok?location.replace("/login"):this.showToast(this.localizeError(t,"unableChangePassword"),"error")},async moduleApi(e,t={}){const s=await fetch(e,t);let i={};try{i=await s.json()}catch(e){i={}}if(!s.ok){const e=new Error(this.localizeError(i,"moduleOperationFailed"));throw e.code=i.code||i.error?.code||i.detail?.code||"",e}return i},moduleTaskIds(){try{const e=JSON.parse(localStorage.getItem(MODULE_TASK_STORAGE_KEY)||"[]");return Array.isArray(e)?e.filter(e=>"string"==typeof e).slice(0,20):[]}catch(e){return[]}},rememberModuleTask(e){const t=[e,...this.moduleTaskIds().filter(t=>t!==e)].slice(0,20);localStorage.setItem(MODULE_TASK_STORAGE_KEY,JSON.stringify(t))},forgetModuleTask(e){localStorage.setItem(MODULE_TASK_STORAGE_KEY,JSON.stringify(this.moduleTaskIds().filter(t=>t!==e)))},createModuleTaskView(e,t="task_center"){const s=MODULE_TERMINAL_STATES.has(e?.state);return{show:!1,task:e,events:[],output:e?.result?.text||e?.chat_projection||"",progress:MODULE_TERMINAL_STATES.has(e?.state)?1:0,progressMessage:"",approval:null,artifacts:Array.isArray(e?.artifacts)?e.artifacts:[],error:"failed"===e?.state?e.outcome_code||"task_failed":null,inline:!1,activities:{},activityOrder:[],activitiesLoaded:!1,currentRunId:null,timelineExpanded:!s,timelineTouched:!1,lastAppliedEventId:0,presentation:t,attentionPending:!1}},upsertModuleTask(e,{select:t=!1,presentation:s=null}={}){const i=e?.task_id;if(!i)return null;let n=this.moduleTasks[i];return n?(n.task=e,s&&(n.presentation=s),n.artifacts=Array.isArray(e.artifacts)?e.artifacts:n.artifacts,MODULE_TERMINAL_STATES.has(e.state)&&(n.progress=1,n.approval=null,n.error="failed"===e.state?e.outcome_code||"task_failed":null,n.timelineTouched||(n.timelineExpanded=!1))):(n=this.createModuleTaskView(e,s||"task_center"),this.moduleTasks[i]=n,this.moduleTaskOrder=[i,...this.moduleTaskOrder.filter(e=>e!==i)].slice(0,20)),t&&this.selectModuleTask(i),n},moduleTaskActivityList:e=>e?e.activityOrder.map(t=>e.activities[t]).filter(Boolean):[],moduleTaskActivityKey:e=>`${e.run_id}:${e.activity_id}`,upsertModuleTaskActivity(e,t){if(!e||!t)return;if(e.currentRunId&&e.currentRunId!==t.run_id)for(const t of Object.values(e.activities))t.run_id===e.currentRunId&&"started"===t.state&&(t.interrupted=!0);e.currentRunId=t.run_id;const s=this.moduleTaskActivityKey(t);if(!e.activities[s]){if(e.activityOrder.length>=512){const t=e.activityOrder.shift();t&&delete e.activities[t]}e.activityOrder.push(s)}e.activities[s]={...t,interrupted:!1},e.activitiesLoaded=!0},moduleTaskActivityIcon:e=>"plan"===e?.kind?"ri-list-check-3":"tool"===e?.kind?"ri-tools-line":"preparing"===e?.detail?.phase?"ri-loader-4-line":"finalizing"===e?.detail?.phase?"ri-quill-pen-line":"ri-play-circle-line",moduleTaskActivityState(e){if(e?.interrupted)return this.t("activityInterrupted");const t={started:"activityRunning",updated:"activityUpdated",succeeded:"stateSucceeded",failed:"stateFailed",cancelled:"stateCancelled"}[e?.state];return this.t(t||"stateUnknown")},formatModuleTaskDuration:e=>!Number.isFinite(e)||e<0?"":e<1e3?`${Math.round(e)} ms`:`${(e/1e3).toFixed(1)} s`,messageContent:e=>"string"==typeof e?.content?e.content:"",syncConversationTaskMessage(e){const t=e?.task?.task_id;if(!t||"conversation"!==e.presentation)return;const s=`module-task:${t}`,i=this.messages.findIndex(e=>e.id===s);i<0||(this.messages[i]={...this.messages[i],content:e.output})},async toggleModuleTaskTimeline(e){e&&(e.timelineExpanded=!e.timelineExpanded,e.timelineTouched=!0,e.timelineExpanded&&MODULE_TERMINAL_STATES.has(e.task?.state)&&!e.activitiesLoaded&&window.ChatRaw.modules.subscribe(e.task.task_id,{onClose:()=>{e.activitiesLoaded=!0}}))},mergeConversationTaskMessages(e,t){const s=Array.isArray(e)?e.map(e=>({...e})):[],i=new Set(s.map(e=>e.id).filter(Boolean)),n=new Map,a=new Map;for(const e of t||[]){if(!e?.task_id||!e.user_message_id)continue;const t=this.upsertModuleTask(e,{presentation:"conversation"});t.inline=!0,e.assistant_message_id&&n.set(e.assistant_message_id,t),a.has(e.user_message_id)||a.set(e.user_message_id,[]),a.get(e.user_message_id).push(t)}const o=[];for(const e of s){const t=n.get(e.id);if(t&&(e.moduleTask=t),o.push(e),"user"!==e.role)continue;const s=a.get(e.id)||[];for(const e of s.reverse()){const t=e.task.assistant_message_id;t&&i.has(t)||o.push({id:`module-task:${e.task.task_id}`,chat_id:e.task.chat_id,role:"assistant",content:e.output,created_at:e.task.created_at,moduleTask:e})}}return o},attachConversationTask(e,t){const s=this.upsertModuleTask(e,{presentation:"conversation"});if(s.inline=!0,e.chat_id!==this.currentChatId||!e.user_message_id)return s;const i=this.messages.filter(e=>!String(e.id||"").startsWith("module-task:"));return i.some(t=>t.id===e.user_message_id)||i.push({id:e.user_message_id,chat_id:e.chat_id,role:"user",content:String(t),created_at:e.accepted_at||e.created_at}),this.messages=this.mergeConversationTaskMessages(i,[e]),this.$nextTick(()=>this.scrollToBottom()),s},moduleTaskViews(){return this.moduleTaskOrder.map(e=>this.moduleTasks[e]).filter(e=>e&&"task_center"===e.presentation&&(!MODULE_TERMINAL_STATES.has(e.task?.state)||e.attentionPending||e.show))},moduleTaskAttentionViews(){return this.moduleTaskViews().filter(e=>!MODULE_TERMINAL_STATES.has(e.task?.state)||e.attentionPending)},selectModuleTask(e){const t=this.moduleTasks[e];t&&(this.moduleTaskUi.show=!1,t.show=!0,this.moduleTaskUi=t)},openModuleTaskCenter(){const e=this.moduleTaskAttentionViews(),t=this.moduleTaskUi.task?.task_id,s=e.some(e=>e.task.task_id===t)?t:e[0]?.task?.task_id;s&&this.selectModuleTask(s)},closeModuleTask(){const e=this.moduleTaskUi;e.show=!1,MODULE_TERMINAL_STATES.has(e.task?.state)&&(e.attentionPending=!1,this.forgetModuleTask(e.task.task_id))},applyModuleTaskEvent(e,t){const s=this.moduleTasks[e];s&&(t.id<=s.lastAppliedEventId||(s.lastAppliedEventId=t.id,s.events.push(t),s.events.length>200&&s.events.shift(),"task.status"===t.event||"task.terminal"===t.event?(s.task.state=t.data.state,t.data.outcome_code&&(s.task.outcome_code=t.data.outcome_code),"task.terminal"===t.event&&(s.progress=1,"failed"===t.data.state&&(s.error=t.data.outcome_code||"task_failed"),s.approval=null,s.activitiesLoaded=!0,s.timelineTouched||(s.timelineExpanded=!1),"task_center"!==s.presentation||"cancelled"===t.data.state||s.show?"task_center"===s.presentation&&"cancelled"!==t.data.state||this.forgetModuleTask(e):s.attentionPending=!0)):"task.progress"===t.event?(s.progress=t.data.progress,s.progressMessage=this.t("taskInProgress")):"activity.updated"===t.event?this.upsertModuleTaskActivity(s,t.data):"output.delta"===t.event?(s.output+=t.data.text,this.syncConversationTaskMessage(s)):"output.snapshot"===t.event?(s.output=t.data.text,this.syncConversationTaskMessage(s)):"approval.requested"===t.event?s.approval=t.data:"approval.resolved"===t.event&&(s.approval=null)))},async resolveModuleTaskApproval(e){return this.resolveModuleTaskApprovalFor(this.moduleTaskUi,e)},async resolveModuleTaskApprovalFor(e,t){const s=e?.task,i=e?.approval;if(s&&i)try{await window.ChatRaw.modules.respondApproval(s.task_id,i.approval_id,t)}catch(t){e.error={code:t?.code||"invalid_request"}}},async cancelVisibleModuleTask(){const e=this.moduleTaskUi.task?.task_id;if(e)try{const t=await window.ChatRaw.modules.cancelTask(e);this.upsertModuleTask(t)}catch(e){this.moduleTaskUi.error={code:e?.code||"invalid_request"}}},async downloadVisibleModuleArtifact(e){return this.downloadModuleTaskArtifact(this.moduleTaskUi,e)},async downloadModuleTaskArtifact(e,t){const s=e?.task?.task_id;if(s)try{await window.ChatRaw.modules.downloadArtifact(s,t.artifact_ref)}catch(t){e.error={code:t?.code||"invalid_request"}}},async resumeModuleTasks(){if(window.ChatRaw?.modules)for(const e of this.moduleTaskIds())try{const t=await window.ChatRaw.modules.getTask(e);if(MODULE_TERMINAL_STATES.has(t.state)){this.forgetModuleTask(e);continue}this.upsertModuleTask(t,{presentation:"task_center"}),window.ChatRaw.modules.subscribe(e)}catch(t){404===t?.status&&this.forgetModuleTask(e)}},residentText(e){return e&&"object"==typeof e&&(e[this.lang]||e.en||e.zh)||""},residentEntries(e){return this.residentIntegrations.filter(e=>!0===e.feature?.visible).flatMap(t=>(t.entrypoints||[]).filter(t=>t.placement===e||"composer"===e&&"sidebar"===t.placement).map(e=>({...e,integration:t}))).sort((e,t)=>(e.order??100)-(t.order??100))},async refreshResidentIntegrationStatuses(){if(window.ChatRaw?.modules)for(const e of this.residentIntegrations)try{e.feature=await window.ChatRaw.modules.getFeatureStatus(e.module_id)}catch(t){e.feature={visible:!1,available:!1,state:"hidden",reason:{code:t?.code||"module_not_found",message:"This feature is currently unavailable."}}}},registerResidentIntegration(e){if(!this.residentRegistrationOpen||!e||"string"!=typeof e.id||"function"!=typeof e.mount||Object.keys(e).some(e=>!["id","mount"].includes(e))||!this.residentBuiltIntegrationIds.includes(e.id)||this.residentIntegrationDefinitions[e.id])throw new Error("Resident Integration registration rejected");this.residentIntegrationDefinitions[e.id]=Object.freeze({id:e.id,mount:e.mount})},async initResidentIntegrations(){const e=await fetch("/api/resident-integrations",{credentials:"same-origin"});if(!e.ok)throw new Error("Resident Integration catalog unavailable");const t=await e.json();if("string"!=typeof t.bundle_version||!/^[0-9a-f]{64}$/.test(t.bundle_version)||!Array.isArray(t.built_integration_ids)||t.built_integration_ids.some(e=>"string"!=typeof e||!e)||new Set(t.built_integration_ids).size!==t.built_integration_ids.length)throw new Error("Resident Integration catalog unavailable");if(this.residentBuiltIntegrationIds=[...t.built_integration_ids],this.residentIntegrations=Array.isArray(t.integrations)?t.integrations.map(e=>({...e,feature:{visible:!1,available:!1,state:"hidden",reason:{code:"module_not_found",message:"This feature is currently unavailable."}}})):[],!this.residentIntegrations.length)return;const s=this;window.ChatRawResident=Object.freeze({register(e){s.registerResidentIntegration(e)}}),this.residentRegistrationOpen=!0;try{await this.loadCSS("/resident-integrations/resident-integrations.min.css?v="+encodeURIComponent(t.bundle_version)),await this.loadScript("/resident-integrations/resident-integrations.min.js?v="+encodeURIComponent(t.bundle_version))}finally{this.residentRegistrationOpen=!1}for(const e of this.residentBuiltIntegrationIds)if(!this.residentIntegrationDefinitions[e])throw new Error(`Resident Integration ${e} did not register`);await this.refreshResidentIntegrationStatuses(),this._residentFocusHandler=()=>{this.refreshResidentIntegrationStatuses().catch(e=>{console.warn("[Resident Integration refresh]",e)})},window.addEventListener("focus",this._residentFocusHandler)},async openResidentIntegration(e,t=null){await this.refreshResidentIntegrationStatuses();const s=this.residentIntegrations.find(t=>t.id===e.id);if(!s?.feature?.available)return void this.showToast(s?.feature?.reason?.message||"This feature is currently unavailable.","error");this.closeResidentWorkspace(!1),this.showExtensionPalette=!1,this._residentWorkspaceReturnFocus=t instanceof HTMLElement&&t.isConnected?t:null,this.residentWorkspace={show:!0,integrationId:s.id,title:this.residentText(s.name),error:null,cleanup:null},await new Promise(e=>requestAnimationFrame(e));const i=document.getElementById("resident-workspace-mount"),n=this.residentIntegrationDefinitions[s.id];if(!i||!n)return this.residentWorkspace.error="Resident Integration failed to mount",void document.getElementById("resident-workspace-title")?.focus();try{const e=await n.mount({container:i,moduleId:s.module_id,modules:window.ChatRaw.modules,t:e=>this.residentText(e),showToast:(e,t="")=>this.showToast(e,t),getCurrentChatId:()=>this.currentChatId});"function"==typeof e&&(this.residentWorkspace.cleanup=e)}catch(e){i.replaceChildren(),this.residentWorkspace.error=e?.message||"Resident Integration failed to mount"}document.getElementById("resident-workspace-title")?.focus()},closeResidentWorkspace(e=!0){try{this.residentWorkspace.cleanup?.()}catch(e){console.warn("[Resident Integration cleanup]",e)}const t=document.getElementById("resident-workspace-mount");t?.replaceChildren(),this.residentWorkspace={show:!1,integrationId:null,title:"",error:null,cleanup:null};const s=this._residentWorkspaceReturnFocus;this._residentWorkspaceReturnFocus=null,e&&this.restoreExtensionPaletteFocus(s)},async loadModules(){if(this.isAdmin())try{const[e,t]=await Promise.all([this.moduleApi("/api/admin/modules"),this.moduleApi("/api/admin/deployment-status")]);this.modules=e.modules||[],this.deploymentStatus=t}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}},async pairModule(){if(this.isAdmin()&&!this.moduleBusy){this.moduleBusy="pair";try{await this.moduleApi("/api/admin/modules/pair",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(this.modulePairForm)}),this.modulePairForm={base_url:"",pairing_code:""},await this.loadModules(),await this.refreshResidentIntegrationStatuses(),this.showToast(this.t("modulePaired"),"success")}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}finally{this.moduleBusy=null}}},async approveModule(e){if(this.isAdmin()&&!this.moduleBusy&&window.confirm(this.t("approveModuleConfirm",{moduleId:e.module_id}))){this.moduleBusy=e.id;try{await this.moduleApi(`/api/admin/modules/${encodeURIComponent(e.id)}/approve`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({manifest_digest:e.manifest_digest,approved_capabilities:e.requested_host_capabilities||[]})}),await this.loadModules(),await this.refreshResidentIntegrationStatuses(),this.showToast(this.t("moduleApproved"),"success")}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}finally{this.moduleBusy=null}}},async runModuleAction(e,t){if(this.isAdmin()&&!this.moduleBusy){this.moduleBusy=e.id;try{await this.moduleApi(`/api/admin/modules/${encodeURIComponent(e.id)}/${t}`,{method:"POST"}),await this.loadModules(),await this.refreshResidentIntegrationStatuses(),this.showToast(this.t("moduleActionCompleted",{action:this.actionLabel(t)}),"success")}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}finally{this.moduleBusy=null}}},moduleConfigFields(){return Object.entries(this.moduleConfig?.schema?.properties||{}).map(([e,t])=>({name:e,schema:t,secret:!0===t?.["x-chatraw-secret"]}))},async openModuleConfig(e){if(this.isAdmin()){this.moduleBusy=e.id;try{const t=await this.moduleApi(`/api/admin/modules/${encodeURIComponent(e.id)}/config`);this.moduleConfig={...t,moduleId:e.id,moduleName:e.name},this.moduleConfigValues={...t.values||{}},this.moduleSecretActions={};for(const e of Object.entries(t.schema?.properties||{}))!0===e[1]?.["x-chatraw-secret"]?this.moduleSecretActions[e[0]]={action:"keep",value:""}:void 0===this.moduleConfigValues[e[0]]&&void 0!==e[1]?.default&&(this.moduleConfigValues[e[0]]=e[1].default)}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}finally{this.moduleBusy=null}}},async saveModuleConfig(){if(!this.isAdmin()||!this.moduleConfig||this.moduleBusy)return;this.moduleBusy=this.moduleConfig.moduleId;const e=this.moduleConfig.moduleId,t={};for(const[e,s]of Object.entries(this.moduleSecretActions))t[e]="replace"===s.action?{action:"replace",value:s.value}:{action:s.action};try{const s=await this.moduleApi(`/api/admin/modules/${encodeURIComponent(this.moduleConfig.moduleId)}/config`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({revision:this.moduleConfig.revision,values:this.moduleConfigValues,secrets:t})});if(this.moduleConfig=null,s.configured)try{await this.recheckModuleAfterConfig(e)}catch(e){console.warn("[Module configuration recheck]",e?.code||e?.name||"failed")}await this.loadModules(),await this.refreshResidentIntegrationStatuses(),this.showToast(this.t("configurationSavedRevision",{revision:s.revision}),"success")}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}finally{this.moduleBusy=null}},async recheckModuleAfterConfig(e){let t=null;for(let s=0;s<6;s+=1){if(s>0&&await new Promise(e=>setTimeout(e,750)),t=await this.moduleApi(`/api/admin/modules/${encodeURIComponent(e)}/check`,{method:"POST"}),"ready"===t.ready_status)return t;if("incompatible"===t.health_status||"missing"===t.config_status)return t}return t},async disconnectModule(e){if(!this.isAdmin()||this.moduleBusy)return;const t=window.prompt(this.t("disconnectModulePrompt",{moduleId:e.module_id}));if(t===e.module_id){this.moduleBusy=e.id;try{await this.moduleApi(`/api/admin/modules/${encodeURIComponent(e.id)}/disconnect`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({confirmation:t})}),await this.loadModules(),await this.refreshResidentIntegrationStatuses(),this.showToast(this.t("moduleDisconnected"),"success")}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}finally{this.moduleBusy=null}}},async purgeModuleData(e){if(!this.isAdmin()||!e.supports_data_purge||this.moduleBusy)return;const t=`PURGE ${e.module_id}`,s=window.prompt(this.t("purgeModulePrompt",{confirmation:t}));if(s===t){this.moduleBusy=e.id;try{await this.moduleApi(`/api/admin/modules/${encodeURIComponent(e.id)}/purge-data`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({confirmation:s})}),await this.loadModules(),this.showToast(this.t("moduleDataPurged"),"success")}catch(e){this.showToast(this.localizeError(e,"moduleOperationFailed"),"error")}finally{this.moduleBusy=null}}},openSettingsPanel(){this.showPlugins=!1,this.settingsTab=this.isAdmin()?"models":"account",this.showSettings=!0,this.isGenerating&&this.stopGeneration(),this.agentOpen=!1,this.agentExpanded=!1,this.agentHistoryOpen=!1,this.showExtensionPalette=!1,this.pluginWorkspace.show&&this.closeActivePluginWorkspace(),this.isAdmin()&&(this.loadAdminUsers(),this.loadModules())},async openPluginsPanel(){this.isAdmin()&&(this.showSettings=!1,this.showPlugins=!0,this.showExtensionPalette=!1,await this.loadInstalledPlugins(),this.loadPluginMarket())},applyTheme(){this.settings.ui_settings.theme_mode="light",document.documentElement.setAttribute("data-theme","light")},async loadSettings(){try{const e=await fetch("/api/settings");if(e.ok){const t=await e.json();t.chat_settings&&(this.settings.chat_settings={...this.settings.chat_settings,...t.chat_settings}),t.rag_settings&&(this.settings.rag_settings={...this.settings.rag_settings,...t.rag_settings}),t.ui_settings&&(this.settings.ui_settings={...this.settings.ui_settings,...t.ui_settings},this.settings.ui_settings.theme_mode="light")}this.loadLogo()}catch(e){console.error("Failed to load settings:",e)}},async loadLogo(){try{const e=await fetch("/api/settings/logo");if(e.ok){const t=await e.json();this.settings.ui_settings.logo_data=t.logo_data||"","string"==typeof t.logo_text&&(this.settings.ui_settings.logo_text=t.logo_text),this.updateFavicon(this.settings.ui_settings.logo_data)}}catch(e){}},async loadModels(){try{const e=await fetch("/api/models");e.ok&&(this.models=await e.json(),this.models.forEach(e=>{e.api_key="",e.api_key_action="preserve",e.capability||(e.capability={vision:!1,reasoning:!1,tools:!1})}),this.syncVisionCapabilityState())}catch(e){console.error("Failed to load models:",e)}},async loadChats(){return this.loadAgentChats()},async loadAgentChats(){try{const e=await fetch("/api/agent/chats");e.ok&&(this.chats=await e.json()||[])}catch(e){console.error("Failed to load chats:",e)}},async loadDocuments(){try{const e=await fetch("/api/documents");e.ok&&(this.documents=await e.json()||[])}catch(e){console.error("Failed to load documents:",e)}},async createNewChat(){this.isGenerating&&this.stopGeneration(),this.showExtensionPalette=!1;try{const e=await fetch("/api/agent/chats",{method:"POST"});if(e.ok){const t=await e.json();this.chats.unshift(t),await this.selectChat(t.id),this.agentHistoryOpen=!1,this.announceAgentChatChange("created",t.id)}}catch(e){this.showToast(this.t("createChatFailed"),"error")}},async ensureCurrentChat(e){if(this.currentChatId)return this.currentChatId;const t=await fetch("/api/agent/chats",{method:"POST",signal:e});if(!t.ok)throw new Error(this.t("createChatFailed"));const s=await t.json();if(!s||"string"!=typeof s.id||!s.id)throw new Error(this.t("createChatFailed"));return this.chats.unshift(s),this.currentChatId=s.id,s.id},async selectChat(e){this.showExtensionPalette=!1,this.currentChatId!==e?(this.isGenerating&&this.stopGeneration(),this.currentChatId=e,this.messages=[],await this.loadMessages(e),this.agentHistoryOpen=!1):this.agentHistoryOpen=!1},async loadMessages(e){try{const t=await fetch(`/api/agent/chats/${e}/messages`);if(!t.ok)throw 404===t.status&&this.currentChatId===e&&(this.currentChatId=null,this.messages=[],await this.loadAgentChats()),new Error(`Agent history request failed (${t.status})`);if(this.currentChatId===e){const s=await t.json()||[];let i=[];if(window.ChatRaw?.modules)try{i=await window.ChatRaw.modules.listTasks({chat_id:e,limit:100})}catch(e){console.error("Failed to load conversation tasks:",e)}this.messages=this.mergeConversationTaskMessages(s,i);for(const e of i)MODULE_TERMINAL_STATES.has(e.state)||this.moduleTaskSubscriptions[e.task_id]||window.ChatRaw.modules.subscribe(e.task_id);this.$nextTick(()=>this.scrollToBottom())}}catch(t){console.error("Failed to load messages:",t),this.currentChatId===e&&(this.messages=[])}},async deleteChat(e){if(confirm(this.t("deleteSharedChat")))try{this.currentChatId===e&&this.isGenerating&&this.stopGeneration();const t=await fetch(`/api/agent/chats/${e}`,{method:"DELETE"});if(!t.ok){const e=await t.json();throw new Error(e.detail||this.t("deleteFailed"))}this.chats=this.chats.filter(t=>t.id!==e),this.currentChatId===e&&(this.currentChatId=null,this.messages=[]),this.announceAgentChatChange("deleted",e)}catch(e){this.showToast(this.t("deleteFailed"),"error")}},async renameChat(e){const t=window.prompt(this.t("renameSharedChat"),e.title);if(!t||t.trim()===e.title)return;const s=await fetch(`/api/agent/chats/${encodeURIComponent(e.id)}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:t.trim()})}),i=await s.json();s.ok?(e.title=i.title,this.announceAgentChatChange("renamed",e.id)):this.showToast(this.localizeError(i,"renameFailed"),"error")},async clearAllChats(){if(confirm(this.t("confirmClearAll"))){this.isGenerating&&this.stopGeneration();try{for(const e of this.chats){if(!(await fetch(`/api/agent/chats/${e.id}`,{method:"DELETE"})).ok)throw new Error(this.t("deleteFailed"))}this.chats=[],this.currentChatId=null,this.messages=[],this.announceAgentChatChange("cleared",""),this.showToast(this.t("allChatsCleared"),"success")}catch(e){this.showToast(this.t("deleteFailed"),"error")}}},stopGeneration(){this.abortController&&(this.markActiveHermesRunCancelled(),this.abortController.abort(),this.abortController=null,this.isGenerating=!1)},getLocalizedText(e,t=""){return e?"string"==typeof e?e:"object"==typeof e?e[this.lang]||e.en||e.zh||Object.values(e).find(Boolean)||t:String(e):t},escapeComposerHtml:e=>String(e||"").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"),getComposerSelection(){const e=this.$refs.inputBox,t=(this.inputMessage||"").length;return{start:e?e.selectionStart:t,end:e?e.selectionEnd:t}},setComposerValue(e,t=null,s=null){this.inputMessage=e,this.$nextTick(()=>{const i=this.$refs.inputBox;if(i){if(i.value=e,Number.isInteger(t)){const e=Number.isInteger(s)?s:t;i.setSelectionRange(t,e)}i.dispatchEvent(new Event("input",{bubbles:!0})),this.autoResize(i)}})},replaceComposerRange(e,t,s){const i=this.inputMessage||"",n=Math.max(0,Math.min(e,i.length)),a=Math.max(n,Math.min(t,i.length)),o=i.slice(0,n)+s+i.slice(a),r=n+s.length;return this.setComposerValue(o,r,r),!0},insertComposerText(e){const t=this.getComposerSelection();return this.replaceComposerRange(t.start,t.end,e)},focusComposer(){this.$nextTick(()=>this.$refs.inputBox?.focus())},handleComposerInput(e){this.inputMessage=e.target.value,this.autoResize(e.target),this.syncComposerHighlightScroll(e),this.isComposing||this.queueCompletionRefresh()},syncComposerHighlightScroll(e=null){const t=e?.target||this.$refs.inputBox,s=this.$refs.composerHighlight;t&&s&&(s.scrollTop=t.scrollTop,s.scrollLeft=t.scrollLeft)},deleteComposerSkillToken(e){const t=this.getComposerSelection();if(t.start!==t.end)return!1;const s=this.inputMessage||"",i=t.start,n=this.getComposerSkillTokenRanges(s,{knownOnly:!0});for(const t of n){let n=t.start,a=t.end,o=!1;if("Backspace"===e?(o=i>t.start&&i<=t.end,!o&&i===t.end+1&&/[ \t]/.test(s[t.end]||"")&&(o=!0,a=t.end+1)):"Delete"===e&&(o=i>=t.start&&i{const e=this.$refs.composerSuggestions,t=e?.querySelector(".composer-suggestion-item.active");t?.scrollIntoView({block:"nearest"})})},async acceptHighlightedCompletion(){const e=this.completionItems[this.completionActiveIndex];e&&await this.acceptCompletion(e)},isCursorInFencedCode(e,t){const s=e.slice(0,t).match(/```/g);return Boolean(s&&s.length%2==1)},isCursorInMarkdownLink(e,t){const s=e.slice(Math.max(0,t-160),t),i=s.lastIndexOf("["),n=s.lastIndexOf("]("),a=s.lastIndexOf(")");return Math.max(i,n)>a},detectSlashToken(e,t){if(!Number.isInteger(t))return null;if(this.isCursorInFencedCode(e,t)||this.isCursorInMarkdownLink(e,t))return null;const s=e.slice(0,t),i=s.lastIndexOf("\n")+1,n=s.slice(i),a=n.match(/(^|\s)(\/\S*)$/);if(!a)return null;const o=a[2];if(!/^\/[a-z0-9-]*$/.test(o))return null;const r=o.slice(1);if(COMMON_PATH_ROOTS.has(r.toLowerCase()))return null;return{start:i+n.length-o.length,end:t,query:r,trigger:"/"}},queueCompletionRefresh(e=80){this.completionDebounceTimer&&clearTimeout(this.completionDebounceTimer),this.completionDebounceTimer=setTimeout(()=>{this.completionDebounceTimer=null,this.refreshCompletions()},e)},async refreshCompletions(){const e=this.$refs.inputBox;if(!e||this.isComposing)return;const t=this.detectSlashToken(this.inputMessage||"",e.selectionStart);if(!t)return void this.closeCompletionMenu();const s=++this.completionRequestId;this.completionRange=t,this.completionQuery=t.query;const i={trigger:t.trigger,query:t.query,range:{...t},value:this.inputMessage||"",selection:this.getComposerSelection()},n=this.getActiveCompletionProviders(t.trigger),a=[];for(const e of n){try{const t=await Promise.resolve(e.getItems(i));if(s!==this.completionRequestId)return;for(const s of t||[])if(s&&s.id&&s.label&&(a.push({...s,providerId:e.fullId,pluginId:e.pluginId,source:s.source||e.source||e.pluginId,icon:s.icon||e.icon||"ri-command-line"}),a.length>=20))break}catch(t){console.error(`[Completion ${e.fullId}] Error:`,t)}if(a.length>=20)break}s===this.completionRequestId&&(this.completionItems=a.slice(0,20),this.completionActiveIndex=0,this.completionMenuOpen=this.completionItems.length>0,this.completionMenuOpen&&this.scrollActiveCompletionIntoView())},closeCompletionMenu(){this.completionDebounceTimer&&(clearTimeout(this.completionDebounceTimer),this.completionDebounceTimer=null),this.completionRequestId+=1,this.completionMenuOpen=!1,this.completionItems=[],this.completionActiveIndex=0,this.completionQuery="",this.completionRange=null},getActiveCompletionProviders(e="/"){const t=[],s=this.getSkillCompletionProvider(e);s&&t.push(s);for(const s of Object.values(this.completionProviders)){if(s.trigger!==e)continue;const i=this.installedPlugins.find(e=>e.id===s.pluginId);i&&!1!==i.enabled&&t.push(s)}return t},registerCompletionProvider(e,t=null){const s=t||this._currentLoadingPlugin||e?.pluginId;if(!s||!e?.id||"function"!=typeof e.getItems)return console.warn("[Plugin Input] Completion provider requires plugin id, id, and getItems"),!1;const i=`${s}:${e.id}`;return this.completionProviders[i]={...e,fullId:i,pluginId:s,trigger:e.trigger||"/"},!0},unregisterCompletionProvider(e,t=null){const s=t||this._currentLoadingPlugin;if(!s||!e)return!1;const i=`${s}:${e}`,n=Boolean(this.completionProviders[i]);return delete this.completionProviders[i],this.completionItems.some(e=>e.providerId===i)&&this.closeCompletionMenu(),n},unregisterPluginCompletions(e){let t=!1;for(const s of Object.keys(this.completionProviders))this.completionProviders[s].pluginId===e&&(delete this.completionProviders[s],t=t||this.completionItems.some(e=>e.providerId===s));"skill-manager"===e&&(this.invalidateSkillCatalog(),t=!0),t&&this.closeCompletionMenu()},async acceptCompletion(e){if(!e)return;const t={query:this.completionQuery,range:this.completionRange?{...this.completionRange}:null,value:this.inputMessage||"",selection:this.getComposerSelection()};"function"==typeof e.onSelect&&await e.onSelect(e,t),this.closeCompletionMenu(),this.focusComposer()},isSkillManagerEnabled(){return this.installedPlugins.some(e=>"skill-manager"===e.id&&!0===e.enabled)},invalidateSkillCatalog(){return this.skillCatalogCache=null,this.skillCatalogLoadedAt=0,this.completionItems.some(e=>"__host:skill-manager-skills"===e.providerId)&&this.closeCompletionMenu(),!0},getSkillCompletionProvider(e){return"/"===e&&this.isSkillManagerEnabled()?{fullId:"__host:skill-manager-skills",pluginId:"skill-manager",source:"skills",icon:"ri-sparkling-2-line",trigger:"/",getItems:async e=>this.getSkillCompletionItems(e)}:null},async loadSkillCatalogForCompletion(){if(!this.isSkillManagerEnabled())return[];const e=Date.now();if(this.skillCatalogCache&&e-this.skillCatalogLoadedAt<3e4)return this.skillCatalogCache;try{const t=await fetch("/api/skills");if(!t.ok)return[];const s=await t.json();return this.skillCatalogCache=Array.isArray(s.skills)?s.skills:[],this.skillCatalogLoadedAt=e,this.skillCatalogCache}catch(e){return console.error("Failed to load skill catalog:",e),[]}},skillAliases(e){const t=e?.metadata||{},s=[];for(const e of["alias","aliases"]){const i=t[e];Array.isArray(i)?s.push(...i.filter(e=>"string"==typeof e)):"string"==typeof i&&s.push(...i.split(",").map(e=>e.trim()).filter(Boolean))}return"string"==typeof t.display_name&&s.push(t.display_name),s},rankSkillCompletion(e,t){const s=(t||"").toLowerCase(),i=(e.name||"").toLowerCase();if(!i||RESERVED_SLASH_COMMANDS.has(i)||COMMON_PATH_ROOTS.has(i))return null;if(!s)return 100;if(RESERVED_SLASH_COMMANDS.has(s)||COMMON_PATH_ROOTS.has(s))return null;if(i===s)return 0;if(i.startsWith(s))return 10;if(i.includes(s))return 20;const n=this.skillAliases(e).map(e=>e.toLowerCase());if(n.some(e=>e===s))return 30;if(n.some(e=>e.startsWith(s)))return 35;if(n.some(e=>e.includes(s)))return 40;const a=(e.description||"").toLowerCase(),o=s.split(/[-\s]+/).filter(Boolean);return o.length&&o.every(e=>a.includes(e))?50:null},async getSkillCompletionItems(e){return(await this.loadSkillCatalogForCompletion()).map(t=>({skill:t,rank:this.rankSkillCompletion(t,e.query)})).filter(e=>null!==e.rank).sort((e,t)=>e.rank-t.rank||e.skill.name.localeCompare(t.skill.name)).slice(0,20).map(({skill:e})=>({id:`skill:${e.name}`,label:`/${e.name}`,description:e.description||"",source:"skills",icon:"ri-sparkling-2-line",type:"skill",metadata:{skill:e},onSelect:(t,s)=>this.selectSkillCompletion(e,s.range)}))},selectSkillCompletion(e,t){if(!e?.name||!t)return;const s=this.inputMessage||"";let i=t.start,n=t.end;for(;s[n]&&!/\s/.test(s[n]);)n+=1;const a=s[n]&&/\s/.test(s[n])?"":" ";this.replaceComposerRange(i,n,`/${e.name}${a}`),this.closeCompletionMenu()},isValidComposerSkillName:e=>SKILL_NAME_PATTERN.test(e)&&!RESERVED_SLASH_COMMANDS.has(e)&&!COMMON_PATH_ROOTS.has(e),isKnownComposerSkillName(e){return!(!this.isValidComposerSkillName(e)||!Array.isArray(this.skillCatalogCache))&&this.skillCatalogCache.some(t=>t?.name===e&&!1!==t.enabled)},getComposerSkillTokenRanges(e,t={}){const s=String(e||""),i=s.matchAll(/(^|\s)\/([a-z0-9][a-z0-9-]{0,62})(?=$|\s)/g),n=new Set,a=[];for(const e of i){const i=e[2];if(!this.isValidComposerSkillName(i))continue;if(t.knownOnly&&!this.isKnownComposerSkillName(i))continue;const o=!n.has(i);if(o&&n.size>=5)continue;o&&n.add(i);const r=e.index+e[1].length;a.push({name:i,start:r,end:r+i.length+1,text:s.slice(r,r+i.length+1)})}return a},renderComposerHighlights(e){const t=String(e||"");if(!t)return"";const s=this.getComposerSkillTokenRanges(t,{knownOnly:!0});if(!s.length)return this.escapeComposerHtml(t);let i=0,n="";for(const e of s)n+=this.escapeComposerHtml(t.slice(i,e.start)),n+=`${this.escapeComposerHtml(e.text)}`,i=e.end;return n+=this.escapeComposerHtml(t.slice(i)),n},async extractActiveSkillNamesFromText(e){if(!this.isSkillManagerEnabled())return[];await this.loadSkillCatalogForCompletion();const t=new Set,s=[];for(const i of this.getComposerSkillTokenRanges(e,{knownOnly:!0}))t.has(i.name)||(t.add(i.name),s.push(i.name));return s},async prepareOutgoingMessage(){const e=(this.inputMessage||"").trim();return e?{message:e,activeSkillNames:await this.extractActiveSkillNamesFromText(e)}:{message:e,activeSkillNames:[]}},async sendMessage(){if(this.isGenerating)return;this.isGenerating=!0;const e=new AbortController;this.abortController=e;try{const t=await this.prepareOutgoingMessage();if(e.signal.aborted)return;let s=t.message;const i=[...t.activeSkillNames];if(!s)return;if(await this.ensureCurrentChat(e.signal),e.signal.aborted)return;this.inputMessage="",this.closeCompletionMenu(),this.$nextTick(()=>this.autoResize(this.$refs.inputBox)),this.messages.push({role:"user",content:s}),this.$nextTick(()=>this.scrollToBottom());let n="",a="";this.parsedUrl&&(n=this.parsedUrl.content,a=this.parsedUrl.url),this.attachedDocument&&(n&&(n+="\n\n---\n\n"),n+=this.attachedDocument.content,a=a?a+", "+this.attachedDocument.filename:this.attachedDocument.filename);let o={chat_id:this.currentChatId,message:s,use_rag:this.useRAG,image_base64:this.uploadedImageBase64,web_content:n,web_url:a};const r=await this.callHook("before_send",{...o});if(e.signal.aborted)return;if(r?.success&&r.body){const e=r.body;for(const t of["use_rag"])"boolean"==typeof e[t]&&(o[t]=e[t]);for(const t of["web_content","web_url"])"string"==typeof e[t]&&(o[t]=e[t])}if(o.chat_id=this.currentChatId,o.message=s,i.length>0?o.active_skills=i:delete o.active_skills,e.signal.aborted)return;this.removeUploadedImage(),this.removeParsedUrl(),this.removeAttachedDocument(),this.settings.chat_settings.stream?await this.handleStreamResponse(o,e.signal):await this.handleNormalResponse(o,e.signal),await this.loadChats()}catch(e){if("AbortError"===e.name);else{if(console.error("Failed to send message:",e),this.currentChatId)try{await this.loadMessages(this.currentChatId)}catch(e){console.error("Failed to recover persisted messages:",e)}this.showToast(this.t("sendFailed"),"error")}}finally{this.abortController===e?(this.abortController=null,this.isGenerating=!1):null===this.abortController&&(this.isGenerating=!1)}},createHermesRunState(e=""){return{runId:"",status:"",events:[],pendingApproval:null,approvalSubmitting:!1,approvalError:"",approvalBridgeResolvedKey:"",chatId:e||this.currentChatId||""}},ensureHermesRunState(e){return e.hermesRun||(e.hermesRun=this.createHermesRunState()),e.hermesRun.events||(e.hermesRun.events=[]),!e.hermesRun.chatId&&this.currentChatId&&(e.hermesRun.chatId=this.currentChatId),e.hermesRun},normalizeHermesRunEvent(e){if(!e||"object"!=typeof e||Array.isArray(e))return null;const t="string"==typeof e.type?e.type:"";if(!t)return null;const s={type:t},i=["run_id","status","tool","preview","error","command","description","choice"];for(const t of i)void 0!==e[t]&&null!==e[t]&&(s[t]=String(e[t]));if(Array.isArray(e.pattern_keys)&&(s.pattern_keys=e.pattern_keys.map(e=>String(e)).filter(Boolean).slice(0,20)),Array.isArray(e.choices)&&(s.choices=e.choices.map(e=>String(e)).filter(Boolean)),Number.isFinite(e.duration_ms)&&(s.duration_ms=Math.round(e.duration_ms)),("boolean"==typeof e.resolved||Number.isFinite(e.resolved))&&(s.resolved=e.resolved),e.usage&&"object"==typeof e.usage&&!Array.isArray(e.usage)){s.usage={};for(const[t,i]of Object.entries(e.usage))Number.isFinite(i)&&(s.usage[t]=i)}return s},hermesApprovalRequestKey(e){if(!e)return"";const t=Array.isArray(e.pattern_keys)?e.pattern_keys.join("|"):"";return[e.run_id||"",e.command||"",e.description||"",t].join("\n")},isHermesRunTerminalStatus:e=>["completed","succeeded","failed","cancelled","canceled","stopped"].includes(e||""),markActiveHermesRunCancelled(){for(let e=this.messages.length-1;e>=0;e-=1){const t=this.messages[e],s=t?.hermesRun;if(s&&Array.isArray(s.events)&&s.events.length){if(this.isHermesRunTerminalStatus(s.status))return;return void this.applyHermesRunEvent(t,{type:"run.cancelled",run_id:s.runId||"",status:"cancelled"},e)}}},applyHermesRunEvent(e,t,s=-1){const i=this.normalizeHermesRunEvent(t);if(!i)return;const n=this.ensureHermesRunState(e);"approval.request"===i.type&&(n.pendingApproval&&this.hermesApprovalRequestKey(n.pendingApproval)===this.hermesApprovalRequestKey(i)||n.approvalBridgeResolvedKey===this.hermesApprovalRequestKey(i))||(i._id=`${i.type}-${n.events.length}`,i.run_id&&(n.runId=i.run_id),i.status&&(n.status=i.status),"approval.request"===i.type?(n.status="pending_approval",n.pendingApproval=i,n.approvalSubmitting=!1,n.approvalError=""):"approval.responded"===i.type?(n.pendingApproval=null,n.approvalSubmitting=!1,n.approvalError="",n.approvalBridgeResolvedKey="",n.status=i.status||"running"):"run.completed"===i.type?(n.pendingApproval=null,n.approvalSubmitting=!1,n.approvalError="",n.approvalBridgeResolvedKey="",n.status="completed"):"run.failed"===i.type?(n.pendingApproval=null,n.approvalSubmitting=!1,n.approvalBridgeResolvedKey="",n.status="failed"):"run.cancelled"===i.type?(n.pendingApproval=null,n.approvalSubmitting=!1,n.approvalBridgeResolvedKey="",n.status="cancelled"):"run.started"!==i.type||n.status||(n.status=i.status||"running"),n.events.push(i),this.commitHermesRunMessage(e,s))},commitHermesRunMessage(e,t=-1){e.hermesRun&&(e.hermesRun={...e.hermesRun,events:[...e.hermesRun.events],pendingApproval:e.hermesRun.pendingApproval?{...e.hermesRun.pendingApproval}:null},t>=0&&(this.messages[t]={...e}),this.$nextTick(()=>this.scrollToBottom()))},hermesRunStatusLabel(e){const t=String(e||"").toLowerCase();return{started:this.t("hermesRunStatusStarted"),queued:this.t("hermesRunStatusStarted"),running:this.t("hermesRunStatusRunning"),in_progress:this.t("hermesRunStatusRunning"),pending_approval:this.t("hermesRunStatusPendingApproval"),completed:this.t("hermesRunStatusCompleted"),succeeded:this.t("hermesRunStatusCompleted"),failed:this.t("hermesRunStatusFailed"),cancelled:this.t("hermesRunStatusCancelled"),canceled:this.t("hermesRunStatusCancelled")}[t]||e||this.t("hermesRunStatusRunning")},hermesRunEventTitle(e){return{"run.started":this.t("hermesRunEventRunStarted"),"tool.started":this.t("hermesRunEventToolStarted"),"tool.completed":this.t("hermesRunEventToolCompleted"),"reasoning.available":this.t("hermesRunEventReasoning"),"approval.request":this.t("hermesRunEventApprovalRequest"),"approval.responded":this.t("hermesRunEventApprovalResponded"),"run.completed":this.t("hermesRunEventRunCompleted"),"run.failed":this.t("hermesRunEventRunFailed"),"run.cancelled":this.t("hermesRunEventRunCancelled")}[e?.type]||e?.type||this.t("hermesRun")},hermesRunEventIcon:e=>({"run.started":"ri-play-circle-line","tool.started":"ri-tools-line","tool.completed":"ri-checkbox-circle-line","reasoning.available":"ri-lightbulb-line","approval.request":"ri-shield-keyhole-line","approval.responded":"ri-check-double-line","run.completed":"ri-checkbox-circle-line","run.failed":"ri-error-warning-line","run.cancelled":"ri-stop-circle-line"}[e?.type]||"ri-terminal-box-line"),hermesRunEventClass(e){const t=String(e?.type||"event").replace(/[^a-z0-9_-]+/gi,"-").toLowerCase(),s=String(e?.status||"").replace(/[^a-z0-9_-]+/gi,"-").toLowerCase();return{[`hermes-event-${t}`]:!0,[`status-${s}`]:Boolean(s)}},formatHermesDuration:e=>Number.isFinite(e)?e<1e3?`${e} ms`:`${(e/1e3).toFixed(e<1e4?1:0)} s`:"",hasHermesDuration:e=>Number.isFinite(e?.duration_ms),isHermesPendingApprovalEvent(e,t){const s=e?.hermesRun?.pendingApproval;return Boolean(s&&t&&s._id===t._id)},async submitHermesApproval(e,t,s=-1){const i=s>=0?s:this.messages.indexOf(e);if(i>=0&&this.messages[i]!==e&&this.messages[i]?.hermesRun?.runId!==e?.hermesRun?.runId)return;const n=i>=0?this.messages[i]:e,a=n?.hermesRun,o=a?.runId,r=a?.chatId;if(a&&o&&r&&!a.approvalSubmitting){a.approvalSubmitting=!0,a.approvalError="",this.commitHermesRunMessage(n,i);try{const e=await fetch(`/api/hermes/runs/${encodeURIComponent(o)}/approval`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({chat_id:r,choice:t,resolve_all:!1})});let s={};try{s=await e.json()}catch(e){s={}}if(!e.ok||!1===s.success)throw new Error(s.error||this.t("hermesApprovalError"));a.status=s.status||a.status||"running",a.approvalBridgeResolvedKey=this.hermesApprovalRequestKey(a.pendingApproval),a.pendingApproval=null,a.approvalSubmitting=!1,a.approvalError="",this.commitHermesRunMessage(n,i)}catch(e){a.approvalSubmitting=!1,a.approvalError=this.t("hermesApprovalError"),this.commitHermesRunMessage(n,i)}}},async handleStreamResponse(e,t=this.abortController?.signal){const s=await fetch("/api/agent/chat",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:t});if(!s.ok){await s.text();throw new Error(this.t("requestFailed"))}const i={role:"assistant",content:"",thinking:"",references:[],showThinking:!1,hermesRun:this.createHermesRunState()};this.messages.push(i);const n=this.messages.length-1,a=s.body.getReader(),o=new TextDecoder;let r="";try{for(;;){const{done:e,value:t}=await a.read();if(e)break;let s;for(r+=o.decode(t,{stream:!0});-1!==(s=r.indexOf("\n"));){const e=r.slice(0,s).trim();if(r=r.slice(s+1),e)try{const t=JSON.parse(e);if(t.chat_id&&(this.currentChatId=t.chat_id,i.hermesRun&&(i.hermesRun.chatId=t.chat_id)),t.hermes_run&&this.applyHermesRunEvent(i,t.hermes_run,n),t.thinking){const e=this.messages[n]?.showThinking||!1;i.thinking+=t.thinking,i.showThinking=e,this.messages[n]={...i},this.$nextTick(()=>this.scrollToBottom())}if(t.content){const e=this.messages[n]?.showThinking||!1;i.content+=t.content,i.showThinking=e,this.messages[n]={...i},this.$nextTick(()=>this.scrollToBottom())}if(t.references){const e=this.messages[n]?.showThinking||!1;i.references=t.references,i.showThinking=e,this.messages[n]={...i},this.$nextTick(()=>this.scrollToBottom())}t.error&&(i.content="Error: "+t.error,this.messages[n]={...i})}catch(e){}}}}catch(e){if("AbortError"!==e.name)throw e;try{await a.cancel()}catch(e){}}},async handleNormalResponse(e,t=this.abortController?.signal){const s=await fetch("/api/agent/chat",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:t}),i=await s.json();if(!s.ok)throw new Error(this.t("requestFailed"));!this.currentChatId&&i.chat_id&&(this.currentChatId=i.chat_id),this.messages.push({role:"assistant",content:i.content,thinking:i.thinking||"",references:i.references||[]}),this.$nextTick(()=>this.scrollToBottom())},async handleImageUpload(e){const t=e.target.files[0];if(t)if(this.chatModelSupportsVision()){try{const e=await this.compressImage(t),s=new FormData;s.append("file",e);const i=await fetch("/api/upload/image",{method:"POST",body:s});if(!i.ok){const e=await i.json();throw new Error(e.error)}{const e=await i.json();this.uploadedImage=t.name,this.uploadedImageBase64=e.base64}}catch(e){this.showToast(this.t("uploadFailed"),"error")}e.target.value=""}else e.target.value=""},handleLogoUpload(e){const t=e.target.files[0];if(!t)return;const s=new FileReader;s.onload=e=>{this.settings.ui_settings.logo_data=e.target.result,this.updateFavicon(e.target.result)},s.readAsDataURL(t),e.target.value=""},removeCustomLogo(){this.settings.ui_settings.logo_data="",this.updateFavicon("")},handleLogoImageError(e){const t=e?.currentTarget;if(!t)return;const s=t.getAttribute("src")||"";s.endsWith("/brand-mark.svg")||"brand-mark.svg"===s||(t.src="/brand-mark.svg")},updateFavicon(e){const t=document.getElementById("favicon");t.href=e||"/brand-mark.svg",this.updateDocumentTitle()},handleAvatarUpload(e,t){const s=e.target.files[0];if(!s)return;const i=new FileReader;i.onload=e=>{"user"===t?this.settings.ui_settings.user_avatar=e.target.result:this.settings.ui_settings.assistant_avatar=e.target.result},i.readAsDataURL(s),e.target.value=""},removeUploadedImage(){this.uploadedImage=null,this.uploadedImageBase64=""},async handleDocumentUpload(e){const t=e.target.files[0];if(t){this.isUploadingDocument=!0;try{const e="."+(t.name.split(".").pop()||"").toLowerCase(),s=this.pluginHooks.parse_document||[];let i=!1;for(const n of s){if(n._pluginId){const e=this.installedPlugins.find(e=>e.id===n._pluginId);if(!e||!e.enabled)continue}const s=n.fileTypes||[];if(s.includes(e)||s.includes(e.toLowerCase()))try{const e=n._pluginId?this.getPluginSettings(n._pluginId):{},s=await n.handler(t,e);if(s?.success&&s.content){this.attachedDocument={filename:t.name,content:s.content},this.showToast(this.t("documentAttached")+": "+t.name,"success"),i=!0;break}if(s?.error){this.showToast(this.t("uploadFailed"),"error"),i=!0;break}}catch(e){console.error(`[Plugin ${n._pluginId}] Parse error:`,e)}}if(!i){const e=new FormData;e.append("file",t);const s=await fetch("/api/upload/document",{method:"POST",body:e});if(!s.ok){const e=await s.text();try{const t=JSON.parse(e);throw new Error(t.error||`HTTP ${s.status}`)}catch{throw new Error(`HTTP ${s.status}: ${e.substring(0,100)}`)}}const i=await s.json();i.success?(this.attachedDocument={filename:i.filename,content:i.content},this.showToast(this.t("documentAttached")+": "+i.filename,"success")):this.showToast(this.t("uploadFailed"),"error")}}catch(e){console.error("Document upload error:",e),this.showToast(this.t("uploadFailed"),"error")}finally{this.isUploadingDocument=!1}e.target.value=""}},removeAttachedDocument(){this.attachedDocument=null},getDocumentAcceptTypes(){const e=this.installedPlugins.filter(e=>e.enabled&&e.fileTypes).flatMap(e=>e.fileTypes);return[...new Set([".pdf",".docx",".doc",".txt",".md",...e])].join(",")},toggleUrlInput(){this.showExtensionPalette=!1,this.showUrlInput=!this.showUrlInput,this.showUrlInput&&(this.urlInputValue="",this.$nextTick(()=>{const e=document.querySelector(".url-input-popup input");e&&e.focus()}))},async parseUrl(){const e=this.urlInputValue.trim();if(e){this.isParsingUrl=!0;try{let t=null;const s=this.pluginHooks.parse_url||[];let i=null;for(const e of s)if(e._pluginId){const t=this.installedPlugins.find(t=>t.id===e._pluginId);if(t&&t.enabled){i=t.id;break}}if(i){const s=this.getPluginSettings(i);if("browser"===(s.parser_mode||"browser")){const i=await fetch("/api/fetch-raw-url",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:e})}),n=await i.json();if(n.success&&null!=n.html){const i=await this.callHook("parse_url",e,n.html,s);i?.success&&null!=i.title&&null!=i.content&&(t={success:!0,title:i.title,content:i.content,url:e})}}else{const i=await this.callHook("parse_url",e,null,s);i?.success&&null!=i.title&&null!=i.content&&(t={success:!0,title:i.title,content:i.content,url:e})}}if(!t){const s=await fetch("/api/parse-url",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:e})});t=await s.json()}t.success?(this.parsedUrl={title:t.title,content:t.content,url:t.url},this.parsedUrlTitle=t.title,this.showUrlInput=!1,this.urlInputValue="",this.showToast(this.t("urlAttached")+": "+t.title,"success")):this.showToast(this.t("parseFailed"),"error")}catch(e){this.showToast(this.t("parseFailed"),"error")}finally{this.isParsingUrl=!1}}},removeParsedUrl(){this.parsedUrl=null,this.parsedUrlTitle=""},cancelUrlInput(){this.showUrlInput=!1,this.urlInputValue=""},async saveAndVerifyModel(e,t){e.status="loading",e.verifyMessage="";try{e.name||(e.name=e.model_id||"Unnamed");const t=await fetch("/api/models",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...e,api_key_action:"clear"===e.api_key_action?"clear":e.api_key?"replace":"preserve"})});if(!t.ok)throw new Error("Failed to save");const s=await t.json();s.id&&(e.id=s.id),e.api_key_configured=s.api_key_configured,e.api_key="",e.api_key_action="preserve";const i=await fetch("/api/models/verify",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id:e.id,api_url:e.api_url,api_key:e.api_key,model_id:e.model_id,type:e.type})}),n=await i.json();i.ok&&n.success?(e.status="success",e.verifyMessage=this.t("verifySuccess")):(e.status="error",e.verifyMessage=n.error||this.t("verifyFailed"))}catch(t){e.status="error",e.verifyMessage=t.message||this.t("verifyFailed")}},async saveAllSettings(){try{this.settings.ui_settings.theme_mode="light";if(!(await fetch("/api/settings",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(this.settings)})).ok)throw new Error(this.t("saveFailed"));for(const e of this.models)e.name||(e.name=e.model_id||"Unnamed"),await fetch("/api/models",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...e,api_key_action:"clear"===e.api_key_action?"clear":e.api_key?"replace":"preserve"})});this.applyTheme(),this.announceSettingsChange(),this.showSettings=!1,this.showToast(this.t("settingsSaved"),"success"),await this.loadModels()}catch(e){await this.loadSettings(),this.applyTheme(),this.showToast(this.t("saveFailed"),"error")}},async copyMessage(e,t){if(e)try{await navigator.clipboard.writeText(e);const s=t.currentTarget,i=s.querySelector("i"),n=i.className;i.className="ri-check-line",s.classList.add("copied"),setTimeout(()=>{i.className=n,s.classList.remove("copied")},2e3)}catch(t){const s=document.createElement("textarea");s.value=e,s.style.position="fixed",s.style.opacity="0",document.body.appendChild(s),s.select(),document.execCommand("copy"),document.body.removeChild(s),this.showToast(this.t("copied")||"Copied!")}},renderMarkdown(e){return window.ChatRawContentSecurity.renderMarkdown(e,this.lang)},horizontalWheelDelta:e=>e.defaultPrevented||!e.cancelable||e.ctrlKey?0:Math.abs(e.deltaX)>Math.abs(e.deltaY)?e.deltaX:e.shiftKey?e.deltaY:0,findHorizontalWheelScroller(e){let t=e instanceof Element?e:null;for(;t&&t!==document.body;){const e=getComputedStyle(t).overflowX;if(t.scrollWidth>t.clientWidth&&("auto"===e||"scroll"===e))return t;t=t.parentElement}return null},consumeHorizontalWheel(e,t=null){const s=this.horizontalWheelDelta(e);if(!s)return!1;if(e.preventDefault(),e.stopPropagation(),!t)return!0;const i=1===e.deltaMode?16:2===e.deltaMode?t.clientWidth:1,n=t.scrollWidth-t.clientWidth;return t.scrollLeft=Math.max(0,Math.min(n,t.scrollLeft+s*i)),!0},handleMarkdownTableWheel(e){const t=e.target instanceof Element?e.target.closest(".markdown-table-scroll"):null;return!(!t||t.scrollWidth<=t.clientWidth)&&this.consumeHorizontalWheel(e,t)},handleAppWheel(e){if(e.defaultPrevented)return!0;if(this.handleMarkdownTableWheel(e))return!0;return!!this.horizontalWheelDelta(e)&&this.consumeHorizontalWheel(e,this.findHorizontalWheelScroller(e.target))},installRootWheelGuard(){return!this._rootWheelHandler&&(this._rootWheelHandler=e=>this.handleAppWheel(e),document.addEventListener("wheel",this._rootWheelHandler,{passive:!1}),!0)},scrollToBottom(){const e=this.$refs.messagesContainer;e&&(e.scrollTop=e.scrollHeight)},autoResize(e){e.style.height="auto",e.style.height=Math.min(e.scrollHeight,200)+"px",e===this.$refs.inputBox&&this.syncComposerHighlightScroll()},showToast(e,t=""){this.toast.message=e,this.toast.type=t,this.toast.show=!0,setTimeout(()=>{this.toast.show=!1},3e3)},getSortedPluginButtons(e="toolbar"){return[...this.pluginToolbarButtons].filter(t=>{const s=this.installedPlugins?.find(e=>e.id===t.pluginId);return!0===s?.enabled&&"hermes"!==t.pluginId&&t.placement===e}).sort((e,t)=>(e.order||100)-(t.order||100))},get extensionPaletteEntries(){return[...this.residentEntries("composer").map(e=>({key:`resident:${e.integration.id}:${e.id}`,kind:"resident",order:e.order??100,stableId:`${e.integration.id}:${e.id}`,icon:e.icon,label:this.residentText(e.label),status:e.integration.feature?.available?"":this.t("unavailable"),active:!1,loading:!1,disabled:!e.integration.feature?.available,source:e})),...this.getSortedPluginButtons("toolbar").map(e=>({key:`plugin:${e.fullId}`,kind:"plugin",order:e.order??100,stableId:e.fullId,icon:e.icon,label:this.getPluginButtonLabel(e),status:e.status?this.getLocalizedText(e.status,""):"",active:!0===e.active,loading:!0===e.loading,disabled:!0===e.disabled,source:e}))].sort((e,t)=>e.order-t.order||(e.kind===t.kind?0:"resident"===e.kind?-1:1)||e.stableId.localeCompare(t.stableId))},syncExtensionPaletteVisibility(e){if(0!==e)return;const t=document.getElementById("agent-extension-palette"),s=document.activeElement,i=this.showExtensionPalette||s===this.$refs?.extensionPaletteToggle||t?.contains(s);this.showExtensionPalette=!1,i&&this.restoreExtensionPaletteFocus(null)},isVisibleFocusTarget(e){if(!(e instanceof HTMLElement&&e.isConnected))return!1;if(e.disabled||e.hidden)return!1;for(let t=e;t instanceof HTMLElement;t=t.parentElement){const e=window.getComputedStyle(t);if(t.hidden||"none"===e.display||"hidden"===e.visibility)return!1}return!0},agentSurfaceFocusTarget(e=null){return[e,this.$refs?.inputBox,this.$refs?.agentLauncher].find(e=>this.isVisibleFocusTarget(e))||null},focusFirstExtensionEntry(){this.$nextTick(()=>{requestAnimationFrame(()=>{if(!this.showExtensionPalette)return;const e=document.getElementById("agent-extension-palette"),t=document.querySelector("#agent-extension-palette .agent-extension-item:not(:disabled)");(t||e)?.focus()})})},restoreExtensionPaletteFocus(e=this.$refs?.extensionPaletteToggle){this.$nextTick(()=>{requestAnimationFrame(()=>{this.showExtensionPalette||this.agentSurfaceFocusTarget(e)?.focus()})})},closeExtensionPalette(e=!1){const t=this.$refs?.extensionPaletteToggle;this.showExtensionPalette=!1,e&&this.restoreExtensionPaletteFocus(t)},toggleExtensionPalette(){if(0===this.extensionPaletteEntries.length)return void(this.showExtensionPalette=!1);const e=!this.showExtensionPalette;this.showUrlInput=!1,this.completionMenuOpen=!1,this.agentHistoryOpen=!1,this.showExtensionPalette=e,e&&this.focusFirstExtensionEntry()},extensionEntryTitle:e=>e?.status?`${e.label} · ${e.status}`:e?.label||"",get pluginWorkspaceCollections(){const e=new Map;for(const t of Object.values(this.pluginWorkspaceDefinitions)){const s=t.collection;if(!s)continue;const i=this.installedPlugins?.find(e=>e.id===t.pluginId);!1!==i?.enabled&&(e.has(s.id)||e.set(s.id,{id:s.id,title:s.title,icon:s.icon,order:s.order,panels:[]}),e.get(s.id).panels.push({pluginId:t.pluginId,panelId:t.id,title:t.title,icon:this.pluginWorkspacePanelIcon(t.pluginId,t.id,t.icon),order:s.tabOrder}))}return[...e.values()].map(e=>({...e,panels:e.panels.sort((e,t)=>e.order-t.order||`${e.pluginId}:${e.panelId}`.localeCompare(`${t.pluginId}:${t.panelId}`))})).sort((e,t)=>e.order-t.order||e.id.localeCompare(t.id))},getPluginButtonLabel(e){const t=this.lang||"en";return e.label?.[t]||e.label?.en||e.id},getPluginWorkspaceCollection(e){return this.pluginWorkspaceCollections.find(t=>t.id===e)||null},activePluginWorkspaceCollectionPanels(){return this.getPluginWorkspaceCollection(this.pluginWorkspace.collectionId)?.panels||[]},switchPluginWorkspaceCollectionPanel(e){const t=this.getPluginWorkspaceCollection(this.pluginWorkspace.collectionId);if(!t?.panels.some(t=>t.pluginId===e?.pluginId&&t.panelId===e?.panelId))throw new Error("Plugin Workspace collection panel mismatch");return this.openPluginWorkspacePanel(e.panelId,{placement:"main"},e.pluginId)},pluginWorkspaceKey:(e,t)=>`${e}:${t}`,assertPluginWorkspaceOwner(e){if("string"!=typeof e||!e.trim())throw new TypeError("Plugin Workspace requires an explicit pluginId");const t=this.installedPlugins.find(t=>t.id===e);if(!t||!1===t.enabled)throw new Error(`Plugin Workspace owner is not enabled: ${e}`);return e},assertPluginWorkspaceTransitionAvailable(){if(this._pluginWorkspaceTransition)throw new Error("Plugin Workspace lifecycle cannot change during "+this._pluginWorkspaceTransition)},restorePluginWorkspaceFocus(e){this.$nextTick(()=>{requestAnimationFrame(()=>{this.pluginWorkspace.show||this.agentSurfaceFocusTarget(e)?.focus()})})},validatePluginWorkspaceDefinition(e){const t=new Set(["id","title","icon","placements","defaultPlacement","collection","mount"]);if(!e||"object"!=typeof e||Array.isArray(e)||Object.keys(e).some(e=>!t.has(e))||"string"!=typeof e.id||!PLUGIN_WORKSPACE_PANEL_ID_PATTERN.test(e.id)||!e.title||!e.icon||!/^ri-[a-z0-9-]+$/.test(e.icon)||!Array.isArray(e.placements)||0===e.placements.length||new Set(e.placements).size!==e.placements.length||e.placements.some(e=>!PLUGIN_WORKSPACE_PLACEMENTS.includes(e))||!e.placements.includes(e.defaultPlacement)||"function"!=typeof e.mount)throw new TypeError("Invalid Plugin Workspace definition");if("string"!=typeof e.title&&("object"!=typeof e.title||Array.isArray(e.title)||0===Object.keys(e.title).length||!Object.values(e.title).every(e=>"string"==typeof e)||!Object.values(e.title).some(e=>e.trim())))throw new TypeError("Invalid Plugin Workspace title");if(void 0!==e.collection){const t=e.collection,s=new Set(["id","title","icon","order","tabOrder"]);if(!t||"object"!=typeof t||Array.isArray(t)||Object.keys(t).some(e=>!s.has(e))||"string"!=typeof t.id||!PLUGIN_WORKSPACE_PANEL_ID_PATTERN.test(t.id)||!t.title||"string"!=typeof t.icon||!/^ri-[a-z0-9-]+$/.test(t.icon)||!Number.isFinite(t.order)||!Number.isFinite(t.tabOrder)||"main"!==e.defaultPlacement||!e.placements.includes("main"))throw new TypeError("Invalid Plugin Workspace collection definition");if("string"!=typeof t.title&&("object"!=typeof t.title||Array.isArray(t.title)||0===Object.keys(t.title).length||!Object.values(t.title).every(e=>"string"==typeof e)||!Object.values(t.title).some(e=>e.trim())))throw new TypeError("Invalid Plugin Workspace collection title");const i=e=>"string"==typeof e?e:JSON.stringify(Object.fromEntries(Object.entries(e).sort(([e],[t])=>e.localeCompare(t))));if(Object.values(this.pluginWorkspaceDefinitions).find(e=>e.collection?.id===t.id&&(e.collection.icon!==t.icon||e.collection.order!==t.order||i(e.collection.title)!==i(t.title))))throw new Error(`Plugin Workspace collection metadata mismatch: ${t.id}`)}return e},focusPluginWorkspaceTitle(e,t,s){this.$nextTick(()=>{requestAnimationFrame(()=>{this.pluginWorkspace.show&&this.pluginWorkspace.pluginId===e&&this.pluginWorkspace.panelId===t&&this.pluginWorkspace.placement===s&&document.getElementById("plugin-workspace-title")?.focus()})})},registerPluginWorkspacePanel(e,t){this.assertPluginWorkspaceTransitionAvailable();const s=this.assertPluginWorkspaceOwner(t);this.validatePluginWorkspaceDefinition(e);const i=this.pluginWorkspaceKey(s,e.id);if(this.pluginWorkspace.show&&this.pluginWorkspace.pluginId===s&&this.pluginWorkspace.panelId===e.id){const e=this.teardownPluginWorkspace();if(e)throw e}return this.pluginWorkspaceDefinitions[i]=Object.freeze({pluginId:s,id:e.id,title:e.title,icon:e.icon,placements:Object.freeze([...e.placements]),defaultPlacement:e.defaultPlacement,collection:e.collection?Object.freeze({id:e.collection.id,title:e.collection.title,icon:e.collection.icon,order:e.collection.order,tabOrder:e.collection.tabOrder}):null,mount:e.mount}),this.recomputeFeatureCatalogRuntime(),!0},unregisterPluginWorkspacePanel(e,t){this.assertPluginWorkspaceTransitionAvailable();const s=this.assertPluginWorkspaceOwner(t);if("string"!=typeof e||!PLUGIN_WORKSPACE_PANEL_ID_PATTERN.test(e))throw new TypeError("Invalid Plugin Workspace panelId");const i=this.pluginWorkspaceKey(s,e);if(!this.pluginWorkspaceDefinitions[i])return!1;if(this.pluginWorkspace.show&&this.pluginWorkspace.pluginId===s&&this.pluginWorkspace.panelId===e){const e=this.teardownPluginWorkspace();if(e)throw e}return delete this.pluginWorkspaceDefinitions[i],this.recomputeFeatureCatalogRuntime(),!0},openPluginWorkspacePanel(e,t,s){this.assertPluginWorkspaceTransitionAvailable();const i=this.assertPluginWorkspaceOwner(s);if("string"!=typeof e||!PLUGIN_WORKSPACE_PANEL_ID_PATTERN.test(e)||void 0!==t&&(!t||"object"!=typeof t||Array.isArray(t)||Object.keys(t).some(e=>"placement"!==e)))throw new TypeError("Invalid Plugin Workspace open request");const n=this.pluginWorkspaceKey(i,e),a=this.pluginWorkspaceDefinitions[n];if(!a)throw new Error(`Plugin Workspace panel is not registered: ${e}`);const o=void 0!==t&&Object.prototype.hasOwnProperty.call(t,"placement")?t.placement:a.defaultPlacement;if(!a.placements.includes(o))throw new TypeError(`Unsupported Plugin Workspace placement: ${o}`);const r=this._pluginWorkspaceActivation,l=r?.pluginId===i&&!0===navigator.userActivation?.isActive&&r.trigger instanceof HTMLElement&&r.trigger.isConnected,d=this._pluginWorkspaceReturnFocus,u=l?r.returnFocus instanceof HTMLElement&&r.returnFocus.isConnected?r.returnFocus:r.trigger:d?.isConnected?d:null;if(this.showExtensionPalette=!1,this.pluginWorkspace.show&&this.pluginWorkspace.pluginId===i&&this.pluginWorkspace.panelId===e&&this.pluginWorkspace.placement===o)return l&&(this._pluginWorkspaceReturnFocus=u,this.focusPluginWorkspaceTitle(i,e,o)),this.rememberPluginWorkspacePage(i,e,o),!0;if(this.pluginWorkspace.show){const e=this.teardownPluginWorkspace(!1);if(e)throw this.restorePluginWorkspaceFocus(u),e}const c=document.getElementById("plugin-workspace-mount");if(!c)throw this.restorePluginWorkspaceFocus(u),new Error("Plugin Workspace mount container is unavailable");this._pluginWorkspaceReturnFocus=u,c.replaceChildren(),this.pluginWorkspace={show:!0,pluginId:i,panelId:e,title:a.title,icon:a.icon,collectionId:a.collection?.id||null,collectionTitle:a.collection?.title||null,collectionIcon:a.collection?.icon||"",placement:o,dispose:null};try{this._pluginWorkspaceTransition="mount";const e=a.mount({container:c,placement:o});if("function"!=typeof e)throw new TypeError("Plugin Workspace mount must synchronously return dispose()");this.pluginWorkspace.dispose=e}catch(e){c.replaceChildren(),this.pluginWorkspace={show:!1,pluginId:null,panelId:null,title:null,icon:"",collectionId:null,collectionTitle:null,collectionIcon:"",placement:null,dispose:null};const t=this._pluginWorkspaceReturnFocus;throw this._pluginWorkspaceReturnFocus=null,this.forgetPluginWorkspacePage(),this.restorePluginWorkspaceFocus(t),e}finally{this._pluginWorkspaceTransition=null}return l&&this.focusPluginWorkspaceTitle(i,e,o),this.rememberPluginWorkspacePage(i,e,o),!0},teardownPluginWorkspace(e=!0){if(this.assertPluginWorkspaceTransitionAvailable(),!this.pluginWorkspace.show)return null;const t=this.pluginWorkspace.dispose;this.pluginWorkspace.dispose=null;let s=null;try{this._pluginWorkspaceTransition="dispose";const e=t?.();if(void 0!==e)throw null===e||"object"!=typeof e&&"function"!=typeof e||"function"!=typeof e.then||Promise.resolve(e).catch(e=>{console.error("[Plugin Workspace async dispose]",e)}),new TypeError("Plugin Workspace dispose must complete synchronously and return undefined")}catch(e){s=e}finally{this._pluginWorkspaceTransition=null}document.getElementById("plugin-workspace-mount")?.replaceChildren(),this.pluginWorkspace={show:!1,pluginId:null,panelId:null,title:null,icon:"",collectionId:null,collectionTitle:null,collectionIcon:"",placement:null,dispose:null},this.forgetPluginWorkspacePage();const i=this._pluginWorkspaceReturnFocus;return this._pluginWorkspaceReturnFocus=null,e&&this.restorePluginWorkspaceFocus(i),s},closeActivePluginWorkspace(){const e=this.teardownPluginWorkspace();e&&console.error("[Plugin Workspace dispose]",e)},closePluginWorkspacePanel(e,t){this.assertPluginWorkspaceTransitionAvailable();const s=this.assertPluginWorkspaceOwner(t);if("string"!=typeof e||!PLUGIN_WORKSPACE_PANEL_ID_PATTERN.test(e))throw new TypeError("Invalid Plugin Workspace panelId");if(!this.pluginWorkspace.show)return!1;if(this.pluginWorkspace.pluginId!==s||this.pluginWorkspace.panelId!==e)throw new Error("Plugin Workspace ownership mismatch");const i=this.teardownPluginWorkspace();if(i)throw i;return!0},unregisterPluginWorkspacePanels(e){if(this.pluginWorkspace.pluginId===e){const e=this.teardownPluginWorkspace();e&&console.error("[Plugin Workspace dispose]",e)}const t=`${e}:`;for(const e of Object.keys(this.pluginWorkspaceDefinitions))e.startsWith(t)&&delete this.pluginWorkspaceDefinitions[e];this.recomputeFeatureCatalogRuntime()},async handlePluginButtonClick(e,t=null,s=t){if(e.loading||e.disabled)return!1;try{const i=!0===navigator.userActivation?.isActive&&t instanceof HTMLElement&&t.isConnected?{pluginId:e.pluginId,trigger:t,returnFocus:s instanceof HTMLElement&&s.isConnected?s:t}:null,n=this._pluginWorkspaceActivation;let a;this._pluginWorkspaceActivation=i;try{a=e.onClick?.(e)}finally{this._pluginWorkspaceActivation=n}return await a,!0}catch(t){return console.error(`[Plugin ${e.pluginId}] Button click error:`,t),e.loading=!1,!1}},async handleExtensionEntryClick(e,t=null){const s=this.extensionPaletteEntries.find(t=>t.key===e?.key);if(!s||s.loading||s.disabled)return this.syncExtensionPaletteVisibility(this.extensionPaletteEntries.length),!1;const i=this.$refs.extensionPaletteToggle;if("resident"===s.kind)return this.showExtensionPalette=!1,await this.openResidentIntegration(s.source.integration,i),!0;return!!await this.handlePluginButtonClick(s.source,t,i)&&(this.showExtensionPalette=!1,this.pluginWorkspace.show||this.pluginFullscreenModal.show||this.residentWorkspace.show||this.restoreExtensionPaletteFocus(i),!0)},closePluginFullscreenModal(){if(this.pluginFullscreenModal.onClose)try{this.pluginFullscreenModal.onClose()}catch(e){console.error("[Plugin] Modal onClose error:",e)}this.pluginFullscreenModal.show=!1,this.pluginFullscreenModal.content="",this.pluginFullscreenModal.onClose=null,this.pluginFullscreenModal.pluginId=null;const e=this._pluginFullscreenReturnFocus;this._pluginFullscreenReturnFocus=null,this.restoreExtensionPaletteFocus(e)},pluginHookRegistry:{},initPluginSystem(){this._currentLoadingPlugin=null;const e=this;class t extends Error{constructor(e,t,s){super(e),this.name="ModuleSdkError",this.code=t||"module_request_failed",this.status=s||0}}const s=async(e,s={})=>{const i=await fetch(e,{credentials:"same-origin",...s});let n=null;try{n=await i.json()}catch(e){n=null}if(!i.ok)throw new t(n?.detail||"Module request failed",n?.code||"module_request_failed",i.status);return n},i=async(e,s,i)=>{let n=null;try{n=await e.json()}catch(e){n=null}return new t(n?.detail||s,n?.code||i,e.status)},n=e=>`/api/module-tasks/${encodeURIComponent(e)}`,a=(e,s,i)=>{if("string"!=typeof e||!e||"string"!=typeof s||!s)throw new t("taskId and resourceRef are required","invalid_sdk_argument",0);return`${n(e)}/resources/${encodeURIComponent(s)}?disposition=${encodeURIComponent(i)}`},o=async(e,s)=>{try{return((e,s)=>{const i=e.headers.get("content-disposition")||"",n=(i.split(";",1)[0]||"").trim().toLowerCase(),a=i.match(/filename\*=UTF-8''([^;]+)/i),o=i.match(/filename="([^"]+)"/i);let r=o?.[1];if(a)try{r=decodeURIComponent(a[1])}catch(e){r=null}const l=(e.headers.get("content-type")||"").split(";",1)[0].trim(),d=e.headers.get("content-length"),u=null===d?NaN:Number(d);if(!r||!l||!Number.isSafeInteger(u)||u<0||!["inline","attachment"].includes(n))throw new t("Task resource metadata is invalid",s,502);return{filename:r,mime:l,size:u,disposition:n}})(e,s)}finally{e.body&&await e.body.cancel()}},r=e=>{let s=null,i="message";const n=[];for(const t of e.split(/\r?\n/)){if(!t||t.startsWith(":"))continue;const e=t.indexOf(":"),a=e<0?t:t.slice(0,e);let o=e<0?"":t.slice(e+1);o.startsWith(" ")&&(o=o.slice(1)),"id"===a?s=o:"event"===a?i=o:"data"===a&&n.push(o)}if(!n.length)return null;const a=Number(s);if(!Number.isInteger(a)||a<1)throw new t("Module event stream returned an invalid cursor","invalid_event_cursor",502);return{id:a,event:i,data:JSON.parse(n.join("\n"))}},l=(i,a=null)=>{if("string"!=typeof i||!i)throw new t("taskId is required","invalid_sdk_argument",0);e.moduleTaskSubscriptions[i]?.abort();const o=new AbortController;e.moduleTaskSubscriptions[i]=o;const l="function"==typeof a?{onEvent:a}:a||{};return(async()=>{let a=0,d=0;for(;!o.signal.aborted;)try{const u=await fetch(`${n(i)}/events`,{headers:{"Last-Event-ID":String(a)},credentials:"same-origin",signal:o.signal});if(!u.ok||!u.body){let e=null;try{e=await u.json()}catch(e){}throw new t(e?.detail||"Module event stream failed",e?.code||"module_event_stream_failed",u.status)}const c=u.body.pipeThrough(new TextDecoderStream).getReader();let h="";for(;!o.signal.aborted;){const{value:t,done:u}=await c.read();if(u)break;h+=t;const p=h.split(/\r?\n\r?\n/);h=p.pop()||"";for(const t of p){const u=r(t);if(u&&!(u.id<=a)){if(d=0,a=u.id,e.applyModuleTaskEvent(i,u),"artifact.added"===u.event||"task.terminal"===u.event){const t=await s(n(i));e.upsertModuleTask(t),"task.terminal"===u.event&&t.chat_id&&e.currentChatId===t.chat_id&&(await e.loadMessages(t.chat_id),await e.loadChats())}if(l.onEvent?.(u),"task.terminal"===u.event||"task.status"===u.event&&MODULE_TERMINAL_STATES.has(u.data?.state)){l.onClose?.(),o.abort();break}}}}if(!o.signal.aborted)throw new t("Module event stream ended before a terminal event","module_event_stream_incomplete",0)}catch(t){if(o.signal.aborted)break;if(d+=1,l.onError?.(t),d>=5){const s=e.moduleTasks[i];s&&(s.error={code:t?.code||"module_event_stream_failed"});break}await new Promise(e=>setTimeout(e,Math.min(500*2**(d-1),8e3)))}e.moduleTaskSubscriptions[i]===o&&delete e.moduleTaskSubscriptions[i]})(),()=>o.abort()},d=Object.freeze({version:"1.6.0",getFeatureStatus:e=>s(`/api/module-features/${encodeURIComponent(e)}`),uploadTaskResource:async e=>{if(!(e instanceof Blob&&"string"==typeof e.name&&e.name))throw new t("file is required","invalid_sdk_argument",0);const s=new FormData;s.append("file",e,e.name);const i=await fetch("/api/module-task-resources",{method:"POST",credentials:"same-origin",body:s});let n=null;try{n=await i.json()}catch(e){}if(!i.ok)throw new t(n?.detail||"Task resource upload failed",n?.code||"task_resource_upload_failed",i.status);if(!(e=>{if(!e||"object"!=typeof e||Array.isArray(e))return!1;const t=["resource_id","filename","media_type","size","sha256","expires_at"],s=Object.keys(e).sort();return!(s.length!==t.length||!t.sort().every((e,t)=>s[t]===e))&&"string"==typeof e.resource_id&&e.resource_id.length>0&&"string"==typeof e.filename&&e.filename.length>0&&"string"==typeof e.media_type&&e.media_type.length>0&&Number.isSafeInteger(e.size)&&e.size>=0&&"string"==typeof e.sha256&&/^[0-9a-f]{64}$/.test(e.sha256)&&"string"==typeof e.expires_at&&/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(e.expires_at)&&!Number.isNaN(Date.parse(e.expires_at))})(n))throw new t("Task resource upload response is invalid","task_resource_upload_response_invalid",502);return n},startTask:async(i,n={})=>{if(!i||"string"!=typeof i.module_id||"string"!=typeof i.action_id||!i.input||"object"!=typeof i.input||Array.isArray(i.input))throw new t("module_id, action_id, and object input are required","invalid_sdk_argument",0);if(!n||"object"!=typeof n||Array.isArray(n)||Object.keys(n).some(e=>"presentation"!==e)||!["task_center","embedded","conversation"].includes(n.presentation||"task_center"))throw new t("presentation must be task_center, embedded, or conversation","invalid_sdk_argument",0);const a=n.presentation||"task_center";if(!("conversation"!==a||"string"==typeof i.chat_id&&i.chat_id&&"string"==typeof i.user_message&&i.user_message.trim()))throw new t("conversation presentation requires chat_id and user_message","invalid_sdk_argument",0);const o={module_id:i.module_id,action_id:i.action_id,input:i.input};for(const e of["chat_id","user_message","resource_ids","active_skill_ids"])void 0!==i[e]&&(o[e]=i[e]);const r=i.idempotency_key||(()=>{if("function"==typeof crypto.randomUUID)return crypto.randomUUID();const e=crypto.getRandomValues(new Uint8Array(16));e[6]=15&e[6]|64,e[8]=63&e[8]|128;const t=Array.from(e,e=>e.toString(16).padStart(2,"0")).join("");return[t.slice(0,8),t.slice(8,12),t.slice(12,16),t.slice(16,20),t.slice(20)].join("-")})(),d=await s("/api/module-tasks",{method:"POST",headers:{"Content-Type":"application/json","Idempotency-Key":r},body:JSON.stringify(o)});return"task_center"===a&&e.rememberModuleTask(d.task_id),e.upsertModuleTask(d,{select:"task_center"===a,presentation:a}),"conversation"===a&&e.attachConversationTask(d,i.user_message),"task_center"!==a&&"conversation"!==a||l(d.task_id),d},listTasks:async(e={})=>{if(!e||"object"!=typeof e||Array.isArray(e)||Object.keys(e).some(e=>!["module_id","action_id","state","chat_id","limit"].includes(e)))throw new t("Task filters are invalid","invalid_sdk_argument",0);const i=new URLSearchParams;for(const s of["module_id","action_id","state","chat_id"])if(void 0!==e[s]){if("string"!=typeof e[s]||!e[s])throw new t("Task filters are invalid","invalid_sdk_argument",0);i.set(s,e[s])}if(void 0!==e.limit){if(!Number.isInteger(e.limit)||e.limit<1||e.limit>100)throw new t("Task limit is invalid","invalid_sdk_argument",0);i.set("limit",String(e.limit))}const n=i.size?`?${i}`:"";return(await s(`/api/module-tasks${n}`)).tasks},getTask:e=>s(n(e)),subscribe:l,cancelTask:e=>s(`${n(e)}/cancel`,{method:"POST",headers:{"Content-Type":"application/json"},body:"{}"}),respondApproval:(e,i,a)=>{if(!["approve","deny"].includes(a))throw new t("decision must be approve or deny","invalid_sdk_argument",0);return s(`${n(e)}/approvals/${encodeURIComponent(i)}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({decision:a})})},downloadArtifact:async(e,s)=>{const i=await fetch(`${n(e)}/artifacts/${encodeURIComponent(s)}`,{credentials:"same-origin"});if(!i.ok){let e=null;try{e=await i.json()}catch(e){}throw new t(e?.detail||"Artifact download failed",e?.code||"artifact_download_failed",i.status)}const a=i.headers.get("content-disposition")||"",o=a.match(/filename\*=UTF-8''([^;]+)/i),r=a.match(/filename="([^"]+)"/i),l=o?decodeURIComponent(o[1]):r?.[1]||"artifact",d=URL.createObjectURL(await i.blob()),u=document.createElement("a");return u.href=d,u.download=l,u.click(),setTimeout(()=>URL.revokeObjectURL(d),0),{filename:l}},getTaskResourceView:async(e,s)=>{const n=a(e,s,"inline"),r=await fetch(n,{method:"GET",credentials:"same-origin"});if(!r.ok)throw await i(r,"Task resource view failed","task_resource_view_failed");const l=await o(r,"task_resource_view_failed");if("inline"!==l.disposition)throw new t("Task resource is not available for browser preview","task_resource_preview_unavailable",415);return{url:n,...l}},downloadTaskResource:async(e,t)=>{const s=a(e,t,"attachment"),n=await fetch(s,{method:"GET",credentials:"same-origin"});if(!n.ok)throw await i(n,"Task resource download failed","task_resource_download_failed");const r=await o(n,"task_resource_download_failed"),l=document.createElement("a");return l.href=s,l.download=r.filename,l.click(),{filename:r.filename}}});return window.ChatRaw=window.ChatRaw||{},window.ChatRaw.modules=d,window.ChatRawPlugin={hooks:{register:(e,t)=>this.registerHook(e,t),available:()=>Object.keys(this.pluginHooks)},require:e=>this.loadedPluginDeps[e],proxy:{request:e=>this.proxyRequest(e),upload:async(e,t,s,i={},n="file")=>{const a=new FormData;a.append("file",e),a.append("service_id",t),a.append("url",s),a.append("extra_fields",JSON.stringify(i)),a.append("file_field_name",n);try{const e=await fetch("/api/proxy/upload",{method:"POST",body:a});return await e.json()}catch(e){return{success:!1,error:e.message}}}},settings:e=>this.getPluginSettings(e||this._currentLoadingPlugin),input:{getValue:()=>e.inputMessage||"",setValue:t=>(e.setComposerValue(String(t||"")),!0),insertText:t=>e.insertComposerText(String(t||"")),replaceRange:(t,s,i)=>e.replaceComposerRange(t,s,String(i||"")),getSelection:()=>e.getComposerSelection(),focus:()=>(e.focusComposer(),!0),registerCompletionProvider:(t,s=null)=>e.registerCompletionProvider(t,s),unregisterCompletionProvider:(t,s=null)=>e.unregisterCompletionProvider(t,s),invalidateSkillCatalog:()=>e.invalidateSkillCatalog()},utils:{loadScript:t=>e.loadScript(t),loadCSS:t=>e.loadCSS(t),loadHighlightJS:()=>"function"==typeof loadHighlightJS?loadHighlightJS():Promise.resolve(),showToast:(t,s="")=>e.showToast(t,s),getLanguage:()=>e.lang,t:t=>e.t(t),showProgress:(t,s="")=>{e.uploadProgress={show:!0,filename:s,progress:t,status:"processing",current:t,total:100}},hideProgress:()=>{e.uploadProgress={show:!1,filename:"",progress:0,status:"",current:0,total:0}},getCurrentChatId:()=>e.currentChatId,getMessages:()=>[...e.messages],addMessage:(t,s)=>{e.messages.push({role:t,content:s}),e.$nextTick(()=>e.scrollToBottom())}},storage:{get:(t,s=null,i=null)=>{const n=i||e._currentLoadingPlugin;if(!n)return console.warn("[Plugin Storage] No plugin context - pass pluginId as third argument"),s;try{const e=`chatraw_plugin_${n}`,i=JSON.parse(localStorage.getItem(e)||"{}");return t in i?i[t]:s}catch(e){return s}},set:(t,s,i=null)=>{const n=i||e._currentLoadingPlugin;if(!n)return console.warn("[Plugin Storage] No plugin context - pass pluginId as third argument"),!1;try{const e=`chatraw_plugin_${n}`,i=JSON.parse(localStorage.getItem(e)||"{}");i[t]=s;const a=JSON.stringify(i);return a.length>1048576?(console.warn("[Plugin Storage] Storage limit exceeded (1MB)"),!1):(localStorage.setItem(e,a),!0)}catch(e){return console.error("[Plugin Storage] Error:",e),!1}},remove:(t,s=null)=>{const i=s||e._currentLoadingPlugin;if(!i)return!1;try{const e=`chatraw_plugin_${i}`,s=JSON.parse(localStorage.getItem(e)||"{}");return delete s[t],localStorage.setItem(e,JSON.stringify(s)),!0}catch(e){return!1}},clear:(t=null)=>{const s=t||e._currentLoadingPlugin;if(!s)return!1;try{return localStorage.removeItem(`chatraw_plugin_${s}`),!0}catch(e){return!1}},getAll:(t=null)=>{const s=t||e._currentLoadingPlugin;if(!s)return{};try{const e=`chatraw_plugin_${s}`;return JSON.parse(localStorage.getItem(e)||"{}")}catch(e){return{}}}},ui:{registerToolbarButton:(t,s=null)=>{const i=s||e._currentLoadingPlugin||t.pluginId;if(!i)return console.warn("[Plugin UI] Cannot register button: unknown plugin"),!1;if(!t.icon||!/^ri-[a-z0-9-]+$/.test(t.icon))return console.warn(`[Plugin ${i}] Invalid icon format. Must be RemixIcon (ri-xxx-xxx)`),!1;if(!t.id||!t.onClick)return console.warn(`[Plugin ${i}] Button requires id and onClick`),!1;const n=`${i}:${t.id}`,a=e.pluginToolbarButtons.findIndex(e=>e.fullId===n),o={fullId:n,pluginId:i,id:t.id,icon:t.icon,label:t.label||{en:t.id},onClick:t.onClick,order:t.order??100,placement:"toolbar",status:t.status||null,disabled:!0===t.disabled,active:!1,loading:!1};return a>=0?e.pluginToolbarButtons[a]=o:e.pluginToolbarButtons.push(o),!0},unregisterToolbarButton:(t,s=null)=>{const i=s||e._currentLoadingPlugin;if(!i)return!1;const n=`${i}:${t}`,a=e.pluginToolbarButtons.findIndex(e=>e.fullId===n);return a>=0&&(e.pluginToolbarButtons.splice(a,1),!0)},setButtonState:(t,s,i=null)=>{const n=i||e._currentLoadingPlugin;if(!n)return!1;const a=`${n}:${t}`,o=e.pluginToolbarButtons.find(e=>e.fullId===a);return!!o&&(void 0!==s.active&&(o.active=s.active),void 0!==s.loading&&(o.loading=s.loading),void 0!==s.disabled&&(o.disabled=!0===s.disabled),void 0!==s.status&&(o.status=s.status),!0)},openFullscreenModal:(t,s=null)=>{const i=s||e._currentLoadingPlugin||t?.pluginId;if("string"==typeof t&&(t={content:t}),!t?.content)return console.warn("[Plugin UI] Cannot open modal: empty content"),!1;const n=e._pluginWorkspaceActivation;return e._pluginFullscreenReturnFocus=n?.returnFocus instanceof HTMLElement&&n.returnFocus.isConnected?n.returnFocus:null,e.showExtensionPalette=!1,e.pluginFullscreenModal={show:!0,content:t.content,closable:!1!==t.closable,onClose:t.onClose||null,pluginId:i},!0},closeFullscreenModal:()=>{e.closePluginFullscreenModal()},registerWorkspacePanel:(t,s)=>e.registerPluginWorkspacePanel(t,s),unregisterWorkspacePanel:(t,s)=>e.unregisterPluginWorkspacePanel(t,s),openWorkspacePanel:(t,s,i)=>e.openPluginWorkspacePanel(t,s,i),closeWorkspacePanel:(t,s)=>e.closePluginWorkspacePanel(t,s)}},this.initPluginRuntimeSync(),this.loadEnabledPlugins()},initPluginRuntimeSync(){if(this._pluginRuntimeSyncInitialized)return;this._pluginRuntimeSyncInitialized=!0;const e=()=>{"visible"===document.visibilityState&&this.syncPluginRuntimes()};"function"==typeof window.addEventListener&&window.addEventListener("focus",e),"undefined"!=typeof document&&"function"==typeof document.addEventListener&&document.addEventListener("visibilitychange",e),"function"==typeof window.BroadcastChannel&&(this._pluginRuntimeChangeChannel=new window.BroadcastChannel("chatraw-plugin-runtime-v1"),this._pluginRuntimeChangeChannel.addEventListener("message",e=>{"plugins-changed"===e.data?.type&&this.syncPluginRuntimes()}))},announcePluginRuntimeChange(e){this._pluginRuntimeChangeChannel?.postMessage({type:"plugins-changed",plugin_id:e})},async refreshFeatureCatalogAfterPluginChange(e){await this.loadFeatureCatalog(),this.announcePluginRuntimeChange(e)},async syncPluginRuntimes(){return this._pluginRuntimeSyncPromise||(this._pluginRuntimeSyncPromise=(async()=>{const e=await fetch("/api/plugins",{credentials:"same-origin",cache:"no-store"});if(!e.ok)return;const t=await e.json(),s=new Map(t.map(e=>[e.id,e]));for(const e of Object.keys(this.pluginRuntimeVersions)){const t=s.get(e);t&&!1!==t.enabled||this.cleanupPluginRuntime(e)}this.installedPlugins=t;for(const e of t){if(!1===e.enabled)continue;this.pluginRuntimeVersions[e.id]!==String(e.version||"0")&&await this.loadPluginJS(e)}this.recomputeFeatureCatalogRuntime()})().catch(e=>{console.error("[Plugin] Failed to synchronize runtimes:",e)}).finally(()=>{this._pluginRuntimeSyncPromise=null})),this._pluginRuntimeSyncPromise},async loadInstalledPlugins(){try{const e=await fetch("/api/plugins");e.ok&&(this.installedPlugins=await e.json())}catch(e){console.error("Failed to load installed plugins:",e)}},async loadEnabledPlugins(){for(const e of this.installedPlugins)e.enabled&&await this.loadPluginJS(e)},async loadPluginJS(e){try{if(this.cleanupPluginRuntime(e.id),e.dependencies)for(const[t,s]of Object.entries(e.dependencies))if(!this.loadedPluginDeps[t])try{if("string"==typeof s&&s.endsWith(".css"))await this.loadCSS(s),this.loadedPluginDeps[t]=!0;else{if(t.startsWith("hljs-"))continue;await this.loadScript(s),"xlsx"===t&&window.XLSX?this.loadedPluginDeps[t]=window.XLSX:"pdfjs"===t&&window.pdfjsLib?this.loadedPluginDeps[t]=window.pdfjsLib:window[t]?this.loadedPluginDeps[t]=window[t]:this.loadedPluginDeps[t]=!0}}catch(s){console.error(`[Plugin] Failed to load dependency ${t} for ${e.id}:`,s)}this._currentLoadingPlugin=e.id;const t=`/api/plugins/${encodeURIComponent(e.id)}/main.js`,s=`${t}?v=${encodeURIComponent(String(e.version||"0"))}`;await this.loadScript(s,{reload:!0,reloadPrefix:t}),this.pluginRuntimeVersions[e.id]=String(e.version||"0"),this._currentLoadingPlugin=null,console.log(`[Plugin] Loaded: ${e.id}`)}catch(t){this._currentLoadingPlugin=null,console.error(`[Plugin] Failed to load ${e.id}:`,t)}},cleanupPluginRuntime(e){delete this.pluginRuntimeVersions[e],this.unregisterPluginHooks(e),this.unregisterPluginCompletions(e),this.pluginToolbarButtons=this.pluginToolbarButtons.filter(t=>t.pluginId!==e),this.pluginFullscreenModal?.pluginId===e&&this.closePluginFullscreenModal(),this.unregisterPluginWorkspacePanels(e)},loadScript:(e,t={})=>new Promise((s,i)=>{const n=document.querySelector(`script[src="${e}"]`);if(n){if(!t.reload)return void s();n.remove()}if(t.reload&&t.reloadPrefix)for(const e of Array.from(document.querySelectorAll("script"))){(e.getAttribute("src")||"").startsWith(t.reloadPrefix)&&e.remove()}const a=document.createElement("script");a.src=e,a.onload=s,a.onerror=()=>i(new Error(`Failed to load script: ${e}`)),document.head.appendChild(a)}),loadCSS:e=>new Promise((t,s)=>{if(document.querySelector(`link[href="${e}"]`))return void t();const i=document.createElement("link");i.rel="stylesheet",i.href=e,i.onload=t,i.onerror=()=>s(new Error(`Failed to load CSS: ${e}`)),document.head.appendChild(i)}),registerHook(e,t){if(!this.pluginHooks[e])return void console.warn(`[Plugin] Unknown hook: ${e}`);const s=this._currentLoadingPlugin,i={...t,_pluginId:s};this.pluginHooks[e].push(i),this.pluginHooks[e].sort((e,t)=>(t.priority||0)-(e.priority||0)),s&&(this.pluginHookRegistry[s]||(this.pluginHookRegistry[s]=[]),this.pluginHookRegistry[s].push({hookName:e,handler:i}))},unregisterPluginHooks(e){const t=this.pluginHookRegistry[e];if(t){for(const{hookName:e,handler:s}of t){const t=this.pluginHooks[e];if(t){const e=t.indexOf(s);-1!==e&&t.splice(e,1)}}delete this.pluginHookRegistry[e]}},async callHook(e,...t){const s=this.pluginHooks[e]||[];for(const i of s){if(i._pluginId){const e=this.installedPlugins.find(e=>e.id===i._pluginId);if(e&&!e.enabled)continue}try{const e=await i.handler(...t);if(e?.success)return e}catch(t){console.error(`[Hook ${e}] Error:`,t)}}return null},getPluginSettings(e){const t=this.installedPlugins.find(t=>t.id===e);if(!t)return{};const s={};if(t.settings)for(const e of t.settings)s[e.id]=e.default;return{...s,...t.settings_values||{}}},async proxyRequest(e){try{const t={service_id:e.serviceId||e.service_id,url:e.url,method:e.method||"POST",headers:e.headers||{},body:e.body||{}},s=await fetch("/api/proxy/request",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});return await s.json()}catch(e){return{success:!1,error:e.message}}},async loadPluginMarket(){if(this.pluginMarketLoading)return;this.pluginMarketLoading=!0,this.pluginMarketError=!1;let e=null;try{const t=new AbortController;e=setTimeout(()=>t.abort(),5e3);let s=await fetch("/api/plugins/market",{signal:t.signal});if(s.ok||(s=await fetch("https://raw.githubusercontent.com/massif-01/ChatRaw/main/Plugins/Plugin_market/index.json",{signal:t.signal})),clearTimeout(e),e=null,!s.ok)throw new Error("Failed to fetch");const i=await s.json();this.marketPlugins=i.plugins||[]}catch(e){"AbortError"===e.name?console.warn("Plugin market request timed out"):console.error("Failed to load plugin market:",e),this.pluginMarketError=!0}finally{e&&clearTimeout(e),this.pluginMarketLoading=!1}},getMarketPluginIconUrl(e){if(e?.id&&this.isPluginInstalled(e.id))return`/api/plugins/${encodeURIComponent(e.id)}/icon`;return`/api/plugins/market/${encodeURIComponent(e?.folder||e?.id||"")}/icon`},handleMarketPluginIconError(e,t){const s=e?.target;if(s)return!s.dataset.remoteFallbackTried&&t?.folder?(s.dataset.remoteFallbackTried="1",void(s.src=`https://raw.githubusercontent.com/massif-01/ChatRaw/main/Plugins/Plugin_market/${t.folder}/icon.png`)):void(s.src="data:image/svg+xml,")},isPluginInstalled(e){return this.installedPlugins.some(t=>t.id===e)},get filteredMarketPlugins(){const e=(this.pluginMarketSearch||"").trim().toLowerCase();return e?this.marketPlugins.filter(t=>{const s=(t.name?.en||"").toLowerCase(),i=t.name?.zh||"",n=(t.description?.en||"").toLowerCase(),a=t.description?.zh||"";return s.includes(e)||i.includes(e)||n.includes(e)||a.includes(e)}):this.marketPlugins},async installPlugin(e){this.pluginInstalling=e.id;try{const t=`https://raw.githubusercontent.com/massif-01/ChatRaw/main/Plugins/Plugin_market/${e.folder}`,s=await fetch("/api/plugins/install",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({source_url:t})});if((await s.json()).success){this.showToast(this.t("installSuccess"),"success"),await this.loadInstalledPlugins();const t=this.installedPlugins.find(t=>t.id===e.id);t&&await this.loadPluginJS(t),await this.refreshFeatureCatalogAfterPluginChange(e.id)}else this.showToast(this.t("installFailed"),"error")}catch(e){this.showToast(this.t("installFailed"),"error")}finally{this.pluginInstalling=null}},async handlePluginUpload(e){const t=e.target?.files?.[0];t&&await this.uploadPluginFile(t,e.target)},async handlePluginDrop(e){const t=e.dataTransfer?.files?.[0];t&&(e.dataTransfer.clearData(),await this.uploadPluginFile(t))},async uploadPluginFile(e,t){if(e.name.endsWith(".zip")){this.pluginUploading=!0;try{const t=new FormData;t.append("file",e);const s=await fetch("/api/plugins/upload",{method:"POST",body:t}),i=await s.json();if(i.success){this.showToast(this.t("installSuccess"),"success"),await this.loadInstalledPlugins();const e=this.installedPlugins.find(e=>e.id===i.plugin_id);e&&await this.loadPluginJS(e),await this.refreshFeatureCatalogAfterPluginChange(i.plugin_id)}else this.showToast(this.t("installFailed"),"error")}catch(e){this.showToast(this.t("installFailed"),"error")}finally{this.pluginUploading=!1,t&&(t.value="")}}else this.showToast(this.t("onlyZipSupported"),"error")},async togglePlugin(e){const t=!e.enabled;try{(await fetch(`/api/plugins/${encodeURIComponent(e.id)}/toggle`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:t})})).ok&&(e.enabled=t,t?(await this.loadPluginJS(e),this.showToast(this.t("settingsSaved"),"success")):(this.cleanupPluginRuntime(e.id),this.showToast(this.t("pluginDisabled"),"success")),await this.refreshFeatureCatalogAfterPluginChange(e.id))}catch(e){this.showToast(this.t("saveFailed"),"error")}},async openPluginSettings(e){this.currentPluginSettings=e;const t={};if(e.settings)for(const s of e.settings)t[s.id]=s.default;if(Object.assign(t,e.settings_values||{}),this.pluginSettingsValues=t,this.pluginApiKeys={},this.pluginApiKeyConfigured={},this.pluginApiKeyActions={},e.proxy&&e.proxy.length>0)try{const t=await fetch("/api/plugins/api-keys");if(t.ok){const s=(await t.json()).api_keys||{};for(const t of e.proxy)this.pluginApiKeys[t.id]="",this.pluginApiKeyConfigured[t.id]=Boolean(s[t.id]?.configured),this.pluginApiKeyActions[t.id]="preserve"}}catch(e){console.error("Failed to load API keys:",e)}this.showPluginSettings=!0,e.customSettings&&setTimeout(()=>{window.dispatchEvent(new CustomEvent("plugin-settings-open",{detail:{pluginId:e.id,customSettings:!0}}))},50)},async savePluginSettings(){if(this.currentPluginSettings)try{if(!(await fetch(`/api/plugins/${encodeURIComponent(this.currentPluginSettings.id)}/settings`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({settings:this.pluginSettingsValues})})).ok)throw new Error("Failed to save settings");for(const[e,t]of Object.entries(this.pluginApiKeys)){const s="clear"===this.pluginApiKeyActions[e]?"clear":t?"replace":"preserve";"preserve"!==s&&await fetch("/api/plugins/api-key",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({service_id:e,api_key:t,action:s})})}const e=this.installedPlugins.find(e=>e.id===this.currentPluginSettings.id);e&&(e.settings_values={...this.pluginSettingsValues}),this.showPluginSettings=!1,this.showToast(this.t("settingsSaved"),"success")}catch(e){this.showToast(this.t("saveFailed"),"error")}},async uninstallPlugin(e){if(e&&confirm(this.t("confirmUninstall")))try{const t=await fetch(`/api/plugins/${encodeURIComponent(e.id)}`,{method:"DELETE"});if(t.ok)this.cleanupPluginRuntime(e.id),this.showPluginSettings=!1,this.currentPluginSettings=null,this.showToast(this.t("uninstallSuccess"),"success"),await this.loadInstalledPlugins(),await this.refreshFeatureCatalogAfterPluginChange(e.id);else{await t.json().catch(()=>({}));this.showToast(this.t("uninstallFailed"),"error")}}catch(e){this.showToast(this.t("uninstallFailed"),"error")}}}} \ No newline at end of file diff --git a/backend/static/frontend-assets.json b/backend/static/frontend-assets.json index 650f596..fb81525 100644 --- a/backend/static/frontend-assets.json +++ b/backend/static/frontend-assets.json @@ -3,12 +3,12 @@ "entrypoint": "index.html", "assets": { "app.js": { - "sha256": "cc4aa6079fd4780a16c66b5a1012792b9014326d4419276d7338c9e94a9f0a3c", - "version": "cc4aa6079fd4780a16c66b5a1012792b9014326d4419276d7338c9e94a9f0a3c" + "sha256": "97e5a4d56d493f21c50511a47044feffc12ea78944d6dbe08b06311eef617fc2", + "version": "97e5a4d56d493f21c50511a47044feffc12ea78944d6dbe08b06311eef617fc2" }, "app.min.js": { - "sha256": "a57414bca76151d047c2df6e186755b45369de30e0fad53cd75700289175a5bf", - "version": "a57414bca76151d047c2df6e186755b45369de30e0fad53cd75700289175a5bf" + "sha256": "3a3a04e64ffe8a8e377f8605abbd14b90f88ea92e9ec0016a74413d1a849a2e4", + "version": "3a3a04e64ffe8a8e377f8605abbd14b90f88ea92e9ec0016a74413d1a849a2e4" }, "auth.css": { "sha256": "f07a31c703b40bdf18566936d997d1ea574d9757113bbeb714040bf7663ee6fe", @@ -59,8 +59,8 @@ "version": "e92dc38bfb1d15dbb9d9ca9e645754f68e7c5b224e33c45a17048bd0cf2407a6" }, "index.html": { - "sha256": "76d787c5477761a729b0e76d98027e2bfb7e0f1610b5a1462ebf823111ef4de0", - "version": "76d787c5477761a729b0e76d98027e2bfb7e0f1610b5a1462ebf823111ef4de0" + "sha256": "3b036329a38bf6a76c4026b8b895b800bd9a1bdd7e8fa9a00a0ba3301210b907", + "version": "3b036329a38bf6a76c4026b8b895b800bd9a1bdd7e8fa9a00a0ba3301210b907" }, "landing-field.svg": { "sha256": "b44d1d8b331f8b7d22bb1127a2c49e4e5d76fd38769fb7ce60c5d533b7f71378", diff --git a/backend/static/index.html b/backend/static/index.html index 1e8bfcd..2832648 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -13,7 +13,7 @@ - + @@ -1883,6 +1883,6 @@

- + diff --git a/frontend-tests/hub-shell.test.mjs b/frontend-tests/hub-shell.test.mjs index f07916d..bc3bc5c 100644 --- a/frontend-tests/hub-shell.test.mjs +++ b/frontend-tests/hub-shell.test.mjs @@ -159,6 +159,25 @@ test('catalog availability requires live main-placement panel registration', () assert.match(app, /const placement = 'main'/); }); +test('initialization restores the saved page only after enabled Plugins load', () => { + const initBody = app.slice( + app.indexOf('async init() {'), + app.indexOf('\n initCrossTabStateSync()', app.indexOf('async init() {')) + ); + assert.match( + initBody, + /await this\.initPluginSystem\(\);[\s\S]*this\.restorePluginWorkspacePage\(\);/ + ); + const pluginInitBody = app.slice( + app.indexOf('initPluginSystem() {'), + app.indexOf('\n initPluginRuntimeSync()', app.indexOf('initPluginSystem() {')) + ); + assert.match( + pluginInitBody, + /this\.initPluginRuntimeSync\(\);[\s\S]*return this\.loadEnabledPlugins\(\);/ + ); +}); + test('installed plugins without a declared icon do not request a missing resource', () => { assert.match(html, /