diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index e8c219bc0..21dcd42f5 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -485,6 +485,14 @@ "url_placeholder": "Enter JSON URL" }, "manage": { + "batch_delete": { + "button": "Batch Delete", + "confirm": "Are you sure you want to delete the selected {{count}} assistants?" + }, + "mode": { + "delete": "Delete", + "sort": "Sort" + }, "title": "Manage Assistants" }, "my_agents": "My Assistants", @@ -1199,6 +1207,7 @@ "saved": "Saved", "search": "Search", "select": "Select", + "select_all": "Select All", "selected": "Selected", "selectedItems": "Selected {{count}} items", "selectedMessages": "Selected {{count}} messages", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 0b9a38e72..c70acb020 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -485,6 +485,14 @@ "url_placeholder": "输入 JSON URL" }, "manage": { + "batch_delete": { + "button": "批量删除", + "confirm": "确定要删除选中的 {{count}} 个助手吗?" + }, + "mode": { + "delete": "删除", + "sort": "排序" + }, "title": "管理助手" }, "my_agents": "我的助手", @@ -1199,6 +1207,7 @@ "saved": "已保存", "search": "搜索", "select": "选择", + "select_all": "全选", "selected": "已选择", "selectedItems": "已选择 {{count}} 项", "selectedMessages": "选中 {{count}} 条消息", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index af29b0c1c..6b18bbe09 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -485,6 +485,14 @@ "url_placeholder": "輸入 JSON URL" }, "manage": { + "batch_delete": { + "button": "批次刪除", + "confirm": "您確定要刪除所選的 {{count}} 個助理嗎?" + }, + "mode": { + "delete": "刪除", + "sort": "排序" + }, "title": "管理助手" }, "my_agents": "我的助手", @@ -1199,6 +1207,7 @@ "saved": "已儲存", "search": "搜尋", "select": "選擇", + "select_all": "全選", "selected": "已選擇", "selectedItems": "已選擇 {{count}} 項", "selectedMessages": "選中 {{count}} 條訊息", diff --git a/src/renderer/src/i18n/translate/de-de.json b/src/renderer/src/i18n/translate/de-de.json index 4ce2518ed..d0db25d93 100644 --- a/src/renderer/src/i18n/translate/de-de.json +++ b/src/renderer/src/i18n/translate/de-de.json @@ -485,6 +485,14 @@ "url_placeholder": "JSON-URL eingeben" }, "manage": { + "batch_delete": { + "button": "Stapel löschen", + "confirm": "Sind Sie sicher, dass Sie die ausgewählten {{count}} Assistenten löschen möchten?" + }, + "mode": { + "delete": "Löschen", + "sort": "Sortieren" + }, "title": "Assistenten verwalten" }, "my_agents": "Meine Assistenten", @@ -1199,6 +1207,7 @@ "saved": "Gespeichert", "search": "Suchen", "select": "Auswählen", + "select_all": "Alle auswählen", "selected": "Ausgewählt", "selectedItems": "{{count}} Elemente ausgewählt", "selectedMessages": "{{count}} Nachrichten ausgewählt", diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 93228396c..9111a2359 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -485,6 +485,14 @@ "url_placeholder": "Εισάγετε JSON URL" }, "manage": { + "batch_delete": { + "button": "Μαζική Διαγραφή", + "confirm": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τους επιλεγμένους {{count}} βοηθούς;" + }, + "mode": { + "delete": "Διαγραφή", + "sort": "Ταξινόμηση" + }, "title": "Διαχείριση βοηθών" }, "my_agents": "Οι βοηθοί μου", @@ -1199,6 +1207,7 @@ "saved": "Αποθηκεύτηκε", "search": "Αναζήτηση", "select": "Επιλογή", + "select_all": "Επιλογή Όλων", "selected": "Επιλεγμένο", "selectedItems": "Επιλέχθηκαν {{count}} αντικείμενα", "selectedMessages": "Επιλέχθηκαν {{count}} μηνύματα", diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index ae5d316f5..b269bb2c6 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -485,6 +485,14 @@ "url_placeholder": "Introducir URL JSON" }, "manage": { + "batch_delete": { + "button": "Eliminación por lotes", + "confirm": "¿Estás seguro de que quieres eliminar los {{count}} asistentes seleccionados?" + }, + "mode": { + "delete": "Eliminar", + "sort": "Ordenar" + }, "title": "Gestionar asistentes" }, "my_agents": "Mis asistentes", @@ -1199,6 +1207,7 @@ "saved": "Guardado", "search": "Buscar", "select": "Seleccionar", + "select_all": "Seleccionar todo", "selected": "Seleccionado", "selectedItems": "{{count}} elementos seleccionados", "selectedMessages": "{{count}} mensajes seleccionados", diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index d3a725ce7..b528283d3 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -485,6 +485,14 @@ "url_placeholder": "Saisir l'URL JSON" }, "manage": { + "batch_delete": { + "button": "Suppression par lot", + "confirm": "Êtes-vous sûr de vouloir supprimer les {{count}} assistants sélectionnés ?" + }, + "mode": { + "delete": "Supprimer", + "sort": "Trier" + }, "title": "Gérer les assistants" }, "my_agents": "Mes assistants", @@ -1199,6 +1207,7 @@ "saved": "enregistré", "search": "Rechercher", "select": "Sélectionner", + "select_all": "Tout sélectionner", "selected": "Sélectionné", "selectedItems": "{{count}} éléments sélectionnés", "selectedMessages": "{{count}} messages sélectionnés", diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index 1f84c4dce..4d6fd8496 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -485,6 +485,14 @@ "url_placeholder": "JSON URLを入力" }, "manage": { + "batch_delete": { + "button": "バッチ削除", + "confirm": "選択した{{count}}件のアシスタントを削除してもよろしいですか?" + }, + "mode": { + "delete": "削除", + "sort": "並べ替え" + }, "title": "アシスタントを管理" }, "my_agents": "マイアシスタント", @@ -1199,6 +1207,7 @@ "saved": "保存されました", "search": "検索", "select": "選択", + "select_all": "すべて選択", "selected": "選択済み", "selectedItems": "{{count}}件の項目を選択しました", "selectedMessages": "{{count}}件のメッセージを選択しました", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 376819af2..5acf21cb1 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -485,6 +485,14 @@ "url_placeholder": "Inserir URL JSON" }, "manage": { + "batch_delete": { + "button": "Exclusão em Lote", + "confirm": "Tem certeza de que deseja excluir os {{count}} assistentes selecionados?" + }, + "mode": { + "delete": "Excluir", + "sort": "Ordenar" + }, "title": "Gerir assistentes" }, "my_agents": "Os meus assistentes", @@ -1199,6 +1207,7 @@ "saved": "Guardado", "search": "Pesquisar", "select": "Selecionar", + "select_all": "Selecionar Tudo", "selected": "Selecionado", "selectedItems": "{{count}} itens selecionados", "selectedMessages": "{{count}} mensagens selecionadas", diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index d0ffcb120..482b6b94d 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -485,6 +485,14 @@ "url_placeholder": "Введите JSON URL" }, "manage": { + "batch_delete": { + "button": "Массовое удаление", + "confirm": "Вы уверены, что хотите удалить выбранных {{count}} ассистентов?" + }, + "mode": { + "delete": "Удалить", + "sort": "Сортировать" + }, "title": "Управление помощниками" }, "my_agents": "Мои помощники", @@ -1199,6 +1207,7 @@ "saved": "Сохранено", "search": "Поиск", "select": "Выбрать", + "select_all": "Выбрать все", "selected": "Выбрано", "selectedItems": "Выбрано {{count}} элементов", "selectedMessages": "Выбрано {{count}} сообщений", diff --git a/src/renderer/src/pages/store/assistants/presets/AssistantPresetsPage.tsx b/src/renderer/src/pages/store/assistants/presets/AssistantPresetsPage.tsx index 91ea8cce7..79d1275b1 100644 --- a/src/renderer/src/pages/store/assistants/presets/AssistantPresetsPage.tsx +++ b/src/renderer/src/pages/store/assistants/presets/AssistantPresetsPage.tsx @@ -11,7 +11,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 } from 'lucide-react' +import { Import, Plus, Rss, Search, Settings2 } from 'lucide-react' import type { FC } from 'react' import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -25,6 +25,7 @@ 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' const AssistantPresetsPage: FC = () => { const [search, setSearch] = useState('') @@ -185,6 +186,10 @@ const AssistantPresetsPage: FC = () => { }) } + const handleManageAgents = () => { + ManageAssistantPresetsPopup.show() + } + return ( @@ -290,6 +295,9 @@ const AssistantPresetsPage: FC = () => { + diff --git a/src/renderer/src/pages/store/assistants/presets/components/AssistantPresetCard.tsx b/src/renderer/src/pages/store/assistants/presets/components/AssistantPresetCard.tsx index 4c9ce082d..908c0c9f3 100644 --- a/src/renderer/src/pages/store/assistants/presets/components/AssistantPresetCard.tsx +++ b/src/renderer/src/pages/store/assistants/presets/components/AssistantPresetCard.tsx @@ -8,7 +8,7 @@ import { getLeadingEmoji } from '@renderer/utils' import { Button, Dropdown } from 'antd' import { t } from 'i18next' import { isArray } from 'lodash' -import { ArrowDownAZ, Ellipsis, PlusIcon, SquareArrowOutUpRight } from 'lucide-react' +import { Ellipsis, PlusIcon, Settings2, SquareArrowOutUpRight } from 'lucide-react' import { type FC, memo, useCallback, useEffect, useRef, useState } from 'react' import styled from 'styled-components' @@ -77,9 +77,9 @@ const AssistantPresetCard: FC = ({ preset, onClick, activegroup, getLocal } }, { - key: 'sort', - label: t('assistants.presets.sorting.title'), - icon: , + key: 'manage', + label: t('assistants.presets.manage.title'), + icon: , onClick: (e: any) => { e.domEvent.stopPropagation() ManageAssistantPresetsPopup.show() 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 760336849..b75a569c5 100644 --- a/src/renderer/src/pages/store/assistants/presets/components/ManageAssistantPresetsPopup.tsx +++ b/src/renderer/src/pages/store/assistants/presets/components/ManageAssistantPresetsPopup.tsx @@ -1,21 +1,23 @@ import { MenuOutlined } from '@ant-design/icons' import { DraggableList } from '@renderer/components/DraggableList' +import { DeleteIcon } from '@renderer/components/Icons' import { Box, HStack } from '@renderer/components/Layout' import { TopView } from '@renderer/components/TopView' import { useAssistantPresets } from '@renderer/hooks/useAssistantPresets' -import { Empty, Modal } from 'antd' -import { useEffect, useState } from 'react' +import type { AssistantPreset } from '@renderer/types' +import { Button, Checkbox, Empty, Modal, Segmented } from 'antd' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' +type Mode = 'sort' | 'delete' + const PopupContainer: React.FC = () => { const [open, setOpen] = useState(true) const { t } = useTranslation() const { presets, setAssistantPresets } = useAssistantPresets() - - const onOk = () => { - setOpen(false) - } + const [mode, setMode] = useState(() => (presets.length > 50 ? 'delete' : 'sort')) + const [selectedIds, setSelectedIds] = useState>(new Set()) const onCancel = () => { setOpen(false) @@ -25,17 +27,74 @@ const PopupContainer: React.FC = () => { ManageAssistantPresetsPopup.hide() } - useEffect(() => { - if (presets.length === 0) { - setOpen(false) + const handleModeChange = (value: Mode) => { + setMode(value) + setSelectedIds(new Set()) + } + + const handleSelectAll = () => { + if (selectedIds.size === presets.length) { + setSelectedIds(new Set()) + } else { + setSelectedIds(new Set(presets.map((p) => p.id))) } - }, [presets]) + } + + const handleSelectNext100 = () => { + // Find the last selected preset's index + let startIndex = 0 + if (selectedIds.size > 0) { + for (let i = presets.length - 1; i >= 0; i--) { + if (selectedIds.has(presets[i].id)) { + startIndex = i + 1 + break + } + } + } + + // Select next 100 unselected presets starting from startIndex + const newSelected = new Set(selectedIds) + let count = 0 + for (let i = startIndex; i < presets.length && count < 100; i++) { + if (!newSelected.has(presets[i].id)) { + newSelected.add(presets[i].id) + count++ + } + } + setSelectedIds(newSelected) + } + + const handleSelect = (preset: AssistantPreset) => { + const newSelected = new Set(selectedIds) + if (newSelected.has(preset.id)) { + newSelected.delete(preset.id) + } else { + newSelected.add(preset.id) + } + setSelectedIds(newSelected) + } + + const handleBatchDelete = () => { + if (selectedIds.size === 0) return + + window.modal.confirm({ + centered: true, + content: t('assistants.presets.manage.batch_delete.confirm', { count: selectedIds.size }), + onOk: () => { + const remainingPresets = presets.filter((p) => !selectedIds.has(p.id)) + setAssistantPresets(remainingPresets) + setSelectedIds(new Set()) + } + }) + } + + const isAllSelected = presets.length > 0 && selectedIds.size === presets.length + const isIndeterminate = selectedIds.size > 0 && selectedIds.size < presets.length return ( { centered> {presets.length > 0 && ( - - {(item) => ( - - - {item.emoji} {item.name} - - - + <> + + {mode === 'delete' ? ( + + + {t('common.select_all')} + + {presets.length > 100 && selectedIds.size < presets.length && ( + + )} - + ) : ( +
+ )} + + {mode === 'delete' && ( + + )} + handleModeChange(value as Mode)} + options={[ + { label: t('assistants.presets.manage.mode.sort'), value: 'sort' }, + { label: t('assistants.presets.manage.mode.delete'), value: 'delete' } + ]} + /> + + + + {mode === 'sort' ? ( + + + {(item) => ( + + + {item.emoji} {item.name} + + + + + + )} + + + ) : ( + + {presets.map((item) => ( + handleSelect(item)} + $selected={selectedIds.has(item.id)}> + + handleSelect(item)} /> + + {item.emoji} {item.name} + + + + ))} + )} - + )} {presets.length === 0 && } @@ -65,6 +184,21 @@ const PopupContainer: React.FC = () => { const Container = styled.div` padding: 12px 0; height: 50vh; + display: flex; + flex-direction: column; +` + +const ActionBar = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 8px 12px; + border-bottom: 1px solid var(--color-border); + margin-bottom: 12px; +` + +const AgentList = styled.div` + flex: 1; overflow-y: auto; &::-webkit-scrollbar { display: none; @@ -90,6 +224,23 @@ const AgentItem = styled.div` } ` +const SelectableAgentItem = styled.div<{ $selected: boolean }>` + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 8px; + border-radius: 8px; + user-select: none; + background-color: ${(props) => (props.$selected ? 'var(--color-primary-mute)' : 'var(--color-background-soft)')}; + margin-bottom: 8px; + cursor: pointer; + transition: background-color 0.2s ease; + &:hover { + background-color: ${(props) => (props.$selected ? 'var(--color-primary-mute)' : 'var(--color-background-mute)')}; + } +` + export default class ManageAssistantPresetsPopup { static topviewId = 0 static hide() {