diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index b6f4b50cd..f38cdc1de 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -472,6 +472,7 @@ "button": "Import", "error": { "fetch_failed": "Failed to fetch from URL", + "file_required": "Please select a file first", "invalid_format": "Invalid assistant format: missing required fields", "url_required": "Please enter a URL" }, @@ -486,11 +487,14 @@ }, "manage": { "batch_delete": { - "button": "Batch Delete", + "button": "Delete", "confirm": "Are you sure you want to delete the selected {{count}} assistants?" }, + "batch_export": { + "button": "Export" + }, "mode": { - "delete": "Delete", + "manage": "Manage", "sort": "Sort" }, "title": "Manage Assistants" @@ -1248,11 +1252,13 @@ } }, "stop": "Stop", + "subscribe": "Subscribe", "success": "Success", "swap": "Swap", "topics": "Topics", "unknown": "Unknown", "unnamed": "Unnamed", + "unsubscribe": "Unsubscribe", "update_success": "Update successfully", "upload_files": "Upload file", "warning": "Warning", @@ -1747,7 +1753,7 @@ "import": { "error": "Import failed" }, - "imported": "Imported successfully" + "imported": "Successfully imported {{count}} assistant(s)" }, "api": { "check": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index ceb8cad73..882b897ef 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -472,6 +472,7 @@ "button": "导入", "error": { "fetch_failed": "从 URL 获取数据失败", + "file_required": "请先选择文件", "invalid_format": "无效的助手格式:缺少必填字段", "url_required": "请输入 URL" }, @@ -486,11 +487,14 @@ }, "manage": { "batch_delete": { - "button": "批量删除", + "button": "删除", "confirm": "确定要删除选中的 {{count}} 个助手吗?" }, + "batch_export": { + "button": "导出" + }, "mode": { - "delete": "删除", + "manage": "管理", "sort": "排序" }, "title": "管理助手" @@ -1248,11 +1252,13 @@ } }, "stop": "停止", + "subscribe": "订阅", "success": "成功", "swap": "交换", "topics": "话题", "unknown": "未知", "unnamed": "未命名", + "unsubscribe": "退订", "update_success": "更新成功", "upload_files": "上传文件", "warning": "警告", @@ -1747,7 +1753,7 @@ "import": { "error": "导入失败" }, - "imported": "导入成功" + "imported": "成功导入 {{count}} 个助手" }, "api": { "check": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index f150f5aef..3feb287c1 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -472,6 +472,7 @@ "button": "匯入", "error": { "fetch_failed": "從 URL 取得資料失敗", + "file_required": "請先選擇一個檔案", "invalid_format": "無效的助手格式:缺少必填欄位", "url_required": "請輸入 URL" }, @@ -489,8 +490,11 @@ "button": "批次刪除", "confirm": "確定要刪除所選的 {{count}} 個助手嗎?" }, + "batch_export": { + "button": "匯出" + }, "mode": { - "delete": "刪除", + "manage": "管理", "sort": "排序" }, "title": "管理助手" @@ -1248,11 +1252,13 @@ } }, "stop": "停止", + "subscribe": "訂閱", "success": "成功", "swap": "交換", "topics": "話題", "unknown": "未知", "unnamed": "未命名", + "unsubscribe": "取消訂閱", "update_success": "更新成功", "upload_files": "上傳檔案", "warning": "警告", diff --git a/src/renderer/src/i18n/translate/de-de.json b/src/renderer/src/i18n/translate/de-de.json index 074b53c4d..f53597860 100644 --- a/src/renderer/src/i18n/translate/de-de.json +++ b/src/renderer/src/i18n/translate/de-de.json @@ -472,6 +472,7 @@ "button": "Importieren", "error": { "fetch_failed": "Daten von URL abrufen fehlgeschlagen", + "file_required": "Bitte wählen Sie zuerst eine Datei aus", "invalid_format": "Ungültiges Assistentenformat: Pflichtfelder fehlen", "url_required": "Bitte geben Sie eine URL ein" }, @@ -489,8 +490,11 @@ "button": "Stapel löschen", "confirm": "Sind Sie sicher, dass Sie die ausgewählten {{count}} Assistenten löschen möchten?" }, + "batch_export": { + "button": "Exportieren" + }, "mode": { - "delete": "Löschen", + "manage": "Verwalten", "sort": "Sortieren" }, "title": "Assistenten verwalten" @@ -1248,11 +1252,13 @@ } }, "stop": "Stoppen", + "subscribe": "Abonnieren", "success": "Erfolgreich", "swap": "Tauschen", "topics": "Themen", "unknown": "Unbekannt", "unnamed": "Unbenannt", + "unsubscribe": "Abmelden", "update_success": "Erfolgreich aktualisiert", "upload_files": "Dateien hochladen", "warning": "Warnung", diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 5f7b00f3b..99592e9ad 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -472,6 +472,7 @@ "button": "Εισαγωγή", "error": { "fetch_failed": "Αποτυχία λήψης δεδομένων από το URL", + "file_required": "Παρακαλώ επιλέξτε πρώτα ένα αρχείο", "invalid_format": "Μη έγκυρη μορφή βοηθού: λείπουν υποχρεωτικά πεδία", "url_required": "Παρακαλώ εισάγετε ένα URL" }, @@ -489,8 +490,11 @@ "button": "Μαζική Διαγραφή", "confirm": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τους επιλεγμένους {{count}} βοηθούς;" }, + "batch_export": { + "button": "Εξαγωγή" + }, "mode": { - "delete": "Διαγραφή", + "manage": "Διαχειριστείτε", "sort": "Ταξινόμηση" }, "title": "Διαχείριση βοηθών" @@ -1248,11 +1252,13 @@ } }, "stop": "σταματήστε", + "subscribe": "Εγγραφείτε", "success": "Επιτυχία", "swap": "Εναλλαγή", "topics": "Θέματα", "unknown": "Άγνωστο", "unnamed": "Χωρίς όνομα", + "unsubscribe": "Απεγγραφή", "update_success": "Επιτυχής ενημέρωση", "upload_files": "Ανέβασμα αρχείου", "warning": "Προσοχή", diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 66746875d..31c715858 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -472,6 +472,7 @@ "button": "Importar", "error": { "fetch_failed": "Error al obtener datos desde la URL", + "file_required": "Por favor, selecciona primero un archivo", "invalid_format": "Formato de asistente inválido: faltan campos obligatorios", "url_required": "Por favor introduce una URL" }, @@ -489,8 +490,11 @@ "button": "Eliminación por lotes", "confirm": "¿Estás seguro de que quieres eliminar los {{count}} asistentes seleccionados?" }, + "batch_export": { + "button": "Exportar" + }, "mode": { - "delete": "Eliminar", + "manage": "Gestionar", "sort": "Ordenar" }, "title": "Gestionar asistentes" @@ -1248,11 +1252,13 @@ } }, "stop": "Detener", + "subscribe": "Suscribirse", "success": "Éxito", "swap": "Intercambiar", "topics": "Temas", "unknown": "Desconocido", "unnamed": "Sin nombre", + "unsubscribe": "Cancelar suscripción", "update_success": "Actualización exitosa", "upload_files": "Subir archivo", "warning": "Advertencia", diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 76efea8e3..da1d297a7 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -472,6 +472,7 @@ "button": "Importer", "error": { "fetch_failed": "Échec de la récupération des données depuis l'URL", + "file_required": "Veuillez d'abord sélectionner un fichier", "invalid_format": "Format d'assistant invalide : champs obligatoires manquants", "url_required": "Veuillez saisir une URL" }, @@ -489,8 +490,11 @@ "button": "Suppression par lot", "confirm": "Êtes-vous sûr de vouloir supprimer les {{count}} assistants sélectionnés ?" }, + "batch_export": { + "button": "Exporter" + }, "mode": { - "delete": "Supprimer", + "manage": "Gérer", "sort": "Trier" }, "title": "Gérer les assistants" @@ -1248,11 +1252,13 @@ } }, "stop": "Arrêter", + "subscribe": "S'abonner", "success": "Succès", "swap": "Échanger", "topics": "Sujets", "unknown": "Inconnu", "unnamed": "Sans nom", + "unsubscribe": "Se désabonner", "update_success": "Mise à jour réussie", "upload_files": "Uploader des fichiers", "warning": "Avertissement", diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index d9e62b622..93bacf506 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -472,6 +472,7 @@ "button": "インポート", "error": { "fetch_failed": "URLからのデータ取得に失敗しました", + "file_required": "まずファイルを選択してください", "invalid_format": "無効なアシスタント形式:必須フィールドが不足しています", "url_required": "URLを入力してください" }, @@ -489,8 +490,11 @@ "button": "バッチ削除", "confirm": "選択した{{count}}件のアシスタントを削除してもよろしいですか?" }, + "batch_export": { + "button": "エクスポート" + }, "mode": { - "delete": "削除", + "manage": "管理", "sort": "並べ替え" }, "title": "アシスタントを管理" @@ -1248,11 +1252,13 @@ } }, "stop": "停止", + "subscribe": "購読", "success": "成功", "swap": "交換", "topics": "トピック", "unknown": "Unknown", "unnamed": "無題", + "unsubscribe": "配信停止", "update_success": "更新成功", "upload_files": "ファイルをアップロードする", "warning": "警告", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 69c2ae260..9bd688167 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -472,6 +472,7 @@ "button": "Importar", "error": { "fetch_failed": "Falha ao obter dados do URL", + "file_required": "Por favor, selecione um arquivo primeiro", "invalid_format": "Formato de assistente inválido: campos obrigatórios em falta", "url_required": "Por favor insere um URL" }, @@ -489,8 +490,11 @@ "button": "Exclusão em Lote", "confirm": "Tem certeza de que deseja excluir os {{count}} assistentes selecionados?" }, + "batch_export": { + "button": "Exportar" + }, "mode": { - "delete": "Excluir", + "manage": "Gerenciar", "sort": "Ordenar" }, "title": "Gerir assistentes" @@ -1248,11 +1252,13 @@ } }, "stop": "Parar", + "subscribe": "Subscrever", "success": "Sucesso", "swap": "Trocar", "topics": "Tópicos", "unknown": "Desconhecido", "unnamed": "Sem nome", + "unsubscribe": "Cancelar inscrição", "update_success": "Atualização bem-sucedida", "upload_files": "Carregar arquivo", "warning": "Aviso", diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 8ef955add..7665115d5 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -472,6 +472,7 @@ "button": "Импортировать", "error": { "fetch_failed": "Ошибка получения данных с URL", + "file_required": "Сначала выберите файл", "invalid_format": "Неверный формат помощника: отсутствуют обязательные поля", "url_required": "Пожалуйста, введите URL" }, @@ -489,8 +490,11 @@ "button": "Массовое удаление", "confirm": "Вы уверены, что хотите удалить выбранных {{count}} ассистентов?" }, + "batch_export": { + "button": "Экспорт" + }, "mode": { - "delete": "Удалить", + "manage": "Управлять", "sort": "Сортировать" }, "title": "Управление помощниками" @@ -1248,11 +1252,13 @@ } }, "stop": "остановить", + "subscribe": "Подписаться", "success": "Успешно", "swap": "Поменять местами", "topics": "Топики", "unknown": "Неизвестно", "unnamed": "Без имени", + "unsubscribe": "Отписаться", "update_success": "Обновление выполнено успешно", "upload_files": "Загрузить файл", "warning": "Предупреждение", diff --git a/src/renderer/src/pages/store/assistants/presets/AssistantPresetsPage.tsx b/src/renderer/src/pages/store/assistants/presets/AssistantPresetsPage.tsx index 79d1275b1..a56c04b15 100644 --- a/src/renderer/src/pages/store/assistants/presets/AssistantPresetsPage.tsx +++ b/src/renderer/src/pages/store/assistants/presets/AssistantPresetsPage.tsx @@ -1,7 +1,6 @@ import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { HStack } from '@renderer/components/Layout' import ListItem from '@renderer/components/ListItem' -import GeneralPopup from '@renderer/components/Popups/GeneralPopup' import Scrollbar from '@renderer/components/Scrollbar' import CustomTag from '@renderer/components/Tags/CustomTag' import { useAssistantPresets } from '@renderer/hooks/useAssistantPresets' @@ -11,7 +10,7 @@ import type { AssistantPreset } from '@renderer/types' import { uuid } from '@renderer/utils' import { Button, Empty, Flex, Input } from 'antd' import { omit } from 'lodash' -import { Import, Plus, Rss, Search, Settings2 } from 'lucide-react' +import { Import, Plus, Search, Settings2 } from 'lucide-react' import type { FC } from 'react' import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -23,7 +22,6 @@ import { groupTranslations } from './assistantPresetGroupTranslations' import AddAssistantPresetPopup from './components/AddAssistantPresetPopup' import AssistantPresetCard from './components/AssistantPresetCard' import { AssistantPresetGroupIcon } from './components/AssistantPresetGroupIcon' -import AssistantsSubscribeUrlSettings from './components/AssistantsSubscribeUrlSettings' import ImportAssistantPresetPopup from './components/ImportAssistantPresetPopup' import ManageAssistantPresetsPopup from './components/ManageAssistantPresetsPopup' @@ -177,15 +175,6 @@ const AssistantPresetsPage: FC = () => { } } - const handleSubscribeSettings = () => { - GeneralPopup.show({ - title: t('assistants.presets.settings.title'), - content: , - footer: null, - width: 600 - }) - } - const handleManageAgents = () => { ManageAssistantPresetsPopup.show() } @@ -292,9 +281,6 @@ const AssistantPresetsPage: FC = () => { - diff --git a/src/renderer/src/pages/store/assistants/presets/components/AssistantsSubscribeUrlSettings.tsx b/src/renderer/src/pages/store/assistants/presets/components/AssistantsSubscribeUrlSettings.tsx deleted file mode 100755 index 8ea3b92fd..000000000 --- a/src/renderer/src/pages/store/assistants/presets/components/AssistantsSubscribeUrlSettings.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { HStack } from '@renderer/components/Layout' -import { useTheme } from '@renderer/context/ThemeProvider' -import { useSettings } from '@renderer/hooks/useSettings' -import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '@renderer/pages/settings' -import { useAppDispatch } from '@renderer/store' -import { setAgentssubscribeUrl } from '@renderer/store/settings' -import Input from 'antd/es/input/Input' -import { HelpCircle } from 'lucide-react' -import type { FC } from 'react' -import { useTranslation } from 'react-i18next' - -const AssistantsSubscribeUrlSettings: FC = () => { - const { t } = useTranslation() - const { theme } = useTheme() - const dispatch = useAppDispatch() - - const { agentssubscribeUrl } = useSettings() - - const handleAgentChange = (e: React.ChangeEvent) => { - dispatch(setAgentssubscribeUrl(e.target.value)) - } - - const handleHelpClick = () => { - window.open('https://docs.cherry-ai.com/data-settings/assistants-subscribe', '_blank') - } - - return ( - - - - {t('assistants.presets.tag.agent')} - {t('settings.tool.websearch.subscribe_add')} - - - - - - {t('settings.tool.websearch.subscribe_url')} - - - - - - ) -} - -export default AssistantsSubscribeUrlSettings diff --git a/src/renderer/src/pages/store/assistants/presets/components/ImportAssistantPresetPopup.tsx b/src/renderer/src/pages/store/assistants/presets/components/ImportAssistantPresetPopup.tsx index 9a76d1d02..37b793824 100644 --- a/src/renderer/src/pages/store/assistants/presets/components/ImportAssistantPresetPopup.tsx +++ b/src/renderer/src/pages/store/assistants/presets/components/ImportAssistantPresetPopup.tsx @@ -1,11 +1,15 @@ import { TopView } from '@renderer/components/TopView' import { useAssistantPresets } from '@renderer/hooks/useAssistantPresets' +import { useSettings } from '@renderer/hooks/useSettings' import { useTimer } from '@renderer/hooks/useTimer' import { getDefaultModel } from '@renderer/services/AssistantService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' +import { useAppDispatch } from '@renderer/store' +import { setAgentssubscribeUrl } from '@renderer/store/settings' import type { AssistantPreset } from '@renderer/types' import { uuid } from '@renderer/utils' -import { Button, Flex, Form, Input, Modal, Radio } from 'antd' +import { Button, Divider, Flex, Form, Input, Modal, Radio, Typography } from 'antd' +import { HelpCircle } from 'lucide-react' import { useState } from 'react' import { useTranslation } from 'react-i18next' @@ -20,35 +24,53 @@ const PopupContainer: React.FC = ({ resolve }) => { const { addAssistantPreset } = useAssistantPresets() const [importType, setImportType] = useState<'url' | 'file'>('url') const [loading, setLoading] = useState(false) + const [subscribeLoading, setSubscribeLoading] = useState(false) const { setTimeoutTimer } = useTimer() + const dispatch = useAppDispatch() + const { agentssubscribeUrl } = useSettings() + const [subscribeUrl, setSubscribeUrl] = useState(agentssubscribeUrl || '') + const [selectedFile, setSelectedFile] = useState<{ name: string; content: Uint8Array } | null>(null) + const [urlValue, setUrlValue] = useState('') + + const isImportDisabled = importType === 'url' ? !urlValue.trim() : !selectedFile + const isSubscribed = !!agentssubscribeUrl + + const handleSelectFile = async () => { + const result = await window.api.file.open({ + filters: [{ name: t('assistants.presets.import.file_filter'), extensions: ['json'] }] + }) + + if (result) { + setSelectedFile({ name: result.fileName, content: result.content }) + } + } + + const onFinish = async () => { + // Validate before setting loading + if (importType === 'url' && !urlValue.trim()) { + window.toast.error(t('assistants.presets.import.error.url_required')) + return + } + if (importType === 'file' && !selectedFile) { + window.toast.error(t('assistants.presets.import.error.file_required')) + return + } - const onFinish = async (values: { url?: string }) => { setLoading(true) try { let presets: AssistantPreset[] = [] if (importType === 'url') { - if (!values.url) { - throw new Error(t('assistants.presets.import.error.url_required')) - } - const response = await fetch(values.url) + const response = await fetch(urlValue.trim()) if (!response.ok) { throw new Error(t('assistants.presets.import.error.fetch_failed')) } const data = await response.json() presets = Array.isArray(data) ? data : [data] } else { - const result = await window.api.file.open({ - filters: [{ name: t('assistants.presets.import.file_filter'), extensions: ['json'] }] - }) - - if (result) { - presets = JSON.parse(new TextDecoder('utf-8').decode(result.content)) - if (!Array.isArray(presets)) { - presets = [presets] - } - } else { - return + presets = JSON.parse(new TextDecoder('utf-8').decode(selectedFile!.content)) + if (!Array.isArray(presets)) { + presets = [presets] } } @@ -74,7 +96,7 @@ const PopupContainer: React.FC = ({ resolve }) => { addAssistantPreset(newPreset) } - window.toast.success(t('message.agents.imported')) + window.toast.success(t('message.agents.imported', { count: presets.length })) setTimeoutTimer('onFinish', () => EventEmitter.emit(EVENT_NAMES.SHOW_ASSISTANTS), 0) setOpen(false) @@ -88,7 +110,42 @@ const PopupContainer: React.FC = ({ resolve }) => { const onCancel = () => { setOpen(false) - resolve(null) + } + + const handleSubscribeUrlChange = (e: React.ChangeEvent) => { + setSubscribeUrl(e.target.value) + } + + const handleSubscribe = async () => { + // If already subscribed, unsubscribe + if (isSubscribed) { + dispatch(setAgentssubscribeUrl('')) + setSubscribeUrl('') + window.location.reload() + return + } + + if (!subscribeUrl.trim()) { + return + } + + setSubscribeLoading(true) + try { + const response = await fetch(subscribeUrl) + if (!response.ok) { + throw new Error(t('assistants.presets.import.error.fetch_failed')) + } + dispatch(setAgentssubscribeUrl(subscribeUrl)) + window.location.reload() + } catch (error) { + window.toast.error(error instanceof Error ? error.message : t('message.agents.import.error')) + } finally { + setSubscribeLoading(false) + } + } + + const handleHelpClick = () => { + window.open('https://docs.cherry-ai.com/data-settings/assistants-subscribe', '_blank') } return ( @@ -96,39 +153,79 @@ const PopupContainer: React.FC = ({ resolve }) => { title={t('assistants.presets.import.title')} open={open} onCancel={onCancel} - maskClosable={false} - footer={ - - - - - } + afterClose={() => resolve(null)} + footer={null} transitionName="animation-move-down" + styles={{ body: { padding: '16px' } }} centered>
- - setImportType(e.target.value)}> - {t('assistants.presets.import.type.url')} - {t('assistants.presets.import.type.file')} - + + + setImportType(e.target.value)}> + {t('assistants.presets.import.type.url')} + {t('assistants.presets.import.type.file')} + + + {importType === 'url' && ( + + setUrlValue(e.target.value)} + /> + + )} + + {importType === 'file' && ( + <> + + {selectedFile && ( + + {selectedFile.name} + + )} +
+ + )} + + + - - {importType === 'url' && ( - - - - )} - - {importType === 'file' && ( - - - - )} + + + + + + {t('assistants.presets.tag.agent')} + {t('settings.tool.websearch.subscribe_add')} + + + + + + + + ) } diff --git a/src/renderer/src/pages/store/assistants/presets/components/ManageAssistantPresetsPopup.tsx b/src/renderer/src/pages/store/assistants/presets/components/ManageAssistantPresetsPopup.tsx index b75a569c5..961ec24ab 100644 --- a/src/renderer/src/pages/store/assistants/presets/components/ManageAssistantPresetsPopup.tsx +++ b/src/renderer/src/pages/store/assistants/presets/components/ManageAssistantPresetsPopup.tsx @@ -1,4 +1,4 @@ -import { MenuOutlined } from '@ant-design/icons' +import { ExportOutlined, MenuOutlined } from '@ant-design/icons' import { DraggableList } from '@renderer/components/DraggableList' import { DeleteIcon } from '@renderer/components/Icons' import { Box, HStack } from '@renderer/components/Layout' @@ -10,13 +10,13 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -type Mode = 'sort' | 'delete' +type Mode = 'sort' | 'manage' const PopupContainer: React.FC = () => { const [open, setOpen] = useState(true) const { t } = useTranslation() const { presets, setAssistantPresets } = useAssistantPresets() - const [mode, setMode] = useState(() => (presets.length > 50 ? 'delete' : 'sort')) + const [mode, setMode] = useState('manage') const [selectedIds, setSelectedIds] = useState>(new Set()) const onCancel = () => { @@ -88,6 +88,23 @@ const PopupContainer: React.FC = () => { }) } + const handleBatchExport = async () => { + if (selectedIds.size === 0) return + + const selectedPresets = presets.filter((p) => selectedIds.has(p.id)) + const exportData = selectedPresets.map((p) => ({ + name: p.name, + emoji: p.emoji, + prompt: p.prompt, + description: p.description, + group: p.group + })) + + const fileName = selectedIds.size === 1 ? `${selectedPresets[0].name}.json` : `assistants_${selectedIds.size}.json` + + await window.api.file.save(fileName, JSON.stringify(exportData, null, 2)) + } + const isAllSelected = presets.length > 0 && selectedIds.size === presets.length const isIndeterminate = selectedIds.size > 0 && selectedIds.size < presets.length @@ -98,13 +115,14 @@ const PopupContainer: React.FC = () => { onCancel={onCancel} afterClose={onClose} footer={null} + width={600} transitionName="animation-move-down" centered> {presets.length > 0 && ( <> - {mode === 'delete' ? ( + {mode === 'manage' ? ( {t('common.select_all')} @@ -119,15 +137,24 @@ const PopupContainer: React.FC = () => {
)} - {mode === 'delete' && ( - + {mode === 'manage' && ( + <> + + + )} { onChange={(value) => handleModeChange(value as Mode)} options={[ { label: t('assistants.presets.manage.mode.sort'), value: 'sort' }, - { label: t('assistants.presets.manage.mode.delete'), value: 'delete' } + { label: t('assistants.presets.manage.mode.manage'), value: 'manage' } ]} />