diff --git a/src/renderer/src/components/Popups/MinAppsPopover.tsx b/src/renderer/src/components/Popups/MinAppsPopover.tsx index effdf1189c..28a9621bc9 100644 --- a/src/renderer/src/components/Popups/MinAppsPopover.tsx +++ b/src/renderer/src/components/Popups/MinAppsPopover.tsx @@ -51,7 +51,6 @@ const MinAppsPopover: FC = ({ children }) => { )} - ) diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 759302c82d..782c4943b4 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -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": "添加位置", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index ceaf666452..9f1a30675e 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -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": "添加位置", diff --git a/src/renderer/src/pages/apps/App.tsx b/src/renderer/src/pages/apps/App.tsx index 41c069f12d..d8e751dee7 100644 --- a/src/renderer/src/pages/apps/App.tsx +++ b/src/renderer/src/pages/apps/App.tsx @@ -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 = ({ 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([]) 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 ( - <> - - - {isLast ? ( - - - - ) : ( - - )} - {isLast ? t('settings.miniapps.custom.title') : app.name} - - - { - setIsModalVisible(false) - setFileList([]) - }} - footer={null} - transitionName="animation-move-down" - centered> -
- - - - - - - - - - - - {t('settings.miniapps.custom.logo_url')} - {t('settings.miniapps.custom.logo_file')} - - - {logoType === 'url' ? ( - - - - ) : ( - - false}> - - - - )} - - - -
-
- + + + + {isLast ? t('settings.miniapps.custom.title') : app.name} + + ) } @@ -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 diff --git a/src/renderer/src/pages/apps/AppsPage.tsx b/src/renderer/src/pages/apps/AppsPage.tsx index 099649fade..5348ac4367 100644 --- a/src/renderer/src/pages/apps/AppsPage.tsx +++ b/src/renderer/src/pages/apps/AppsPage.tsx @@ -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 = () => { - {isEmpty(filteredApps) ? ( -
- -
- ) : ( - - {filteredApps.map((app) => ( - - ))} - - - )} + + {filteredApps.map((app) => ( + + ))} + +
) diff --git a/src/renderer/src/pages/apps/NewAppButton.tsx b/src/renderer/src/pages/apps/NewAppButton.tsx new file mode 100644 index 0000000000..a09f4c86f3 --- /dev/null +++ b/src/renderer/src/pages/apps/NewAppButton.tsx @@ -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 = ({ size = 60 }) => { + const { t } = useTranslation() + const [isModalVisible, setIsModalVisible] = useState(false) + const [fileList, setFileList] = useState([]) + 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 ( + <> + setIsModalVisible(true)}> + + + + {t('settings.miniapps.custom.title')} + + { + setIsModalVisible(false) + setFileList([]) + }} + footer={null} + transitionName="animation-move-down" + centered> +
+ + + + + + + + + + + + {t('settings.miniapps.custom.logo_url')} + {t('settings.miniapps.custom.logo_file')} + + + {logoType === 'url' ? ( + + + + ) : ( + + false}> + + + + )} + + + +
+
+ + ) +} + +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