mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-31 00:10:22 +08:00
feat: allow new-topic bindkey to create new session for agent as well (#10862)
* fix: allow new-topic shortcut to create agent sessions * revert: restore ProxyManager.ts * fix: make agent session shortcut sidebar-independent * refactor: centralize default session creation * feat: add new session button to agent inputbar * fix: encapsulate agent session creation state * refactor: remove redundant useMemo in useCreateDefaultSession The useMemo wrapper around the return object was unnecessary because: - createDefaultSession is already memoized via useCallback - creatingSession is a primitive boolean that doesn't need memoization - The object gets recreated on every creatingSession change anyway This simplifies the code and removes unnecessary overhead. --------- Co-authored-by: wangdenghui <wangdenghui@xiaomi.com>
This commit is contained in:
parent
5986800c9d
commit
9a01e092f6
49
src/renderer/src/hooks/agents/useCreateDefaultSession.ts
Normal file
49
src/renderer/src/hooks/agents/useCreateDefaultSession.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
@ -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> = (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<HTMLDivElement>(null)
|
||||
const contentSearchRef = React.useRef<ContentSearchRef>(null)
|
||||
@ -90,6 +93,21 @@ const Chat: FC<Props> = (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')
|
||||
|
||||
@ -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<Props> = ({ 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<TextAreaRef>(null)
|
||||
@ -87,6 +91,22 @@ const AgentSessionInputbar: FC<Props> = ({ 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<HTMLTextAreaElement>) => {
|
||||
//to check if the SendMessage key is pressed
|
||||
@ -286,8 +306,18 @@ const AgentSessionInputbar: FC<Props> = ({ agentId, sessionId }) => {
|
||||
}}
|
||||
onBlur={() => setInputFocus(false)}
|
||||
/>
|
||||
<div className="flex justify-end px-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<Toolbar>
|
||||
<ToolbarGroup>
|
||||
<Tooltip placement="top" content={t('chat.input.new_topic', { Command: newTopicShortcut })} delay={0}>
|
||||
<ActionIconButton
|
||||
onClick={handleCreateSession}
|
||||
disabled={createSessionDisabled}
|
||||
loading={creatingSession}>
|
||||
<MessageSquareDiff size={19} />
|
||||
</ActionIconButton>
|
||||
</Tooltip>
|
||||
</ToolbarGroup>
|
||||
<ToolbarGroup>
|
||||
<SendMessageButton sendMessage={sendMessage} disabled={sendDisabled} />
|
||||
{canAbort && (
|
||||
<Tooltip placement="top" content={t('chat.input.pause')}>
|
||||
@ -296,8 +326,8 @@ const AgentSessionInputbar: FC<Props> = ({ agentId, sessionId }) => {
|
||||
</ActionIconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ToolbarGroup>
|
||||
</Toolbar>
|
||||
</InputBarContainer>
|
||||
</Container>
|
||||
</NarrowLayout>
|
||||
@ -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
|
||||
|
||||
@ -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<SessionsProps> = ({ 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<SessionsProps> = ({ 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<SessionsProps> = ({ agentId }) => {
|
||||
|
||||
return (
|
||||
<div className="sessions-tab flex h-full w-full flex-col p-2">
|
||||
<AddButton onPress={handleCreateSession} className="mb-2">
|
||||
<AddButton onPress={createDefaultSession} className="mb-2" isDisabled={creatingSession}>
|
||||
{t('agent.session.add.title')}
|
||||
</AddButton>
|
||||
{/* h-9 */}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user