mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-23 01:50:13 +08:00
feat(assistants): merge import/subscribe popups and add export to manage (#11946)
feat(assistants): merge import and subscribe popups, add export to manage - Merge import and subscribe buttons into single unified popup - Add export functionality to manage assistant presets - Change delete mode to manage mode with both export and delete options - Show import count in success message - Default to manage mode when opening manage popup - Fix unsubscribe button to clear URL properly - Fix file import not working issue 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
99d7223a0a
commit
e85009fcd6
@ -472,6 +472,7 @@
|
|||||||
"button": "Import",
|
"button": "Import",
|
||||||
"error": {
|
"error": {
|
||||||
"fetch_failed": "Failed to fetch from URL",
|
"fetch_failed": "Failed to fetch from URL",
|
||||||
|
"file_required": "Please select a file first",
|
||||||
"invalid_format": "Invalid assistant format: missing required fields",
|
"invalid_format": "Invalid assistant format: missing required fields",
|
||||||
"url_required": "Please enter a URL"
|
"url_required": "Please enter a URL"
|
||||||
},
|
},
|
||||||
@ -486,11 +487,14 @@
|
|||||||
},
|
},
|
||||||
"manage": {
|
"manage": {
|
||||||
"batch_delete": {
|
"batch_delete": {
|
||||||
"button": "Batch Delete",
|
"button": "Delete",
|
||||||
"confirm": "Are you sure you want to delete the selected {{count}} assistants?"
|
"confirm": "Are you sure you want to delete the selected {{count}} assistants?"
|
||||||
},
|
},
|
||||||
|
"batch_export": {
|
||||||
|
"button": "Export"
|
||||||
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"delete": "Delete",
|
"manage": "Manage",
|
||||||
"sort": "Sort"
|
"sort": "Sort"
|
||||||
},
|
},
|
||||||
"title": "Manage Assistants"
|
"title": "Manage Assistants"
|
||||||
@ -1248,11 +1252,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stop": "Stop",
|
"stop": "Stop",
|
||||||
|
"subscribe": "Subscribe",
|
||||||
"success": "Success",
|
"success": "Success",
|
||||||
"swap": "Swap",
|
"swap": "Swap",
|
||||||
"topics": "Topics",
|
"topics": "Topics",
|
||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
"unnamed": "Unnamed",
|
"unnamed": "Unnamed",
|
||||||
|
"unsubscribe": "Unsubscribe",
|
||||||
"update_success": "Update successfully",
|
"update_success": "Update successfully",
|
||||||
"upload_files": "Upload file",
|
"upload_files": "Upload file",
|
||||||
"warning": "Warning",
|
"warning": "Warning",
|
||||||
@ -1747,7 +1753,7 @@
|
|||||||
"import": {
|
"import": {
|
||||||
"error": "Import failed"
|
"error": "Import failed"
|
||||||
},
|
},
|
||||||
"imported": "Imported successfully"
|
"imported": "Successfully imported {{count}} assistant(s)"
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"check": {
|
"check": {
|
||||||
|
|||||||
@ -472,6 +472,7 @@
|
|||||||
"button": "导入",
|
"button": "导入",
|
||||||
"error": {
|
"error": {
|
||||||
"fetch_failed": "从 URL 获取数据失败",
|
"fetch_failed": "从 URL 获取数据失败",
|
||||||
|
"file_required": "请先选择文件",
|
||||||
"invalid_format": "无效的助手格式:缺少必填字段",
|
"invalid_format": "无效的助手格式:缺少必填字段",
|
||||||
"url_required": "请输入 URL"
|
"url_required": "请输入 URL"
|
||||||
},
|
},
|
||||||
@ -486,11 +487,14 @@
|
|||||||
},
|
},
|
||||||
"manage": {
|
"manage": {
|
||||||
"batch_delete": {
|
"batch_delete": {
|
||||||
"button": "批量删除",
|
"button": "删除",
|
||||||
"confirm": "确定要删除选中的 {{count}} 个助手吗?"
|
"confirm": "确定要删除选中的 {{count}} 个助手吗?"
|
||||||
},
|
},
|
||||||
|
"batch_export": {
|
||||||
|
"button": "导出"
|
||||||
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"delete": "删除",
|
"manage": "管理",
|
||||||
"sort": "排序"
|
"sort": "排序"
|
||||||
},
|
},
|
||||||
"title": "管理助手"
|
"title": "管理助手"
|
||||||
@ -1248,11 +1252,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stop": "停止",
|
"stop": "停止",
|
||||||
|
"subscribe": "订阅",
|
||||||
"success": "成功",
|
"success": "成功",
|
||||||
"swap": "交换",
|
"swap": "交换",
|
||||||
"topics": "话题",
|
"topics": "话题",
|
||||||
"unknown": "未知",
|
"unknown": "未知",
|
||||||
"unnamed": "未命名",
|
"unnamed": "未命名",
|
||||||
|
"unsubscribe": "退订",
|
||||||
"update_success": "更新成功",
|
"update_success": "更新成功",
|
||||||
"upload_files": "上传文件",
|
"upload_files": "上传文件",
|
||||||
"warning": "警告",
|
"warning": "警告",
|
||||||
@ -1747,7 +1753,7 @@
|
|||||||
"import": {
|
"import": {
|
||||||
"error": "导入失败"
|
"error": "导入失败"
|
||||||
},
|
},
|
||||||
"imported": "导入成功"
|
"imported": "成功导入 {{count}} 个助手"
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"check": {
|
"check": {
|
||||||
|
|||||||
@ -472,6 +472,7 @@
|
|||||||
"button": "匯入",
|
"button": "匯入",
|
||||||
"error": {
|
"error": {
|
||||||
"fetch_failed": "從 URL 取得資料失敗",
|
"fetch_failed": "從 URL 取得資料失敗",
|
||||||
|
"file_required": "請先選擇一個檔案",
|
||||||
"invalid_format": "無效的助手格式:缺少必填欄位",
|
"invalid_format": "無效的助手格式:缺少必填欄位",
|
||||||
"url_required": "請輸入 URL"
|
"url_required": "請輸入 URL"
|
||||||
},
|
},
|
||||||
@ -489,8 +490,11 @@
|
|||||||
"button": "批次刪除",
|
"button": "批次刪除",
|
||||||
"confirm": "確定要刪除所選的 {{count}} 個助手嗎?"
|
"confirm": "確定要刪除所選的 {{count}} 個助手嗎?"
|
||||||
},
|
},
|
||||||
|
"batch_export": {
|
||||||
|
"button": "匯出"
|
||||||
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"delete": "刪除",
|
"manage": "管理",
|
||||||
"sort": "排序"
|
"sort": "排序"
|
||||||
},
|
},
|
||||||
"title": "管理助手"
|
"title": "管理助手"
|
||||||
@ -1248,11 +1252,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stop": "停止",
|
"stop": "停止",
|
||||||
|
"subscribe": "訂閱",
|
||||||
"success": "成功",
|
"success": "成功",
|
||||||
"swap": "交換",
|
"swap": "交換",
|
||||||
"topics": "話題",
|
"topics": "話題",
|
||||||
"unknown": "未知",
|
"unknown": "未知",
|
||||||
"unnamed": "未命名",
|
"unnamed": "未命名",
|
||||||
|
"unsubscribe": "取消訂閱",
|
||||||
"update_success": "更新成功",
|
"update_success": "更新成功",
|
||||||
"upload_files": "上傳檔案",
|
"upload_files": "上傳檔案",
|
||||||
"warning": "警告",
|
"warning": "警告",
|
||||||
|
|||||||
@ -472,6 +472,7 @@
|
|||||||
"button": "Importieren",
|
"button": "Importieren",
|
||||||
"error": {
|
"error": {
|
||||||
"fetch_failed": "Daten von URL abrufen fehlgeschlagen",
|
"fetch_failed": "Daten von URL abrufen fehlgeschlagen",
|
||||||
|
"file_required": "Bitte wählen Sie zuerst eine Datei aus",
|
||||||
"invalid_format": "Ungültiges Assistentenformat: Pflichtfelder fehlen",
|
"invalid_format": "Ungültiges Assistentenformat: Pflichtfelder fehlen",
|
||||||
"url_required": "Bitte geben Sie eine URL ein"
|
"url_required": "Bitte geben Sie eine URL ein"
|
||||||
},
|
},
|
||||||
@ -489,8 +490,11 @@
|
|||||||
"button": "Stapel löschen",
|
"button": "Stapel löschen",
|
||||||
"confirm": "Sind Sie sicher, dass Sie die ausgewählten {{count}} Assistenten löschen möchten?"
|
"confirm": "Sind Sie sicher, dass Sie die ausgewählten {{count}} Assistenten löschen möchten?"
|
||||||
},
|
},
|
||||||
|
"batch_export": {
|
||||||
|
"button": "Exportieren"
|
||||||
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"delete": "Löschen",
|
"manage": "Verwalten",
|
||||||
"sort": "Sortieren"
|
"sort": "Sortieren"
|
||||||
},
|
},
|
||||||
"title": "Assistenten verwalten"
|
"title": "Assistenten verwalten"
|
||||||
@ -1248,11 +1252,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stop": "Stoppen",
|
"stop": "Stoppen",
|
||||||
|
"subscribe": "Abonnieren",
|
||||||
"success": "Erfolgreich",
|
"success": "Erfolgreich",
|
||||||
"swap": "Tauschen",
|
"swap": "Tauschen",
|
||||||
"topics": "Themen",
|
"topics": "Themen",
|
||||||
"unknown": "Unbekannt",
|
"unknown": "Unbekannt",
|
||||||
"unnamed": "Unbenannt",
|
"unnamed": "Unbenannt",
|
||||||
|
"unsubscribe": "Abmelden",
|
||||||
"update_success": "Erfolgreich aktualisiert",
|
"update_success": "Erfolgreich aktualisiert",
|
||||||
"upload_files": "Dateien hochladen",
|
"upload_files": "Dateien hochladen",
|
||||||
"warning": "Warnung",
|
"warning": "Warnung",
|
||||||
|
|||||||
@ -472,6 +472,7 @@
|
|||||||
"button": "Εισαγωγή",
|
"button": "Εισαγωγή",
|
||||||
"error": {
|
"error": {
|
||||||
"fetch_failed": "Αποτυχία λήψης δεδομένων από το URL",
|
"fetch_failed": "Αποτυχία λήψης δεδομένων από το URL",
|
||||||
|
"file_required": "Παρακαλώ επιλέξτε πρώτα ένα αρχείο",
|
||||||
"invalid_format": "Μη έγκυρη μορφή βοηθού: λείπουν υποχρεωτικά πεδία",
|
"invalid_format": "Μη έγκυρη μορφή βοηθού: λείπουν υποχρεωτικά πεδία",
|
||||||
"url_required": "Παρακαλώ εισάγετε ένα URL"
|
"url_required": "Παρακαλώ εισάγετε ένα URL"
|
||||||
},
|
},
|
||||||
@ -489,8 +490,11 @@
|
|||||||
"button": "Μαζική Διαγραφή",
|
"button": "Μαζική Διαγραφή",
|
||||||
"confirm": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τους επιλεγμένους {{count}} βοηθούς;"
|
"confirm": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τους επιλεγμένους {{count}} βοηθούς;"
|
||||||
},
|
},
|
||||||
|
"batch_export": {
|
||||||
|
"button": "Εξαγωγή"
|
||||||
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"delete": "Διαγραφή",
|
"manage": "Διαχειριστείτε",
|
||||||
"sort": "Ταξινόμηση"
|
"sort": "Ταξινόμηση"
|
||||||
},
|
},
|
||||||
"title": "Διαχείριση βοηθών"
|
"title": "Διαχείριση βοηθών"
|
||||||
@ -1248,11 +1252,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stop": "σταματήστε",
|
"stop": "σταματήστε",
|
||||||
|
"subscribe": "Εγγραφείτε",
|
||||||
"success": "Επιτυχία",
|
"success": "Επιτυχία",
|
||||||
"swap": "Εναλλαγή",
|
"swap": "Εναλλαγή",
|
||||||
"topics": "Θέματα",
|
"topics": "Θέματα",
|
||||||
"unknown": "Άγνωστο",
|
"unknown": "Άγνωστο",
|
||||||
"unnamed": "Χωρίς όνομα",
|
"unnamed": "Χωρίς όνομα",
|
||||||
|
"unsubscribe": "Απεγγραφή",
|
||||||
"update_success": "Επιτυχής ενημέρωση",
|
"update_success": "Επιτυχής ενημέρωση",
|
||||||
"upload_files": "Ανέβασμα αρχείου",
|
"upload_files": "Ανέβασμα αρχείου",
|
||||||
"warning": "Προσοχή",
|
"warning": "Προσοχή",
|
||||||
|
|||||||
@ -472,6 +472,7 @@
|
|||||||
"button": "Importar",
|
"button": "Importar",
|
||||||
"error": {
|
"error": {
|
||||||
"fetch_failed": "Error al obtener datos desde la URL",
|
"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",
|
"invalid_format": "Formato de asistente inválido: faltan campos obligatorios",
|
||||||
"url_required": "Por favor introduce una URL"
|
"url_required": "Por favor introduce una URL"
|
||||||
},
|
},
|
||||||
@ -489,8 +490,11 @@
|
|||||||
"button": "Eliminación por lotes",
|
"button": "Eliminación por lotes",
|
||||||
"confirm": "¿Estás seguro de que quieres eliminar los {{count}} asistentes seleccionados?"
|
"confirm": "¿Estás seguro de que quieres eliminar los {{count}} asistentes seleccionados?"
|
||||||
},
|
},
|
||||||
|
"batch_export": {
|
||||||
|
"button": "Exportar"
|
||||||
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"delete": "Eliminar",
|
"manage": "Gestionar",
|
||||||
"sort": "Ordenar"
|
"sort": "Ordenar"
|
||||||
},
|
},
|
||||||
"title": "Gestionar asistentes"
|
"title": "Gestionar asistentes"
|
||||||
@ -1248,11 +1252,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stop": "Detener",
|
"stop": "Detener",
|
||||||
|
"subscribe": "Suscribirse",
|
||||||
"success": "Éxito",
|
"success": "Éxito",
|
||||||
"swap": "Intercambiar",
|
"swap": "Intercambiar",
|
||||||
"topics": "Temas",
|
"topics": "Temas",
|
||||||
"unknown": "Desconocido",
|
"unknown": "Desconocido",
|
||||||
"unnamed": "Sin nombre",
|
"unnamed": "Sin nombre",
|
||||||
|
"unsubscribe": "Cancelar suscripción",
|
||||||
"update_success": "Actualización exitosa",
|
"update_success": "Actualización exitosa",
|
||||||
"upload_files": "Subir archivo",
|
"upload_files": "Subir archivo",
|
||||||
"warning": "Advertencia",
|
"warning": "Advertencia",
|
||||||
|
|||||||
@ -472,6 +472,7 @@
|
|||||||
"button": "Importer",
|
"button": "Importer",
|
||||||
"error": {
|
"error": {
|
||||||
"fetch_failed": "Échec de la récupération des données depuis l'URL",
|
"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",
|
"invalid_format": "Format d'assistant invalide : champs obligatoires manquants",
|
||||||
"url_required": "Veuillez saisir une URL"
|
"url_required": "Veuillez saisir une URL"
|
||||||
},
|
},
|
||||||
@ -489,8 +490,11 @@
|
|||||||
"button": "Suppression par lot",
|
"button": "Suppression par lot",
|
||||||
"confirm": "Êtes-vous sûr de vouloir supprimer les {{count}} assistants sélectionnés ?"
|
"confirm": "Êtes-vous sûr de vouloir supprimer les {{count}} assistants sélectionnés ?"
|
||||||
},
|
},
|
||||||
|
"batch_export": {
|
||||||
|
"button": "Exporter"
|
||||||
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"delete": "Supprimer",
|
"manage": "Gérer",
|
||||||
"sort": "Trier"
|
"sort": "Trier"
|
||||||
},
|
},
|
||||||
"title": "Gérer les assistants"
|
"title": "Gérer les assistants"
|
||||||
@ -1248,11 +1252,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stop": "Arrêter",
|
"stop": "Arrêter",
|
||||||
|
"subscribe": "S'abonner",
|
||||||
"success": "Succès",
|
"success": "Succès",
|
||||||
"swap": "Échanger",
|
"swap": "Échanger",
|
||||||
"topics": "Sujets",
|
"topics": "Sujets",
|
||||||
"unknown": "Inconnu",
|
"unknown": "Inconnu",
|
||||||
"unnamed": "Sans nom",
|
"unnamed": "Sans nom",
|
||||||
|
"unsubscribe": "Se désabonner",
|
||||||
"update_success": "Mise à jour réussie",
|
"update_success": "Mise à jour réussie",
|
||||||
"upload_files": "Uploader des fichiers",
|
"upload_files": "Uploader des fichiers",
|
||||||
"warning": "Avertissement",
|
"warning": "Avertissement",
|
||||||
|
|||||||
@ -472,6 +472,7 @@
|
|||||||
"button": "インポート",
|
"button": "インポート",
|
||||||
"error": {
|
"error": {
|
||||||
"fetch_failed": "URLからのデータ取得に失敗しました",
|
"fetch_failed": "URLからのデータ取得に失敗しました",
|
||||||
|
"file_required": "まずファイルを選択してください",
|
||||||
"invalid_format": "無効なアシスタント形式:必須フィールドが不足しています",
|
"invalid_format": "無効なアシスタント形式:必須フィールドが不足しています",
|
||||||
"url_required": "URLを入力してください"
|
"url_required": "URLを入力してください"
|
||||||
},
|
},
|
||||||
@ -489,8 +490,11 @@
|
|||||||
"button": "バッチ削除",
|
"button": "バッチ削除",
|
||||||
"confirm": "選択した{{count}}件のアシスタントを削除してもよろしいですか?"
|
"confirm": "選択した{{count}}件のアシスタントを削除してもよろしいですか?"
|
||||||
},
|
},
|
||||||
|
"batch_export": {
|
||||||
|
"button": "エクスポート"
|
||||||
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"delete": "削除",
|
"manage": "管理",
|
||||||
"sort": "並べ替え"
|
"sort": "並べ替え"
|
||||||
},
|
},
|
||||||
"title": "アシスタントを管理"
|
"title": "アシスタントを管理"
|
||||||
@ -1248,11 +1252,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stop": "停止",
|
"stop": "停止",
|
||||||
|
"subscribe": "購読",
|
||||||
"success": "成功",
|
"success": "成功",
|
||||||
"swap": "交換",
|
"swap": "交換",
|
||||||
"topics": "トピック",
|
"topics": "トピック",
|
||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
"unnamed": "無題",
|
"unnamed": "無題",
|
||||||
|
"unsubscribe": "配信停止",
|
||||||
"update_success": "更新成功",
|
"update_success": "更新成功",
|
||||||
"upload_files": "ファイルをアップロードする",
|
"upload_files": "ファイルをアップロードする",
|
||||||
"warning": "警告",
|
"warning": "警告",
|
||||||
|
|||||||
@ -472,6 +472,7 @@
|
|||||||
"button": "Importar",
|
"button": "Importar",
|
||||||
"error": {
|
"error": {
|
||||||
"fetch_failed": "Falha ao obter dados do URL",
|
"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",
|
"invalid_format": "Formato de assistente inválido: campos obrigatórios em falta",
|
||||||
"url_required": "Por favor insere um URL"
|
"url_required": "Por favor insere um URL"
|
||||||
},
|
},
|
||||||
@ -489,8 +490,11 @@
|
|||||||
"button": "Exclusão em Lote",
|
"button": "Exclusão em Lote",
|
||||||
"confirm": "Tem certeza de que deseja excluir os {{count}} assistentes selecionados?"
|
"confirm": "Tem certeza de que deseja excluir os {{count}} assistentes selecionados?"
|
||||||
},
|
},
|
||||||
|
"batch_export": {
|
||||||
|
"button": "Exportar"
|
||||||
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"delete": "Excluir",
|
"manage": "Gerenciar",
|
||||||
"sort": "Ordenar"
|
"sort": "Ordenar"
|
||||||
},
|
},
|
||||||
"title": "Gerir assistentes"
|
"title": "Gerir assistentes"
|
||||||
@ -1248,11 +1252,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stop": "Parar",
|
"stop": "Parar",
|
||||||
|
"subscribe": "Subscrever",
|
||||||
"success": "Sucesso",
|
"success": "Sucesso",
|
||||||
"swap": "Trocar",
|
"swap": "Trocar",
|
||||||
"topics": "Tópicos",
|
"topics": "Tópicos",
|
||||||
"unknown": "Desconhecido",
|
"unknown": "Desconhecido",
|
||||||
"unnamed": "Sem nome",
|
"unnamed": "Sem nome",
|
||||||
|
"unsubscribe": "Cancelar inscrição",
|
||||||
"update_success": "Atualização bem-sucedida",
|
"update_success": "Atualização bem-sucedida",
|
||||||
"upload_files": "Carregar arquivo",
|
"upload_files": "Carregar arquivo",
|
||||||
"warning": "Aviso",
|
"warning": "Aviso",
|
||||||
|
|||||||
@ -472,6 +472,7 @@
|
|||||||
"button": "Импортировать",
|
"button": "Импортировать",
|
||||||
"error": {
|
"error": {
|
||||||
"fetch_failed": "Ошибка получения данных с URL",
|
"fetch_failed": "Ошибка получения данных с URL",
|
||||||
|
"file_required": "Сначала выберите файл",
|
||||||
"invalid_format": "Неверный формат помощника: отсутствуют обязательные поля",
|
"invalid_format": "Неверный формат помощника: отсутствуют обязательные поля",
|
||||||
"url_required": "Пожалуйста, введите URL"
|
"url_required": "Пожалуйста, введите URL"
|
||||||
},
|
},
|
||||||
@ -489,8 +490,11 @@
|
|||||||
"button": "Массовое удаление",
|
"button": "Массовое удаление",
|
||||||
"confirm": "Вы уверены, что хотите удалить выбранных {{count}} ассистентов?"
|
"confirm": "Вы уверены, что хотите удалить выбранных {{count}} ассистентов?"
|
||||||
},
|
},
|
||||||
|
"batch_export": {
|
||||||
|
"button": "Экспорт"
|
||||||
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"delete": "Удалить",
|
"manage": "Управлять",
|
||||||
"sort": "Сортировать"
|
"sort": "Сортировать"
|
||||||
},
|
},
|
||||||
"title": "Управление помощниками"
|
"title": "Управление помощниками"
|
||||||
@ -1248,11 +1252,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stop": "остановить",
|
"stop": "остановить",
|
||||||
|
"subscribe": "Подписаться",
|
||||||
"success": "Успешно",
|
"success": "Успешно",
|
||||||
"swap": "Поменять местами",
|
"swap": "Поменять местами",
|
||||||
"topics": "Топики",
|
"topics": "Топики",
|
||||||
"unknown": "Неизвестно",
|
"unknown": "Неизвестно",
|
||||||
"unnamed": "Без имени",
|
"unnamed": "Без имени",
|
||||||
|
"unsubscribe": "Отписаться",
|
||||||
"update_success": "Обновление выполнено успешно",
|
"update_success": "Обновление выполнено успешно",
|
||||||
"upload_files": "Загрузить файл",
|
"upload_files": "Загрузить файл",
|
||||||
"warning": "Предупреждение",
|
"warning": "Предупреждение",
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import ListItem from '@renderer/components/ListItem'
|
import ListItem from '@renderer/components/ListItem'
|
||||||
import GeneralPopup from '@renderer/components/Popups/GeneralPopup'
|
|
||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import CustomTag from '@renderer/components/Tags/CustomTag'
|
import CustomTag from '@renderer/components/Tags/CustomTag'
|
||||||
import { useAssistantPresets } from '@renderer/hooks/useAssistantPresets'
|
import { useAssistantPresets } from '@renderer/hooks/useAssistantPresets'
|
||||||
@ -11,7 +10,7 @@ import type { AssistantPreset } from '@renderer/types'
|
|||||||
import { uuid } from '@renderer/utils'
|
import { uuid } from '@renderer/utils'
|
||||||
import { Button, Empty, Flex, Input } from 'antd'
|
import { Button, Empty, Flex, Input } from 'antd'
|
||||||
import { omit } from 'lodash'
|
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 type { FC } from 'react'
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -23,7 +22,6 @@ import { groupTranslations } from './assistantPresetGroupTranslations'
|
|||||||
import AddAssistantPresetPopup from './components/AddAssistantPresetPopup'
|
import AddAssistantPresetPopup from './components/AddAssistantPresetPopup'
|
||||||
import AssistantPresetCard from './components/AssistantPresetCard'
|
import AssistantPresetCard from './components/AssistantPresetCard'
|
||||||
import { AssistantPresetGroupIcon } from './components/AssistantPresetGroupIcon'
|
import { AssistantPresetGroupIcon } from './components/AssistantPresetGroupIcon'
|
||||||
import AssistantsSubscribeUrlSettings from './components/AssistantsSubscribeUrlSettings'
|
|
||||||
import ImportAssistantPresetPopup from './components/ImportAssistantPresetPopup'
|
import ImportAssistantPresetPopup from './components/ImportAssistantPresetPopup'
|
||||||
import ManageAssistantPresetsPopup from './components/ManageAssistantPresetsPopup'
|
import ManageAssistantPresetsPopup from './components/ManageAssistantPresetsPopup'
|
||||||
|
|
||||||
@ -177,15 +175,6 @@ const AssistantPresetsPage: FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubscribeSettings = () => {
|
|
||||||
GeneralPopup.show({
|
|
||||||
title: t('assistants.presets.settings.title'),
|
|
||||||
content: <AssistantsSubscribeUrlSettings />,
|
|
||||||
footer: null,
|
|
||||||
width: 600
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleManageAgents = () => {
|
const handleManageAgents = () => {
|
||||||
ManageAssistantPresetsPopup.show()
|
ManageAssistantPresetsPopup.show()
|
||||||
}
|
}
|
||||||
@ -292,9 +281,6 @@ const AssistantPresetsPage: FC = () => {
|
|||||||
<Button type="text" onClick={handleImportAgent} icon={<Import size={18} color="var(--color-icon)" />}>
|
<Button type="text" onClick={handleImportAgent} icon={<Import size={18} color="var(--color-icon)" />}>
|
||||||
{t('assistants.presets.import.title')}
|
{t('assistants.presets.import.title')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="text" onClick={handleSubscribeSettings} icon={<Rss size={18} color="var(--color-icon)" />}>
|
|
||||||
{t('assistants.presets.settings.title')}
|
|
||||||
</Button>
|
|
||||||
<Button type="text" onClick={handleManageAgents} icon={<Settings2 size={18} color="var(--color-icon)" />}>
|
<Button type="text" onClick={handleManageAgents} icon={<Settings2 size={18} color="var(--color-icon)" />}>
|
||||||
{t('assistants.presets.manage.title')}
|
{t('assistants.presets.manage.title')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -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<HTMLInputElement>) => {
|
|
||||||
dispatch(setAgentssubscribeUrl(e.target.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleHelpClick = () => {
|
|
||||||
window.open('https://docs.cherry-ai.com/data-settings/assistants-subscribe', '_blank')
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SettingGroup theme={theme}>
|
|
||||||
<HStack alignItems="center" gap="8px">
|
|
||||||
<SettingTitle>
|
|
||||||
{t('assistants.presets.tag.agent')}
|
|
||||||
{t('settings.tool.websearch.subscribe_add')}
|
|
||||||
</SettingTitle>
|
|
||||||
<HelpCircle
|
|
||||||
size={16}
|
|
||||||
color="var(--color-icon)"
|
|
||||||
onClick={handleHelpClick}
|
|
||||||
className="hover:!text-[var(--color-primary)] cursor-pointer transition-colors"
|
|
||||||
/>
|
|
||||||
</HStack>
|
|
||||||
<SettingDivider />
|
|
||||||
<SettingRow>
|
|
||||||
<SettingRowTitle>{t('settings.tool.websearch.subscribe_url')}</SettingRowTitle>
|
|
||||||
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
value={agentssubscribeUrl || ''}
|
|
||||||
onChange={handleAgentChange}
|
|
||||||
style={{ width: 315 }}
|
|
||||||
placeholder={t('settings.tool.websearch.subscribe_url')}
|
|
||||||
/>
|
|
||||||
</HStack>
|
|
||||||
</SettingRow>
|
|
||||||
</SettingGroup>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AssistantsSubscribeUrlSettings
|
|
||||||
@ -1,11 +1,15 @@
|
|||||||
import { TopView } from '@renderer/components/TopView'
|
import { TopView } from '@renderer/components/TopView'
|
||||||
import { useAssistantPresets } from '@renderer/hooks/useAssistantPresets'
|
import { useAssistantPresets } from '@renderer/hooks/useAssistantPresets'
|
||||||
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import { getDefaultModel } from '@renderer/services/AssistantService'
|
import { getDefaultModel } from '@renderer/services/AssistantService'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
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 type { AssistantPreset } from '@renderer/types'
|
||||||
import { uuid } from '@renderer/utils'
|
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 { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
@ -20,36 +24,54 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
const { addAssistantPreset } = useAssistantPresets()
|
const { addAssistantPreset } = useAssistantPresets()
|
||||||
const [importType, setImportType] = useState<'url' | 'file'>('url')
|
const [importType, setImportType] = useState<'url' | 'file'>('url')
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [subscribeLoading, setSubscribeLoading] = useState(false)
|
||||||
const { setTimeoutTimer } = useTimer()
|
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)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
let presets: AssistantPreset[] = []
|
let presets: AssistantPreset[] = []
|
||||||
|
|
||||||
if (importType === 'url') {
|
if (importType === 'url') {
|
||||||
if (!values.url) {
|
const response = await fetch(urlValue.trim())
|
||||||
throw new Error(t('assistants.presets.import.error.url_required'))
|
|
||||||
}
|
|
||||||
const response = await fetch(values.url)
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(t('assistants.presets.import.error.fetch_failed'))
|
throw new Error(t('assistants.presets.import.error.fetch_failed'))
|
||||||
}
|
}
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
presets = Array.isArray(data) ? data : [data]
|
presets = Array.isArray(data) ? data : [data]
|
||||||
} else {
|
} else {
|
||||||
const result = await window.api.file.open({
|
presets = JSON.parse(new TextDecoder('utf-8').decode(selectedFile!.content))
|
||||||
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)) {
|
if (!Array.isArray(presets)) {
|
||||||
presets = [presets]
|
presets = [presets]
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate and process agents
|
// Validate and process agents
|
||||||
@ -74,7 +96,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
addAssistantPreset(newPreset)
|
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)
|
setTimeoutTimer('onFinish', () => EventEmitter.emit(EVENT_NAMES.SHOW_ASSISTANTS), 0)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
@ -88,7 +110,42 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
resolve(null)
|
}
|
||||||
|
|
||||||
|
const handleSubscribeUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
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 (
|
return (
|
||||||
@ -96,39 +153,79 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
title={t('assistants.presets.import.title')}
|
title={t('assistants.presets.import.title')}
|
||||||
open={open}
|
open={open}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
maskClosable={false}
|
afterClose={() => resolve(null)}
|
||||||
footer={
|
footer={null}
|
||||||
<Flex justify="end" gap={8}>
|
|
||||||
<Button onClick={onCancel}>{t('common.cancel')}</Button>
|
|
||||||
<Button type="primary" onClick={() => form.submit()} loading={loading}>
|
|
||||||
{t('assistants.presets.import.button')}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
}
|
|
||||||
transitionName="animation-move-down"
|
transitionName="animation-move-down"
|
||||||
|
styles={{ body: { padding: '16px' } }}
|
||||||
centered>
|
centered>
|
||||||
<Form form={form} onFinish={onFinish} layout="vertical">
|
<Form form={form} onFinish={onFinish} layout="vertical">
|
||||||
<Form.Item>
|
<Form.Item style={{ marginBottom: 0 }}>
|
||||||
|
<Flex align="center" gap={12} style={{ width: '100%' }}>
|
||||||
<Radio.Group value={importType} onChange={(e) => setImportType(e.target.value)}>
|
<Radio.Group value={importType} onChange={(e) => setImportType(e.target.value)}>
|
||||||
<Radio.Button value="url">{t('assistants.presets.import.type.url')}</Radio.Button>
|
<Radio.Button value="url">{t('assistants.presets.import.type.url')}</Radio.Button>
|
||||||
<Radio.Button value="file">{t('assistants.presets.import.type.file')}</Radio.Button>
|
<Radio.Button value="file">{t('assistants.presets.import.type.file')}</Radio.Button>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
{importType === 'url' && (
|
{importType === 'url' && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="url"
|
name="url"
|
||||||
rules={[{ required: true, message: t('assistants.presets.import.error.url_required') }]}>
|
rules={[{ required: true, message: t('assistants.presets.import.error.url_required') }]}
|
||||||
<Input placeholder={t('assistants.presets.import.url_placeholder')} />
|
style={{ flex: 1, marginBottom: 0 }}>
|
||||||
|
<Input
|
||||||
|
placeholder={t('assistants.presets.import.url_placeholder')}
|
||||||
|
value={urlValue}
|
||||||
|
onChange={(e) => setUrlValue(e.target.value)}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{importType === 'file' && (
|
{importType === 'file' && (
|
||||||
<Form.Item>
|
<>
|
||||||
<Button onClick={() => form.submit()}>{t('assistants.presets.import.select_file')}</Button>
|
<Button onClick={handleSelectFile}>{t('assistants.presets.import.select_file')}</Button>
|
||||||
</Form.Item>
|
{selectedFile && (
|
||||||
|
<Typography.Text type="secondary" ellipsis style={{ maxWidth: 200 }}>
|
||||||
|
{selectedFile.name}
|
||||||
|
</Typography.Text>
|
||||||
)}
|
)}
|
||||||
|
<div style={{ flex: 1 }} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button type="primary" onClick={onFinish} loading={loading} disabled={isImportDisabled}>
|
||||||
|
{t('assistants.presets.import.button')}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
<Divider style={{ margin: '16px 0' }} />
|
||||||
|
|
||||||
|
<Flex align="center" gap={4}>
|
||||||
|
<Typography.Text strong style={{ flexShrink: 0, fontSize: 16 }}>
|
||||||
|
{t('assistants.presets.tag.agent')}
|
||||||
|
{t('settings.tool.websearch.subscribe_add')}
|
||||||
|
</Typography.Text>
|
||||||
|
<HelpCircle
|
||||||
|
size={16}
|
||||||
|
color="var(--color-icon)"
|
||||||
|
onClick={handleHelpClick}
|
||||||
|
className="hover:!text-[var(--color-primary)] cursor-pointer transition-colors"
|
||||||
|
style={{ flexShrink: 0 }}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex align="center" gap={12} style={{ marginTop: 10 }}>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={subscribeUrl}
|
||||||
|
onChange={handleSubscribeUrlChange}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
placeholder={t('settings.tool.websearch.subscribe_url')}
|
||||||
|
/>
|
||||||
|
<Button type="primary" onClick={handleSubscribe} loading={subscribeLoading} disabled={!subscribeUrl.trim()}>
|
||||||
|
{isSubscribed ? t('common.unsubscribe') : t('common.subscribe')}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { MenuOutlined } from '@ant-design/icons'
|
import { ExportOutlined, MenuOutlined } from '@ant-design/icons'
|
||||||
import { DraggableList } from '@renderer/components/DraggableList'
|
import { DraggableList } from '@renderer/components/DraggableList'
|
||||||
import { DeleteIcon } from '@renderer/components/Icons'
|
import { DeleteIcon } from '@renderer/components/Icons'
|
||||||
import { Box, HStack } from '@renderer/components/Layout'
|
import { Box, HStack } from '@renderer/components/Layout'
|
||||||
@ -10,13 +10,13 @@ import { useState } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
type Mode = 'sort' | 'delete'
|
type Mode = 'sort' | 'manage'
|
||||||
|
|
||||||
const PopupContainer: React.FC = () => {
|
const PopupContainer: React.FC = () => {
|
||||||
const [open, setOpen] = useState(true)
|
const [open, setOpen] = useState(true)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { presets, setAssistantPresets } = useAssistantPresets()
|
const { presets, setAssistantPresets } = useAssistantPresets()
|
||||||
const [mode, setMode] = useState<Mode>(() => (presets.length > 50 ? 'delete' : 'sort'))
|
const [mode, setMode] = useState<Mode>('manage')
|
||||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
|
||||||
|
|
||||||
const onCancel = () => {
|
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 isAllSelected = presets.length > 0 && selectedIds.size === presets.length
|
||||||
const isIndeterminate = selectedIds.size > 0 && selectedIds.size < presets.length
|
const isIndeterminate = selectedIds.size > 0 && selectedIds.size < presets.length
|
||||||
|
|
||||||
@ -98,13 +115,14 @@ const PopupContainer: React.FC = () => {
|
|||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
afterClose={onClose}
|
afterClose={onClose}
|
||||||
footer={null}
|
footer={null}
|
||||||
|
width={600}
|
||||||
transitionName="animation-move-down"
|
transitionName="animation-move-down"
|
||||||
centered>
|
centered>
|
||||||
<Container>
|
<Container>
|
||||||
{presets.length > 0 && (
|
{presets.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<ActionBar>
|
<ActionBar>
|
||||||
{mode === 'delete' ? (
|
{mode === 'manage' ? (
|
||||||
<HStack alignItems="center">
|
<HStack alignItems="center">
|
||||||
<Checkbox checked={isAllSelected} indeterminate={isIndeterminate} onChange={handleSelectAll}>
|
<Checkbox checked={isAllSelected} indeterminate={isIndeterminate} onChange={handleSelectAll}>
|
||||||
{t('common.select_all')}
|
{t('common.select_all')}
|
||||||
@ -119,7 +137,15 @@ const PopupContainer: React.FC = () => {
|
|||||||
<div />
|
<div />
|
||||||
)}
|
)}
|
||||||
<HStack gap="8px" alignItems="center">
|
<HStack gap="8px" alignItems="center">
|
||||||
{mode === 'delete' && (
|
{mode === 'manage' && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<ExportOutlined />}
|
||||||
|
disabled={selectedIds.size === 0}
|
||||||
|
onClick={handleBatchExport}>
|
||||||
|
{t('assistants.presets.manage.batch_export.button')} ({selectedIds.size})
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
danger
|
danger
|
||||||
type="text"
|
type="text"
|
||||||
@ -128,6 +154,7 @@ const PopupContainer: React.FC = () => {
|
|||||||
onClick={handleBatchDelete}>
|
onClick={handleBatchDelete}>
|
||||||
{t('assistants.presets.manage.batch_delete.button')} ({selectedIds.size})
|
{t('assistants.presets.manage.batch_delete.button')} ({selectedIds.size})
|
||||||
</Button>
|
</Button>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<Segmented
|
<Segmented
|
||||||
size="small"
|
size="small"
|
||||||
@ -135,7 +162,7 @@ const PopupContainer: React.FC = () => {
|
|||||||
onChange={(value) => handleModeChange(value as Mode)}
|
onChange={(value) => handleModeChange(value as Mode)}
|
||||||
options={[
|
options={[
|
||||||
{ label: t('assistants.presets.manage.mode.sort'), value: 'sort' },
|
{ 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' }
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user