mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-26 20:12:38 +08:00
refactor(home/Tabs): restructure tabs components and add section names
- Split AssistantsTab into separate components (Assistants and Agents)
- Add SectionName component for better UI organization
- Remove unused tabs ('agents' and 'sessions') from chat type
- Clean up imports and type definitions
This commit is contained in:
parent
55645a75cc
commit
b43b4b581e
@ -7,8 +7,8 @@ import { useTimer } from '@renderer/hooks/useTimer'
|
||||
import PasteService from '@renderer/services/PasteService'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { sendMessage as dispatchSendMessage } from '@renderer/store/thunk/messageThunk'
|
||||
import type { Assistant, Message, MessageBlock, Model, Topic } from '@renderer/types'
|
||||
import { MessageBlockStatus } from '@renderer/types/newMessage'
|
||||
import type { Assistant, Message, Model, Topic } from '@renderer/types'
|
||||
import { MessageBlock, MessageBlockStatus } from '@renderer/types/newMessage'
|
||||
import { classNames } from '@renderer/utils'
|
||||
import { buildAgentSessionTopicId } from '@renderer/utils/agentSession'
|
||||
import { getSendMessageShortcutLabel, isSendMessageKeyPressed } from '@renderer/utils/input'
|
||||
@ -126,7 +126,7 @@ const AgentSessionInputbar: FC<Props> = ({ agentId, sessionId }) => {
|
||||
|
||||
const userMessage: Message = createMessage('user', sessionTopicId, agentId, {
|
||||
id: userMessageId,
|
||||
blocks: userMessageBlocks.map((block) => block.id),
|
||||
blocks: userMessageBlocks.map((block) => block?.id),
|
||||
model,
|
||||
modelId: model?.id
|
||||
})
|
||||
|
||||
@ -9,10 +9,11 @@ import { FC, useCallback, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import AgentItem from './components/AgentItem'
|
||||
import { SectionName } from './components/SectionName'
|
||||
|
||||
interface AssistantsTabProps {}
|
||||
|
||||
export const AgentsTab: FC<AssistantsTabProps> = () => {
|
||||
export const Agents: FC<AssistantsTabProps> = () => {
|
||||
const { agents, deleteAgent, isLoading } = useAgents()
|
||||
const { t } = useTranslation()
|
||||
const { chat } = useRuntime()
|
||||
@ -34,7 +35,8 @@ export const AgentsTab: FC<AssistantsTabProps> = () => {
|
||||
}, [isLoading, agents, activeAgentId, setActiveAgentId])
|
||||
|
||||
return (
|
||||
<div className="agents-tab h-full w-full p-2">
|
||||
<div className="agents-tab h-full w-full">
|
||||
<SectionName name={t('common.agent_other')} />
|
||||
{isLoading && <Spinner />}
|
||||
{!isLoading &&
|
||||
agents.map((agent) => (
|
||||
@ -51,8 +53,8 @@ export const AgentsTab: FC<AssistantsTabProps> = () => {
|
||||
content: (
|
||||
<Button
|
||||
onPress={(e) => e.continuePropagation()}
|
||||
startContent={<Plus size={16} className="mr-1 shrink-0 translate-x-[-2px]" />}
|
||||
className="w-full justify-start bg-transparent text-foreground-500 hover:bg-accent">
|
||||
<Plus size={16} className="mr-1 shrink-0" />
|
||||
{t('agent.add.title')}
|
||||
</Button>
|
||||
)
|
||||
206
src/renderer/src/pages/home/Tabs/Assistants.tsx
Normal file
206
src/renderer/src/pages/home/Tabs/Assistants.tsx
Normal file
@ -0,0 +1,206 @@
|
||||
import { DownOutlined, RightOutlined } from '@ant-design/icons'
|
||||
import { Button } from '@heroui/react'
|
||||
import { DraggableList } from '@renderer/components/DraggableList'
|
||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||
import { useAssistantPresets } from '@renderer/hooks/useAssistantPresets'
|
||||
import { useAssistantsTabSortType } from '@renderer/hooks/useStore'
|
||||
import { useTags } from '@renderer/hooks/useTags'
|
||||
import { Assistant, AssistantsSortType } from '@renderer/types'
|
||||
import { Tooltip } from 'antd'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { FC, useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import AssistantItem from './components/AssistantItem'
|
||||
import { SectionName } from './components/SectionName'
|
||||
|
||||
interface AssistantsProps {
|
||||
activeAssistant: Assistant
|
||||
setActiveAssistant: (assistant: Assistant) => void
|
||||
onCreateAssistant: () => void
|
||||
onCreateDefaultAssistant: () => void
|
||||
}
|
||||
|
||||
const Assistants: FC<AssistantsProps> = ({
|
||||
activeAssistant,
|
||||
setActiveAssistant,
|
||||
onCreateAssistant,
|
||||
onCreateDefaultAssistant
|
||||
}) => {
|
||||
const { assistants, removeAssistant, copyAssistant, updateAssistants } = useAssistants()
|
||||
const [dragging, setDragging] = useState(false)
|
||||
const { addAssistantPreset } = useAssistantPresets()
|
||||
const { t } = useTranslation()
|
||||
const { getGroupedAssistants, collapsedTags, toggleTagCollapse } = useTags()
|
||||
const { assistantsTabSortType = 'list', setAssistantsTabSortType } = useAssistantsTabSortType()
|
||||
|
||||
const onDelete = useCallback(
|
||||
(assistant: Assistant) => {
|
||||
const remaining = assistants.filter((a) => a.id !== assistant.id)
|
||||
if (assistant.id === activeAssistant?.id) {
|
||||
const newActive = remaining[remaining.length - 1]
|
||||
newActive ? setActiveAssistant(newActive) : onCreateDefaultAssistant()
|
||||
}
|
||||
removeAssistant(assistant.id)
|
||||
},
|
||||
[activeAssistant, assistants, removeAssistant, setActiveAssistant, onCreateDefaultAssistant]
|
||||
)
|
||||
|
||||
const handleSortByChange = useCallback(
|
||||
(sortType: AssistantsSortType) => {
|
||||
setAssistantsTabSortType(sortType)
|
||||
},
|
||||
[setAssistantsTabSortType]
|
||||
)
|
||||
|
||||
const handleGroupReorder = useCallback(
|
||||
(tag: string, newGroupList: Assistant[]) => {
|
||||
let insertIndex = 0
|
||||
const newGlobal = assistants.map((a) => {
|
||||
const tags = a.tags?.length ? a.tags : [t('assistants.tags.untagged')]
|
||||
if (tags.includes(tag)) {
|
||||
const replaced = newGroupList[insertIndex]
|
||||
insertIndex += 1
|
||||
return replaced
|
||||
}
|
||||
return a
|
||||
})
|
||||
updateAssistants(newGlobal)
|
||||
},
|
||||
[assistants, t, updateAssistants]
|
||||
)
|
||||
|
||||
const renderAddAssistantButton = useMemo(() => {
|
||||
return (
|
||||
<Button onPress={onCreateAssistant} className="justify-start bg-transparent text-foreground-500 hover:bg-accent">
|
||||
<Plus size={16} style={{ marginRight: 4, flexShrink: 0 }} />
|
||||
{t('chat.add.assistant.title')}
|
||||
</Button>
|
||||
)
|
||||
}, [onCreateAssistant, t])
|
||||
|
||||
if (assistantsTabSortType === 'tags') {
|
||||
return (
|
||||
<>
|
||||
<SectionName name={t('common.assistant_other')} />
|
||||
<div style={{ marginBottom: '8px' }}>
|
||||
{getGroupedAssistants.map((group) => (
|
||||
<TagsContainer key={group.tag}>
|
||||
{group.tag !== t('assistants.tags.untagged') && (
|
||||
<GroupTitle onClick={() => toggleTagCollapse(group.tag)}>
|
||||
<Tooltip title={group.tag}>
|
||||
<GroupTitleName>
|
||||
{collapsedTags[group.tag] ? (
|
||||
<RightOutlined style={{ fontSize: '10px', marginRight: '5px' }} />
|
||||
) : (
|
||||
<DownOutlined style={{ fontSize: '10px', marginRight: '5px' }} />
|
||||
)}
|
||||
{group.tag}
|
||||
</GroupTitleName>
|
||||
</Tooltip>
|
||||
<GroupTitleDivider />
|
||||
</GroupTitle>
|
||||
)}
|
||||
{!collapsedTags[group.tag] && (
|
||||
<div>
|
||||
<DraggableList
|
||||
list={group.assistants}
|
||||
onUpdate={(newList) => handleGroupReorder(group.tag, newList)}
|
||||
onDragStart={() => setDragging(true)}
|
||||
onDragEnd={() => setDragging(false)}>
|
||||
{(assistant) => (
|
||||
<AssistantItem
|
||||
key={assistant.id}
|
||||
assistant={assistant}
|
||||
isActive={assistant.id === activeAssistant.id}
|
||||
sortBy={assistantsTabSortType}
|
||||
onSwitch={setActiveAssistant}
|
||||
onDelete={onDelete}
|
||||
addPreset={addAssistantPreset}
|
||||
copyAssistant={copyAssistant}
|
||||
onCreateDefaultAssistant={onCreateDefaultAssistant}
|
||||
handleSortByChange={handleSortByChange}
|
||||
/>
|
||||
)}
|
||||
</DraggableList>
|
||||
</div>
|
||||
)}
|
||||
</TagsContainer>
|
||||
))}
|
||||
</div>
|
||||
{renderAddAssistantButton}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SectionName name={t('common.assistant_other')} />
|
||||
<DraggableList
|
||||
list={assistants}
|
||||
onUpdate={updateAssistants}
|
||||
onDragStart={() => setDragging(true)}
|
||||
onDragEnd={() => setDragging(false)}>
|
||||
{(assistant) => (
|
||||
<AssistantItem
|
||||
key={assistant.id}
|
||||
assistant={assistant}
|
||||
isActive={assistant.id === activeAssistant.id}
|
||||
sortBy={assistantsTabSortType}
|
||||
onSwitch={setActiveAssistant}
|
||||
onDelete={onDelete}
|
||||
addPreset={addAssistantPreset}
|
||||
copyAssistant={copyAssistant}
|
||||
onCreateDefaultAssistant={onCreateDefaultAssistant}
|
||||
handleSortByChange={handleSortByChange}
|
||||
/>
|
||||
)}
|
||||
</DraggableList>
|
||||
{!dragging && renderAddAssistantButton}
|
||||
<div style={{ minHeight: 10 }}></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 样式组件
|
||||
|
||||
const TagsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
const GroupTitle = styled.div`
|
||||
color: var(--color-text-2);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
margin: 5px 0;
|
||||
`
|
||||
|
||||
const GroupTitleName = styled.div`
|
||||
max-width: 50%;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 0 4px;
|
||||
color: var(--color-text);
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
margin-right: 5px;
|
||||
display: flex;
|
||||
`
|
||||
|
||||
const GroupTitleDivider = styled.div`
|
||||
flex: 1;
|
||||
border-top: 1px solid var(--color-border);
|
||||
`
|
||||
|
||||
export default Assistants
|
||||
@ -1,19 +1,10 @@
|
||||
import { DownOutlined, RightOutlined } from '@ant-design/icons'
|
||||
import { Button } from '@heroui/react'
|
||||
import { DraggableList } from '@renderer/components/DraggableList'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||
import { useAssistantPresets } from '@renderer/hooks/useAssistantPresets'
|
||||
import { useAssistantsTabSortType } from '@renderer/hooks/useStore'
|
||||
import { useTags } from '@renderer/hooks/useTags'
|
||||
import { Assistant, AssistantsSortType } from '@renderer/types'
|
||||
import { Tooltip } from 'antd'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { FC, useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Assistant } from '@renderer/types'
|
||||
import { FC, useRef } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import AssistantItem from './components/AssistantItem'
|
||||
import { Agents } from './Agents'
|
||||
import Assistants from './Assistants'
|
||||
|
||||
interface AssistantsTabProps {
|
||||
activeAssistant: Assistant
|
||||
@ -21,147 +12,17 @@ interface AssistantsTabProps {
|
||||
onCreateAssistant: () => void
|
||||
onCreateDefaultAssistant: () => void
|
||||
}
|
||||
const Assistants: FC<AssistantsTabProps> = ({
|
||||
activeAssistant,
|
||||
setActiveAssistant,
|
||||
onCreateAssistant,
|
||||
onCreateDefaultAssistant
|
||||
}) => {
|
||||
const { assistants, removeAssistant, copyAssistant, updateAssistants } = useAssistants()
|
||||
const [dragging, setDragging] = useState(false)
|
||||
const { addAssistantPreset } = useAssistantPresets()
|
||||
const { t } = useTranslation()
|
||||
const { getGroupedAssistants, collapsedTags, toggleTagCollapse } = useTags()
|
||||
const { assistantsTabSortType = 'list', setAssistantsTabSortType } = useAssistantsTabSortType()
|
||||
|
||||
const AssistantsTab: FC<AssistantsTabProps> = (props) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const onDelete = useCallback(
|
||||
(assistant: Assistant) => {
|
||||
const remaining = assistants.filter((a) => a.id !== assistant.id)
|
||||
if (assistant.id === activeAssistant?.id) {
|
||||
const newActive = remaining[remaining.length - 1]
|
||||
newActive ? setActiveAssistant(newActive) : onCreateDefaultAssistant()
|
||||
}
|
||||
removeAssistant(assistant.id)
|
||||
},
|
||||
[activeAssistant, assistants, removeAssistant, setActiveAssistant, onCreateDefaultAssistant]
|
||||
)
|
||||
|
||||
const handleSortByChange = useCallback(
|
||||
(sortType: AssistantsSortType) => {
|
||||
setAssistantsTabSortType(sortType)
|
||||
},
|
||||
[setAssistantsTabSortType]
|
||||
)
|
||||
|
||||
const handleGroupReorder = useCallback(
|
||||
(tag: string, newGroupList: Assistant[]) => {
|
||||
let insertIndex = 0
|
||||
const newGlobal = assistants.map((a) => {
|
||||
const tags = a.tags?.length ? a.tags : [t('assistants.tags.untagged')]
|
||||
if (tags.includes(tag)) {
|
||||
const replaced = newGroupList[insertIndex]
|
||||
insertIndex += 1
|
||||
return replaced
|
||||
}
|
||||
return a
|
||||
})
|
||||
updateAssistants(newGlobal)
|
||||
},
|
||||
[assistants, t, updateAssistants]
|
||||
)
|
||||
|
||||
const renderAddAssistantButton = useMemo(() => {
|
||||
return (
|
||||
<Button onPress={onCreateAssistant} className="justify-start bg-transparent text-foreground-500 hover:bg-accent">
|
||||
<Plus size={16} style={{ marginRight: 4, flexShrink: 0 }} />
|
||||
{t('chat.add.assistant.title')}
|
||||
</Button>
|
||||
)
|
||||
}, [onCreateAssistant, t])
|
||||
|
||||
if (assistantsTabSortType === 'tags') {
|
||||
return (
|
||||
<Container className="assistants-tab" ref={containerRef}>
|
||||
<div style={{ marginBottom: '8px' }}>
|
||||
{getGroupedAssistants.map((group) => (
|
||||
<TagsContainer key={group.tag}>
|
||||
{group.tag !== t('assistants.tags.untagged') && (
|
||||
<GroupTitle onClick={() => toggleTagCollapse(group.tag)}>
|
||||
<Tooltip title={group.tag}>
|
||||
<GroupTitleName>
|
||||
{collapsedTags[group.tag] ? (
|
||||
<RightOutlined style={{ fontSize: '10px', marginRight: '5px' }} />
|
||||
) : (
|
||||
<DownOutlined style={{ fontSize: '10px', marginRight: '5px' }} />
|
||||
)}
|
||||
{group.tag}
|
||||
</GroupTitleName>
|
||||
</Tooltip>
|
||||
<GroupTitleDivider />
|
||||
</GroupTitle>
|
||||
)}
|
||||
{!collapsedTags[group.tag] && (
|
||||
<div>
|
||||
<DraggableList
|
||||
list={group.assistants}
|
||||
onUpdate={(newList) => handleGroupReorder(group.tag, newList)}
|
||||
onDragStart={() => setDragging(true)}
|
||||
onDragEnd={() => setDragging(false)}>
|
||||
{(assistant) => (
|
||||
<AssistantItem
|
||||
key={assistant.id}
|
||||
assistant={assistant}
|
||||
isActive={assistant.id === activeAssistant.id}
|
||||
sortBy={assistantsTabSortType}
|
||||
onSwitch={setActiveAssistant}
|
||||
onDelete={onDelete}
|
||||
addPreset={addAssistantPreset}
|
||||
copyAssistant={copyAssistant}
|
||||
onCreateDefaultAssistant={onCreateDefaultAssistant}
|
||||
handleSortByChange={handleSortByChange}
|
||||
/>
|
||||
)}
|
||||
</DraggableList>
|
||||
</div>
|
||||
)}
|
||||
</TagsContainer>
|
||||
))}
|
||||
</div>
|
||||
{renderAddAssistantButton}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="assistants-tab" ref={containerRef}>
|
||||
<DraggableList
|
||||
list={assistants}
|
||||
onUpdate={updateAssistants}
|
||||
onDragStart={() => setDragging(true)}
|
||||
onDragEnd={() => setDragging(false)}>
|
||||
{(assistant) => (
|
||||
<AssistantItem
|
||||
key={assistant.id}
|
||||
assistant={assistant}
|
||||
isActive={assistant.id === activeAssistant.id}
|
||||
sortBy={assistantsTabSortType}
|
||||
onSwitch={setActiveAssistant}
|
||||
onDelete={onDelete}
|
||||
addPreset={addAssistantPreset}
|
||||
copyAssistant={copyAssistant}
|
||||
onCreateDefaultAssistant={onCreateDefaultAssistant}
|
||||
handleSortByChange={handleSortByChange}
|
||||
/>
|
||||
)}
|
||||
</DraggableList>
|
||||
{!dragging && renderAddAssistantButton}
|
||||
<div style={{ minHeight: 10 }}></div>
|
||||
<Agents />
|
||||
<Assistants {...props} />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
// 样式组件
|
||||
const Container = styled(Scrollbar)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -169,42 +30,4 @@ const Container = styled(Scrollbar)`
|
||||
margin-top: 3px;
|
||||
`
|
||||
|
||||
const TagsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
const GroupTitle = styled.div`
|
||||
color: var(--color-text-2);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
margin: 5px 0;
|
||||
`
|
||||
|
||||
const GroupTitleName = styled.div`
|
||||
max-width: 50%;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 0 4px;
|
||||
color: var(--color-text);
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
margin-right: 5px;
|
||||
display: flex;
|
||||
`
|
||||
|
||||
const GroupTitleDivider = styled.div`
|
||||
flex: 1;
|
||||
border-top: 1px solid var(--color-border);
|
||||
`
|
||||
|
||||
export default Assistants
|
||||
export default AssistantsTab
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
type Props = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export const SectionName: React.FC<Props> = ({ name }) => {
|
||||
return <div className="mb-2 text-gray-500 text-sm">{name}</div>
|
||||
}
|
||||
@ -13,9 +13,7 @@ import { FC, useCallback, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { AgentsTab as Agents } from './AgentsTab'
|
||||
import Assistants from './AssistantsTab'
|
||||
import Sessions from './SessionsTab'
|
||||
import Settings from './SettingsTab'
|
||||
import Topics from './TopicsTab'
|
||||
|
||||
@ -127,15 +125,9 @@ const HomeTabs: FC<Props> = ({
|
||||
<TabItem active={tab === 'assistants'} onClick={() => setTab('assistants')}>
|
||||
{t('assistants.abbr')}
|
||||
</TabItem>
|
||||
<TabItem active={tab === 'agents'} onClick={() => setTab('agents')}>
|
||||
{t('agents.title')}
|
||||
</TabItem>
|
||||
<TabItem active={tab === 'topic'} onClick={() => setTab('topic')}>
|
||||
{t('common.topics')}
|
||||
</TabItem>
|
||||
<TabItem active={tab === 'sessions'} onClick={() => setTab('sessions')}>
|
||||
{t('agent.session.label_other')}
|
||||
</TabItem>
|
||||
<TabItem active={tab === 'settings'} onClick={() => setTab('settings')}>
|
||||
{t('settings.title')}
|
||||
</TabItem>
|
||||
@ -170,8 +162,6 @@ const HomeTabs: FC<Props> = ({
|
||||
position={position}
|
||||
/>
|
||||
)}
|
||||
{tab === 'agents' && <Agents />}
|
||||
{tab === 'sessions' && <Sessions />}
|
||||
{tab === 'settings' && <Settings assistant={activeAssistant} />}
|
||||
</TabContent>
|
||||
</Container>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
*
|
||||
* WARNING: Any null value will be converted to undefined from api.
|
||||
*/
|
||||
import { TextStreamPart } from 'ai'
|
||||
import { ModelMessage, TextStreamPart } from 'ai'
|
||||
import { z } from 'zod'
|
||||
|
||||
import type { Message, MessageBlock } from './newMessage'
|
||||
|
||||
@ -1 +1 @@
|
||||
export type Tab = 'assistants' | 'topic' | 'settings' | 'agents' | 'sessions'
|
||||
export type Tab = 'assistants' | 'topic' | 'settings'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user