mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 22:10:21 +08:00
feat: Assistant add tag (#6065)
* feat: 添加助手标签显示逻辑 -增加助手的标签属性 -能够删除,修改,调整助手的标签 Signed-off-by: LeeSH <shuhao_lin@fzzixun.com> * fix: 修复不能输入新增标签的问题 * feat: 完善不同状态下,提示文本展示 * feat: 调整标签展示逻辑 1,左键调整列表页展示逻辑 2,新增标签改为使用+号提示 * feat: 移除搜索栏可以直接增加tag值的功能 Signed-off-by: LeeSH <shuhao_lin@fzzixun.com> * fix: 修复点击不能切换话题的bug * feat: 调整了标签修改的交互 1,添加和管理分开处理 2,可以点击标签之间切换 3,点击删除可以之间移除所有关联助手的标签 tips:为了简单实现,标签本身不具有具体类,都是助手的子属性。所以如果关联的所有助手都没了该属性,标签会直接消失,而且标签目前无法排序 Signed-off-by: LeeSH <shuhao_lin@fzzixun.com> * feat:优化标签管理 1,列表状态管理向上提,切换左侧列表不会影响原来的列表状态 2,标签名称增加最大宽度 3,标签内的助手顺序,参照原顺序排列 4,增加标签ui,提示语调整 5,标签管理ui,提示语调整 6,标签管理增加标签暂时态,防止误删没有其他助手的标签项的时候,标签在弹窗内整个消失(如果关闭弹窗那标签就无法找回) 7,如果没有标签的时候,右键仅展示添加标签 Signed-off-by: LeeSH <shuhao_lin@fzzixun.com> --------- Signed-off-by: LeeSH <shuhao_lin@fzzixun.com> Co-authored-by: linshuhao <nmnm1996> Co-authored-by: Lee SH <shuhao_lin@fzzixun.com>
This commit is contained in:
parent
65273b055c
commit
cf1d5c098f
70
src/renderer/src/components/Popups/TagsPopup.tsx
Normal file
70
src/renderer/src/components/Popups/TagsPopup.tsx
Normal file
@ -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<Props> = ({ assistant, updateAssistant, resolve, mode }) => {
|
||||||
|
const [open, setOpen] = useState(true)
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
resolve({})
|
||||||
|
}
|
||||||
|
|
||||||
|
TagsPopup.hide = onCancel
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onCancel={onCancel}
|
||||||
|
afterClose={onClose}
|
||||||
|
title="标签管理"
|
||||||
|
width="600px"
|
||||||
|
transitionName="animation-move-down"
|
||||||
|
styles={{
|
||||||
|
content: {
|
||||||
|
padding: '16px',
|
||||||
|
border: `1px solid var(--color-frame-border)`
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
centered
|
||||||
|
footer={null}>
|
||||||
|
<AssistantTagsSettings assistant={assistant} updateAssistant={updateAssistant} mode={mode} />
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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<any>((resolve) => {
|
||||||
|
TopView.show(
|
||||||
|
<PopupContainer
|
||||||
|
assistant={assistant}
|
||||||
|
updateAssistant={updateAssistant}
|
||||||
|
resolve={(v) => {
|
||||||
|
resolve(v)
|
||||||
|
TopView.hide('TagsPopup')
|
||||||
|
}}
|
||||||
|
mode={mode}
|
||||||
|
/>,
|
||||||
|
'TagsPopup'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
72
src/renderer/src/hooks/useTags.ts
Normal file
72
src/renderer/src/hooks/useTags.ts
Normal file
@ -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<string[]>([])
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
// 计算所有标签
|
||||||
|
const calculateTags = useCallback(() => {
|
||||||
|
const tags = new Set<string>()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -102,6 +102,26 @@
|
|||||||
"titlePlaceholder": "Enter title",
|
"titlePlaceholder": "Enter title",
|
||||||
"contentLabel": "Content",
|
"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}."
|
"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": {
|
"auth": {
|
||||||
|
|||||||
@ -100,6 +100,26 @@
|
|||||||
"settings.knowledge_base.recognition": "ナレッジベースの呼び出し",
|
"settings.knowledge_base.recognition": "ナレッジベースの呼び出し",
|
||||||
"settings.knowledge_base.recognition.off": "強制検索",
|
"settings.knowledge_base.recognition.off": "強制検索",
|
||||||
"settings.knowledge_base.recognition.on": "意図認識",
|
"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": "工具調用方式",
|
||||||
"settings.tool_use_mode.function": "関数",
|
"settings.tool_use_mode.function": "関数",
|
||||||
"settings.tool_use_mode.prompt": "提示詞"
|
"settings.tool_use_mode.prompt": "提示詞"
|
||||||
|
|||||||
@ -102,6 +102,27 @@
|
|||||||
"titlePlaceholder": "Введите заголовок",
|
"titlePlaceholder": "Введите заголовок",
|
||||||
"contentLabel": "Содержание",
|
"contentLabel": "Содержание",
|
||||||
"contentPlaceholder": "Введите содержание фразы, поддерживает использование переменных, и нажмите Tab для быстрого перехода к переменной для изменения. Например: \nПомоги мне спланировать маршрут от ${from} до ${to} и отправить его на ${email}."
|
"contentPlaceholder": "Введите содержание фразы, поддерживает использование переменных, и нажмите Tab для быстрого перехода к переменной для изменения. Например: \nПомоги мне спланировать маршрут от ${from} до ${to} и отправить его на ${email}."
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"showByList": "Список",
|
||||||
|
"showByTags": "По тегам"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"untagged": "Несгруппированные метки",
|
||||||
|
"none": "Нет тегов",
|
||||||
|
"manage": "Управление тегами",
|
||||||
|
"add": "Добавить тег",
|
||||||
|
"modify": "Изменить тег",
|
||||||
|
"delete": "Удалить тег",
|
||||||
|
|
||||||
|
"settings": {
|
||||||
|
"title": "Настройки тегов",
|
||||||
|
"current": "Текущие теги",
|
||||||
|
"searchTagsPlaceholder": "Введите название тега для фильтрации",
|
||||||
|
"addTagsPlaceholder": "Введите название тега для добавления",
|
||||||
|
"tagsLsitTitle": "Список тегов",
|
||||||
|
"tagsLsitTitleTips": "Нажмите на тег для переключения"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
|
|||||||
@ -102,6 +102,26 @@
|
|||||||
"titlePlaceholder": "输入标题",
|
"titlePlaceholder": "输入标题",
|
||||||
"contentLabel": "内容",
|
"contentLabel": "内容",
|
||||||
"contentPlaceholder": "请输入短语内容,支持使用变量,然后按Tab键可以快速定位到变量进行修改。比如:\n帮我规划从${from}到${to}的路线,然后发送到${email}"
|
"contentPlaceholder": "请输入短语内容,支持使用变量,然后按Tab键可以快速定位到变量进行修改。比如:\n帮我规划从${from}到${to}的路线,然后发送到${email}"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"showByList": "列表展示",
|
||||||
|
"showByTags": "标签展示"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"none": "暂无标签",
|
||||||
|
"manage": "标签管理",
|
||||||
|
"add": "添加标签",
|
||||||
|
"untagged": "未分组标签",
|
||||||
|
"modify": "修改标签",
|
||||||
|
"delete": "删除标签",
|
||||||
|
"settings": {
|
||||||
|
"title": "标签设置",
|
||||||
|
"current": "当前标签",
|
||||||
|
"searchTagsPlaceholder": "请输入标签名称以过滤标签",
|
||||||
|
"addTagsPlaceholder": "请输入标签名称以添加",
|
||||||
|
"tagsLsitTitle": "标签列表",
|
||||||
|
"tagsLsitTitleTips": "点击标签可切换"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
|
|||||||
@ -100,6 +100,26 @@
|
|||||||
"settings.knowledge_base.recognition": "調用知識庫",
|
"settings.knowledge_base.recognition": "調用知識庫",
|
||||||
"settings.knowledge_base.recognition.off": "強制檢索",
|
"settings.knowledge_base.recognition.off": "強制檢索",
|
||||||
"settings.knowledge_base.recognition.on": "意圖識別",
|
"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": "工具調用方式",
|
||||||
"settings.tool_use_mode.function": "函數",
|
"settings.tool_use_mode.function": "函數",
|
||||||
"settings.tool_use_mode.prompt": "提示詞"
|
"settings.tool_use_mode.prompt": "提示詞"
|
||||||
|
|||||||
@ -3,21 +3,28 @@ import DragableList from '@renderer/components/DragableList'
|
|||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import { useAgents } from '@renderer/hooks/useAgents'
|
import { useAgents } from '@renderer/hooks/useAgents'
|
||||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||||
|
import { useTags } from '@renderer/hooks/useTags'
|
||||||
import { Assistant } from '@renderer/types'
|
import { Assistant } from '@renderer/types'
|
||||||
|
import { Divider, Tooltip } from 'antd'
|
||||||
import { FC, useCallback, useRef, useState } from 'react'
|
import { FC, useCallback, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import AssistantItem from './components/AssistantItem'
|
import AssistantItem from './components/AssistantItem'
|
||||||
|
|
||||||
|
type SortType = '' | 'tags' | 'list'
|
||||||
|
|
||||||
interface AssistantsTabProps {
|
interface AssistantsTabProps {
|
||||||
|
sortBy: SortType
|
||||||
|
setSortBy: (assistant: SortType) => void
|
||||||
activeAssistant: Assistant
|
activeAssistant: Assistant
|
||||||
setActiveAssistant: (assistant: Assistant) => void
|
setActiveAssistant: (assistant: Assistant) => void
|
||||||
onCreateAssistant: () => void
|
onCreateAssistant: () => void
|
||||||
onCreateDefaultAssistant: () => void
|
onCreateDefaultAssistant: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Assistants: FC<AssistantsTabProps> = ({
|
const Assistants: FC<AssistantsTabProps> = ({
|
||||||
|
sortBy,
|
||||||
|
setSortBy,
|
||||||
activeAssistant,
|
activeAssistant,
|
||||||
setActiveAssistant,
|
setActiveAssistant,
|
||||||
onCreateAssistant,
|
onCreateAssistant,
|
||||||
@ -27,6 +34,7 @@ const Assistants: FC<AssistantsTabProps> = ({
|
|||||||
const [dragging, setDragging] = useState(false)
|
const [dragging, setDragging] = useState(false)
|
||||||
const { addAgent } = useAgents()
|
const { addAgent } = useAgents()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { getGroupedAssistants } = useTags()
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const onDelete = useCallback(
|
const onDelete = useCallback(
|
||||||
@ -41,27 +49,65 @@ const Assistants: FC<AssistantsTabProps> = ({
|
|||||||
[activeAssistant, assistants, removeAssistant, setActiveAssistant, onCreateDefaultAssistant]
|
[activeAssistant, assistants, removeAssistant, setActiveAssistant, onCreateDefaultAssistant]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleSortByChange = useCallback(
|
||||||
|
(sortType: SortType) => {
|
||||||
|
setSortBy(sortType)
|
||||||
|
},
|
||||||
|
[setSortBy]
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<Container className="assistants-tab" ref={containerRef}>
|
<Container className="assistants-tab" ref={containerRef}>
|
||||||
<DragableList
|
{sortBy === 'tags' && (
|
||||||
list={assistants}
|
<div style={{ marginBottom: '8px' }}>
|
||||||
onUpdate={updateAssistants}
|
{getGroupedAssistants.map((group) => (
|
||||||
style={{ paddingBottom: dragging ? '34px' : 0 }}
|
<TagsContainer key={group.tag}>
|
||||||
onDragStart={() => setDragging(true)}
|
<GroupTitle>
|
||||||
onDragEnd={() => setDragging(false)}>
|
<Tooltip title={group.tag}>
|
||||||
{(assistant) => (
|
<GroupTitleName>{group.tag}</GroupTitleName>
|
||||||
<AssistantItem
|
</Tooltip>
|
||||||
key={assistant.id}
|
<Divider style={{ margin: '12px 0' }}></Divider>
|
||||||
assistant={assistant}
|
</GroupTitle>
|
||||||
isActive={assistant.id === activeAssistant.id}
|
{group.assistants.map((assistant) => (
|
||||||
onSwitch={setActiveAssistant}
|
<AssistantItem
|
||||||
onDelete={onDelete}
|
key={assistant.id}
|
||||||
addAgent={addAgent}
|
assistant={assistant}
|
||||||
addAssistant={addAssistant}
|
isActive={assistant.id === activeAssistant.id}
|
||||||
onCreateDefaultAssistant={onCreateDefaultAssistant}
|
sortBy={sortBy}
|
||||||
/>
|
onSwitch={setActiveAssistant}
|
||||||
)}
|
onDelete={onDelete}
|
||||||
</DragableList>
|
addAgent={addAgent}
|
||||||
|
addAssistant={addAssistant}
|
||||||
|
onCreateDefaultAssistant={onCreateDefaultAssistant}
|
||||||
|
handleSortByChange={handleSortByChange}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</TagsContainer>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{sortBy === 'list' && (
|
||||||
|
<DragableList
|
||||||
|
list={assistants}
|
||||||
|
onUpdate={updateAssistants}
|
||||||
|
style={{ paddingBottom: dragging ? '34px' : 0 }}
|
||||||
|
onDragStart={() => setDragging(true)}
|
||||||
|
onDragEnd={() => setDragging(false)}>
|
||||||
|
{(assistant) => (
|
||||||
|
<AssistantItem
|
||||||
|
key={assistant.id}
|
||||||
|
assistant={assistant}
|
||||||
|
isActive={assistant.id === activeAssistant.id}
|
||||||
|
sortBy={sortBy}
|
||||||
|
onSwitch={setActiveAssistant}
|
||||||
|
onDelete={onDelete}
|
||||||
|
addAgent={addAgent}
|
||||||
|
addAssistant={addAssistant}
|
||||||
|
onCreateDefaultAssistant={onCreateDefaultAssistant}
|
||||||
|
handleSortByChange={handleSortByChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DragableList>
|
||||||
|
)}
|
||||||
{!dragging && (
|
{!dragging && (
|
||||||
<AssistantAddItem onClick={onCreateAssistant}>
|
<AssistantAddItem onClick={onCreateAssistant}>
|
||||||
<AssistantName>
|
<AssistantName>
|
||||||
@ -82,6 +128,13 @@ const Container = styled(Scrollbar)`
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const TagsContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: px;
|
||||||
|
`
|
||||||
|
|
||||||
const AssistantAddItem = styled.div`
|
const AssistantAddItem = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
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`
|
const AssistantName = styled.div`
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
|
|||||||
@ -1,19 +1,23 @@
|
|||||||
import {
|
import {
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
|
MenuOutlined,
|
||||||
MinusCircleOutlined,
|
MinusCircleOutlined,
|
||||||
|
PlusOutlined,
|
||||||
SaveOutlined,
|
SaveOutlined,
|
||||||
SmileOutlined,
|
SmileOutlined,
|
||||||
SortAscendingOutlined,
|
SortAscendingOutlined,
|
||||||
SortDescendingOutlined
|
SortDescendingOutlined,
|
||||||
|
TagsOutlined
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
||||||
import EmojiIcon from '@renderer/components/EmojiIcon'
|
import EmojiIcon from '@renderer/components/EmojiIcon'
|
||||||
import CopyIcon from '@renderer/components/Icons/CopyIcon'
|
import CopyIcon from '@renderer/components/Icons/CopyIcon'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import TagsPopup from '@renderer/components/Popups/TagsPopup'
|
||||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
|
||||||
import { modelGenerating } from '@renderer/hooks/useRuntime'
|
import { modelGenerating } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
|
import { useTags } from '@renderer/hooks/useTags'
|
||||||
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
|
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
|
||||||
import { getDefaultModel, getDefaultTopic } from '@renderer/services/AssistantService'
|
import { getDefaultModel, getDefaultTopic } from '@renderer/services/AssistantService'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
@ -31,15 +35,28 @@ import * as tinyPinyin from 'tiny-pinyin'
|
|||||||
interface AssistantItemProps {
|
interface AssistantItemProps {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
|
sortBy: 'tags' | 'list'
|
||||||
onSwitch: (assistant: Assistant) => void
|
onSwitch: (assistant: Assistant) => void
|
||||||
onDelete: (assistant: Assistant) => void
|
onDelete: (assistant: Assistant) => void
|
||||||
onCreateDefaultAssistant: () => void
|
onCreateDefaultAssistant: () => void
|
||||||
addAgent: (agent: any) => void
|
addAgent: (agent: any) => void
|
||||||
addAssistant: (assistant: Assistant) => void
|
addAssistant: (assistant: Assistant) => void
|
||||||
|
onTagClick?: (tag: string) => void
|
||||||
|
handleSortByChange?: (sortType: '' | 'tags' | 'list') => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const AssistantItem: FC<AssistantItemProps> = ({ assistant, isActive, onSwitch, onDelete, addAgent, addAssistant }) => {
|
const AssistantItem: FC<AssistantItemProps> = ({
|
||||||
|
assistant,
|
||||||
|
isActive,
|
||||||
|
sortBy,
|
||||||
|
onSwitch,
|
||||||
|
onDelete,
|
||||||
|
addAgent,
|
||||||
|
addAssistant,
|
||||||
|
handleSortByChange
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { allTags } = useTags()
|
||||||
const { removeAllTopics } = useAssistant(assistant.id) // 使用当前助手的ID
|
const { removeAllTopics } = useAssistant(assistant.id) // 使用当前助手的ID
|
||||||
const { clickAssistantToShowTopic, topicPosition, assistantIconType, setAssistantIconType } = useSettings()
|
const { clickAssistantToShowTopic, topicPosition, assistantIconType, setAssistantIconType } = useSettings()
|
||||||
const defaultModel = getDefaultModel()
|
const defaultModel = getDefaultModel()
|
||||||
@ -144,6 +161,65 @@ const AssistantItem: FC<AssistantItemProps> = ({ assistant, isActive, onSwitch,
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
label: t('assistants.tags.manage'),
|
||||||
|
key: 'all-tags',
|
||||||
|
icon: <PlusOutlined />,
|
||||||
|
children: [
|
||||||
|
...allTags.map((tag) => ({
|
||||||
|
label: tag,
|
||||||
|
icon: assistant.tags?.includes(tag) ? <DeleteOutlined /> : <TagsOutlined />,
|
||||||
|
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' ? <TagsOutlined /> : <MenuOutlined />,
|
||||||
|
onClick: () => {
|
||||||
|
sortBy === 'list' ? handleSortByChange?.('tags') : handleSortByChange?.('list')
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t('common.sort.pinyin.asc'),
|
label: t('common.sort.pinyin.asc'),
|
||||||
key: 'sort-asc',
|
key: 'sort-asc',
|
||||||
@ -176,13 +252,18 @@ const AssistantItem: FC<AssistantItemProps> = ({ assistant, isActive, onSwitch,
|
|||||||
[
|
[
|
||||||
addAgent,
|
addAgent,
|
||||||
addAssistant,
|
addAssistant,
|
||||||
|
allTags,
|
||||||
|
assistants,
|
||||||
|
handleSortByChange,
|
||||||
onDelete,
|
onDelete,
|
||||||
onSwitch,
|
onSwitch,
|
||||||
removeAllTopics,
|
removeAllTopics,
|
||||||
setAssistantIconType,
|
setAssistantIconType,
|
||||||
|
sortBy,
|
||||||
sortByPinyinAsc,
|
sortByPinyinAsc,
|
||||||
sortByPinyinDesc,
|
sortByPinyinDesc,
|
||||||
t
|
t,
|
||||||
|
updateAssistants
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Tab = 'assistants' | 'topic' | 'settings'
|
type Tab = 'assistants' | 'topic' | 'settings'
|
||||||
|
type SortType = '' | 'tags' | 'list'
|
||||||
|
|
||||||
let _tab: any = ''
|
let _tab: any = ''
|
||||||
|
|
||||||
@ -38,6 +39,7 @@ const HomeTabs: FC<Props> = ({
|
|||||||
style
|
style
|
||||||
}) => {
|
}) => {
|
||||||
const { addAssistant } = useAssistants()
|
const { addAssistant } = useAssistants()
|
||||||
|
const [sortBy, setSortBy] = useState<SortType>('list')
|
||||||
const [tab, setTab] = useState<Tab>(position === 'left' ? _tab || 'assistants' : 'topic')
|
const [tab, setTab] = useState<Tab>(position === 'left' ? _tab || 'assistants' : 'topic')
|
||||||
const { topicPosition } = useSettings()
|
const { topicPosition } = useSettings()
|
||||||
const { defaultAssistant } = useDefaultAssistant()
|
const { defaultAssistant } = useDefaultAssistant()
|
||||||
@ -129,6 +131,8 @@ const HomeTabs: FC<Props> = ({
|
|||||||
<TabContent className="home-tabs-content">
|
<TabContent className="home-tabs-content">
|
||||||
{tab === 'assistants' && (
|
{tab === 'assistants' && (
|
||||||
<Assistants
|
<Assistants
|
||||||
|
setSortBy={setSortBy}
|
||||||
|
sortBy={sortBy}
|
||||||
activeAssistant={activeAssistant}
|
activeAssistant={activeAssistant}
|
||||||
setActiveAssistant={setActiveAssistant}
|
setActiveAssistant={setActiveAssistant}
|
||||||
onCreateAssistant={onCreateAssistant}
|
onCreateAssistant={onCreateAssistant}
|
||||||
|
|||||||
@ -0,0 +1,171 @@
|
|||||||
|
import { CheckOutlined, CloseOutlined } from '@ant-design/icons'
|
||||||
|
import { Box } from '@renderer/components/Layout'
|
||||||
|
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||||
|
import { useTags } from '@renderer/hooks/useTags'
|
||||||
|
import { Assistant } from '@renderer/types'
|
||||||
|
import type { InputRef } from 'antd'
|
||||||
|
import { Divider, Input, Space, Tag } from 'antd'
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
assistant: Assistant
|
||||||
|
updateAssistant: (assistant: Assistant) => void
|
||||||
|
mode?: 'add' | 'manage'
|
||||||
|
}
|
||||||
|
|
||||||
|
const AssistantTagsSettings: React.FC<Props> = ({ assistant, updateAssistant, mode = 'manage' }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { allTags } = useTags()
|
||||||
|
const [showMode, setShowMode] = useState<'add' | 'manage'>(mode)
|
||||||
|
const [filteredTags, setFilteredTags] = useState<string[]>(allTags)
|
||||||
|
const [currentTags, setCurrentTags] = useState<string[]>([...(assistant.tags || [])])
|
||||||
|
const [tempTag, setTempTag] = useState<string | undefined>(mode === 'add' ? '' : assistant.tags?.[0])
|
||||||
|
const [inputTag, setInputTag] = useState<string>('')
|
||||||
|
const { assistants, updateAssistants } = useAssistants()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFilteredTags(allTags)
|
||||||
|
}, [allTags])
|
||||||
|
|
||||||
|
const inputRef = useRef<InputRef>(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 (
|
||||||
|
<Container>
|
||||||
|
{showMode === 'manage' && (
|
||||||
|
<>
|
||||||
|
<Box mb={8} style={{ fontWeight: 'bold' }}>
|
||||||
|
{t('assistants.tags.settings.title')}
|
||||||
|
</Box>
|
||||||
|
<TagsContainer>
|
||||||
|
<span>{t('assistants.tags.settings.current')}:</span>
|
||||||
|
{tempTag && (
|
||||||
|
<Tag
|
||||||
|
key={tempTag}
|
||||||
|
closeIcon={<CloseOutlined />}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
onClose={() => {
|
||||||
|
setTempTag('')
|
||||||
|
// 防止删除以后,没有tag的助手被删除,写个暂存的。方便恢复回去
|
||||||
|
setCurrentTags([...new Set([...currentTags, tempTag])])
|
||||||
|
updateAssistant({ ...assistant, tags: [] })
|
||||||
|
}}>
|
||||||
|
{tempTag}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
{!tempTag && <span style={{ color: 'var(--color-text-3)' }}>{t('assistants.tags.none')}</span>}
|
||||||
|
</TagsContainer>
|
||||||
|
<Divider style={{ margin: 8 }}></Divider>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Space.Compact direction="vertical" style={{ width: '100%', gap: 8 }}>
|
||||||
|
{showMode === 'add' && (
|
||||||
|
<>
|
||||||
|
<Box mb={8} style={{ fontWeight: 'bold' }}>
|
||||||
|
{t('assistants.tags.settings.addTagsPlaceholder')}
|
||||||
|
</Box>
|
||||||
|
<Input
|
||||||
|
ref={inputRef}
|
||||||
|
value={inputTag}
|
||||||
|
autoFocus
|
||||||
|
onChange={(e) => setInputTag(e.target.value)}
|
||||||
|
suffix={
|
||||||
|
<>
|
||||||
|
{+inputTag?.length > 0 && (
|
||||||
|
<CheckOutlined
|
||||||
|
onClick={() => {
|
||||||
|
if (inputTag) {
|
||||||
|
setInputTag('')
|
||||||
|
setTempTag(inputTag)
|
||||||
|
updateAssistant({ ...assistant, tags: [inputTag] })
|
||||||
|
setShowMode('manage')
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ color: 'var(--color-primary)', cursor: 'pointer' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{showMode === 'manage' && (
|
||||||
|
<div>
|
||||||
|
<Box mb={8} style={{ fontWeight: 'bold' }}>
|
||||||
|
{t('assistants.tags.settings.searchTagsPlaceholder')}
|
||||||
|
</Box>
|
||||||
|
<Input
|
||||||
|
placeholder={t('assistants.tags.settings.searchTagsPlaceholder')}
|
||||||
|
style={{ marginBottom: 8 }}
|
||||||
|
onChange={(e) => {
|
||||||
|
const searchValue = e.target.value.toLowerCase()
|
||||||
|
setFilteredTags(allTags?.filter((tag) => tag.toLowerCase().includes(searchValue)))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box mb={8} style={{ fontWeight: 'bold' }}>
|
||||||
|
{t('assistants.tags.settings.tagsLsitTitle')}
|
||||||
|
<em style={{ fontSize: '12px' }}>({t('assistants.tags.settings.tagsLsitTitleTips')})</em>
|
||||||
|
</Box>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
|
||||||
|
{[...new Set([...filteredTags, ...currentTags])]
|
||||||
|
.filter((_) => _ !== tempTag)
|
||||||
|
?.map((tag) => (
|
||||||
|
<Tag
|
||||||
|
key={tag}
|
||||||
|
closeIcon={<CloseOutlined style={{ color: 'var(--color-text)' }} />}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
background: 'var(--color-background-mute)',
|
||||||
|
color: 'var(--color-text)'
|
||||||
|
}}
|
||||||
|
onClose={() => handleClose(tag)}
|
||||||
|
onClick={() => {
|
||||||
|
setTempTag(tag)
|
||||||
|
updateAssistant({ ...assistant, tags: [tag] })
|
||||||
|
}}>
|
||||||
|
{tag}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* <PlusCircleOutlined onClick={handleAddClick} style={{ color: 'var(--color-primary)', cursor: 'pointer' }} /> */}
|
||||||
|
</Space.Compact>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@ -15,13 +15,14 @@ import AssistantMessagesSettings from './AssistantMessagesSettings'
|
|||||||
import AssistantModelSettings from './AssistantModelSettings'
|
import AssistantModelSettings from './AssistantModelSettings'
|
||||||
import AssistantPromptSettings from './AssistantPromptSettings'
|
import AssistantPromptSettings from './AssistantPromptSettings'
|
||||||
import AssistantRegularPromptsSettings from './AssistantRegularPromptsSettings'
|
import AssistantRegularPromptsSettings from './AssistantRegularPromptsSettings'
|
||||||
|
import AssistantTagsSettings from './AssistantTagsSettings'
|
||||||
|
|
||||||
interface AssistantSettingPopupShowParams {
|
interface AssistantSettingPopupShowParams {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
tab?: AssistantSettingPopupTab
|
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 {
|
interface Props extends AssistantSettingPopupShowParams {
|
||||||
resolve: (assistant: Assistant) => void
|
resolve: (assistant: Assistant) => void
|
||||||
@ -78,6 +79,10 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ resolve, tab, ...prop
|
|||||||
{
|
{
|
||||||
key: 'regular_phrases',
|
key: 'regular_phrases',
|
||||||
label: t('assistants.settings.regular_phrases.title', 'Regular Prompts')
|
label: t('assistants.settings.regular_phrases.title', 'Regular Prompts')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tags',
|
||||||
|
label: t('assistants.tags.settings.title')
|
||||||
}
|
}
|
||||||
].filter(Boolean) as { key: string; label: string }[]
|
].filter(Boolean) as { key: string; label: string }[]
|
||||||
|
|
||||||
@ -150,6 +155,7 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ resolve, tab, ...prop
|
|||||||
{menu === 'regular_phrases' && (
|
{menu === 'regular_phrases' && (
|
||||||
<AssistantRegularPromptsSettings assistant={assistant} updateAssistant={updateAssistant} />
|
<AssistantRegularPromptsSettings assistant={assistant} updateAssistant={updateAssistant} />
|
||||||
)}
|
)}
|
||||||
|
{menu === 'tags' && <AssistantTagsSettings assistant={assistant} updateAssistant={updateAssistant} />}
|
||||||
</Settings>
|
</Settings>
|
||||||
</HStack>
|
</HStack>
|
||||||
</StyledModal>
|
</StyledModal>
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export type Assistant = {
|
|||||||
mcpServers?: MCPServer[]
|
mcpServers?: MCPServer[]
|
||||||
knowledgeRecognition?: 'off' | 'on'
|
knowledgeRecognition?: 'off' | 'on'
|
||||||
regularPhrases?: QuickPhrase[] // Added for regular phrase
|
regularPhrases?: QuickPhrase[] // Added for regular phrase
|
||||||
|
tags?: string[] // 助手标签
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AssistantMessage = {
|
export type AssistantMessage = {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user