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 = () => {
}>
{t('assistants.presets.import.title')}
- }>
- {t('assistants.presets.settings.title')}
-
}>
{t('assistants.presets.manage.title')}
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' && (
- }
- disabled={selectedIds.size === 0}
- onClick={handleBatchDelete}>
- {t('assistants.presets.manage.batch_delete.button')} ({selectedIds.size})
-
+ {mode === 'manage' && (
+ <>
+ }
+ disabled={selectedIds.size === 0}
+ onClick={handleBatchExport}>
+ {t('assistants.presets.manage.batch_export.button')} ({selectedIds.size})
+
+ }
+ disabled={selectedIds.size === 0}
+ onClick={handleBatchDelete}>
+ {t('assistants.presets.manage.batch_delete.button')} ({selectedIds.size})
+
+ >
)}
{
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' }
]}
/>