From 2385fba6956f485a36f5d3bbe57fa6067e3720ab Mon Sep 17 00:00:00 2001 From: Vaayne Date: Mon, 22 Sep 2025 16:32:32 +0800 Subject: [PATCH 01/10] Refactor logging levels in transform.ts and adjust JSON body parser configuration in app.ts --- src/main/apiServer/app.ts | 7 +++++-- src/main/apiServer/utils/index.ts | 2 +- .../services/agents/services/claudecode/transform.ts | 11 +++-------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/apiServer/app.ts b/src/main/apiServer/app.ts index 1046399954..0c6615f1e8 100644 --- a/src/main/apiServer/app.ts +++ b/src/main/apiServer/app.ts @@ -15,6 +15,11 @@ import { modelsRoutes } from './routes/models' const logger = loggerService.withContext('ApiServer') const app = express() +app.use( + express.json({ + limit: '50mb' + }) +) // Global middleware app.use((req, res, next) => { @@ -111,7 +116,6 @@ app.get('/', (_req, res) => { // Provider-specific API routes with auth (must be before /v1 to avoid conflicts) const providerRouter = express.Router({ mergeParams: true }) providerRouter.use(authMiddleware) -providerRouter.use(express.json()) // Mount provider-specific messages route providerRouter.use('/v1/messages', messagesProviderRoutes) app.use('/:provider', providerRouter) @@ -119,7 +123,6 @@ app.use('/:provider', providerRouter) // API v1 routes with auth const apiRouter = express.Router() apiRouter.use(authMiddleware) -apiRouter.use(express.json()) // Mount routes apiRouter.use('/chat', chatRoutes) apiRouter.use('/mcps', mcpRoutes) diff --git a/src/main/apiServer/utils/index.ts b/src/main/apiServer/utils/index.ts index eebf226090..8f3f36a30b 100644 --- a/src/main/apiServer/utils/index.ts +++ b/src/main/apiServer/utils/index.ts @@ -7,7 +7,7 @@ const logger = loggerService.withContext('ApiServerUtils') // Cache configuration const PROVIDERS_CACHE_KEY = 'api-server:providers' -const PROVIDERS_CACHE_TTL = 5 * 60 * 1000 // 5 minutes +const PROVIDERS_CACHE_TTL = 1 * 60 * 1000 // 1 minutes export async function getAvailableProviders(): Promise { try { diff --git a/src/main/services/agents/services/claudecode/transform.ts b/src/main/services/agents/services/claudecode/transform.ts index 8c0d429a14..32a52875f6 100644 --- a/src/main/services/agents/services/claudecode/transform.ts +++ b/src/main/services/agents/services/claudecode/transform.ts @@ -34,7 +34,7 @@ const generateMessageId = (): string => `msg_${uuidv4().replace(/-/g, '')}` // Main transform function export function transformSDKMessageToStreamParts(sdkMessage: SDKMessage): AgentStreamPart[] { const chunks: AgentStreamPart[] = [] - logger.debug('Transforming SDKMessage to stream parts', sdkMessage) + logger.silly('Transforming SDKMessage to stream parts', sdkMessage) switch (sdkMessage.type) { case 'assistant': case 'user': @@ -105,7 +105,6 @@ function handleUserOrAssistantMessage(message: Extract): AgentStreamPart[] { const chunks: AgentStreamPart[] = [] - logger.debug('Received system message', { - subtype: message.subtype - }) switch (message.subtype) { case 'init': { chunks.push({ From 942543748046a2e834fd57af720b41b88e36c0f4 Mon Sep 17 00:00:00 2001 From: icarus Date: Mon, 22 Sep 2025 17:14:58 +0800 Subject: [PATCH 02/10] refactor(sessions): simplify session creation by removing modal and using direct button The SessionModal component was removed and replaced with a direct button click handler that creates a session using the agent data. Also added error handling to display an alert when session fetching fails. --- .../pages/home/Tabs/components/Sessions.tsx | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/renderer/src/pages/home/Tabs/components/Sessions.tsx b/src/renderer/src/pages/home/Tabs/components/Sessions.tsx index a8ccab7d00..f3bfed18d6 100644 --- a/src/renderer/src/pages/home/Tabs/components/Sessions.tsx +++ b/src/renderer/src/pages/home/Tabs/components/Sessions.tsx @@ -1,9 +1,10 @@ -import { Button, Spinner } from '@heroui/react' -import { SessionModal } from '@renderer/components/Popups/agent/SessionModal' +import { Alert, Button, Spinner } from '@heroui/react' +import { useAgent } from '@renderer/hooks/agents/useAgent' import { useSessions } from '@renderer/hooks/agents/useSessions' import { useRuntime } from '@renderer/hooks/useRuntime' import { useAppDispatch } from '@renderer/store' import { setActiveSessionIdAction, setActiveTopicOrSessionAction } from '@renderer/store/runtime' +import { CreateSessionForm } from '@renderer/types' import { AnimatePresence, motion } from 'framer-motion' import { Plus } from 'lucide-react' import { memo, useCallback, useEffect } from 'react' @@ -19,7 +20,8 @@ interface SessionsProps { const Sessions: React.FC = ({ agentId }) => { const { t } = useTranslation() - const { sessions, isLoading, deleteSession } = useSessions(agentId) + const { agent } = useAgent(agentId) + const { sessions, isLoading, error, deleteSession, createSession } = useSessions(agentId) const { chat } = useRuntime() const { activeSessionId } = chat const dispatch = useAppDispatch() @@ -32,6 +34,15 @@ const Sessions: React.FC = ({ agentId }) => { [dispatch] ) + const handleCreateSession = useCallback(() => { + if (!agent) return + const session = { + ...agent, + id: undefined + } satisfies CreateSessionForm + createSession(session) + }, [agent, createSession]) + const currentActiveSessionId = activeSessionId[agentId] useEffect(() => { @@ -52,7 +63,7 @@ const Sessions: React.FC = ({ agentId }) => { ) } - // if (error) return + if (error) return return ( = ({ agentId }) => { initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.2, delay: 0.1 }}> - setActiveSessionId(agentId, created.id)} - trigger={{ - content: ( - - ) - }} - /> + {sessions.map((session, index) => ( From 3c12f9052e220cb885cd8f6103ee8ac504b07375 Mon Sep 17 00:00:00 2001 From: icarus Date: Mon, 22 Sep 2025 17:48:05 +0800 Subject: [PATCH 03/10] feat(agents): add api server check and warning for agent features - Add api server enabled check in multiple components - Show warning alert when api server is disabled - Add dismissable warning in AgentSection - Disable send button when api server is disabled - Add iknow state to store dismissed warnings --- src/renderer/src/pages/home/Chat.tsx | 11 +++++- .../home/Inputbar/AgentSessionInputbar.tsx | 8 ++-- .../src/pages/home/Tabs/AssistantsTab.tsx | 4 +- .../src/pages/home/Tabs/SessionsTab.tsx | 12 +++++- .../home/Tabs/components/AgentSection.tsx | 39 +++++++++++++++++++ .../src/pages/home/Tabs/components/Agents.tsx | 6 +-- .../home/Tabs/components/SessionsSection.tsx | 0 src/renderer/src/store/runtime.ts | 8 +++- 8 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 src/renderer/src/pages/home/Tabs/components/AgentSection.tsx create mode 100644 src/renderer/src/pages/home/Tabs/components/SessionsSection.tsx diff --git a/src/renderer/src/pages/home/Chat.tsx b/src/renderer/src/pages/home/Chat.tsx index 5290d6285c..d5870add7f 100644 --- a/src/renderer/src/pages/home/Chat.tsx +++ b/src/renderer/src/pages/home/Chat.tsx @@ -1,3 +1,4 @@ +import { Alert } from '@heroui/react' import { loggerService } from '@logger' import { ContentSearch, ContentSearchRef } from '@renderer/components/ContentSearch' import { HStack } from '@renderer/components/Layout' @@ -49,6 +50,7 @@ const Chat: FC = (props) => { const chatMaxWidth = useChatMaxWidth() const { chat } = useRuntime() const { activeTopicOrSession, activeAgentId, activeSessionId } = chat + const { apiServer } = useSettings() const mainRef = React.useRef(null) const contentSearchRef = React.useRef(null) @@ -149,8 +151,15 @@ const Chat: FC = (props) => { if (!sessionId) { return () =>
Active Session ID is invalid.
} + if (!apiServer.enabled) { + return () => ( +
+ +
+ ) + } return () => - }, [activeAgentId, activeSessionId]) + }, [activeAgentId, activeSessionId, apiServer.enabled, t]) const SessionInputBar = useMemo(() => { if (activeAgentId === null) { diff --git a/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx b/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx index 000337152f..71d12a3929 100644 --- a/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx @@ -36,6 +36,7 @@ const AgentSessionInputbar: FC = ({ agentId, sessionId }) => { const [text, setText] = useState(_text) const [inputFocus, setInputFocus] = useState(false) const { session } = useSession(agentId, sessionId) + const { apiServer } = useSettings() const { sendMessageShortcut, fontSize, enableSpellCheck } = useSettings() const textareaRef = useRef(null) @@ -52,6 +53,7 @@ const AgentSessionInputbar: FC = ({ agentId, sessionId }) => { }, []) const inputEmpty = isEmpty(text) + const sendDisabled = inputEmpty || !apiServer.enabled const handleKeyDown = (event: React.KeyboardEvent) => { //to check if the SendMessage key is pressed @@ -96,7 +98,7 @@ const AgentSessionInputbar: FC = ({ agentId, sessionId }) => { } const sendMessage = useCallback(async () => { - if (inputEmpty) { + if (sendDisabled) { return } @@ -158,7 +160,7 @@ const AgentSessionInputbar: FC = ({ agentId, sessionId }) => { }, [ agentId, dispatch, - inputEmpty, + sendDisabled, session?.agent_id, session?.instructions, session?.model, @@ -231,7 +233,7 @@ const AgentSessionInputbar: FC = ({ agentId, sessionId }) => { onBlur={() => setInputFocus(false)} />
- +
diff --git a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx index 9299a75e2f..0fef9a968f 100644 --- a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx @@ -3,7 +3,7 @@ import { Assistant } from '@renderer/types' import { FC, useRef } from 'react' import styled from 'styled-components' -import { Agents } from './components/Agents' +import { AgentSection } from './components/AgentSection' import Assistants from './components/Assistants' interface AssistantsTabProps { @@ -17,7 +17,7 @@ const AssistantsTab: FC = (props) => { const containerRef = useRef(null) return ( - + ) diff --git a/src/renderer/src/pages/home/Tabs/SessionsTab.tsx b/src/renderer/src/pages/home/Tabs/SessionsTab.tsx index eb0d994d6c..fd75efc1e3 100644 --- a/src/renderer/src/pages/home/Tabs/SessionsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/SessionsTab.tsx @@ -1,5 +1,6 @@ -import { Spinner } from '@heroui/react' +import { Alert, Spinner } from '@heroui/react' import { useRuntime } from '@renderer/hooks/useRuntime' +import { useSettings } from '@renderer/hooks/useSettings' import { AnimatePresence, motion } from 'framer-motion' import { FC, memo } from 'react' import { useTranslation } from 'react-i18next' @@ -12,6 +13,15 @@ const SessionsTab: FC = () => { const { chat } = useRuntime() const { activeAgentId } = chat const { t } = useTranslation() + const { apiServer } = useSettings() + + if (!apiServer.enabled) { + return ( +
+ +
+ ) + } return ( diff --git a/src/renderer/src/pages/home/Tabs/components/AgentSection.tsx b/src/renderer/src/pages/home/Tabs/components/AgentSection.tsx new file mode 100644 index 0000000000..06eb072219 --- /dev/null +++ b/src/renderer/src/pages/home/Tabs/components/AgentSection.tsx @@ -0,0 +1,39 @@ +import { Alert } from '@heroui/react' +import { useRuntime } from '@renderer/hooks/useRuntime' +import { useSettings } from '@renderer/hooks/useSettings' +import { useAppDispatch } from '@renderer/store' +import { addIknowAction } from '@renderer/store/runtime' +import { useTranslation } from 'react-i18next' + +import { Agents } from './Agents' +import { SectionName } from './SectionName' + +const ALERT_KEY = 'enable_api_server_to_use_agent' + +export const AgentSection = () => { + const { t } = useTranslation() + const { apiServer } = useSettings() + const { iknow } = useRuntime() + const dispatch = useAppDispatch() + + if (!apiServer.enabled) { + if (iknow[ALERT_KEY]) return null + return ( + { + dispatch(addIknowAction(ALERT_KEY)) + }} + /> + ) + } + + return ( +
+ + +
+ ) +} diff --git a/src/renderer/src/pages/home/Tabs/components/Agents.tsx b/src/renderer/src/pages/home/Tabs/components/Agents.tsx index d6ef7b2c24..253f0888a0 100644 --- a/src/renderer/src/pages/home/Tabs/components/Agents.tsx +++ b/src/renderer/src/pages/home/Tabs/components/Agents.tsx @@ -9,7 +9,6 @@ import { FC, useCallback, useEffect } from 'react' import { useTranslation } from 'react-i18next' import AgentItem from './AgentItem' -import { SectionName } from './SectionName' interface AssistantsTabProps {} @@ -35,8 +34,7 @@ export const Agents: FC = () => { }, [isLoading, agents, activeAgentId, setActiveAgentId]) return ( -
- + <> {isLoading && } {error && } {!isLoading && @@ -65,6 +63,6 @@ export const Agents: FC = () => { ) }} /> -
+ ) } diff --git a/src/renderer/src/pages/home/Tabs/components/SessionsSection.tsx b/src/renderer/src/pages/home/Tabs/components/SessionsSection.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/renderer/src/store/runtime.ts b/src/renderer/src/store/runtime.ts index fafee81808..99dcc1914f 100644 --- a/src/renderer/src/store/runtime.ts +++ b/src/renderer/src/store/runtime.ts @@ -53,6 +53,7 @@ export interface RuntimeState { export: ExportState chat: ChatState websearch: WebSearchState + iknow: Record } export interface ExportState { @@ -93,7 +94,8 @@ const initialState: RuntimeState = { }, websearch: { activeSearches: {} - } + }, + iknow: {} } const runtimeSlice = createSlice({ @@ -179,6 +181,9 @@ const runtimeSlice = createSlice({ delete state.websearch.activeSearches[requestId] } state.websearch.activeSearches[requestId] = status + }, + addIknowAction: (state, action: PayloadAction) => { + state.iknow[action.payload] = true } } }) @@ -197,6 +202,7 @@ export const { setResourcesPath, setUpdateState, setExportState, + addIknowAction, // Chat related actions toggleMultiSelectMode, setSelectedMessageIds, From 1e9a811065244ae0f153b86a9e289a23cde11136 Mon Sep 17 00:00:00 2001 From: icarus Date: Mon, 22 Sep 2025 17:49:55 +0800 Subject: [PATCH 04/10] feat(i18n): add warning message for enabling API server Add warning message in multiple languages to inform users they need to enable API server to use agent features --- src/renderer/src/i18n/locales/en-us.json | 3 +++ src/renderer/src/i18n/locales/zh-cn.json | 3 +++ src/renderer/src/i18n/locales/zh-tw.json | 3 +++ src/renderer/src/i18n/translate/el-gr.json | 3 +++ src/renderer/src/i18n/translate/es-es.json | 3 +++ src/renderer/src/i18n/translate/fr-fr.json | 3 +++ src/renderer/src/i18n/translate/ja-jp.json | 3 +++ src/renderer/src/i18n/translate/pt-pt.json | 3 +++ src/renderer/src/i18n/translate/ru-ru.json | 3 +++ 9 files changed, 27 insertions(+) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 8af40a56e4..9aa40c3e0a 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -84,6 +84,9 @@ "error": { "failed": "Failed to update the agent" } + }, + "warning": { + "enable_server": "Enable API Server to use agents." } }, "agents": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 592a31612a..a91e78959b 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -84,6 +84,9 @@ "error": { "failed": "更新智能体失败" } + }, + "warning": { + "enable_server": "请启用 API 服务器以使用智能体功能" } }, "agents": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 3e2670ac45..ee56af1c9b 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -84,6 +84,9 @@ "error": { "failed": "無法更新代理程式" } + }, + "warning": { + "enable_server": "[to be translated]:Enable API Server to use agents." } }, "agents": { diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index c171834bd8..e18e8abb83 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -84,6 +84,9 @@ "error": { "failed": "Αποτυχία ενημέρωσης του πράκτορα" } + }, + "warning": { + "enable_server": "[to be translated]:Enable API Server to use agents." } }, "agents": { diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index a0b3e0b911..3579018426 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -84,6 +84,9 @@ "error": { "failed": "Error al actualizar el agente" } + }, + "warning": { + "enable_server": "[to be translated]:Enable API Server to use agents." } }, "agents": { diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 699afd5aaf..c1950340ae 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -84,6 +84,9 @@ "error": { "failed": "Échec de la mise à jour de l'agent" } + }, + "warning": { + "enable_server": "[to be translated]:Enable API Server to use agents." } }, "agents": { diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index fc36f737c3..8450c73f59 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -84,6 +84,9 @@ "error": { "failed": "エージェントの更新に失敗しました" } + }, + "warning": { + "enable_server": "[to be translated]:Enable API Server to use agents." } }, "agents": { diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index e87d37fa32..9a6bb00ee2 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -84,6 +84,9 @@ "error": { "failed": "Falha ao atualizar o agente" } + }, + "warning": { + "enable_server": "[to be translated]:Enable API Server to use agents." } }, "agents": { diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 7c98010d35..00b582f4a5 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -84,6 +84,9 @@ "error": { "failed": "Не удалось обновить агента" } + }, + "warning": { + "enable_server": "[to be translated]:Enable API Server to use agents." } }, "agents": { From b237d9d38d83058d020dc197f219239fe060e11f Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 22 Sep 2025 09:50:47 +0000 Subject: [PATCH 05/10] fix(i18n): Auto update translations for PR #10096 --- src/renderer/src/i18n/locales/zh-tw.json | 2 +- src/renderer/src/i18n/translate/el-gr.json | 2 +- src/renderer/src/i18n/translate/es-es.json | 2 +- src/renderer/src/i18n/translate/fr-fr.json | 2 +- src/renderer/src/i18n/translate/ja-jp.json | 2 +- src/renderer/src/i18n/translate/pt-pt.json | 2 +- src/renderer/src/i18n/translate/ru-ru.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index ee56af1c9b..75300f059f 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -86,7 +86,7 @@ } }, "warning": { - "enable_server": "[to be translated]:Enable API Server to use agents." + "enable_server": "啟用 API 伺服器以使用代理程式。" } }, "agents": { diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index e18e8abb83..203e925d0d 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -86,7 +86,7 @@ } }, "warning": { - "enable_server": "[to be translated]:Enable API Server to use agents." + "enable_server": "Ενεργοποίηση του διακομιστή API για χρήση πρακτόρων." } }, "agents": { diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 3579018426..dee1890436 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -86,7 +86,7 @@ } }, "warning": { - "enable_server": "[to be translated]:Enable API Server to use agents." + "enable_server": "Habilitar el servidor API para usar agentes." } }, "agents": { diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index c1950340ae..1a4f715565 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -86,7 +86,7 @@ } }, "warning": { - "enable_server": "[to be translated]:Enable API Server to use agents." + "enable_server": "Permettre au serveur API d'utiliser des agents." } }, "agents": { diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index 8450c73f59..da6a34f463 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -86,7 +86,7 @@ } }, "warning": { - "enable_server": "[to be translated]:Enable API Server to use agents." + "enable_server": "APIサーバーがエージェントを使用できるようにする。" } }, "agents": { diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 9a6bb00ee2..1c99745dfb 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -86,7 +86,7 @@ } }, "warning": { - "enable_server": "[to be translated]:Enable API Server to use agents." + "enable_server": "Ativar o Servidor de API para usar agentes." } }, "agents": { diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 00b582f4a5..2fc0cb859a 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -86,7 +86,7 @@ } }, "warning": { - "enable_server": "[to be translated]:Enable API Server to use agents." + "enable_server": "Разрешить серверу API использовать агентов." } }, "agents": { From 42dbc6555c34017101d267c30eca8b1a508218e9 Mon Sep 17 00:00:00 2001 From: icarus Date: Mon, 22 Sep 2025 17:55:21 +0800 Subject: [PATCH 06/10] feat(sessions): make session creation async and set active session Dispatch active session id after successful creation to ensure UI reflects current state --- src/renderer/src/pages/home/Tabs/components/Sessions.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/pages/home/Tabs/components/Sessions.tsx b/src/renderer/src/pages/home/Tabs/components/Sessions.tsx index f3bfed18d6..455e32aeca 100644 --- a/src/renderer/src/pages/home/Tabs/components/Sessions.tsx +++ b/src/renderer/src/pages/home/Tabs/components/Sessions.tsx @@ -34,14 +34,17 @@ const Sessions: React.FC = ({ agentId }) => { [dispatch] ) - const handleCreateSession = useCallback(() => { + const handleCreateSession = useCallback(async () => { if (!agent) return const session = { ...agent, id: undefined } satisfies CreateSessionForm - createSession(session) - }, [agent, createSession]) + const created = await createSession(session) + if (created) { + dispatch(setActiveSessionIdAction({ agentId, sessionId: created.id })) + } + }, [agent, agentId, createSession, dispatch]) const currentActiveSessionId = activeSessionId[agentId] From 52f00f08f21869463d597eba9b0cd536f34f108d Mon Sep 17 00:00:00 2001 From: icarus Date: Mon, 22 Sep 2025 18:47:24 +0800 Subject: [PATCH 07/10] feat(sessions): add session waiting state and improve deletion handling - Add sessionWaiting state to track updating/deleting sessions - Extract updateSession logic into separate hook - Improve session deletion with waiting state and fallback session selection - Disable session items during deletion to prevent duplicate actions --- .../components/Popups/agent/SessionModal.tsx | 6 ++-- src/renderer/src/hooks/agents/useSessions.ts | 22 +++--------- .../src/hooks/agents/useUpdateSession.ts | 36 +++++++++++++++++++ .../home/Tabs/components/SessionItem.tsx | 10 ++++-- .../pages/home/Tabs/components/Sessions.tsx | 35 ++++++++++++++++-- src/renderer/src/store/runtime.ts | 10 +++++- 6 files changed, 94 insertions(+), 25 deletions(-) create mode 100644 src/renderer/src/hooks/agents/useUpdateSession.ts diff --git a/src/renderer/src/components/Popups/agent/SessionModal.tsx b/src/renderer/src/components/Popups/agent/SessionModal.tsx index cf5750c124..64f8b1e628 100644 --- a/src/renderer/src/components/Popups/agent/SessionModal.tsx +++ b/src/renderer/src/components/Popups/agent/SessionModal.tsx @@ -20,6 +20,7 @@ import { getModelLogo } from '@renderer/config/models' import { useAgent } from '@renderer/hooks/agents/useAgent' import { useApiModels } from '@renderer/hooks/agents/useModels' import { useSessions } from '@renderer/hooks/agents/useSessions' +import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession' import { AgentEntity, AgentSessionEntity, BaseSessionForm, CreateSessionForm, UpdateSessionForm } from '@renderer/types' import { ChangeEvent, FormEvent, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -86,7 +87,8 @@ export const SessionModal: React.FC = ({ const { t } = useTranslation() const loadingRef = useRef(false) // const { setTimeoutTimer } = useTimer() - const { createSession, updateSession } = useSessions(agentId) + const { createSession } = useSessions(agentId) + const updateSession = useUpdateSession(agentId) // Only support claude code for now const { models } = useApiModels({ providerType: 'anthropic' }) const { agent } = useAgent(agentId) @@ -164,7 +166,7 @@ export const SessionModal: React.FC = ({ } if (form.accessible_paths.length === 0) { - window.toast.error(t('agent.session.accessible_paths.required')) + window.toast.error(t('agent.session.accessible_paths.error.at_least_one')) loadingRef.current = false return } diff --git a/src/renderer/src/hooks/agents/useSessions.ts b/src/renderer/src/hooks/agents/useSessions.ts index 9e83ac5afc..3307d0353d 100644 --- a/src/renderer/src/hooks/agents/useSessions.ts +++ b/src/renderer/src/hooks/agents/useSessions.ts @@ -1,4 +1,4 @@ -import { CreateSessionForm, UpdateSessionForm } from '@renderer/types' +import { CreateSessionForm } from '@renderer/types' import { formatErrorMessageWithPrefix } from '@renderer/utils/error' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' @@ -46,25 +46,14 @@ export const useSessions = (agentId: string) => { const deleteSession = useCallback( async (id: string) => { - if (!agentId) return + if (!agentId) return false try { await client.deleteSession(agentId, id) mutate((prev) => prev?.filter((session) => session.id !== id)) + return true } catch (error) { window.toast.error(formatErrorMessageWithPrefix(error, t('agent.session.delete.error.failed'))) - } - }, - [agentId, client, mutate, t] - ) - - const updateSession = useCallback( - async (form: UpdateSessionForm) => { - if (!agentId) return - try { - const result = await client.updateSession(agentId, form) - mutate((prev) => prev?.map((session) => (session.id === form.id ? result : session))) - } catch (error) { - window.toast.error(formatErrorMessageWithPrefix(error, t('agent.session.update.error.failed'))) + return false } }, [agentId, client, mutate, t] @@ -76,7 +65,6 @@ export const useSessions = (agentId: string) => { isLoading, createSession, getSession, - deleteSession, - updateSession + deleteSession } } diff --git a/src/renderer/src/hooks/agents/useUpdateSession.ts b/src/renderer/src/hooks/agents/useUpdateSession.ts new file mode 100644 index 0000000000..4e91c2fda9 --- /dev/null +++ b/src/renderer/src/hooks/agents/useUpdateSession.ts @@ -0,0 +1,36 @@ +import { ListAgentSessionsResponse, UpdateSessionForm } from '@renderer/types' +import { formatErrorMessageWithPrefix } from '@renderer/utils/error' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import { mutate } from 'swr' + +import { useAgentClient } from './useAgentClient' + +export const useUpdateSession = (agentId: string) => { + const { t } = useTranslation() + const client = useAgentClient() + const paths = client.getSessionPaths(agentId) + const listKey = paths.base + + const updateSession = useCallback( + async (form: UpdateSessionForm) => { + const sessionId = form.id + try { + const itemKey = paths.withId(sessionId) + // may change to optimistic update + const result = await client.updateSession(agentId, form) + mutate( + listKey, + (prev) => prev?.map((session) => (session.id === result.id ? result : session)) ?? [] + ) + mutate(itemKey, result) + window.toast.success(t('common.update_success')) + } catch (error) { + window.toast.error(formatErrorMessageWithPrefix(error, t('agent.update.error.failed'))) + } + }, + [agentId, client, listKey, paths, t] + ) + + return updateSession +} diff --git a/src/renderer/src/pages/home/Tabs/components/SessionItem.tsx b/src/renderer/src/pages/home/Tabs/components/SessionItem.tsx index 9d49627f1f..e51d485dd4 100644 --- a/src/renderer/src/pages/home/Tabs/components/SessionItem.tsx +++ b/src/renderer/src/pages/home/Tabs/components/SessionItem.tsx @@ -13,11 +13,13 @@ interface SessionItemProps { session: AgentSessionEntity // use external agentId as SSOT, instead of session.agent_id agentId: string + isDisabled?: boolean + isLoading?: boolean onDelete: () => void onPress: () => void } -const SessionItem: FC = ({ session, agentId, onDelete, onPress }) => { +const SessionItem: FC = ({ session, agentId, isDisabled, isLoading, onDelete, onPress }) => { const { t } = useTranslation() const { isOpen, onOpen, onClose } = useDisclosure() const { chat } = useRuntime() @@ -38,7 +40,11 @@ const SessionItem: FC = ({ session, agentId, onDelete, onPress <> - + diff --git a/src/renderer/src/pages/home/Tabs/components/Sessions.tsx b/src/renderer/src/pages/home/Tabs/components/Sessions.tsx index 455e32aeca..dc2263661a 100644 --- a/src/renderer/src/pages/home/Tabs/components/Sessions.tsx +++ b/src/renderer/src/pages/home/Tabs/components/Sessions.tsx @@ -1,9 +1,14 @@ import { Alert, Button, Spinner } from '@heroui/react' import { useAgent } from '@renderer/hooks/agents/useAgent' import { useSessions } from '@renderer/hooks/agents/useSessions' +import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession' import { useRuntime } from '@renderer/hooks/useRuntime' import { useAppDispatch } from '@renderer/store' -import { setActiveSessionIdAction, setActiveTopicOrSessionAction } from '@renderer/store/runtime' +import { + setActiveSessionIdAction, + setActiveTopicOrSessionAction, + setSessionWaitingAction +} from '@renderer/store/runtime' import { CreateSessionForm } from '@renderer/types' import { AnimatePresence, motion } from 'framer-motion' import { Plus } from 'lucide-react' @@ -22,8 +27,9 @@ const Sessions: React.FC = ({ agentId }) => { const { t } = useTranslation() const { agent } = useAgent(agentId) const { sessions, isLoading, error, deleteSession, createSession } = useSessions(agentId) + const updateSession = useUpdateSession(agentId) const { chat } = useRuntime() - const { activeSessionId } = chat + const { activeSessionId, sessionWaiting } = chat const dispatch = useAppDispatch() const setActiveSessionId = useCallback( @@ -46,6 +52,27 @@ const Sessions: React.FC = ({ agentId }) => { } }, [agent, agentId, createSession, dispatch]) + const handleDeleteSession = useCallback( + async (id: string) => { + if (sessions.length === 1) { + window.toast.error(t('agent.session.delete.error.last')) + return + } + dispatch(setSessionWaitingAction({ id, value: true })) + const success = await deleteSession(id) + if (success) { + const newSessionId = sessions.find((s) => s.id !== id)?.id + if (newSessionId) { + dispatch(setActiveSessionIdAction({ agentId, sessionId: newSessionId })) + } else { + // may clear messages instead of forbidden deletion + } + } + dispatch(setSessionWaitingAction({ id, value: false })) + }, + [agentId, deleteSession, dispatch, sessions] + ) + const currentActiveSessionId = activeSessionId[agentId] useEffect(() => { @@ -96,7 +123,9 @@ const Sessions: React.FC = ({ agentId }) => { deleteSession(session.id)} + isDisabled={sessionWaiting[session.id]} + isLoading={sessionWaiting[session.id]} + onDelete={() => handleDeleteSession(session.id)} onPress={() => setActiveSessionId(agentId, session.id)} /> diff --git a/src/renderer/src/store/runtime.ts b/src/renderer/src/store/runtime.ts index 99dcc1914f..4c0183e9e4 100644 --- a/src/renderer/src/store/runtime.ts +++ b/src/renderer/src/store/runtime.ts @@ -18,6 +18,8 @@ export interface ChatState { renamingTopics: string[] /** topic ids that are newly renamed */ newlyRenamedTopics: string[] + /** is a session waiting for updating/deleting. undefined and false share same semantics. */ + sessionWaiting: Record } export interface WebSearchState { @@ -90,7 +92,8 @@ const initialState: RuntimeState = { activeTopicOrSession: 'topic', activeSessionId: {}, renamingTopics: [], - newlyRenamedTopics: [] + newlyRenamedTopics: [], + sessionWaiting: {} }, websearch: { activeSearches: {} @@ -184,6 +187,10 @@ const runtimeSlice = createSlice({ }, addIknowAction: (state, action: PayloadAction) => { state.iknow[action.payload] = true + }, + setSessionWaitingAction: (state, action: PayloadAction<{ id: string; value: boolean }>) => { + const { id, value } = action.payload + state.chat.sessionWaiting[id] = value } } }) @@ -212,6 +219,7 @@ export const { setActiveTopicOrSessionAction, setRenamingTopics, setNewlyRenamedTopics, + setSessionWaitingAction, // WebSearch related actions setActiveSearches, setWebSearchStatus From 632871b2f89ef45f4da0c6a2139dd47ea5e224ee Mon Sep 17 00:00:00 2001 From: icarus Date: Mon, 22 Sep 2025 18:48:11 +0800 Subject: [PATCH 08/10] feat(i18n): add error message for last session deletion Add error message to prevent deletion of the last session in all supported languages --- src/renderer/src/i18n/locales/en-us.json | 3 ++- src/renderer/src/i18n/locales/zh-cn.json | 3 ++- src/renderer/src/i18n/locales/zh-tw.json | 3 ++- src/renderer/src/i18n/translate/el-gr.json | 3 ++- src/renderer/src/i18n/translate/es-es.json | 3 ++- src/renderer/src/i18n/translate/fr-fr.json | 3 ++- src/renderer/src/i18n/translate/ja-jp.json | 3 ++- src/renderer/src/i18n/translate/pt-pt.json | 3 ++- src/renderer/src/i18n/translate/ru-ru.json | 3 ++- 9 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 9aa40c3e0a..b7cba46a9d 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -52,7 +52,8 @@ "delete": { "content": "Are you sure to delete this session?", "error": { - "failed": "Failed to delete the session" + "failed": "Failed to delete the session", + "last": "At least one session must be kept" }, "title": "Delete session" }, diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index a91e78959b..d7287036a6 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -52,7 +52,8 @@ "delete": { "content": "确定要删除此会话吗?", "error": { - "failed": "删除会话失败" + "failed": "删除会话失败", + "last": "至少需要保留一个会话" }, "title": "删除会话" }, diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index ee56af1c9b..90f559a2fa 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -52,7 +52,8 @@ "delete": { "content": "您確定要刪除此工作階段嗎?", "error": { - "failed": "無法刪除工作階段" + "failed": "無法刪除工作階段", + "last": "[to be translated]:At least one session must be kept" }, "title": "刪除工作階段" }, diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index e18e8abb83..a82352dbf8 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -52,7 +52,8 @@ "delete": { "content": "Είσαι σίγουρος ότι θέλεις να διαγράψεις αυτήν τη συνεδρία;", "error": { - "failed": "Αποτυχία διαγραφής της συνεδρίας" + "failed": "Αποτυχία διαγραφής της συνεδρίας", + "last": "[to be translated]:At least one session must be kept" }, "title": "Διαγραφή συνεδρίας" }, diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 3579018426..f8e074db67 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -52,7 +52,8 @@ "delete": { "content": "¿Estás seguro de eliminar esta sesión?", "error": { - "failed": "Error al eliminar la sesión" + "failed": "Error al eliminar la sesión", + "last": "[to be translated]:At least one session must be kept" }, "title": "Eliminar sesión" }, diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index c1950340ae..9c9fab70bb 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -52,7 +52,8 @@ "delete": { "content": "Êtes-vous sûr de vouloir supprimer cette session ?", "error": { - "failed": "Échec de la suppression de la session" + "failed": "Échec de la suppression de la session", + "last": "[to be translated]:At least one session must be kept" }, "title": "Supprimer la session" }, diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index 8450c73f59..3c09c2ea58 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -52,7 +52,8 @@ "delete": { "content": "このセッションを削除してもよろしいですか?", "error": { - "failed": "セッションの削除に失敗しました" + "failed": "セッションの削除に失敗しました", + "last": "[to be translated]:At least one session must be kept" }, "title": "セッションを削除" }, diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 9a6bb00ee2..5e6611daad 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -52,7 +52,8 @@ "delete": { "content": "Tem certeza de que deseja excluir esta sessão?", "error": { - "failed": "Falha ao excluir a sessão" + "failed": "Falha ao excluir a sessão", + "last": "[to be translated]:At least one session must be kept" }, "title": "Excluir sessão" }, diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 00b582f4a5..e84f6ca85c 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -52,7 +52,8 @@ "delete": { "content": "Вы уверены, что хотите удалить этот сеанс?", "error": { - "failed": "Не удалось удалить сеанс" + "failed": "Не удалось удалить сеанс", + "last": "[to be translated]:At least one session must be kept" }, "title": "Удалить сеанс" }, From c9d1e30f8b5cb9b85262652c478db76b86e84dbc Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 22 Sep 2025 10:49:01 +0000 Subject: [PATCH 09/10] fix(i18n): Auto update translations for PR #10096 --- src/renderer/src/i18n/locales/zh-tw.json | 2 +- src/renderer/src/i18n/translate/el-gr.json | 2 +- src/renderer/src/i18n/translate/es-es.json | 2 +- src/renderer/src/i18n/translate/fr-fr.json | 2 +- src/renderer/src/i18n/translate/ja-jp.json | 2 +- src/renderer/src/i18n/translate/pt-pt.json | 2 +- src/renderer/src/i18n/translate/ru-ru.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index bf61c815f8..fbe922dbe3 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -53,7 +53,7 @@ "content": "您確定要刪除此工作階段嗎?", "error": { "failed": "無法刪除工作階段", - "last": "[to be translated]:At least one session must be kept" + "last": "至少必須保留一個工作階段" }, "title": "刪除工作階段" }, diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 627c820816..8af53b669c 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -53,7 +53,7 @@ "content": "Είσαι σίγουρος ότι θέλεις να διαγράψεις αυτήν τη συνεδρία;", "error": { "failed": "Αποτυχία διαγραφής της συνεδρίας", - "last": "[to be translated]:At least one session must be kept" + "last": "Τουλάχιστον μία συνεδρία πρέπει να διατηρηθεί" }, "title": "Διαγραφή συνεδρίας" }, diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 2e15e3cfe7..0be385d740 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -53,7 +53,7 @@ "content": "¿Estás seguro de eliminar esta sesión?", "error": { "failed": "Error al eliminar la sesión", - "last": "[to be translated]:At least one session must be kept" + "last": "Debe mantenerse al menos una sesión" }, "title": "Eliminar sesión" }, diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index d35be1a3c3..93209acb5a 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -53,7 +53,7 @@ "content": "Êtes-vous sûr de vouloir supprimer cette session ?", "error": { "failed": "Échec de la suppression de la session", - "last": "[to be translated]:At least one session must be kept" + "last": "Au moins une session doit être conservée" }, "title": "Supprimer la session" }, diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index 7b0465d278..d23b5d15c9 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -53,7 +53,7 @@ "content": "このセッションを削除してもよろしいですか?", "error": { "failed": "セッションの削除に失敗しました", - "last": "[to be translated]:At least one session must be kept" + "last": "少なくとも1つのセッションを維持する必要があります" }, "title": "セッションを削除" }, diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 856b46b958..728347565c 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -53,7 +53,7 @@ "content": "Tem certeza de que deseja excluir esta sessão?", "error": { "failed": "Falha ao excluir a sessão", - "last": "[to be translated]:At least one session must be kept" + "last": "Pelo menos uma sessão deve ser mantida" }, "title": "Excluir sessão" }, diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 538fc7f1da..59ff59243a 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -53,7 +53,7 @@ "content": "Вы уверены, что хотите удалить этот сеанс?", "error": { "failed": "Не удалось удалить сеанс", - "last": "[to be translated]:At least one session must be kept" + "last": "Должен быть сохранён хотя бы один сеанс" }, "title": "Удалить сеанс" }, From 373b2fcd78fb17b71274fea15308e9ab9f35a182 Mon Sep 17 00:00:00 2001 From: suyao Date: Mon, 22 Sep 2025 19:24:20 +0800 Subject: [PATCH 10/10] feat: abort --- .../home/Inputbar/AgentSessionInputbar.tsx | 65 ++++++++++++++++++- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx b/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx index 71d12a3929..618d70e668 100644 --- a/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx @@ -1,21 +1,28 @@ +import { Tooltip } from '@heroui/react' import { loggerService } from '@logger' +import { ActionIconButton } from '@renderer/components/Buttons' import { QuickPanelView } from '@renderer/components/QuickPanel' 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 { useTimer } from '@renderer/hooks/useTimer' import PasteService from '@renderer/services/PasteService' -import { useAppDispatch } from '@renderer/store' +import { pauseTrace } from '@renderer/services/SpanManagerService' +import { useAppDispatch, useAppSelector } from '@renderer/store' +import { newMessagesActions, selectMessagesForTopic } from '@renderer/store/newMessage' import { sendMessage as dispatchSendMessage } from '@renderer/store/thunk/messageThunk' import type { Assistant, Message, Model, Topic } from '@renderer/types' import { MessageBlock, MessageBlockStatus } from '@renderer/types/newMessage' import { classNames } from '@renderer/utils' +import { abortCompletion } from '@renderer/utils/abortController' import { buildAgentSessionTopicId } from '@renderer/utils/agentSession' import { getSendMessageShortcutLabel, isSendMessageKeyPressed } from '@renderer/utils/input' import { createMainTextBlock, createMessage } from '@renderer/utils/messageUtils/create' import TextArea, { TextAreaRef } from 'antd/es/input/TextArea' import { isEmpty } from 'lodash' -import React, { CSSProperties, FC, useCallback, useEffect, useRef, useState } from 'react' +import { CirclePause } from 'lucide-react' +import React, { CSSProperties, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' import { v4 as uuid } from 'uuid' @@ -47,6 +54,8 @@ const AgentSessionInputbar: FC = ({ agentId, sessionId }) => { const { setTimeoutTimer } = useTimer() const dispatch = useAppDispatch() const sessionTopicId = buildAgentSessionTopicId(sessionId) + const topicMessages = useAppSelector((state) => selectMessagesForTopic(state, sessionTopicId)) + const loading = useAppSelector((state) => selectNewTopicLoading(state, sessionTopicId)) const focusTextarea = useCallback(() => { textareaRef.current?.focus() @@ -55,6 +64,28 @@ const AgentSessionInputbar: FC = ({ agentId, sessionId }) => { const inputEmpty = isEmpty(text) const sendDisabled = inputEmpty || !apiServer.enabled + const streamingAskIds = useMemo(() => { + if (!topicMessages) { + return [] + } + + const askIdSet = new Set() + for (const message of topicMessages) { + if (!message) continue + if (message.status === 'processing' || message.status === 'pending') { + if (message.askId) { + askIdSet.add(message.askId) + } else if (message.id) { + askIdSet.add(message.id) + } + } + } + + return Array.from(askIdSet) + }, [topicMessages]) + + const canAbort = loading && streamingAskIds.length > 0 + const handleKeyDown = (event: React.KeyboardEvent) => { //to check if the SendMessage key is pressed //other keys should be ignored @@ -97,6 +128,25 @@ const AgentSessionInputbar: FC = ({ agentId, sessionId }) => { } } + const abortAgentSession = useCallback(async () => { + if (!streamingAskIds.length) { + logger.debug('No active agent session streams to abort', { sessionTopicId }) + return + } + + logger.info('Aborting agent session message generation', { + sessionTopicId, + askIds: streamingAskIds + }) + + for (const askId of streamingAskIds) { + abortCompletion(askId) + } + + pauseTrace(sessionTopicId) + dispatch(newMessagesActions.setTopicLoading({ topicId: sessionTopicId, loading: false })) + }, [dispatch, sessionTopicId, streamingAskIds]) + const sendMessage = useCallback(async () => { if (sendDisabled) { return @@ -233,7 +283,16 @@ const AgentSessionInputbar: FC = ({ agentId, sessionId }) => { onBlur={() => setInputFocus(false)} />
- +
+ + {canAbort && ( + + + + + + )} +