fix:清空话题总是修复当前话题 (#2167)

This commit is contained in:
wnzzer 2025-02-23 14:26:31 +08:00 committed by GitHub
parent cacd0a1387
commit fc59144b1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 267 additions and 191 deletions

View File

@ -0,0 +1,197 @@
import CopyIcon from "@renderer/components/Icons/CopyIcon"
import { useAssistant } from "@renderer/hooks/useAssistant"
import { modelGenerating } from "@renderer/hooks/useRuntime"
import { useSettings } from "@renderer/hooks/useSettings"
import AssistantSettingsPopup from "@renderer/pages/settings/AssistantSettings"
import { getDefaultTopic } from "@renderer/services/AssistantService"
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { Assistant } from "@renderer/types"
import { uuid } from "@renderer/utils"
import { Dropdown } from "antd"
import { ItemType } from "antd/es/menu/interface"
import { omit } from "lodash"
import { FC, useCallback } from "react"
import { useTranslation } from "react-i18next"
import { DeleteOutlined, EditOutlined, MinusCircleOutlined, SaveOutlined } from '@ant-design/icons'
import styled from "styled-components"
interface AssistantItemProps {
assistant: Assistant
isActive: boolean
onSwitch: (assistant: Assistant) => void
onDelete: (assistant: Assistant) => void
onCreateDefaultAssistant: () => void
addAgent: (agent: any) => void
addAssistant: (assistant: Assistant) => void
}
const AssistantItemComponent: FC<AssistantItemProps> = ({
assistant,
isActive,
onSwitch,
onDelete,
addAgent,
addAssistant
}) => {
const { t } = useTranslation()
const { removeAllTopics } = useAssistant(assistant.id) // 使用当前助手的ID
const { clickAssistantToShowTopic, topicPosition } = useSettings()
const getMenuItems = useCallback(
(assistant: Assistant): ItemType[] => [
{
label: t('assistants.edit.title'),
key: 'edit',
icon: <EditOutlined />,
onClick: () => AssistantSettingsPopup.show({ assistant })
},
{
label: t('assistants.copy.title'),
key: 'duplicate',
icon: <CopyIcon />,
onClick: async () => {
const _assistant: Assistant = { ...assistant, id: uuid(), topics: [getDefaultTopic(assistant.id)] }
addAssistant(_assistant)
onSwitch(_assistant)
}
},
{
label: t('assistants.clear.title'),
key: 'clear',
icon: <MinusCircleOutlined />,
onClick: () => {
window.modal.confirm({
title: t('assistants.clear.title'),
content: t('assistants.clear.content'),
centered: true,
okButtonProps: { danger: true },
onOk: () => removeAllTopics() // 使用当前助手的removeAllTopics
})
}
},
{
label: t('assistants.save.title'),
key: 'save-to-agent',
icon: <SaveOutlined />,
onClick: async () => {
const agent = omit(assistant, ['model', 'emoji'])
agent.id = uuid()
agent.type = 'agent'
addAgent(agent)
window.message.success({
content: t('assistants.save.success'),
key: 'save-to-agent'
})
}
},
{ type: 'divider' },
{
label: t('common.delete'),
key: 'delete',
icon: <DeleteOutlined />,
danger: true,
onClick: () => {
window.modal.confirm({
title: t('assistants.delete.title'),
content: t('assistants.delete.content'),
centered: true,
okButtonProps: { danger: true },
onOk: () => onDelete(assistant)
})
}
}
],
[addAgent, addAssistant, onSwitch, removeAllTopics, t, onDelete]
)
const handleSwitch = useCallback(async () => {
await modelGenerating()
if (topicPosition === 'left' && clickAssistantToShowTopic) {
EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)
}
onSwitch(assistant)
}, [clickAssistantToShowTopic, onSwitch, assistant, topicPosition])
return (
<Dropdown menu={{ items: getMenuItems(assistant) }} trigger={['contextMenu']}>
<AssistantItem onClick={handleSwitch} className={isActive ? 'active' : ''}>
<AssistantName className="name">{assistant.name || t('chat.default.name')}</AssistantName>
{isActive && (
<MenuButton onClick={() => EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)}>
<TopicCount className="topics-count">{assistant.topics.length}</TopicCount>
</MenuButton>
)}
</AssistantItem>
</Dropdown>
)
}
export default AssistantItemComponent // 使用默认导出
const AssistantItem = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 7px 12px;
position: relative;
margin: 0 10px;
padding-right: 35px;
font-family: Ubuntu;
border-radius: var(--list-item-border-radius);
border: 0.5px solid transparent;
cursor: pointer;
.iconfont {
opacity: 0;
color: var(--color-text-3);
}
&:hover {
background-color: var(--color-background-soft);
}
&.active {
background-color: var(--color-background-soft);
border: 0.5px solid var(--color-border);
.name {
}
}
`
const AssistantName = styled.div`
color: var(--color-text);
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
font-size: 13px;
`
const MenuButton = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
min-width: 22px;
height: 22px;
min-width: 22px;
min-height: 22px;
border-radius: 11px;
position: absolute;
background-color: var(--color-background);
right: 9px;
top: 6px;
`
const TopicCount = styled.div`
color: var(--color-text);
font-size: 10px;
border-radius: 10px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
`

View File

@ -1,170 +1,23 @@
import { DeleteOutlined, EditOutlined, MinusCircleOutlined, PlusOutlined, SaveOutlined } from '@ant-design/icons' import { useCallback, useState, FC } from "react"
import DragableList from '@renderer/components/DragableList' import { useTranslation } from "react-i18next"
import CopyIcon from '@renderer/components/Icons/CopyIcon' import { PlusOutlined } from '@ant-design/icons'
import Scrollbar from '@renderer/components/Scrollbar' import DragableList from "@renderer/components/DragableList"
import { useAgents } from '@renderer/hooks/useAgents' import Scrollbar from "@renderer/components/Scrollbar"
import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant' import { useAgents } from "@renderer/hooks/useAgents"
import { modelGenerating } from '@renderer/hooks/useRuntime' import { useAssistants } from "@renderer/hooks/useAssistant"
import { useSettings } from '@renderer/hooks/useSettings' import { Assistant } from "@renderer/types"
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings' import styled from "styled-components"
import { getDefaultTopic } from '@renderer/services/AssistantService' import AssistantItemComponent from "@renderer/pages/home/Tabs/AssistantItemComponent"
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { Assistant } from '@renderer/types'
import { uuid } from '@renderer/utils'
import { Dropdown } from 'antd'
import { ItemType } from 'antd/es/menu/interface'
import { last, omit } from 'lodash'
import { FC, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
interface Props { // 类型定义
interface AssistantsProps {
activeAssistant: Assistant activeAssistant: Assistant
setActiveAssistant: (assistant: Assistant) => void setActiveAssistant: (assistant: Assistant) => void
onCreateDefaultAssistant: () => void
onCreateAssistant: () => void onCreateAssistant: () => void
onCreateDefaultAssistant: () => void
} }
const Assistants: FC<Props> = ({ // 样式组件(只定义一次)
activeAssistant,
setActiveAssistant,
onCreateAssistant,
onCreateDefaultAssistant
}) => {
const { assistants, removeAssistant, addAssistant, updateAssistants } = useAssistants()
const [dragging, setDragging] = useState(false)
const { removeAllTopics } = useAssistant(activeAssistant.id)
const { clickAssistantToShowTopic, topicPosition } = useSettings()
const { t } = useTranslation()
const { addAgent } = useAgents()
const onDelete = useCallback(
(assistant: Assistant) => {
const _assistant: Assistant | undefined = last(assistants.filter((a) => a.id !== assistant.id))
_assistant ? setActiveAssistant(_assistant) : onCreateDefaultAssistant()
removeAssistant(assistant.id)
},
[assistants, onCreateDefaultAssistant, removeAssistant, setActiveAssistant]
)
const getMenuItems = useCallback(
(assistant: Assistant) =>
[
{
label: t('assistants.edit.title'),
key: 'edit',
icon: <EditOutlined />,
onClick: () => AssistantSettingsPopup.show({ assistant })
},
{
label: t('assistants.copy.title'),
key: 'duplicate',
icon: <CopyIcon />,
onClick: async () => {
const _assistant: Assistant = { ...assistant, id: uuid(), topics: [getDefaultTopic(assistant.id)] }
addAssistant(_assistant)
setActiveAssistant(_assistant)
}
},
{
label: t('assistants.clear.title'),
key: 'clear',
icon: <MinusCircleOutlined />,
onClick: () => {
window.modal.confirm({
title: t('assistants.clear.title'),
content: t('assistants.clear.content'),
centered: true,
okButtonProps: { danger: true },
onOk: removeAllTopics
})
}
},
{
label: t('assistants.save.title'),
key: 'save-to-agent',
icon: <SaveOutlined />,
onClick: async () => {
const agent = omit(assistant, ['model', 'emoji'])
agent.id = uuid()
agent.type = 'agent'
addAgent(agent)
window.message.success({
content: t('assistants.save.success'),
key: 'save-to-agent'
})
}
},
{ type: 'divider' },
{
label: t('common.delete'),
key: 'delete',
icon: <DeleteOutlined />,
danger: true,
onClick: () => {
window.modal.confirm({
title: t('assistants.delete.title'),
content: t('assistants.delete.content'),
centered: true,
okButtonProps: { danger: true },
onOk: () => onDelete(assistant)
})
}
}
] as ItemType[],
[addAgent, addAssistant, onDelete, removeAllTopics, setActiveAssistant, t]
)
const onSwitchAssistant = useCallback(
async (assistant: Assistant) => {
await modelGenerating()
if (topicPosition === 'left' && clickAssistantToShowTopic) {
EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)
}
setActiveAssistant(assistant)
},
[clickAssistantToShowTopic, setActiveAssistant, topicPosition]
)
return (
<Container className="assistants-tab">
<DragableList
list={assistants}
onUpdate={updateAssistants}
style={{ paddingBottom: dragging ? '34px' : 0 }}
onDragStart={() => setDragging(true)}
onDragEnd={() => setDragging(false)}>
{(assistant) => {
const isCurrent = assistant.id === activeAssistant?.id
return (
<Dropdown key={assistant.id} menu={{ items: getMenuItems(assistant) }} trigger={['contextMenu']}>
<AssistantItem onClick={() => onSwitchAssistant(assistant)} className={isCurrent ? 'active' : ''}>
<AssistantName className="name">{assistant.name || t('chat.default.name')}</AssistantName>
{isCurrent && (
<MenuButton onClick={() => EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)}>
<TopicCount className="topics-count">{assistant.topics.length}</TopicCount>
</MenuButton>
)}
</AssistantItem>
</Dropdown>
)
}}
</DragableList>
{!dragging && (
<AssistantItem onClick={onCreateAssistant}>
<AssistantName>
<PlusOutlined style={{ color: 'var(--color-text-2)', marginRight: 4 }} />
{t('chat.add.assistant.title')}
</AssistantName>
</AssistantItem>
)}
<div style={{ minHeight: 10 }}></div>
</Container>
)
}
const Container = styled(Scrollbar)` const Container = styled(Scrollbar)`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -184,18 +37,14 @@ const AssistantItem = styled.div`
border-radius: var(--list-item-border-radius); border-radius: var(--list-item-border-radius);
border: 0.5px solid transparent; border: 0.5px solid transparent;
cursor: pointer; cursor: pointer;
.iconfont {
opacity: 0;
color: var(--color-text-3);
}
&:hover { &:hover {
background-color: var(--color-background-soft); background-color: var(--color-background-soft);
} }
&.active { &.active {
background-color: var(--color-background-soft); background-color: var(--color-background-soft);
border: 0.5px solid var(--color-border); border: 0.5px solid var(--color-border);
.name {
}
} }
` `
@ -208,30 +57,60 @@ const AssistantName = styled.div`
font-size: 13px; font-size: 13px;
` `
const MenuButton = styled.div` const Assistants: FC<AssistantsProps> = ({
display: flex; activeAssistant,
flex-direction: row; setActiveAssistant,
justify-content: center; onCreateAssistant,
align-items: center; onCreateDefaultAssistant
min-width: 22px; }) => {
height: 22px; const { assistants, removeAssistant, addAssistant, updateAssistants } = useAssistants()
min-width: 22px; const [dragging, setDragging] = useState(false)
min-height: 22px; const { addAgent } = useAgents()
border-radius: 11px; const { t } = useTranslation()
position: absolute;
background-color: var(--color-background);
right: 9px;
top: 6px;
`
const TopicCount = styled.div` const onDelete = useCallback(
color: var(--color-text); (assistant: Assistant) => {
font-size: 10px; const remaining = assistants.filter(a => a.id !== assistant.id)
border-radius: 10px; const newActive = remaining[remaining.length - 1]
display: flex; newActive ? setActiveAssistant(newActive) : onCreateDefaultAssistant()
flex-direction: row; removeAssistant(assistant.id)
justify-content: center; },
align-items: center; [assistants, removeAssistant, setActiveAssistant, onCreateDefaultAssistant]
` )
return (
<Container className="assistants-tab">
<DragableList
list={assistants}
onUpdate={updateAssistants}
style={{ paddingBottom: dragging ? '34px' : 0 }}
onDragStart={() => setDragging(true)}
onDragEnd={() => setDragging(false)}
>
{(assistant) => (
<AssistantItemComponent
key={assistant.id}
assistant={assistant}
isActive={assistant.id === activeAssistant.id}
onSwitch={setActiveAssistant}
onDelete={onDelete}
addAgent={addAgent}
addAssistant={addAssistant}
onCreateDefaultAssistant={onCreateDefaultAssistant}
/>
)}
</DragableList>
{!dragging && (
<AssistantItem onClick={onCreateAssistant}>
<AssistantName>
<PlusOutlined style={{ color: 'var(--color-text-2)', marginRight: 4 }} />
{t('chat.add.assistant.title')}
</AssistantName>
</AssistantItem>
)}
<div style={{ minHeight: 10 }}></div>
</Container>
)
}
export default Assistants export default Assistants