mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 18:50:56 +08:00
fix: minapp search error
This commit is contained in:
parent
1b125270b5
commit
446acbc662
@ -51,7 +51,6 @@ const MinAppsPopover: FC<Props> = ({ children }) => {
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
</Center>
|
||||
)}
|
||||
<App isLast app={minapps[0]} onClick={handleClose} size={50} />
|
||||
</AppsContainer>
|
||||
</PopoverContent>
|
||||
)
|
||||
|
||||
@ -100,7 +100,7 @@
|
||||
"titleLabel": "标题",
|
||||
"titlePlaceholder": "输入标题",
|
||||
"contentLabel": "内容",
|
||||
"contentPlaceholder": "请输入短语内容,支持使用变量,然后按Tab键可以快速定位到变量进行修改。比如:\n帮我规划从${from}到${to}的路线,然后发送到${email}。"
|
||||
"contentPlaceholder": "请输入短语内容,支持使用变量,然后按Tab键可以快速定位到变量进行修改。比如:\n帮我规划从${from}到${to}的路线,然后发送到${email}"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
@ -113,7 +113,7 @@
|
||||
"backup": {
|
||||
"confirm": "确定要备份数据吗?",
|
||||
"confirm.button": "选择备份位置",
|
||||
"content": "备份全部数据,包括聊天记录、设置、知识库等所有数据。请注意,备份过程可能需要一些时间,感谢您的耐心等待。",
|
||||
"content": "备份全部数据,包括聊天记录、设置、知识库等所有数据。请注意,备份过程可能需要一些时间,感谢您的耐心等待",
|
||||
"progress": {
|
||||
"completed": "备份完成",
|
||||
"compressing": "压缩文件...",
|
||||
@ -144,7 +144,7 @@
|
||||
"artifacts.preview.openExternal.error.content": "外部浏览器打开出错",
|
||||
"assistant.search.placeholder": "搜索",
|
||||
"deeply_thought": "已深度思考(用时 {{seconds}} 秒)",
|
||||
"default.description": "你好,我是默认助手。你可以立刻开始跟我聊天。",
|
||||
"default.description": "你好,我是默认助手。你可以立刻开始跟我聊天",
|
||||
"default.name": "默认助手",
|
||||
"default.topic.name": "默认话题",
|
||||
"history": {
|
||||
@ -238,7 +238,7 @@
|
||||
"settings.code_cacheable": "代码块缓存",
|
||||
"settings.code_cacheable.tip": "缓存代码块可以减少长代码块的渲染时间,但会增加内存占用",
|
||||
"settings.code_cache_max_size": "缓存上限",
|
||||
"settings.code_cache_max_size.tip": "允许缓存的字符数上限(千字符),按照高亮后的代码计算。高亮后的代码长度相比于纯文本会长很多。",
|
||||
"settings.code_cache_max_size.tip": "允许缓存的字符数上限(千字符),按照高亮后的代码计算。高亮后的代码长度相比于纯文本会长很多",
|
||||
"settings.code_cache_ttl": "缓存期限",
|
||||
"settings.code_cache_ttl.tip": "缓存过期时间(分钟)",
|
||||
"settings.code_cache_threshold": "缓存阈值",
|
||||
@ -942,7 +942,7 @@
|
||||
"restore": {
|
||||
"confirm": "确定要恢复数据吗?",
|
||||
"confirm.button": "选择备份文件",
|
||||
"content": "恢复操作将使用备份数据覆盖当前所有应用数据。请注意,恢复过程可能需要一些时间,感谢您的耐心等待。",
|
||||
"content": "恢复操作将使用备份数据覆盖当前所有应用数据。请注意,恢复过程可能需要一些时间,感谢您的耐心等待",
|
||||
"progress": {
|
||||
"completed": "恢复完成",
|
||||
"copying_files": "复制文件... {{progress}}%",
|
||||
@ -995,7 +995,7 @@
|
||||
"app_knowledge.remove_all_success": "文件删除成功",
|
||||
"app_logs": "应用日志",
|
||||
"backup.skip_file_data_title": "精简备份",
|
||||
"backup.skip_file_data_help": "备份时跳过备份图片、知识库等数据文件,仅备份聊天记录和设置。减少空间占用, 加快备份速度。",
|
||||
"backup.skip_file_data_help": "备份时跳过备份图片、知识库等数据文件,仅备份聊天记录和设置。减少空间占用, 加快备份速度",
|
||||
"clear_cache": {
|
||||
"button": "清除缓存",
|
||||
"confirm": "清除缓存将删除应用缓存的数据,包括小程序数据。此操作不可恢复,是否继续?",
|
||||
@ -1037,7 +1037,7 @@
|
||||
"url": "Joplin 剪裁服务监听 URL",
|
||||
"url_placeholder": "http://127.0.0.1:41184/"
|
||||
},
|
||||
"markdown_export.force_dollar_math.help": "开启后,导出Markdown时会将强制使用$$来标记LaTeX公式。注意:该项也会影响所有通过Markdown导出的方式,如Notion、语雀等。",
|
||||
"markdown_export.force_dollar_math.help": "开启后,导出Markdown时会将强制使用$$来标记LaTeX公式。注意:该项也会影响所有通过Markdown导出的方式,如Notion、语雀等",
|
||||
"markdown_export.force_dollar_math.title": "强制使用$$来标记LaTeX公式",
|
||||
"markdown_export.help": "若填入,则每次导出时将自动保存到该路径;否则,将弹出保存对话框",
|
||||
"markdown_export.path": "默认导出路径",
|
||||
@ -1045,7 +1045,7 @@
|
||||
"markdown_export.select": "选择",
|
||||
"markdown_export.title": "Markdown 导出",
|
||||
"message_title.use_topic_naming.title": "使用话题命名模型为导出的消息创建标题",
|
||||
"message_title.use_topic_naming.help": "开启后,使用话题命名模型为导出的消息创建标题。该项也会影响所有通过Markdown导出的方式。",
|
||||
"message_title.use_topic_naming.help": "开启后,使用话题命名模型为导出的消息创建标题。该项也会影响所有通过Markdown导出的方式",
|
||||
"minute_interval_one": "{{count}} 分钟",
|
||||
"minute_interval_other": "{{count}} 分钟",
|
||||
"notion.api_key": "Notion 密钥",
|
||||
@ -1084,8 +1084,8 @@
|
||||
"backup.manager.restore.success": "恢复成功,应用将在几秒后刷新",
|
||||
"backup.manager.restore.error": "恢复失败",
|
||||
"backup.manager.delete.confirm.title": "确认删除",
|
||||
"backup.manager.delete.confirm.single": "确定要删除备份文件 \"{{fileName}}\" 吗?此操作不可恢复。",
|
||||
"backup.manager.delete.confirm.multiple": "确定要删除选中的 {{count}} 个备份文件吗?此操作不可恢复。",
|
||||
"backup.manager.delete.confirm.single": "确定要删除备份文件 \"{{fileName}}\" 吗?此操作不可恢复",
|
||||
"backup.manager.delete.confirm.multiple": "确定要删除选中的 {{count}} 个备份文件吗?此操作不可恢复",
|
||||
"backup.manager.delete.success.single": "删除成功",
|
||||
"backup.manager.delete.success.multiple": "成功删除 {{count}} 个备份文件",
|
||||
"backup.manager.delete.error": "删除失败",
|
||||
@ -1215,20 +1215,20 @@
|
||||
"custom": {
|
||||
"title": "自定义",
|
||||
"edit_title": "编辑自定义小程序",
|
||||
"save_success": "自定义小程序保存成功。",
|
||||
"save_error": "自定义小程序保存失败。",
|
||||
"remove_success": "自定义小程序删除成功。",
|
||||
"remove_error": "自定义小程序删除失败。",
|
||||
"logo_upload_success": "Logo 上传成功。",
|
||||
"logo_upload_error": "Logo 上传失败。",
|
||||
"save_success": "自定义小程序保存成功",
|
||||
"save_error": "自定义小程序保存失败",
|
||||
"remove_success": "自定义小程序删除成功",
|
||||
"remove_error": "自定义小程序删除失败",
|
||||
"logo_upload_success": "Logo 上传成功",
|
||||
"logo_upload_error": "Logo 上传失败",
|
||||
"id": "ID",
|
||||
"id_error": "ID 是必填项。",
|
||||
"id_error": "ID 是必填项",
|
||||
"id_placeholder": "请输入 ID",
|
||||
"name": "名称",
|
||||
"name_error": "名称是必填项。",
|
||||
"name_error": "名称是必填项",
|
||||
"name_placeholder": "请输入名称",
|
||||
"url": "URL",
|
||||
"url_error": "URL 是必填项。",
|
||||
"url_error": "URL 是必填项",
|
||||
"url_placeholder": "请输入 URL",
|
||||
"logo": "Logo",
|
||||
"logo_url": "Logo URL",
|
||||
@ -1238,7 +1238,7 @@
|
||||
"logo_upload_label": "上传 Logo",
|
||||
"logo_upload_button": "上传",
|
||||
"save": "保存",
|
||||
"edit_description": "在这里编辑自定义小应用的配置。每个应用需要包含 id、name、url 和 logo 字段。",
|
||||
"edit_description": "在这里编辑自定义小应用的配置。每个应用需要包含 id、name、url 和 logo 字段",
|
||||
"placeholder": "请输入自定义小程序配置(JSON格式)",
|
||||
"duplicate_ids": "发现重复的ID: {{ids}}",
|
||||
"conflicting_ids": "与默认应用ID冲突: {{ids}}"
|
||||
@ -1286,7 +1286,7 @@
|
||||
"addServer": "添加服务器",
|
||||
"addServer.create": "快速创建",
|
||||
"addServer.importFrom": "从 JSON 导入",
|
||||
"addServer.importFrom.tooltip": "请从 MCP Servers 的介绍页面复制配置JSON(优先使用\n NPX或 UVX 配置),并粘贴到输入框中。",
|
||||
"addServer.importFrom.tooltip": "请从 MCP Servers 的介绍页面复制配置JSON(优先使用\n NPX或 UVX 配置),并粘贴到输入框中",
|
||||
"addServer.importFrom.placeholder": "粘贴 MCP 服务器 JSON 配置",
|
||||
"addServer.importFrom.invalid": "无效输入,请检查 JSON 格式",
|
||||
"addServer.importFrom.nameExists": "服务器已存在:{{name}}",
|
||||
@ -1321,7 +1321,7 @@
|
||||
"installError": "安装依赖项失败",
|
||||
"installSuccess": "依赖项安装成功",
|
||||
"jsonFormatError": "JSON格式化错误",
|
||||
"jsonModeHint": "编辑MCP服务器配置的JSON表示。保存前请确保格式正确。",
|
||||
"jsonModeHint": "编辑MCP服务器配置的JSON表示。保存前请确保格式正确",
|
||||
"jsonSaveError": "保存JSON配置失败",
|
||||
"jsonSaveSuccess": "JSON配置已保存",
|
||||
"missingDependencies": "缺失,请安装它以继续",
|
||||
@ -1388,7 +1388,7 @@
|
||||
"deleteServer": "删除服务器",
|
||||
"deleteServerConfirm": "确定要删除此服务器吗?",
|
||||
"registry": "包管理源",
|
||||
"registryTooltip": "选择用于安装包的源,以解决默认源的网络问题。",
|
||||
"registryTooltip": "选择用于安装包的源,以解决默认源的网络问题",
|
||||
"registryDefault": "默认",
|
||||
"not_support": "模型不支持",
|
||||
"user": "用户",
|
||||
@ -1472,7 +1472,7 @@
|
||||
"models.check.model_status_partial": "其中 {{count}} 个模型用某些密钥无法访问",
|
||||
"models.check.model_status_passed": "{{count}} 个模型通过健康检测",
|
||||
"models.check.model_status_summary": "{{provider}}: {{summary}}",
|
||||
"models.check.no_api_keys": "未找到API密钥,请先添加API密钥。",
|
||||
"models.check.no_api_keys": "未找到API密钥,请先添加API密钥",
|
||||
"models.check.passed": "通过",
|
||||
"models.check.select_api_key": "选择要使用的API密钥:",
|
||||
"models.check.single": "单个",
|
||||
@ -1520,7 +1520,7 @@
|
||||
"api_key.tip": "多个密钥使用逗号分隔",
|
||||
"api_version": "API 版本",
|
||||
"basic_auth": "HTTP 认证",
|
||||
"basic_auth.tip": "适用于通过服务器部署的实例(参见文档)。目前仅支持 Basic 方案(RFC7617)。",
|
||||
"basic_auth.tip": "适用于通过服务器部署的实例(参见文档)。目前仅支持 Basic 方案(RFC7617)",
|
||||
"basic_auth.user_name": "用户名",
|
||||
"basic_auth.user_name.tip": "留空以禁用",
|
||||
"basic_auth.password": "密码",
|
||||
@ -1682,7 +1682,7 @@
|
||||
"titleLabel": "标题",
|
||||
"contentLabel": "内容",
|
||||
"titlePlaceholder": "请输入短语标题",
|
||||
"contentPlaceholder": "请输入短语内容,支持使用变量,然后按Tab键可以快速定位到变量进行修改。比如:\n帮我规划从${from}到${to}的路线,然后发送到${email}。",
|
||||
"contentPlaceholder": "请输入短语内容,支持使用变量,然后按Tab键可以快速定位到变量进行修改。比如:\n帮我规划从${from}到${to}的路线,然后发送到${email}",
|
||||
"delete": "删除短语",
|
||||
"deleteConfirm": "删除短语后将无法恢复,是否继续?",
|
||||
"locationLabel": "添加位置",
|
||||
|
||||
@ -93,7 +93,7 @@
|
||||
"titleLabel": "標題",
|
||||
"titlePlaceholder": "輸入標題",
|
||||
"contentLabel": "內容",
|
||||
"contentPlaceholder": "請輸入短語內容,支持使用變量,然後按Tab鍵可以快速定位到變量進行修改。比如:\n幫我規劃從${from}到${to}的行程,然後發送到${email}。"
|
||||
"contentPlaceholder": "請輸入短語內容,支持使用變量,然後按Tab鍵可以快速定位到變量進行修改。比如:\n幫我規劃從${from}到${to}的行程,然後發送到${email}"
|
||||
},
|
||||
"settings.knowledge_base.recognition.tip": "智慧代理人將調用大語言模型的意圖識別能力,判斷是否需要調用知識庫進行回答,該功能將依賴模型的能力",
|
||||
"settings.knowledge_base.recognition": "調用知識庫",
|
||||
@ -113,7 +113,7 @@
|
||||
"backup": {
|
||||
"confirm": "確定要備份資料嗎?",
|
||||
"confirm.button": "選擇備份位置",
|
||||
"content": "備份全部資料,包括聊天記錄、設定、知識庫等全部資料。請注意,備份過程可能需要一些時間,感謝您的耐心等待。",
|
||||
"content": "備份全部資料,包括聊天記錄、設定、知識庫等全部資料。請注意,備份過程可能需要一些時間,感謝您的耐心等待",
|
||||
"progress": {
|
||||
"completed": "備份完成",
|
||||
"compressing": "壓縮檔案...",
|
||||
@ -144,7 +144,7 @@
|
||||
"artifacts.preview.openExternal.error.content": "外部瀏覽器開啟出錯",
|
||||
"assistant.search.placeholder": "搜尋",
|
||||
"deeply_thought": "已深度思考(用時 {{seconds}} 秒)",
|
||||
"default.description": "你好,我是預設助手。你可以立即開始與我聊天。",
|
||||
"default.description": "你好,我是預設助手。你可以立即開始與我聊天",
|
||||
"default.name": "預設助手",
|
||||
"default.topic.name": "預設話題",
|
||||
"history": {
|
||||
@ -224,18 +224,18 @@
|
||||
"settings.code_cacheable": "程式碼區塊快取",
|
||||
"settings.code_cacheable.tip": "快取程式碼區塊可以減少長程式碼區塊的渲染時間,但會增加記憶體使用量",
|
||||
"settings.code_cache_max_size": "快取上限",
|
||||
"settings.code_cache_max_size.tip": "允許快取的字元數上限(千字符),按照高亮後的程式碼計算。高亮後的程式碼長度相比純文字會長很多。",
|
||||
"settings.code_cache_max_size.tip": "允許快取的字元數上限(千字符),按照高亮後的程式碼計算。高亮後的程式碼長度相比純文字會長很多",
|
||||
"settings.code_cache_ttl": "快取期限",
|
||||
"settings.code_cache_ttl.tip": "快取的存活時間(分鐘)",
|
||||
"settings.code_cache_threshold": "快取門檻",
|
||||
"settings.code_cache_threshold.tip": "允許快取的最小程式碼長度(千字符),超過門檻的程式碼區塊才會被快取",
|
||||
"settings.context_count": "上下文",
|
||||
"settings.context_count.tip": "在上下文中保留的前幾則訊息。",
|
||||
"settings.context_count.tip": "在上下文中保留的前幾則訊息",
|
||||
"settings.max": "最大",
|
||||
"settings.max_tokens": "最大 Token 數",
|
||||
"settings.max_tokens.confirm": "設置最大 Token 數",
|
||||
"settings.max_tokens.confirm_content": "設置單次交互所用的最大 Token 數,會影響返回結果的長度。要根據模型上下文限制來設定,否則會發生錯誤。",
|
||||
"settings.max_tokens.tip": "模型可以生成的最大 Token 數。要根據模型上下文限制來設定,否則會發生錯誤。",
|
||||
"settings.max_tokens.confirm_content": "設置單次交互所用的最大 Token 數,會影響返回結果的長度。要根據模型上下文限制來設定,否則會發生錯誤",
|
||||
"settings.max_tokens.tip": "模型可以生成的最大 Token 數。要根據模型上下文限制來設定,否則會發生錯誤",
|
||||
"settings.reset": "重設",
|
||||
"settings.set_as_default": "設為預設助手",
|
||||
"settings.show_line_numbers": "程式碼顯示行號",
|
||||
@ -467,7 +467,7 @@
|
||||
"type": "類型"
|
||||
},
|
||||
"gpustack": {
|
||||
"keep_alive_time.description": "模型在記憶體中保持的時間(預設為 5 分鐘)。",
|
||||
"keep_alive_time.description": "模型在記憶體中保持的時間(預設為 5 分鐘)",
|
||||
"keep_alive_time.placeholder": "分鐘",
|
||||
"keep_alive_time.title": "保持活躍時間",
|
||||
"title": "GPUStack"
|
||||
@ -568,7 +568,7 @@
|
||||
"spanish": "西班牙文"
|
||||
},
|
||||
"lmstudio": {
|
||||
"keep_alive_time.description": "對話後模型在記憶體中保持的時間(預設為 5 分鐘)。",
|
||||
"keep_alive_time.description": "對話後模型在記憶體中保持的時間(預設為 5 分鐘)",
|
||||
"keep_alive_time.placeholder": "分鐘",
|
||||
"keep_alive_time.title": "保持活躍時間",
|
||||
"title": "LM Studio"
|
||||
@ -788,7 +788,7 @@
|
||||
"knowledge.error": "無法將 {{type}} 加入知識庫: {{error}}"
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time.description": "對話後模型在記憶體中保持的時間(預設為 5 分鐘)。",
|
||||
"keep_alive_time.description": "對話後模型在記憶體中保持的時間(預設為 5 分鐘)",
|
||||
"keep_alive_time.placeholder": "分鐘",
|
||||
"keep_alive_time.title": "保持活躍時間",
|
||||
"title": "Ollama"
|
||||
@ -943,7 +943,7 @@
|
||||
"restore": {
|
||||
"confirm": "確定要復原資料嗎?",
|
||||
"confirm.button": "選擇備份檔案",
|
||||
"content": "復原操作將使用備份資料覆蓋目前所有應用程式資料。請注意,復原過程可能需要一些時間,感謝您的耐心等待。",
|
||||
"content": "復原操作將使用備份資料覆蓋目前所有應用程式資料。請注意,復原過程可能需要一些時間,感謝您的耐心等待",
|
||||
"progress": {
|
||||
"completed": "復原完成",
|
||||
"copying_files": "複製檔案... {{progress}}%",
|
||||
@ -996,7 +996,7 @@
|
||||
"app_knowledge.remove_all_success": "檔案刪除成功",
|
||||
"app_logs": "應用程式日誌",
|
||||
"backup.skip_file_data_title": "精簡備份",
|
||||
"backup.skip_file_data_help": "備份時跳過備份圖片、知識庫等數據文件,僅備份聊天記錄和設置。減少空間佔用, 加快備份速度。",
|
||||
"backup.skip_file_data_help": "備份時跳過備份圖片、知識庫等數據文件,僅備份聊天記錄和設置。減少空間佔用, 加快備份速度",
|
||||
"clear_cache": {
|
||||
"button": "清除快取",
|
||||
"confirm": "清除快取將刪除應用快取資料,包括小工具資料。此操作不可恢復,是否繼續?",
|
||||
@ -1038,9 +1038,9 @@
|
||||
"url": "Joplin 剪輯服務 URL",
|
||||
"url_placeholder": "http://127.0.0.1:41184/"
|
||||
},
|
||||
"markdown_export.force_dollar_math.help": "開啟後,匯出Markdown時會強制使用$$來標記LaTeX公式。注意:該項也會影響所有透過Markdown匯出的方式,如Notion、語雀等。",
|
||||
"markdown_export.force_dollar_math.help": "開啟後,匯出Markdown時會強制使用$$來標記LaTeX公式。注意:該項也會影響所有透過Markdown匯出的方式,如Notion、語雀等",
|
||||
"markdown_export.force_dollar_math.title": "LaTeX公式強制使用$$",
|
||||
"markdown_export.help": "若填入,每次匯出時將自動儲存至該路徑;否則,將彈出儲存對話框。",
|
||||
"markdown_export.help": "若填入,每次匯出時將自動儲存至該路徑;否則,將彈出儲存對話框",
|
||||
"markdown_export.path": "預設匯出路徑",
|
||||
"markdown_export.path_placeholder": "匯出路徑",
|
||||
"markdown_export.select": "選擇",
|
||||
@ -1083,8 +1083,8 @@
|
||||
"backup.manager.restore.success": "恢復成功,應用將在幾秒後刷新",
|
||||
"backup.manager.restore.error": "恢復失敗",
|
||||
"backup.manager.delete.confirm.title": "確認刪除",
|
||||
"backup.manager.delete.confirm.single": "確定要刪除備份文件 \"{{fileName}}\" 嗎?此操作不可恢復。",
|
||||
"backup.manager.delete.confirm.multiple": "確定要刪除選中的 {{count}} 個備份文件嗎?此操作不可恢復。",
|
||||
"backup.manager.delete.confirm.single": "確定要刪除備份文件 \"{{fileName}}\" 嗎?此操作不可恢復",
|
||||
"backup.manager.delete.confirm.multiple": "確定要刪除選中的 {{count}} 個備份文件嗎?此操作不可恢復",
|
||||
"backup.manager.delete.success.single": "刪除成功",
|
||||
"backup.manager.delete.success.multiple": "成功刪除 {{count}} 個備份文件",
|
||||
"backup.manager.delete.error": "刪除失敗",
|
||||
@ -1187,7 +1187,7 @@
|
||||
"new_folder.button": "新建文件夾"
|
||||
},
|
||||
"message_title.use_topic_naming.title": "使用話題命名模型為導出的消息創建標題",
|
||||
"message_title.use_topic_naming.help": "此設定會影響所有通過Markdown導出的方式,如Notion、語雀等。"
|
||||
"message_title.use_topic_naming.help": "此設定會影響所有通過Markdown導出的方式,如Notion、語雀等"
|
||||
},
|
||||
"display.assistant.title": "助手設定",
|
||||
"display.custom.css": "自訂 CSS",
|
||||
@ -1218,20 +1218,20 @@
|
||||
"conflicting_ids": "與預設應用ID衝突: {{ids}}",
|
||||
"title": "自定義",
|
||||
"edit_title": "編輯自定義小程序",
|
||||
"save_success": "自定義小程序保存成功。",
|
||||
"save_error": "自定義小程序保存失敗。",
|
||||
"remove_success": "自定義小程序刪除成功。",
|
||||
"remove_error": "自定義小程序刪除失敗。",
|
||||
"logo_upload_success": "Logo 上傳成功。",
|
||||
"logo_upload_error": "Logo 上傳失敗。",
|
||||
"save_success": "自定義小程序保存成功",
|
||||
"save_error": "自定義小程序保存失敗",
|
||||
"remove_success": "自定義小程序刪除成功",
|
||||
"remove_error": "自定義小程序刪除失敗",
|
||||
"logo_upload_success": "Logo 上傳成功",
|
||||
"logo_upload_error": "Logo 上傳失敗",
|
||||
"id": "ID",
|
||||
"id_error": "ID 是必填項。",
|
||||
"id_error": "ID 是必填項",
|
||||
"id_placeholder": "請輸入 ID",
|
||||
"name": "名稱",
|
||||
"name_error": "名稱是必填項。",
|
||||
"name_error": "名稱是必填項",
|
||||
"name_placeholder": "請輸入名稱",
|
||||
"url": "URL",
|
||||
"url_error": "URL 是必填項。",
|
||||
"url_error": "URL 是必填項",
|
||||
"url_placeholder": "請輸入 URL",
|
||||
"logo": "Logo",
|
||||
"logo_url": "Logo URL",
|
||||
@ -1286,7 +1286,7 @@
|
||||
"addServer": "新增伺服器",
|
||||
"addServer.create": "快速創建",
|
||||
"addServer.importFrom": "從 JSON 導入",
|
||||
"addServer.importFrom.tooltip": "請從 MCP Servers 的介紹頁面複製配置JSON(優先使用\n NPX或 UVX 配置),並粘貼到輸入框中。",
|
||||
"addServer.importFrom.tooltip": "請從 MCP Servers 的介紹頁面複製配置JSON(優先使用\n NPX或 UVX 配置),並粘貼到輸入框中",
|
||||
"addServer.importFrom.placeholder": "貼上 MCP 伺服器 JSON 設定",
|
||||
"addServer.importFrom.invalid": "無效的輸入,請檢查 JSON 格式",
|
||||
"addServer.importFrom.nameExists": "伺服器已存在:{{name}}",
|
||||
@ -1321,7 +1321,7 @@
|
||||
"installError": "安裝相依套件失敗",
|
||||
"installSuccess": "相依套件安裝成功",
|
||||
"jsonFormatError": "JSON格式錯誤",
|
||||
"jsonModeHint": "編輯MCP伺服器配置的JSON表示。保存前請確保格式正確。",
|
||||
"jsonModeHint": "編輯MCP伺服器配置的JSON表示。保存前請確保格式正確",
|
||||
"jsonSaveError": "保存JSON配置失敗",
|
||||
"jsonSaveSuccess": "JSON配置已儲存",
|
||||
"missingDependencies": "缺失,請安裝它以繼續",
|
||||
@ -1388,7 +1388,7 @@
|
||||
"deleteServer": "刪除伺服器",
|
||||
"deleteServerConfirm": "確定要刪除此伺服器嗎?",
|
||||
"registry": "套件管理源",
|
||||
"registryTooltip": "選擇用於安裝套件的源,以解決預設源的網路問題。",
|
||||
"registryTooltip": "選擇用於安裝套件的源,以解決預設源的網路問題",
|
||||
"registryDefault": "預設",
|
||||
"not_support": "不支援此模型",
|
||||
"user": "用戶",
|
||||
@ -1472,7 +1472,7 @@
|
||||
"models.check.model_status_partial": "其中 {{count}} 個模型用某些密鑰無法訪問",
|
||||
"models.check.model_status_passed": "{{count}} 個模型通過健康檢查",
|
||||
"models.check.model_status_summary": "{{provider}}: {{summary}}",
|
||||
"models.check.no_api_keys": "未找到API密鑰,請先添加API密鑰。",
|
||||
"models.check.no_api_keys": "未找到API密鑰,請先添加API密鑰",
|
||||
"models.check.passed": "通過",
|
||||
"models.check.select_api_key": "選擇要使用的API密鑰:",
|
||||
"models.check.single": "單個",
|
||||
@ -1514,7 +1514,7 @@
|
||||
"api_key.tip": "多個金鑰使用逗號分隔",
|
||||
"api_version": "API 版本",
|
||||
"basic_auth": "HTTP 認證",
|
||||
"basic_auth.tip": "適用於透過伺服器部署的實例(請參閱文檔)。目前僅支援 Basic 方案(RFC7617)。",
|
||||
"basic_auth.tip": "適用於透過伺服器部署的實例(請參閱文檔)。目前僅支援 Basic 方案(RFC7617)",
|
||||
"basic_auth.user_name": "用戶",
|
||||
"basic_auth.user_name.tip": "留空以停用",
|
||||
"basic_auth.password": "密碼",
|
||||
@ -1572,7 +1572,7 @@
|
||||
"markdown_editor_default_value": "預覽區域"
|
||||
},
|
||||
"openai": {
|
||||
"alert": "OpenAI Provider 不再支援舊的呼叫方法。如果使用第三方 API,請建立新的服務供應商。"
|
||||
"alert": "OpenAI Provider 不再支援舊的呼叫方法。如果使用第三方 API,請建立新的服務供應商"
|
||||
}
|
||||
},
|
||||
"proxy": {
|
||||
@ -1667,7 +1667,7 @@
|
||||
"apikey": "API 金鑰",
|
||||
"free": "免費",
|
||||
"content_limit": "內容長度限制",
|
||||
"content_limit_tooltip": "限制搜尋結果的內容長度,超過限制的內容將被截斷。"
|
||||
"content_limit_tooltip": "限制搜尋結果的內容長度,超過限制的內容將被截斷"
|
||||
},
|
||||
"general.auto_check_update.title": "啟用自動更新",
|
||||
"quickPhrase": {
|
||||
@ -1677,7 +1677,7 @@
|
||||
"titleLabel": "標題",
|
||||
"contentLabel": "內容",
|
||||
"titlePlaceholder": "請輸入短語標題",
|
||||
"contentPlaceholder": "請輸入短語內容,支持使用變量,然後按Tab鍵可以快速定位到變量進行修改。比如:\n幫我規劃從${from}到${to}的行程,然後發送到${email}。",
|
||||
"contentPlaceholder": "請輸入短語內容,支持使用變量,然後按Tab鍵可以快速定位到變量進行修改。比如:\n幫我規劃從${from}到${to}的行程,然後發送到${email}",
|
||||
"delete": "刪除短語",
|
||||
"deleteConfirm": "刪除後無法復原,是否繼續?",
|
||||
"locationLabel": "添加位置",
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
import { PlusOutlined, UploadOutlined } from '@ant-design/icons'
|
||||
import MinAppIcon from '@renderer/components/Icons/MinAppIcon'
|
||||
import { loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateDefaultMinApps } from '@renderer/config/minapps'
|
||||
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
||||
import { useMinapps } from '@renderer/hooks/useMinapps'
|
||||
import { MinAppType } from '@renderer/types'
|
||||
import type { MenuProps } from 'antd'
|
||||
import { Button, Dropdown, Form, Input, message, Modal, Radio, Upload } from 'antd'
|
||||
import type { UploadFile } from 'antd/es/upload/interface'
|
||||
import { FC, useState } from 'react'
|
||||
import { Dropdown, message } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@ -24,215 +22,72 @@ const App: FC<Props> = ({ app, onClick, size = 60, isLast }) => {
|
||||
const { minapps, pinned, disabled, updateMinapps, updateDisabledMinapps, updatePinnedMinapps } = useMinapps()
|
||||
const isPinned = pinned.some((p) => p.id === app.id)
|
||||
const isVisible = minapps.some((m) => m.id === app.id)
|
||||
const [isModalVisible, setIsModalVisible] = useState(false)
|
||||
const [form] = Form.useForm()
|
||||
const [logoType, setLogoType] = useState<'url' | 'file'>('url')
|
||||
const [fileList, setFileList] = useState<UploadFile[]>([])
|
||||
|
||||
const handleClick = () => {
|
||||
if (isLast) {
|
||||
setIsModalVisible(true)
|
||||
return
|
||||
}
|
||||
openMinappKeepAlive(app)
|
||||
onClick?.()
|
||||
}
|
||||
|
||||
const handleAddCustomApp = async (values: any) => {
|
||||
try {
|
||||
const content = await window.api.file.read('custom-minapps.json')
|
||||
const customApps = JSON.parse(content)
|
||||
|
||||
// Check for duplicate ID
|
||||
if (customApps.some((app: MinAppType) => app.id === values.id)) {
|
||||
message.error(t('settings.miniapps.custom.duplicate_ids', { ids: values.id }))
|
||||
return
|
||||
const menuItems: MenuProps['items'] = [
|
||||
{
|
||||
key: 'togglePin',
|
||||
label: isPinned ? t('minapp.sidebar.remove.title') : t('minapp.sidebar.add.title'),
|
||||
onClick: () => {
|
||||
const newPinned = isPinned ? pinned.filter((item) => item.id !== app.id) : [...(pinned || []), app]
|
||||
updatePinnedMinapps(newPinned)
|
||||
}
|
||||
if (ORIGIN_DEFAULT_MIN_APPS.some((app: MinAppType) => app.id === values.id)) {
|
||||
message.error(t('settings.miniapps.custom.conflicting_ids', { ids: values.id }))
|
||||
return
|
||||
},
|
||||
{
|
||||
key: 'hide',
|
||||
label: t('minapp.sidebar.hide.title'),
|
||||
onClick: () => {
|
||||
const newMinapps = minapps.filter((item) => item.id !== app.id)
|
||||
updateMinapps(newMinapps)
|
||||
const newDisabled = [...(disabled || []), app]
|
||||
updateDisabledMinapps(newDisabled)
|
||||
const newPinned = pinned.filter((item) => item.id !== app.id)
|
||||
updatePinnedMinapps(newPinned)
|
||||
}
|
||||
|
||||
const newApp: MinAppType = {
|
||||
id: values.id,
|
||||
name: values.name,
|
||||
url: values.url,
|
||||
logo: form.getFieldValue('logo') || '',
|
||||
type: 'Custom',
|
||||
addTime: new Date().toISOString()
|
||||
}
|
||||
customApps.push(newApp)
|
||||
await window.api.file.writeWithId('custom-minapps.json', JSON.stringify(customApps, null, 2))
|
||||
message.success(t('settings.miniapps.custom.save_success'))
|
||||
setIsModalVisible(false)
|
||||
form.resetFields()
|
||||
setFileList([])
|
||||
// 重新加载应用列表
|
||||
const reloadedApps = [...ORIGIN_DEFAULT_MIN_APPS, ...(await loadCustomMiniApp())]
|
||||
updateDefaultMinApps(reloadedApps)
|
||||
updateMinapps([...minapps, newApp])
|
||||
} catch (error) {
|
||||
message.error(t('settings.miniapps.custom.save_error'))
|
||||
console.error('Failed to save custom mini app:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleLogoTypeChange = (e: any) => {
|
||||
setLogoType(e.target.value)
|
||||
form.setFieldValue('logo', '')
|
||||
setFileList([])
|
||||
}
|
||||
|
||||
const handleFileChange = async (info: any) => {
|
||||
const file = info.fileList[info.fileList.length - 1]?.originFileObj
|
||||
setFileList(info.fileList.slice(-1))
|
||||
|
||||
if (file) {
|
||||
try {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (event) => {
|
||||
const base64Data = event.target?.result
|
||||
if (typeof base64Data === 'string') {
|
||||
message.success(t('settings.miniapps.custom.logo_upload_success'))
|
||||
form.setFieldValue('logo', base64Data)
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
} catch (error) {
|
||||
console.error('Failed to read file:', error)
|
||||
message.error(t('settings.miniapps.custom.logo_upload_error'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const menuItems: MenuProps['items'] = isLast
|
||||
? []
|
||||
: [
|
||||
{
|
||||
key: 'togglePin',
|
||||
label: isPinned ? t('minapp.sidebar.remove.title') : t('minapp.sidebar.add.title'),
|
||||
onClick: () => {
|
||||
const newPinned = isPinned ? pinned.filter((item) => item.id !== app.id) : [...(pinned || []), app]
|
||||
updatePinnedMinapps(newPinned)
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'hide',
|
||||
label: t('minapp.sidebar.hide.title'),
|
||||
onClick: () => {
|
||||
const newMinapps = minapps.filter((item) => item.id !== app.id)
|
||||
updateMinapps(newMinapps)
|
||||
const newDisabled = [...(disabled || []), app]
|
||||
updateDisabledMinapps(newDisabled)
|
||||
const newPinned = pinned.filter((item) => item.id !== app.id)
|
||||
updatePinnedMinapps(newPinned)
|
||||
}
|
||||
},
|
||||
...(app.type === 'Custom'
|
||||
? [
|
||||
{
|
||||
key: 'removeCustom',
|
||||
label: t('minapp.sidebar.remove_custom.title'),
|
||||
danger: true,
|
||||
onClick: async () => {
|
||||
try {
|
||||
const content = await window.api.file.read('custom-minapps.json')
|
||||
const customApps = JSON.parse(content)
|
||||
const updatedApps = customApps.filter((customApp: MinAppType) => customApp.id !== app.id)
|
||||
await window.api.file.writeWithId('custom-minapps.json', JSON.stringify(updatedApps, null, 2))
|
||||
message.success(t('settings.miniapps.custom.remove_success'))
|
||||
const reloadedApps = [...ORIGIN_DEFAULT_MIN_APPS, ...(await loadCustomMiniApp())]
|
||||
updateDefaultMinApps(reloadedApps)
|
||||
updateMinapps(minapps.filter((item) => item.id !== app.id))
|
||||
updatePinnedMinapps(pinned.filter((item) => item.id !== app.id))
|
||||
updateDisabledMinapps(disabled.filter((item) => item.id !== app.id))
|
||||
} catch (error) {
|
||||
message.error(t('settings.miniapps.custom.remove_error'))
|
||||
console.error('Failed to remove custom mini app:', error)
|
||||
}
|
||||
}
|
||||
},
|
||||
...(app.type === 'Custom'
|
||||
? [
|
||||
{
|
||||
key: 'removeCustom',
|
||||
label: t('minapp.sidebar.remove_custom.title'),
|
||||
danger: true,
|
||||
onClick: async () => {
|
||||
try {
|
||||
const content = await window.api.file.read('custom-minapps.json')
|
||||
const customApps = JSON.parse(content)
|
||||
const updatedApps = customApps.filter((customApp: MinAppType) => customApp.id !== app.id)
|
||||
await window.api.file.writeWithId('custom-minapps.json', JSON.stringify(updatedApps, null, 2))
|
||||
message.success(t('settings.miniapps.custom.remove_success'))
|
||||
const reloadedApps = [...ORIGIN_DEFAULT_MIN_APPS, ...(await loadCustomMiniApp())]
|
||||
updateDefaultMinApps(reloadedApps)
|
||||
updateMinapps(minapps.filter((item) => item.id !== app.id))
|
||||
updatePinnedMinapps(pinned.filter((item) => item.id !== app.id))
|
||||
updateDisabledMinapps(disabled.filter((item) => item.id !== app.id))
|
||||
} catch (error) {
|
||||
message.error(t('settings.miniapps.custom.remove_error'))
|
||||
console.error('Failed to remove custom mini app:', error)
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
|
||||
if (!isVisible && !isLast) {
|
||||
if (!isVisible) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dropdown menu={{ items: menuItems }} trigger={['contextMenu']}>
|
||||
<Container onClick={handleClick}>
|
||||
{isLast ? (
|
||||
<AddButton size={size}>
|
||||
<PlusOutlined />
|
||||
</AddButton>
|
||||
) : (
|
||||
<MinAppIcon size={size} app={app} />
|
||||
)}
|
||||
<AppTitle>{isLast ? t('settings.miniapps.custom.title') : app.name}</AppTitle>
|
||||
</Container>
|
||||
</Dropdown>
|
||||
<Modal
|
||||
title={t('settings.miniapps.custom.edit_title')}
|
||||
open={isModalVisible}
|
||||
onCancel={() => {
|
||||
setIsModalVisible(false)
|
||||
setFileList([])
|
||||
}}
|
||||
footer={null}
|
||||
transitionName="animation-move-down"
|
||||
centered>
|
||||
<Form form={form} onFinish={handleAddCustomApp} layout="vertical">
|
||||
<Form.Item
|
||||
name="id"
|
||||
label={t('settings.miniapps.custom.id')}
|
||||
rules={[{ required: true, message: t('settings.miniapps.custom.id_error') }]}>
|
||||
<Input placeholder={t('settings.miniapps.custom.id_placeholder')} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label={t('settings.miniapps.custom.name')}
|
||||
rules={[{ required: true, message: t('settings.miniapps.custom.name_error') }]}>
|
||||
<Input placeholder={t('settings.miniapps.custom.name_placeholder')} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="url"
|
||||
label={t('settings.miniapps.custom.url')}
|
||||
rules={[{ required: true, message: t('settings.miniapps.custom.url_error') }]}>
|
||||
<Input placeholder={t('settings.miniapps.custom.url_placeholder')} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('settings.miniapps.custom.logo')}>
|
||||
<Radio.Group value={logoType} onChange={handleLogoTypeChange}>
|
||||
<Radio value="url">{t('settings.miniapps.custom.logo_url')}</Radio>
|
||||
<Radio value="file">{t('settings.miniapps.custom.logo_file')}</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
{logoType === 'url' ? (
|
||||
<Form.Item name="logo" label={t('settings.miniapps.custom.logo_url_label')}>
|
||||
<Input placeholder={t('settings.miniapps.custom.logo_url_placeholder')} />
|
||||
</Form.Item>
|
||||
) : (
|
||||
<Form.Item label={t('settings.miniapps.custom.logo_upload_label')}>
|
||||
<Upload
|
||||
accept="image/*"
|
||||
maxCount={1}
|
||||
fileList={fileList}
|
||||
onChange={handleFileChange}
|
||||
beforeUpload={() => false}>
|
||||
<Button icon={<UploadOutlined />}>{t('settings.miniapps.custom.logo_upload_button')}</Button>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{t('settings.miniapps.custom.save')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</>
|
||||
<Dropdown menu={{ items: menuItems }} trigger={['contextMenu']}>
|
||||
<Container onClick={handleClick}>
|
||||
<MinAppIcon size={size} app={app} />
|
||||
<AppTitle>{isLast ? t('settings.miniapps.custom.title') : app.name}</AppTitle>
|
||||
</Container>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
@ -254,25 +109,4 @@ const AppTitle = styled.div`
|
||||
white-space: nowrap;
|
||||
`
|
||||
|
||||
const AddButton = styled.div<{ size?: number }>`
|
||||
width: ${({ size }) => size || 60}px;
|
||||
height: ${({ size }) => size || 60}px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-background-soft);
|
||||
border: 1px dashed var(--color-border);
|
||||
color: var(--color-text-soft);
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
`
|
||||
|
||||
export default App
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||
import { Center } from '@renderer/components/Layout'
|
||||
import { useMinapps } from '@renderer/hooks/useMinapps'
|
||||
import { Input } from 'antd'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { Search } from 'lucide-react'
|
||||
import React, { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import App from './App'
|
||||
import NewAppButton from './NewAppButton'
|
||||
|
||||
const AppsPage: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
@ -51,18 +50,12 @@ const AppsPage: FC = () => {
|
||||
</NavbarCenter>
|
||||
</Navbar>
|
||||
<ContentContainer id="content-container">
|
||||
{isEmpty(filteredApps) ? (
|
||||
<Center>
|
||||
<App isLast app={filteredApps[0]} />
|
||||
</Center>
|
||||
) : (
|
||||
<AppsContainer style={{ height: containerHeight }}>
|
||||
{filteredApps.map((app) => (
|
||||
<App key={app.id} app={app} />
|
||||
))}
|
||||
<App isLast app={filteredApps[0]} />
|
||||
</AppsContainer>
|
||||
)}
|
||||
<AppsContainer style={{ height: containerHeight }}>
|
||||
{filteredApps.map((app) => (
|
||||
<App key={app.id} app={app} />
|
||||
))}
|
||||
<NewAppButton />
|
||||
</AppsContainer>
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
)
|
||||
|
||||
197
src/renderer/src/pages/apps/NewAppButton.tsx
Normal file
197
src/renderer/src/pages/apps/NewAppButton.tsx
Normal file
@ -0,0 +1,197 @@
|
||||
import { PlusOutlined, UploadOutlined } from '@ant-design/icons'
|
||||
import { loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateDefaultMinApps } from '@renderer/config/minapps'
|
||||
import { useMinapps } from '@renderer/hooks/useMinapps'
|
||||
import { MinAppType } from '@renderer/types'
|
||||
import { Button, Form, Input, message, Modal, Radio, Upload } from 'antd'
|
||||
import type { UploadFile } from 'antd/es/upload/interface'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface Props {
|
||||
size?: number
|
||||
}
|
||||
|
||||
const NewAppButton: FC<Props> = ({ size = 60 }) => {
|
||||
const { t } = useTranslation()
|
||||
const [isModalVisible, setIsModalVisible] = useState(false)
|
||||
const [fileList, setFileList] = useState<UploadFile[]>([])
|
||||
const [logoType, setLogoType] = useState<'url' | 'file'>('url')
|
||||
const [form] = Form.useForm()
|
||||
const { minapps, updateMinapps } = useMinapps()
|
||||
|
||||
const handleLogoTypeChange = (e: any) => {
|
||||
setLogoType(e.target.value)
|
||||
form.setFieldValue('logo', '')
|
||||
setFileList([])
|
||||
}
|
||||
|
||||
const handleAddCustomApp = async (values: any) => {
|
||||
try {
|
||||
const content = await window.api.file.read('custom-minapps.json')
|
||||
const customApps = JSON.parse(content)
|
||||
|
||||
// Check for duplicate ID
|
||||
if (customApps.some((app: MinAppType) => app.id === values.id)) {
|
||||
message.error(t('settings.miniapps.custom.duplicate_ids', { ids: values.id }))
|
||||
return
|
||||
}
|
||||
if (ORIGIN_DEFAULT_MIN_APPS.some((app: MinAppType) => app.id === values.id)) {
|
||||
message.error(t('settings.miniapps.custom.conflicting_ids', { ids: values.id }))
|
||||
return
|
||||
}
|
||||
|
||||
const newApp: MinAppType = {
|
||||
id: values.id,
|
||||
name: values.name,
|
||||
url: values.url,
|
||||
logo: form.getFieldValue('logo') || '',
|
||||
type: 'Custom',
|
||||
addTime: new Date().toISOString()
|
||||
}
|
||||
customApps.push(newApp)
|
||||
await window.api.file.writeWithId('custom-minapps.json', JSON.stringify(customApps, null, 2))
|
||||
message.success(t('settings.miniapps.custom.save_success'))
|
||||
setIsModalVisible(false)
|
||||
form.resetFields()
|
||||
setFileList([])
|
||||
const reloadedApps = [...ORIGIN_DEFAULT_MIN_APPS, ...(await loadCustomMiniApp())]
|
||||
updateDefaultMinApps(reloadedApps)
|
||||
updateMinapps([...minapps, newApp])
|
||||
} catch (error) {
|
||||
message.error(t('settings.miniapps.custom.save_error'))
|
||||
console.error('Failed to save custom mini app:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileChange = async (info: any) => {
|
||||
const file = info.fileList[info.fileList.length - 1]?.originFileObj
|
||||
setFileList(info.fileList.slice(-1))
|
||||
|
||||
if (file) {
|
||||
try {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (event) => {
|
||||
const base64Data = event.target?.result
|
||||
if (typeof base64Data === 'string') {
|
||||
message.success(t('settings.miniapps.custom.logo_upload_success'))
|
||||
form.setFieldValue('logo', base64Data)
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
} catch (error) {
|
||||
console.error('Failed to read file:', error)
|
||||
message.error(t('settings.miniapps.custom.logo_upload_error'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container onClick={() => setIsModalVisible(true)}>
|
||||
<AddButton size={size}>
|
||||
<PlusOutlined />
|
||||
</AddButton>
|
||||
<AppTitle>{t('settings.miniapps.custom.title')}</AppTitle>
|
||||
</Container>
|
||||
<Modal
|
||||
title={t('settings.miniapps.custom.edit_title')}
|
||||
open={isModalVisible}
|
||||
onCancel={() => {
|
||||
setIsModalVisible(false)
|
||||
setFileList([])
|
||||
}}
|
||||
footer={null}
|
||||
transitionName="animation-move-down"
|
||||
centered>
|
||||
<Form form={form} onFinish={handleAddCustomApp} layout="vertical">
|
||||
<Form.Item
|
||||
name="id"
|
||||
label={t('settings.miniapps.custom.id')}
|
||||
rules={[{ required: true, message: t('settings.miniapps.custom.id_error') }]}>
|
||||
<Input placeholder={t('settings.miniapps.custom.id_placeholder')} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label={t('settings.miniapps.custom.name')}
|
||||
rules={[{ required: true, message: t('settings.miniapps.custom.name_error') }]}>
|
||||
<Input placeholder={t('settings.miniapps.custom.name_placeholder')} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="url"
|
||||
label={t('settings.miniapps.custom.url')}
|
||||
rules={[{ required: true, message: t('settings.miniapps.custom.url_error') }]}>
|
||||
<Input placeholder={t('settings.miniapps.custom.url_placeholder')} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('settings.miniapps.custom.logo')}>
|
||||
<Radio.Group value={logoType} onChange={handleLogoTypeChange}>
|
||||
<Radio value="url">{t('settings.miniapps.custom.logo_url')}</Radio>
|
||||
<Radio value="file">{t('settings.miniapps.custom.logo_file')}</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
{logoType === 'url' ? (
|
||||
<Form.Item name="logo" label={t('settings.miniapps.custom.logo_url_label')}>
|
||||
<Input placeholder={t('settings.miniapps.custom.logo_url_placeholder')} />
|
||||
</Form.Item>
|
||||
) : (
|
||||
<Form.Item label={t('settings.miniapps.custom.logo_upload_label')}>
|
||||
<Upload
|
||||
accept="image/*"
|
||||
maxCount={1}
|
||||
fileList={fileList}
|
||||
onChange={handleFileChange}
|
||||
beforeUpload={() => false}>
|
||||
<Button icon={<UploadOutlined />}>{t('settings.miniapps.custom.logo_upload_button')}</Button>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{t('settings.miniapps.custom.save')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
const AddButton = styled.div<{ size?: number }>`
|
||||
width: ${({ size }) => size || 60}px;
|
||||
height: ${({ size }) => size || 60}px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-background-soft);
|
||||
border: 1px dashed var(--color-border);
|
||||
color: var(--color-text-soft);
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
`
|
||||
|
||||
const AppTitle = styled.div`
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
color: var(--color-text-soft);
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
`
|
||||
|
||||
export default NewAppButton
|
||||
Loading…
Reference in New Issue
Block a user