From cf1d5c098f779b294246de019d224b95e4a4b90a Mon Sep 17 00:00:00 2001 From: nmnmtttt <34001432+nmnmtttt@users.noreply.github.com> Date: Tue, 27 May 2025 21:57:15 +0800 Subject: [PATCH] feat: Assistant add tag (#6065) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 添加助手标签显示逻辑 -增加助手的标签属性 -能够删除,修改,调整助手的标签 Signed-off-by: LeeSH * fix: 修复不能输入新增标签的问题 * feat: 完善不同状态下,提示文本展示 * feat: 调整标签展示逻辑 1,左键调整列表页展示逻辑 2,新增标签改为使用+号提示 * feat: 移除搜索栏可以直接增加tag值的功能 Signed-off-by: LeeSH * fix: 修复点击不能切换话题的bug * feat: 调整了标签修改的交互 1,添加和管理分开处理 2,可以点击标签之间切换 3,点击删除可以之间移除所有关联助手的标签 tips:为了简单实现,标签本身不具有具体类,都是助手的子属性。所以如果关联的所有助手都没了该属性,标签会直接消失,而且标签目前无法排序 Signed-off-by: LeeSH * feat:优化标签管理 1,列表状态管理向上提,切换左侧列表不会影响原来的列表状态 2,标签名称增加最大宽度 3,标签内的助手顺序,参照原顺序排列 4,增加标签ui,提示语调整 5,标签管理ui,提示语调整 6,标签管理增加标签暂时态,防止误删没有其他助手的标签项的时候,标签在弹窗内整个消失(如果关闭弹窗那标签就无法找回) 7,如果没有标签的时候,右键仅展示添加标签 Signed-off-by: LeeSH --------- Signed-off-by: LeeSH Co-authored-by: linshuhao Co-authored-by: Lee SH --- .../src/components/Popups/TagsPopup.tsx | 70 +++++++ src/renderer/src/hooks/useTags.ts | 72 ++++++++ src/renderer/src/i18n/locales/en-us.json | 20 ++ src/renderer/src/i18n/locales/ja-jp.json | 20 ++ src/renderer/src/i18n/locales/ru-ru.json | 21 +++ src/renderer/src/i18n/locales/zh-cn.json | 20 ++ src/renderer/src/i18n/locales/zh-tw.json | 20 ++ .../src/pages/home/Tabs/AssistantsTab.tsx | 115 ++++++++++-- .../home/Tabs/components/AssistantItem.tsx | 91 +++++++++- src/renderer/src/pages/home/Tabs/index.tsx | 4 + .../AssistantTagsSettings.tsx | 171 ++++++++++++++++++ .../settings/AssistantSettings/index.tsx | 8 +- src/renderer/src/types/index.ts | 1 + 13 files changed, 607 insertions(+), 26 deletions(-) create mode 100644 src/renderer/src/components/Popups/TagsPopup.tsx create mode 100644 src/renderer/src/hooks/useTags.ts create mode 100644 src/renderer/src/pages/settings/AssistantSettings/AssistantTagsSettings.tsx diff --git a/src/renderer/src/components/Popups/TagsPopup.tsx b/src/renderer/src/components/Popups/TagsPopup.tsx new file mode 100644 index 0000000000..7a4c14a3e6 --- /dev/null +++ b/src/renderer/src/components/Popups/TagsPopup.tsx @@ -0,0 +1,70 @@ +import AssistantTagsSettings from '@renderer/pages/settings/AssistantSettings/AssistantTagsSettings' +import { Assistant } from '@renderer/types' +import { Modal } from 'antd' +import { useState } from 'react' + +import { TopView } from '../TopView' + +interface Props { + assistant: Assistant + updateAssistant: (assistant: Assistant) => void + resolve: (data: any) => void + mode?: 'add' | 'manage' +} + +const PopupContainer: React.FC = ({ assistant, updateAssistant, resolve, mode }) => { + const [open, setOpen] = useState(true) + + const onCancel = () => { + setOpen(false) + } + + const onClose = () => { + resolve({}) + } + + TagsPopup.hide = onCancel + + return ( + + + + ) +} + +export default class TagsPopup { + static topviewId = 0 + static hide() { + TopView.hide('TagsPopup') + } + static show(assistant: Assistant, updateAssistant: (assistant: Assistant) => void, mode?: 'add' | 'manage') { + return new Promise((resolve) => { + TopView.show( + { + resolve(v) + TopView.hide('TagsPopup') + }} + mode={mode} + />, + 'TagsPopup' + ) + }) + } +} diff --git a/src/renderer/src/hooks/useTags.ts b/src/renderer/src/hooks/useTags.ts new file mode 100644 index 0000000000..2e1a22f069 --- /dev/null +++ b/src/renderer/src/hooks/useTags.ts @@ -0,0 +1,72 @@ +import { Assistant } from '@renderer/types' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' + +import { useAssistants } from './useAssistant' + +// 定义useTags的返回类型,包含所有标签和获取特定标签的助手函数 +// 为了不增加新的概念,标签直接作为助手的属性,所以这里的标签是指助手的标签属性 +// 但是为了方便管理,增加了一个获取特定标签的助手函数 + +export const useTags = () => { + const { assistants } = useAssistants() + const [allTags, setAllTags] = useState([]) + const { t } = useTranslation() + + // 计算所有标签 + const calculateTags = useCallback(() => { + const tags = new Set() + assistants.forEach((assistant) => { + assistant.tags?.forEach((tag) => tags.add(tag)) + }) + return Array.from(tags) + }, [assistants]) + + // 当assistants变化时重新计算标签 + useEffect(() => { + setAllTags(calculateTags()) + }, [assistants, calculateTags]) + + const getAssistantsByTag = useCallback( + (tag: string) => { + return assistants.filter((assistant) => assistant.tags?.includes(tag)) + }, + [assistants] + ) + + const addTag = useCallback((tag: string) => { + setAllTags((prev) => [...prev, tag]) + }, []) + + const getGroupedAssistants = useMemo(() => { + const grouped: { tag: string; assistants: Assistant[] }[] = [] + + allTags.forEach((tag) => { + const taggedAssistants = assistants.filter((a) => a.tags?.includes(tag)) + if (taggedAssistants.length > 0) { + grouped.push({ + tag, + assistants: taggedAssistants.sort((a, b) => a.name.localeCompare(b.name)) + }) + } + }) + + grouped.sort((a, b) => a.tag.localeCompare(b.tag)) + + const untagged = assistants.filter((a) => !a.tags?.length) + if (untagged.length > 0) { + grouped.unshift({ + tag: t('assistants.tags.untagged'), + assistants: untagged + }) + } + return grouped + }, [allTags, assistants, t]) + + return { + allTags, + getAssistantsByTag, + getGroupedAssistants, + addTag + } +} diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 8e42dda829..d1e774cfe7 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -102,6 +102,26 @@ "titlePlaceholder": "Enter title", "contentLabel": "Content", "contentPlaceholder": "Please enter phrase content, support using variables, and press Tab to quickly locate the variable to modify. For example: \nHelp me plan a route from ${from} to ${to}, and send it to ${email}." + }, + "list": { + "showByList": "List View", + "showByTags": "Tag View" + }, + "tags": { + "untagged": "Untagged", + "none": "No tags", + "manage": "Tag Management", + "modify": "Modify Tag", + "add": "Add Tag", + "delete": "Delete Tag", + "settings": { + "title": "Tag Settings", + "current": "Current Tags", + "searchTagsPlaceholder": "Enter tag name to filter tags", + "addTagsPlaceholder": "Enter tag name to add", + "tagsLsitTitle": "Tag List", + "tagsLsitTitleTips": "Click tag to toggle" + } } }, "auth": { diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 679849fbaa..81b16577d6 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -100,6 +100,26 @@ "settings.knowledge_base.recognition": "ナレッジベースの呼び出し", "settings.knowledge_base.recognition.off": "強制検索", "settings.knowledge_base.recognition.on": "意図認識", + "list": { + "showByList": "リスト表示", + "showByTags": "タグ表示" + }, + "tags": { + "untagged": "未分類タグ", + "none": "タグなし", + "manage": "タグ管理", + "add": "タグ追加", + "modify": "タグ修正", + "delete": "タグ削除", + "settings": { + "title": "タグ設定", + "current": "現在のタグ", + "searchTagsPlaceholder": "タグ名を入力してフィルタリング", + "addTagsPlaceholder": "追加するタグ名を入力", + "tagsLsitTitle": "タグ一覧", + "tagsLsitTitleTips": "タグをクリックで切り替え" + } + }, "settings.tool_use_mode": "工具調用方式", "settings.tool_use_mode.function": "関数", "settings.tool_use_mode.prompt": "提示詞" diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 3e1049f524..54194628b3 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -102,6 +102,27 @@ "titlePlaceholder": "Введите заголовок", "contentLabel": "Содержание", "contentPlaceholder": "Введите содержание фразы, поддерживает использование переменных, и нажмите Tab для быстрого перехода к переменной для изменения. Например: \nПомоги мне спланировать маршрут от ${from} до ${to} и отправить его на ${email}." + }, + "list": { + "showByList": "Список", + "showByTags": "По тегам" + }, + "tags": { + "untagged": "Несгруппированные метки", + "none": "Нет тегов", + "manage": "Управление тегами", + "add": "Добавить тег", + "modify": "Изменить тег", + "delete": "Удалить тег", + + "settings": { + "title": "Настройки тегов", + "current": "Текущие теги", + "searchTagsPlaceholder": "Введите название тега для фильтрации", + "addTagsPlaceholder": "Введите название тега для добавления", + "tagsLsitTitle": "Список тегов", + "tagsLsitTitleTips": "Нажмите на тег для переключения" + } } }, "auth": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 55af6f7fd7..6c22d830e6 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -102,6 +102,26 @@ "titlePlaceholder": "输入标题", "contentLabel": "内容", "contentPlaceholder": "请输入短语内容,支持使用变量,然后按Tab键可以快速定位到变量进行修改。比如:\n帮我规划从${from}到${to}的路线,然后发送到${email}" + }, + "list": { + "showByList": "列表展示", + "showByTags": "标签展示" + }, + "tags": { + "none": "暂无标签", + "manage": "标签管理", + "add": "添加标签", + "untagged": "未分组标签", + "modify": "修改标签", + "delete": "删除标签", + "settings": { + "title": "标签设置", + "current": "当前标签", + "searchTagsPlaceholder": "请输入标签名称以过滤标签", + "addTagsPlaceholder": "请输入标签名称以添加", + "tagsLsitTitle": "标签列表", + "tagsLsitTitleTips": "点击标签可切换" + } } }, "auth": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index ce4294ec01..cd234a2934 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -100,6 +100,26 @@ "settings.knowledge_base.recognition": "調用知識庫", "settings.knowledge_base.recognition.off": "強制檢索", "settings.knowledge_base.recognition.on": "意圖識別", + "list": { + "showByList": "列表展示", + "showByTags": "標籤展示" + }, + "tags": { + "untagged": "未分組標籤", + "none": "暫無標籤", + "manage": "標籤管理", + "add": "添加標籤", + "modify": "修改標籤", + "delete": "刪除標籤", + "settings": { + "title": "標籤設定", + "current": "當前標籤", + "searchTagsPlaceholder": "請輸入標籤名稱以過濾標籤", + "addTagsPlaceholder": "請輸入標籤名稱以新增", + "tagsLsitTitle": "標籤列表", + "tagsLsitTitleTips": "點擊標籤可切換" + } + }, "settings.tool_use_mode": "工具調用方式", "settings.tool_use_mode.function": "函數", "settings.tool_use_mode.prompt": "提示詞" diff --git a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx index 537c7583f8..a9df36c352 100644 --- a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx @@ -3,21 +3,28 @@ import DragableList from '@renderer/components/DragableList' import Scrollbar from '@renderer/components/Scrollbar' import { useAgents } from '@renderer/hooks/useAgents' import { useAssistants } from '@renderer/hooks/useAssistant' +import { useTags } from '@renderer/hooks/useTags' import { Assistant } from '@renderer/types' +import { Divider, Tooltip } from 'antd' import { FC, useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' import AssistantItem from './components/AssistantItem' +type SortType = '' | 'tags' | 'list' + interface AssistantsTabProps { + sortBy: SortType + setSortBy: (assistant: SortType) => void activeAssistant: Assistant setActiveAssistant: (assistant: Assistant) => void onCreateAssistant: () => void onCreateDefaultAssistant: () => void } - const Assistants: FC = ({ + sortBy, + setSortBy, activeAssistant, setActiveAssistant, onCreateAssistant, @@ -27,6 +34,7 @@ const Assistants: FC = ({ const [dragging, setDragging] = useState(false) const { addAgent } = useAgents() const { t } = useTranslation() + const { getGroupedAssistants } = useTags() const containerRef = useRef(null) const onDelete = useCallback( @@ -41,27 +49,65 @@ const Assistants: FC = ({ [activeAssistant, assistants, removeAssistant, setActiveAssistant, onCreateDefaultAssistant] ) + const handleSortByChange = useCallback( + (sortType: SortType) => { + setSortBy(sortType) + }, + [setSortBy] + ) return ( - setDragging(true)} - onDragEnd={() => setDragging(false)}> - {(assistant) => ( - - )} - + {sortBy === 'tags' && ( +
+ {getGroupedAssistants.map((group) => ( + + + + {group.tag} + + + + {group.assistants.map((assistant) => ( + + ))} + + ))} +
+ )} + {sortBy === 'list' && ( + setDragging(true)} + onDragEnd={() => setDragging(false)}> + {(assistant) => ( + + )} + + )} {!dragging && ( @@ -82,6 +128,13 @@ const Container = styled(Scrollbar)` padding: 10px; ` +const TagsContainer = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: px; +` + const AssistantAddItem = styled.div` display: flex; flex-direction: row; @@ -103,6 +156,28 @@ const AssistantAddItem = styled.div` } ` +const GroupTitle = styled.div` + padding: 8px 0px; + position: relative; + color: var(--color-text-2); + font-size: 12px; + font-weight: 500; +` + +const GroupTitleName = styled.div` + max-width: 50%; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + background-color: var(--color-background); + box-sizing: border-box; + padding: 0 4px; + color: var(--color-text); + position: absolute; + transform: translateY(2px); + font-size: 13px; +` + const AssistantName = styled.div` color: var(--color-text); display: -webkit-box; diff --git a/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx b/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx index ac8e355d5f..94cfaac550 100644 --- a/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx +++ b/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx @@ -1,19 +1,23 @@ import { DeleteOutlined, EditOutlined, + MenuOutlined, MinusCircleOutlined, + PlusOutlined, SaveOutlined, SmileOutlined, SortAscendingOutlined, - SortDescendingOutlined + SortDescendingOutlined, + TagsOutlined } from '@ant-design/icons' import ModelAvatar from '@renderer/components/Avatar/ModelAvatar' import EmojiIcon from '@renderer/components/EmojiIcon' import CopyIcon from '@renderer/components/Icons/CopyIcon' -import { useAssistant } from '@renderer/hooks/useAssistant' -import { useAssistants } from '@renderer/hooks/useAssistant' +import TagsPopup from '@renderer/components/Popups/TagsPopup' +import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant' import { modelGenerating } from '@renderer/hooks/useRuntime' import { useSettings } from '@renderer/hooks/useSettings' +import { useTags } from '@renderer/hooks/useTags' import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings' import { getDefaultModel, getDefaultTopic } from '@renderer/services/AssistantService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' @@ -31,15 +35,28 @@ import * as tinyPinyin from 'tiny-pinyin' interface AssistantItemProps { assistant: Assistant isActive: boolean + sortBy: 'tags' | 'list' onSwitch: (assistant: Assistant) => void onDelete: (assistant: Assistant) => void onCreateDefaultAssistant: () => void addAgent: (agent: any) => void addAssistant: (assistant: Assistant) => void + onTagClick?: (tag: string) => void + handleSortByChange?: (sortType: '' | 'tags' | 'list') => void } -const AssistantItem: FC = ({ assistant, isActive, onSwitch, onDelete, addAgent, addAssistant }) => { +const AssistantItem: FC = ({ + assistant, + isActive, + sortBy, + onSwitch, + onDelete, + addAgent, + addAssistant, + handleSortByChange +}) => { const { t } = useTranslation() + const { allTags } = useTags() const { removeAllTopics } = useAssistant(assistant.id) // 使用当前助手的ID const { clickAssistantToShowTopic, topicPosition, assistantIconType, setAssistantIconType } = useSettings() const defaultModel = getDefaultModel() @@ -144,6 +161,65 @@ const AssistantItem: FC = ({ assistant, isActive, onSwitch, ] }, { type: 'divider' }, + { + label: t('assistants.tags.manage'), + key: 'all-tags', + icon: , + children: [ + ...allTags.map((tag) => ({ + label: tag, + icon: assistant.tags?.includes(tag) ? : , + danger: assistant.tags?.includes(tag) ? true : false, + key: `all-tag-${tag}`, + onClick: () => { + if (assistant.tags?.includes(tag)) { + // 如果已有该标签,则移除 + updateAssistants(assistants.map((a) => (a.id === assistant.id ? { ...a, tags: [] } : a))) + } else { + // 如果没有该标签,则切换到该标签分类 + updateAssistants(assistants.map((a) => (a.id === assistant.id ? { ...a, tags: [tag] } : a))) + } + } + })), + allTags.length > 0 ? { type: 'divider' } : null, + { + label: t('assistants.tags.add'), + key: 'new-tag', + onClick: () => { + TagsPopup.show( + assistant, + (updated) => { + updateAssistants(assistants.map((a) => (a.id === assistant.id ? updated : a))) + }, + 'add' + ) + } + }, + allTags.length > 0 + ? { + label: t('assistants.tags.manage'), + key: 'manage-tags', + onClick: () => { + TagsPopup.show( + assistant, + (updated) => { + updateAssistants(assistants.map((a) => (a.id === assistant.id ? updated : a))) + }, + 'manage' + ) + } + } + : null + ] + }, + { + label: sortBy === 'list' ? t('assistants.list.showByTags') : t('assistants.list.showByList'), + key: 'switch-view', + icon: sortBy === 'list' ? : , + onClick: () => { + sortBy === 'list' ? handleSortByChange?.('tags') : handleSortByChange?.('list') + } + }, { label: t('common.sort.pinyin.asc'), key: 'sort-asc', @@ -176,13 +252,18 @@ const AssistantItem: FC = ({ assistant, isActive, onSwitch, [ addAgent, addAssistant, + allTags, + assistants, + handleSortByChange, onDelete, onSwitch, removeAllTopics, setAssistantIconType, + sortBy, sortByPinyinAsc, sortByPinyinDesc, - t + t, + updateAssistants ] ) diff --git a/src/renderer/src/pages/home/Tabs/index.tsx b/src/renderer/src/pages/home/Tabs/index.tsx index b4a0fc2c61..51567c80e7 100644 --- a/src/renderer/src/pages/home/Tabs/index.tsx +++ b/src/renderer/src/pages/home/Tabs/index.tsx @@ -25,6 +25,7 @@ interface Props { } type Tab = 'assistants' | 'topic' | 'settings' +type SortType = '' | 'tags' | 'list' let _tab: any = '' @@ -38,6 +39,7 @@ const HomeTabs: FC = ({ style }) => { const { addAssistant } = useAssistants() + const [sortBy, setSortBy] = useState('list') const [tab, setTab] = useState(position === 'left' ? _tab || 'assistants' : 'topic') const { topicPosition } = useSettings() const { defaultAssistant } = useDefaultAssistant() @@ -129,6 +131,8 @@ const HomeTabs: FC = ({ {tab === 'assistants' && ( void + mode?: 'add' | 'manage' +} + +const AssistantTagsSettings: React.FC = ({ assistant, updateAssistant, mode = 'manage' }) => { + const { t } = useTranslation() + const { allTags } = useTags() + const [showMode, setShowMode] = useState<'add' | 'manage'>(mode) + const [filteredTags, setFilteredTags] = useState(allTags) + const [currentTags, setCurrentTags] = useState([...(assistant.tags || [])]) + const [tempTag, setTempTag] = useState(mode === 'add' ? '' : assistant.tags?.[0]) + const [inputTag, setInputTag] = useState('') + const { assistants, updateAssistants } = useAssistants() + + useEffect(() => { + setFilteredTags(allTags) + }, [allTags]) + + const inputRef = useRef(null) + + const { getAssistantsByTag } = useTags() + + const handleClose = (removedTag: string) => { + // 更新所有关联该tag的助手 + const relatedAssistants = getAssistantsByTag(removedTag) + setCurrentTags(currentTags.filter((tag) => tag !== removedTag)) // 点击下面移除是不需要缓存的 + updateAssistants( + assistants.map((assistant) => { + const findedAssitant = relatedAssistants.find((_assistant) => _assistant.id === assistant.id) + if (findedAssitant) { + return { ...findedAssitant, tags: [] } + } + return assistant + }) + ) + } + + return ( + + {showMode === 'manage' && ( + <> + + {t('assistants.tags.settings.title')} + + + {t('assistants.tags.settings.current')}: + {tempTag && ( + } + style={{ + cursor: 'pointer' + }} + onClose={() => { + setTempTag('') + // 防止删除以后,没有tag的助手被删除,写个暂存的。方便恢复回去 + setCurrentTags([...new Set([...currentTags, tempTag])]) + updateAssistant({ ...assistant, tags: [] }) + }}> + {tempTag} + + )} + {!tempTag && {t('assistants.tags.none')}} + + + + )} + + + {showMode === 'add' && ( + <> + + {t('assistants.tags.settings.addTagsPlaceholder')} + + setInputTag(e.target.value)} + suffix={ + <> + {+inputTag?.length > 0 && ( + { + if (inputTag) { + setInputTag('') + setTempTag(inputTag) + updateAssistant({ ...assistant, tags: [inputTag] }) + setShowMode('manage') + } + }} + style={{ color: 'var(--color-primary)', cursor: 'pointer' }} + /> + )} + + } + /> + + )} + {showMode === 'manage' && ( +
+ + {t('assistants.tags.settings.searchTagsPlaceholder')} + + { + const searchValue = e.target.value.toLowerCase() + setFilteredTags(allTags?.filter((tag) => tag.toLowerCase().includes(searchValue))) + }} + /> + + {t('assistants.tags.settings.tagsLsitTitle')} + ({t('assistants.tags.settings.tagsLsitTitleTips')}) + +
+ {[...new Set([...filteredTags, ...currentTags])] + .filter((_) => _ !== tempTag) + ?.map((tag) => ( + } + style={{ + cursor: 'pointer', + background: 'var(--color-background-mute)', + color: 'var(--color-text)' + }} + onClose={() => handleClose(tag)} + onClick={() => { + setTempTag(tag) + updateAssistant({ ...assistant, tags: [tag] }) + }}> + {tag} + + ))} +
+
+ )} + {/* */} +
+
+ ) +} + +const Container = styled.div` + padding: 10px; +` + +const TagsContainer = styled.div` + margin: 10px 0; + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; +` + +export default AssistantTagsSettings diff --git a/src/renderer/src/pages/settings/AssistantSettings/index.tsx b/src/renderer/src/pages/settings/AssistantSettings/index.tsx index bc74b25006..a2794e7c36 100644 --- a/src/renderer/src/pages/settings/AssistantSettings/index.tsx +++ b/src/renderer/src/pages/settings/AssistantSettings/index.tsx @@ -15,13 +15,14 @@ import AssistantMessagesSettings from './AssistantMessagesSettings' import AssistantModelSettings from './AssistantModelSettings' import AssistantPromptSettings from './AssistantPromptSettings' import AssistantRegularPromptsSettings from './AssistantRegularPromptsSettings' +import AssistantTagsSettings from './AssistantTagsSettings' interface AssistantSettingPopupShowParams { assistant: Assistant tab?: AssistantSettingPopupTab } -type AssistantSettingPopupTab = 'prompt' | 'model' | 'messages' | 'knowledge_base' | 'mcp' | 'regular_phrases' +type AssistantSettingPopupTab = 'prompt' | 'model' | 'messages' | 'knowledge_base' | 'mcp' | 'regular_phrases' | 'tags' interface Props extends AssistantSettingPopupShowParams { resolve: (assistant: Assistant) => void @@ -78,6 +79,10 @@ const AssistantSettingPopupContainer: React.FC = ({ resolve, tab, ...prop { key: 'regular_phrases', label: t('assistants.settings.regular_phrases.title', 'Regular Prompts') + }, + { + key: 'tags', + label: t('assistants.tags.settings.title') } ].filter(Boolean) as { key: string; label: string }[] @@ -150,6 +155,7 @@ const AssistantSettingPopupContainer: React.FC = ({ resolve, tab, ...prop {menu === 'regular_phrases' && ( )} + {menu === 'tags' && } diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 1e463e67c5..9544375f80 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -25,6 +25,7 @@ export type Assistant = { mcpServers?: MCPServer[] knowledgeRecognition?: 'off' | 'on' regularPhrases?: QuickPhrase[] // Added for regular phrase + tags?: string[] // 助手标签 } export type AssistantMessage = {