From b43b4b581e09ae78136211a71d6302d748354a1a Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 20 Sep 2025 19:44:20 +0800 Subject: [PATCH] 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 --- .../home/Inputbar/AgentSessionInputbar.tsx | 6 +- .../home/Tabs/{AgentsTab.tsx => Agents.tsx} | 8 +- .../src/pages/home/Tabs/Assistants.tsx | 206 ++++++++++++++++++ .../src/pages/home/Tabs/AssistantsTab.tsx | 195 +---------------- .../home/Tabs/components/SectionName.tsx | 7 + src/renderer/src/pages/home/Tabs/index.tsx | 10 - src/renderer/src/types/agent.ts | 2 +- src/renderer/src/types/chat.ts | 2 +- 8 files changed, 232 insertions(+), 204 deletions(-) rename src/renderer/src/pages/home/Tabs/{AgentsTab.tsx => Agents.tsx} (85%) create mode 100644 src/renderer/src/pages/home/Tabs/Assistants.tsx create mode 100644 src/renderer/src/pages/home/Tabs/components/SectionName.tsx diff --git a/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx b/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx index f771f61800..000337152f 100644 --- a/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx @@ -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 = ({ 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 }) diff --git a/src/renderer/src/pages/home/Tabs/AgentsTab.tsx b/src/renderer/src/pages/home/Tabs/Agents.tsx similarity index 85% rename from src/renderer/src/pages/home/Tabs/AgentsTab.tsx rename to src/renderer/src/pages/home/Tabs/Agents.tsx index a4f30d9d26..b1a37c06e2 100644 --- a/src/renderer/src/pages/home/Tabs/AgentsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/Agents.tsx @@ -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 = () => { +export const Agents: FC = () => { const { agents, deleteAgent, isLoading } = useAgents() const { t } = useTranslation() const { chat } = useRuntime() @@ -34,7 +35,8 @@ export const AgentsTab: FC = () => { }, [isLoading, agents, activeAgentId, setActiveAgentId]) return ( -
+
+ {isLoading && } {!isLoading && agents.map((agent) => ( @@ -51,8 +53,8 @@ export const AgentsTab: FC = () => { content: ( ) diff --git a/src/renderer/src/pages/home/Tabs/Assistants.tsx b/src/renderer/src/pages/home/Tabs/Assistants.tsx new file mode 100644 index 0000000000..ecd0f6d78f --- /dev/null +++ b/src/renderer/src/pages/home/Tabs/Assistants.tsx @@ -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 = ({ + 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 ( + + ) + }, [onCreateAssistant, t]) + + if (assistantsTabSortType === 'tags') { + return ( + <> + +
+ {getGroupedAssistants.map((group) => ( + + {group.tag !== t('assistants.tags.untagged') && ( + toggleTagCollapse(group.tag)}> + + + {collapsedTags[group.tag] ? ( + + ) : ( + + )} + {group.tag} + + + + + )} + {!collapsedTags[group.tag] && ( +
+ handleGroupReorder(group.tag, newList)} + onDragStart={() => setDragging(true)} + onDragEnd={() => setDragging(false)}> + {(assistant) => ( + + )} + +
+ )} +
+ ))} +
+ {renderAddAssistantButton} + + ) + } + + return ( +
+ + setDragging(true)} + onDragEnd={() => setDragging(false)}> + {(assistant) => ( + + )} + + {!dragging && renderAddAssistantButton} +
+
+ ) +} + +// 样式组件 + +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 diff --git a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx index 8c175c435d..f0593acf7a 100644 --- a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx @@ -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 = ({ - 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 = (props) => { const containerRef = useRef(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 ( - - ) - }, [onCreateAssistant, t]) - - if (assistantsTabSortType === 'tags') { - return ( - -
- {getGroupedAssistants.map((group) => ( - - {group.tag !== t('assistants.tags.untagged') && ( - toggleTagCollapse(group.tag)}> - - - {collapsedTags[group.tag] ? ( - - ) : ( - - )} - {group.tag} - - - - - )} - {!collapsedTags[group.tag] && ( -
- handleGroupReorder(group.tag, newList)} - onDragStart={() => setDragging(true)} - onDragEnd={() => setDragging(false)}> - {(assistant) => ( - - )} - -
- )} -
- ))} -
- {renderAddAssistantButton} -
- ) - } - return ( - setDragging(true)} - onDragEnd={() => setDragging(false)}> - {(assistant) => ( - - )} - - {!dragging && renderAddAssistantButton} -
+ +
) } -// 样式组件 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 diff --git a/src/renderer/src/pages/home/Tabs/components/SectionName.tsx b/src/renderer/src/pages/home/Tabs/components/SectionName.tsx new file mode 100644 index 0000000000..5527a73d50 --- /dev/null +++ b/src/renderer/src/pages/home/Tabs/components/SectionName.tsx @@ -0,0 +1,7 @@ +type Props = { + name: string +} + +export const SectionName: React.FC = ({ name }) => { + return
{name}
+} diff --git a/src/renderer/src/pages/home/Tabs/index.tsx b/src/renderer/src/pages/home/Tabs/index.tsx index 3a1d97b9f0..a8b8cdeedc 100644 --- a/src/renderer/src/pages/home/Tabs/index.tsx +++ b/src/renderer/src/pages/home/Tabs/index.tsx @@ -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 = ({ setTab('assistants')}> {t('assistants.abbr')} - setTab('agents')}> - {t('agents.title')} - setTab('topic')}> {t('common.topics')} - setTab('sessions')}> - {t('agent.session.label_other')} - setTab('settings')}> {t('settings.title')} @@ -170,8 +162,6 @@ const HomeTabs: FC = ({ position={position} /> )} - {tab === 'agents' && } - {tab === 'sessions' && } {tab === 'settings' && } diff --git a/src/renderer/src/types/agent.ts b/src/renderer/src/types/agent.ts index e28043eea5..74b2fef4de 100644 --- a/src/renderer/src/types/agent.ts +++ b/src/renderer/src/types/agent.ts @@ -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' diff --git a/src/renderer/src/types/chat.ts b/src/renderer/src/types/chat.ts index e36dc83ac9..2961b8d06a 100644 --- a/src/renderer/src/types/chat.ts +++ b/src/renderer/src/types/chat.ts @@ -1 +1 @@ -export type Tab = 'assistants' | 'topic' | 'settings' | 'agents' | 'sessions' +export type Tab = 'assistants' | 'topic' | 'settings'