diff --git a/src/renderer/src/hooks/agents/useCreateDefaultSession.ts b/src/renderer/src/hooks/agents/useCreateDefaultSession.ts new file mode 100644 index 0000000000..d2af4dcda0 --- /dev/null +++ b/src/renderer/src/hooks/agents/useCreateDefaultSession.ts @@ -0,0 +1,49 @@ +import { useAgent } from '@renderer/hooks/agents/useAgent' +import { useSessions } from '@renderer/hooks/agents/useSessions' +import { useAppDispatch } from '@renderer/store' +import { setActiveSessionIdAction, setActiveTopicOrSessionAction } from '@renderer/store/runtime' +import type { CreateSessionForm } from '@renderer/types' +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' + +/** + * Returns a stable callback that creates a default agent session and updates UI state. + */ +export const useCreateDefaultSession = (agentId: string | null) => { + const { agent } = useAgent(agentId) + const { createSession } = useSessions(agentId) + const dispatch = useAppDispatch() + const { t } = useTranslation() + const [creatingSession, setCreatingSession] = useState(false) + + const createDefaultSession = useCallback(async () => { + if (!agentId || !agent || creatingSession) { + return null + } + + setCreatingSession(true) + try { + const session = { + ...agent, + id: undefined, + name: t('common.unnamed') + } satisfies CreateSessionForm + + const created = await createSession(session) + + if (created) { + dispatch(setActiveSessionIdAction({ agentId, sessionId: created.id })) + dispatch(setActiveTopicOrSessionAction('session')) + } + + return created + } finally { + setCreatingSession(false) + } + }, [agentId, agent, createSession, creatingSession, dispatch, t]) + + return { + createDefaultSession, + creatingSession + } +} diff --git a/src/renderer/src/pages/home/Chat.tsx b/src/renderer/src/pages/home/Chat.tsx index 0c2d76a6e6..c90d21c8ce 100644 --- a/src/renderer/src/pages/home/Chat.tsx +++ b/src/renderer/src/pages/home/Chat.tsx @@ -5,6 +5,7 @@ import { HStack } from '@renderer/components/Layout' import MultiSelectActionPopup from '@renderer/components/Popups/MultiSelectionPopup' import PromptPopup from '@renderer/components/Popups/PromptPopup' import { QuickPanelProvider } from '@renderer/components/QuickPanel' +import { useCreateDefaultSession } from '@renderer/hooks/agents/useCreateDefaultSession' import { useAssistant } from '@renderer/hooks/useAssistant' import { useChatContext } from '@renderer/hooks/useChatContext' import { useRuntime } from '@renderer/hooks/useRuntime' @@ -52,6 +53,8 @@ const Chat: FC = (props) => { const { activeTopicOrSession, activeAgentId, activeSessionIdMap } = chat const activeSessionId = activeAgentId ? activeSessionIdMap[activeAgentId] : null const { apiServer } = useSettings() + const sessionAgentId = activeTopicOrSession === 'session' ? activeAgentId : null + const { createDefaultSession } = useCreateDefaultSession(sessionAgentId) const mainRef = React.useRef(null) const contentSearchRef = React.useRef(null) @@ -90,6 +93,21 @@ const Chat: FC = (props) => { } }) + useShortcut( + 'new_topic', + () => { + if (activeTopicOrSession !== 'session' || !activeAgentId) { + return + } + void createDefaultSession() + }, + { + enabled: activeTopicOrSession === 'session', + preventDefault: true, + enableOnFormTags: true + } + ) + const contentSearchFilter: NodeFilter = { acceptNode(node) { const container = node.parentElement?.closest('.message-content-container') diff --git a/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx b/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx index 3c87462c6a..57497b6044 100644 --- a/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx @@ -3,10 +3,12 @@ import { loggerService } from '@logger' import { ActionIconButton } from '@renderer/components/Buttons' import { QuickPanelView } from '@renderer/components/QuickPanel' import { useAgent } from '@renderer/hooks/agents/useAgent' +import { useCreateDefaultSession } from '@renderer/hooks/agents/useCreateDefaultSession' import { useSession } from '@renderer/hooks/agents/useSession' import { selectNewTopicLoading } from '@renderer/hooks/useMessageOperations' import { getModel } from '@renderer/hooks/useModel' import { useSettings } from '@renderer/hooks/useSettings' +import { useShortcutDisplay } from '@renderer/hooks/useShortcuts' import { useTimer } from '@renderer/hooks/useTimer' import PasteService from '@renderer/services/PasteService' import { pauseTrace } from '@renderer/services/SpanManagerService' @@ -22,7 +24,7 @@ import { getSendMessageShortcutLabel, isSendMessageKeyPressed } from '@renderer/ import { createMainTextBlock, createMessage } from '@renderer/utils/messageUtils/create' import TextArea, { TextAreaRef } from 'antd/es/input/TextArea' import { isEmpty } from 'lodash' -import { CirclePause } from 'lucide-react' +import { CirclePause, MessageSquareDiff } from 'lucide-react' import React, { CSSProperties, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -46,6 +48,8 @@ const AgentSessionInputbar: FC = ({ agentId, sessionId }) => { const { session } = useSession(agentId, sessionId) const { agent } = useAgent(agentId) const { apiServer } = useSettings() + const { createDefaultSession, creatingSession } = useCreateDefaultSession(agentId) + const newTopicShortcut = useShortcutDisplay('new_topic') const { sendMessageShortcut, fontSize, enableSpellCheck } = useSettings() const textareaRef = useRef(null) @@ -87,6 +91,22 @@ const AgentSessionInputbar: FC = ({ agentId, sessionId }) => { }, [topicMessages]) const canAbort = loading && streamingAskIds.length > 0 + const createSessionDisabled = creatingSession || !apiServer.enabled + + const handleCreateSession = useCallback(async () => { + if (createSessionDisabled) { + return + } + + try { + const created = await createDefaultSession() + if (created) { + focusTextarea() + } + } catch (error) { + logger.warn('Failed to create agent session via toolbar:', error as Error) + } + }, [createDefaultSession, createSessionDisabled, focusTextarea]) const handleKeyDown = (event: React.KeyboardEvent) => { //to check if the SendMessage key is pressed @@ -286,8 +306,18 @@ const AgentSessionInputbar: FC = ({ agentId, sessionId }) => { }} onBlur={() => setInputFocus(false)} /> -
-
+ + + + + + + + + {canAbort && ( @@ -296,8 +326,8 @@ const AgentSessionInputbar: FC = ({ agentId, sessionId }) => { )} -
-
+ + @@ -343,6 +373,25 @@ const InputBarContainer = styled.div` } ` +const Toolbar = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 5px 8px; + height: 40px; + gap: 16px; + position: relative; + z-index: 2; + flex-shrink: 0; +` + +const ToolbarGroup = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 6px; +` + const TextareaStyle: CSSProperties = { paddingLeft: 0, padding: '6px 15px 0px' // 减小顶部padding diff --git a/src/renderer/src/pages/home/Tabs/components/Sessions.tsx b/src/renderer/src/pages/home/Tabs/components/Sessions.tsx index 62b1df41de..5a36ce5d22 100644 --- a/src/renderer/src/pages/home/Tabs/components/Sessions.tsx +++ b/src/renderer/src/pages/home/Tabs/components/Sessions.tsx @@ -1,6 +1,6 @@ import { Alert, Spinner } from '@heroui/react' import { DynamicVirtualList } from '@renderer/components/VirtualList' -import { useAgent } from '@renderer/hooks/agents/useAgent' +import { useCreateDefaultSession } from '@renderer/hooks/agents/useCreateDefaultSession' import { useSessions } from '@renderer/hooks/agents/useSessions' import { useRuntime } from '@renderer/hooks/useRuntime' import { useAppDispatch } from '@renderer/store' @@ -10,7 +10,6 @@ import { setActiveTopicOrSessionAction, setSessionWaitingAction } from '@renderer/store/runtime' -import { CreateSessionForm } from '@renderer/types' import { buildAgentSessionTopicId } from '@renderer/utils/agentSession' import { motion } from 'framer-motion' import { memo, useCallback, useEffect } from 'react' @@ -27,11 +26,11 @@ interface SessionsProps { const Sessions: React.FC = ({ agentId }) => { const { t } = useTranslation() - const { agent } = useAgent(agentId) - const { sessions, isLoading, error, deleteSession, createSession } = useSessions(agentId) + const { sessions, isLoading, error, deleteSession } = useSessions(agentId) const { chat } = useRuntime() const { activeSessionIdMap } = chat const dispatch = useAppDispatch() + const { createDefaultSession, creatingSession } = useCreateDefaultSession(agentId) const setActiveSessionId = useCallback( (agentId: string, sessionId: string | null) => { @@ -41,19 +40,6 @@ const Sessions: React.FC = ({ agentId }) => { [dispatch] ) - const handleCreateSession = useCallback(async () => { - if (!agent) return - const session = { - ...agent, - id: undefined, - name: t('common.unnamed') - } satisfies CreateSessionForm - const created = await createSession(session) - if (created) { - dispatch(setActiveSessionIdAction({ agentId, sessionId: created.id })) - } - }, [agent, agentId, createSession, dispatch, t]) - const handleDeleteSession = useCallback( async (id: string) => { if (sessions.length === 1) { @@ -110,7 +96,7 @@ const Sessions: React.FC = ({ agentId }) => { return (
- + {t('agent.session.add.title')} {/* h-9 */}