From 4eb3aa31eeedba0fa4d35c20097e149f1f219f64 Mon Sep 17 00:00:00 2001 From: Phantom Date: Fri, 17 Oct 2025 19:44:47 +0800 Subject: [PATCH] feat: session settings (#10773) * fix(home/Tabs): remove redundant isTopicView check in tab rendering * refactor(runtime): rename activeSessionId to activeSessionIdMap for clarity Update variable name to better reflect its purpose as a mapping structure * refactor(agent): add CreateAgentSessionResponse type and schema Add new type and schema for create session response to better reflect API contract * fix(useSessions): return null instead of undefined on session creation error Returning null provides better type safety and aligns with the response type Promise * refactor(useSessions): add return type to deleteSession callback * fix(useSessions): return session data or null from getSession Ensure getSession callback always returns a value (session data or null) to handle error cases properly and improve type safety * feat(hooks): add useActiveSession hook and handle null agentId in useSessions Add new hook to get active session from runtime and sessions data. Update useSessions to handle null agentId cases by returning early and adding null checks. * feat(hooks): add useActiveAgent hook to get active agent Expose active agent data by combining useRuntime and useAgent hooks * fix(agents): remove fake agent id handling and improve null checks - Replace fake agent id with null in HomePage - Remove fake id check in useAgent hook and throw error for null id - Simplify agent session initialization by removing fake id checks * refactor(hooks): replace useAgent with useActiveAgent for active agent state * feat(home): add session settings tab component Replace AgentSettingsTab with SessionSettingsTab to better handle session-specific settings. The new component includes essential and advanced settings sections with a more settings button. * refactor(settings): consolidate agent and session essential settings into single component Replace AgentEssentialSettings and SessionEssentialSettings with a unified EssentialSettings component that handles both agent and session types. This reduces code duplication and improves maintainability. * style(SelectAgentModelButton): improve model name display with truncation Add overflow-x-hidden to container and truncate to model name span to prevent text overflow * refactor(AgentSettings): replace Ellipsis with truncate for text overflow Use CSS truncate instead of Ellipsis component for better performance and consistency * refactor(chat-navbar): replace useAgent and useSession with useActiveAgent and useActiveSession Simplify component logic by using dedicated hooks for active agent and session * feat(ChatNavbar): add session settings button to breadcrumb Add clickable session label chip that opens session settings popup when active session exists * refactor(agents): improve session update hook and type definitions - Extract UpdateAgentBaseOptions type to shared types file - Update useUpdateSession to return both updateSession and updateModel functions - Modify components to use destructured updateSession from hook - Add null check for agentId in useUpdateSession - Add success toast option to session updates * refactor(components): rename agent prop to agentBase for clarity Update component name and prop to better reflect its purpose and improve code readability * refactor(ChatNavbar): rename SelectAgentModelButton to SelectAgentBaseModelButton and update usage Update component name to better reflect its purpose and adjust props to use activeSession instead of activeAgent for consistency * feat(i18n): add null id error message for agent retrieval Add error message for when agent ID is null across all supported languages * refactor(hooks): simplify agent and session hooks by returning destructured values Remove unnecessary intermediate variables and directly return hook results Update useSession to handle null agentId and sessionId cases * feat(i18n): add null session ID error message for all locales * refactor(home): rename SelectAgentModelButton to SelectAgentBaseModelButton The component was renamed to better reflect its purpose of selecting base models for agents. The functionality remains unchanged. * refactor(session): rename useUpdateAgent to useUpdateSession for clarity * refactor(home-tabs): replace useUpdateAgent with useUpdateSession hook Update session settings tab to use the new useUpdateSession hook which requires activeAgentId * style(AgentSettings): remove unnecessary gap class from ModelSetting * refactor(agents): improve error handling and remove duplicate code - Replace formatErrorMessageWithPrefix with getErrorMessage for better error handling - Move updateSession logic to useUpdateSession hook to avoid duplication * fix(ChatNavbar): prevent model update when activeAgent is missing Add activeAgent to dependency array and check its existence before updating model to avoid potential errors * feat(home/Tabs): add loading and error states for session settings Add Skeleton loader and Alert component to handle loading and error states when fetching session data in the settings tab * fix(home/Tabs): add h-full class to Skeleton for proper height * fix(AssistantsTab): remove weird effect hook for agent selection * refactor(chat-navbar): clean up unused code and update session handling remove commented out code in ChatNavbar.tsx and update ChatNavbarContent to use active agent/session hooks * style(home): remove negative margin from model name span * refactor(Agents): mark Agents component as deprecated * refactor: remove unused Agents and Assistants code --------- Co-authored-by: dev --- src/renderer/src/api/agent.ts | 6 +- .../components/Popups/agent/SessionModal.tsx | 2 +- src/renderer/src/hooks/agents/types.ts | 4 + .../src/hooks/agents/useActiveAgent.ts | 8 + .../src/hooks/agents/useActiveSession.ts | 9 + src/renderer/src/hooks/agents/useAgent.ts | 4 +- .../agents/useAgentSessionInitializer.ts | 14 +- src/renderer/src/hooks/agents/useSession.ts | 28 +-- src/renderer/src/hooks/agents/useSessions.ts | 20 +- .../src/hooks/agents/useUpdateAgent.ts | 10 +- .../src/hooks/agents/useUpdateSession.ts | 36 ++- src/renderer/src/i18n/locales/en-us.json | 6 +- src/renderer/src/i18n/locales/zh-cn.json | 6 +- src/renderer/src/i18n/locales/zh-tw.json | 6 +- src/renderer/src/i18n/translate/el-gr.json | 6 +- src/renderer/src/i18n/translate/es-es.json | 6 +- src/renderer/src/i18n/translate/fr-fr.json | 6 +- src/renderer/src/i18n/translate/ja-jp.json | 6 +- src/renderer/src/i18n/translate/pt-pt.json | 6 +- src/renderer/src/i18n/translate/ru-ru.json | 6 +- src/renderer/src/pages/home/Chat.tsx | 19 +- src/renderer/src/pages/home/ChatNavbar.tsx | 8 + src/renderer/src/pages/home/HomePage.tsx | 2 +- .../src/pages/home/Tabs/AgentSettingsTab.tsx | 40 ---- .../src/pages/home/Tabs/AssistantsTab.tsx | 8 +- .../pages/home/Tabs/SessionSettingsTab.tsx | 43 ++++ .../src/pages/home/Tabs/components/Agents.tsx | 71 ------ .../pages/home/Tabs/components/Assistants.tsx | 208 ------------------ .../home/Tabs/components/SessionItem.tsx | 4 +- .../pages/home/Tabs/components/Sessions.tsx | 14 +- src/renderer/src/pages/home/Tabs/index.tsx | 30 ++- .../home/components/ChatNavbarContent.tsx | 80 ++++--- ...ton.tsx => SelectAgentBaseModelButton.tsx} | 10 +- .../AgentSettings/AdvancedSettings.tsx | 2 +- .../AgentSettings/AgentEssentialSettings.tsx | 47 ---- .../AgentSettings/AgentSettingsPopup.tsx | 4 +- .../AgentSettings/EssentialSettings.tsx | 56 +++++ .../settings/AgentSettings/ModelSetting.tsx | 6 +- .../settings/AgentSettings/PromptSettings.tsx | 2 +- .../SessionEssentialSettings.tsx | 29 --- .../AgentSettings/SessionSettingsPopup.tsx | 6 +- .../pages/settings/AgentSettings/shared.tsx | 9 +- src/renderer/src/store/runtime.ts | 6 +- src/renderer/src/types/agent.ts | 4 + 44 files changed, 348 insertions(+), 555 deletions(-) create mode 100644 src/renderer/src/hooks/agents/types.ts create mode 100644 src/renderer/src/hooks/agents/useActiveAgent.ts create mode 100644 src/renderer/src/hooks/agents/useActiveSession.ts delete mode 100644 src/renderer/src/pages/home/Tabs/AgentSettingsTab.tsx create mode 100644 src/renderer/src/pages/home/Tabs/SessionSettingsTab.tsx delete mode 100644 src/renderer/src/pages/home/Tabs/components/Agents.tsx delete mode 100644 src/renderer/src/pages/home/Tabs/components/Assistants.tsx rename src/renderer/src/pages/home/components/{SelectAgentModelButton.tsx => SelectAgentBaseModelButton.tsx} (87%) delete mode 100644 src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx create mode 100644 src/renderer/src/pages/settings/AgentSettings/EssentialSettings.tsx delete mode 100644 src/renderer/src/pages/settings/AgentSettings/SessionEssentialSettings.tsx diff --git a/src/renderer/src/api/agent.ts b/src/renderer/src/api/agent.ts index 2b31873ce1..a16e4eb600 100644 --- a/src/renderer/src/api/agent.ts +++ b/src/renderer/src/api/agent.ts @@ -9,6 +9,8 @@ import { CreateAgentRequest, CreateAgentResponse, CreateAgentResponseSchema, + CreateAgentSessionResponse, + CreateAgentSessionResponseSchema, CreateSessionForm, CreateSessionRequest, GetAgentResponse, @@ -171,12 +173,12 @@ export class AgentApiClient { } } - public async createSession(agentId: string, session: CreateSessionForm): Promise { + public async createSession(agentId: string, session: CreateSessionForm): Promise { const url = this.getSessionPaths(agentId).base try { const payload = session satisfies CreateSessionRequest const response = await this.axios.post(url, payload) - const data = GetAgentSessionResponseSchema.parse(response.data) + const data = CreateAgentSessionResponseSchema.parse(response.data) return data } catch (error) { throw processError(error, 'Failed to add session.') diff --git a/src/renderer/src/components/Popups/agent/SessionModal.tsx b/src/renderer/src/components/Popups/agent/SessionModal.tsx index aaf0029ee7..80ca25f475 100644 --- a/src/renderer/src/components/Popups/agent/SessionModal.tsx +++ b/src/renderer/src/components/Popups/agent/SessionModal.tsx @@ -98,7 +98,7 @@ export const SessionModal: React.FC = ({ const loadingRef = useRef(false) // const { setTimeoutTimer } = useTimer() const { createSession } = useSessions(agentId) - const updateSession = useUpdateSession(agentId) + const { updateSession } = useUpdateSession(agentId) const { agent } = useAgent(agentId) const isEditing = (session?: AgentSessionEntity) => session !== undefined diff --git a/src/renderer/src/hooks/agents/types.ts b/src/renderer/src/hooks/agents/types.ts new file mode 100644 index 0000000000..9cf5769f57 --- /dev/null +++ b/src/renderer/src/hooks/agents/types.ts @@ -0,0 +1,4 @@ +export type UpdateAgentBaseOptions = { + /** Whether to show success toast after updating. Defaults to true. */ + showSuccessToast?: boolean +} diff --git a/src/renderer/src/hooks/agents/useActiveAgent.ts b/src/renderer/src/hooks/agents/useActiveAgent.ts new file mode 100644 index 0000000000..f522ac7f28 --- /dev/null +++ b/src/renderer/src/hooks/agents/useActiveAgent.ts @@ -0,0 +1,8 @@ +import { useRuntime } from '../useRuntime' +import { useAgent } from './useAgent' + +export const useActiveAgent = () => { + const { chat } = useRuntime() + const { activeAgentId } = chat + return useAgent(activeAgentId) +} diff --git a/src/renderer/src/hooks/agents/useActiveSession.ts b/src/renderer/src/hooks/agents/useActiveSession.ts new file mode 100644 index 0000000000..dd744b6a50 --- /dev/null +++ b/src/renderer/src/hooks/agents/useActiveSession.ts @@ -0,0 +1,9 @@ +import { useRuntime } from '../useRuntime' +import { useSession } from './useSession' + +export const useActiveSession = () => { + const { chat } = useRuntime() + const { activeSessionIdMap, activeAgentId } = chat + const activeSessionId = activeAgentId ? activeSessionIdMap[activeAgentId] : null + return useSession(activeAgentId, activeSessionId) +} diff --git a/src/renderer/src/hooks/agents/useAgent.ts b/src/renderer/src/hooks/agents/useAgent.ts index a6755a4293..e1c682046f 100644 --- a/src/renderer/src/hooks/agents/useAgent.ts +++ b/src/renderer/src/hooks/agents/useAgent.ts @@ -11,8 +11,8 @@ export const useAgent = (id: string | null) => { const key = id ? client.agentPaths.withId(id) : null const { apiServerConfig, apiServerRunning } = useApiServer() const fetcher = useCallback(async () => { - if (!id || id === 'fake') { - return null + if (!id) { + throw new Error(t('agent.get.error.null_id')) } if (!apiServerConfig.enabled) { throw new Error(t('apiServer.messages.notEnabled')) diff --git a/src/renderer/src/hooks/agents/useAgentSessionInitializer.ts b/src/renderer/src/hooks/agents/useAgentSessionInitializer.ts index 23161b4223..98609f5b29 100644 --- a/src/renderer/src/hooks/agents/useAgentSessionInitializer.ts +++ b/src/renderer/src/hooks/agents/useAgentSessionInitializer.ts @@ -17,7 +17,7 @@ export const useAgentSessionInitializer = () => { const dispatch = useAppDispatch() const client = useAgentClient() const { chat } = useRuntime() - const { activeAgentId, activeSessionId } = chat + const { activeAgentId, activeSessionIdMap } = chat /** * Initialize session for the given agent by loading its sessions @@ -25,11 +25,11 @@ export const useAgentSessionInitializer = () => { */ const initializeAgentSession = useCallback( async (agentId: string) => { - if (!agentId || agentId === 'fake') return + if (!agentId) return try { // Check if this agent already has an active session - const currentSessionId = activeSessionId[agentId] + const currentSessionId = activeSessionIdMap[agentId] if (currentSessionId) { // Session already exists, just switch to session view dispatch(setActiveTopicOrSessionAction('session')) @@ -58,21 +58,21 @@ export const useAgentSessionInitializer = () => { dispatch(setActiveTopicOrSessionAction('session')) } }, - [client, dispatch, activeSessionId] + [client, dispatch, activeSessionIdMap] ) /** * Auto-initialize when activeAgentId changes */ useEffect(() => { - if (activeAgentId && activeAgentId !== 'fake') { + if (activeAgentId) { // Check if we need to initialize this agent's session - const hasActiveSession = activeSessionId[activeAgentId] + const hasActiveSession = activeSessionIdMap[activeAgentId] if (!hasActiveSession) { initializeAgentSession(activeAgentId) } } - }, [activeAgentId, activeSessionId, initializeAgentSession]) + }, [activeAgentId, activeSessionIdMap, initializeAgentSession]) return { initializeAgentSession diff --git a/src/renderer/src/hooks/agents/useSession.ts b/src/renderer/src/hooks/agents/useSession.ts index ded5b9aabe..1c6ebd608f 100644 --- a/src/renderer/src/hooks/agents/useSession.ts +++ b/src/renderer/src/hooks/agents/useSession.ts @@ -1,21 +1,24 @@ import { useAppDispatch } from '@renderer/store' import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk' -import { UpdateSessionForm } from '@renderer/types' import { buildAgentSessionTopicId } from '@renderer/utils/agentSession' -import { useCallback, useEffect, useMemo } from 'react' +import { useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import useSWR from 'swr' import { useAgentClient } from './useAgentClient' +import { useUpdateSession } from './useUpdateSession' -export const useSession = (agentId: string, sessionId: string) => { +export const useSession = (agentId: string | null, sessionId: string | null) => { const { t } = useTranslation() const client = useAgentClient() - const key = client.getSessionPaths(agentId).withId(sessionId) + const key = agentId && sessionId ? client.getSessionPaths(agentId).withId(sessionId) : null const dispatch = useAppDispatch() - const sessionTopicId = useMemo(() => buildAgentSessionTopicId(sessionId), [sessionId]) + const sessionTopicId = useMemo(() => (sessionId ? buildAgentSessionTopicId(sessionId) : null), [sessionId]) + const { updateSession } = useUpdateSession(agentId) const fetcher = async () => { + if (!agentId) throw new Error(t('agent.get.error.null_id')) + if (!sessionId) throw new Error(t('agent.session.get.error.null_id')) const data = await client.getSession(agentId, sessionId) return data } @@ -24,26 +27,13 @@ export const useSession = (agentId: string, sessionId: string) => { // Use loadTopicMessagesThunk to load messages (with caching mechanism) // This ensures messages are preserved when switching between sessions/tabs useEffect(() => { - if (sessionId) { + if (sessionTopicId) { // loadTopicMessagesThunk will check if messages already exist in Redux // and skip loading if they do (unless forceReload is true) dispatch(loadTopicMessagesThunk(sessionTopicId)) } }, [dispatch, sessionId, sessionTopicId]) - const updateSession = useCallback( - async (form: UpdateSessionForm) => { - if (!agentId) return - try { - const result = await client.updateSession(agentId, form) - mutate(result) - } catch (error) { - window.toast.error(t('agent.session.update.error.failed')) - } - }, - [agentId, client, mutate, t] - ) - return { session: data, error, diff --git a/src/renderer/src/hooks/agents/useSessions.ts b/src/renderer/src/hooks/agents/useSessions.ts index fce29b6bf9..2be85d5eba 100644 --- a/src/renderer/src/hooks/agents/useSessions.ts +++ b/src/renderer/src/hooks/agents/useSessions.ts @@ -1,4 +1,4 @@ -import { CreateSessionForm } from '@renderer/types' +import { CreateAgentSessionResponse, CreateSessionForm, GetAgentSessionResponse } from '@renderer/types' import { formatErrorMessageWithPrefix } from '@renderer/utils/error' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' @@ -6,46 +6,50 @@ import useSWR from 'swr' import { useAgentClient } from './useAgentClient' -export const useSessions = (agentId: string) => { +export const useSessions = (agentId: string | null) => { const { t } = useTranslation() const client = useAgentClient() - const key = client.getSessionPaths(agentId).base + const key = agentId ? client.getSessionPaths(agentId).base : null const fetcher = async () => { + if (!agentId) throw new Error('No active agent.') const data = await client.listSessions(agentId) return data.data } const { data, error, isLoading, mutate } = useSWR(key, fetcher) const createSession = useCallback( - async (form: CreateSessionForm) => { + async (form: CreateSessionForm): Promise => { + if (!agentId) return null try { const result = await client.createSession(agentId, form) await mutate((prev) => [result, ...(prev ?? [])], { revalidate: false }) return result } catch (error) { window.toast.error(formatErrorMessageWithPrefix(error, t('agent.session.create.error.failed'))) - return undefined + return null } }, [agentId, client, mutate, t] ) - // TODO: including messages field const getSession = useCallback( - async (id: string) => { + async (id: string): Promise => { + if (!agentId) return null try { const result = await client.getSession(agentId, id) mutate((prev) => prev?.map((session) => (session.id === result.id ? result : session))) + return result } catch (error) { window.toast.error(formatErrorMessageWithPrefix(error, t('agent.session.get.error.failed'))) + return null } }, [agentId, client, mutate, t] ) const deleteSession = useCallback( - async (id: string) => { + async (id: string): Promise => { if (!agentId) return false try { await client.deleteSession(agentId, id) diff --git a/src/renderer/src/hooks/agents/useUpdateAgent.ts b/src/renderer/src/hooks/agents/useUpdateAgent.ts index b2589095ef..db3825b9bb 100644 --- a/src/renderer/src/hooks/agents/useUpdateAgent.ts +++ b/src/renderer/src/hooks/agents/useUpdateAgent.ts @@ -4,20 +4,16 @@ import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { mutate } from 'swr' +import { UpdateAgentBaseOptions } from './types' import { useAgentClient } from './useAgentClient' -export type UpdateAgentOptions = { - /** Whether to show success toast after updating. Defaults to true. */ - showSuccessToast?: boolean -} - export const useUpdateAgent = () => { const { t } = useTranslation() const client = useAgentClient() const listKey = client.agentPaths.base const updateAgent = useCallback( - async (form: UpdateAgentForm, options?: UpdateAgentOptions) => { + async (form: UpdateAgentForm, options?: UpdateAgentBaseOptions) => { try { const itemKey = client.agentPaths.withId(form.id) // may change to optimistic update @@ -35,7 +31,7 @@ export const useUpdateAgent = () => { ) const updateModel = useCallback( - async (agentId: string, modelId: string, options?: UpdateAgentOptions) => { + async (agentId: string, modelId: string, options?: UpdateAgentBaseOptions) => { updateAgent({ id: agentId, model: modelId }, options) }, [updateAgent] diff --git a/src/renderer/src/hooks/agents/useUpdateSession.ts b/src/renderer/src/hooks/agents/useUpdateSession.ts index 4e91c2fda9..cf52a64630 100644 --- a/src/renderer/src/hooks/agents/useUpdateSession.ts +++ b/src/renderer/src/hooks/agents/useUpdateSession.ts @@ -1,19 +1,21 @@ import { ListAgentSessionsResponse, UpdateSessionForm } from '@renderer/types' -import { formatErrorMessageWithPrefix } from '@renderer/utils/error' +import { getErrorMessage } from '@renderer/utils/error' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { mutate } from 'swr' +import { UpdateAgentBaseOptions } from './types' import { useAgentClient } from './useAgentClient' -export const useUpdateSession = (agentId: string) => { +export const useUpdateSession = (agentId: string | null) => { const { t } = useTranslation() const client = useAgentClient() - const paths = client.getSessionPaths(agentId) - const listKey = paths.base const updateSession = useCallback( - async (form: UpdateSessionForm) => { + async (form: UpdateSessionForm, options?: UpdateAgentBaseOptions) => { + if (!agentId) return + const paths = client.getSessionPaths(agentId) + const listKey = paths.base const sessionId = form.id try { const itemKey = paths.withId(sessionId) @@ -24,13 +26,29 @@ export const useUpdateSession = (agentId: string) => { (prev) => prev?.map((session) => (session.id === result.id ? result : session)) ?? [] ) mutate(itemKey, result) - window.toast.success(t('common.update_success')) + if (options?.showSuccessToast ?? true) { + window.toast.success(t('common.update_success')) + } } catch (error) { - window.toast.error(formatErrorMessageWithPrefix(error, t('agent.update.error.failed'))) + window.toast.error({ title: t('agent.session.update.error.failed'), description: getErrorMessage(error) }) } }, - [agentId, client, listKey, paths, t] + [agentId, client, t] ) - return updateSession + const updateModel = useCallback( + async (sessionId: string, modelId: string, options?: UpdateAgentBaseOptions) => { + if (!agentId) return + return updateSession( + { + id: sessionId, + model: modelId + }, + options + ) + }, + [agentId, updateSession] + ) + + return { updateSession, updateModel } } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 98cce3a51e..9dbdd32dae 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -22,7 +22,8 @@ }, "get": { "error": { - "failed": "Failed to get the agent." + "failed": "Failed to get the agent.", + "null_id": "Agent ID is null." } }, "list": { @@ -73,7 +74,8 @@ }, "get": { "error": { - "failed": "Failed to get the session" + "failed": "Failed to get the session", + "null_id": "Session ID is null" } }, "label_one": "Session", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 8e5e4ebe38..9b146596fc 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -22,7 +22,8 @@ }, "get": { "error": { - "failed": "获取智能体失败" + "failed": "获取智能体失败", + "null_id": "智能体 ID 为空。" } }, "list": { @@ -73,7 +74,8 @@ }, "get": { "error": { - "failed": "获取会话失败" + "failed": "获取会话失败", + "null_id": "会话 ID 为空" } }, "label_one": "会话", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 0b88424662..57dc3480b5 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -22,7 +22,8 @@ }, "get": { "error": { - "failed": "無法取得代理程式。" + "failed": "無法取得代理程式。", + "null_id": "代理程式 ID 為空。" } }, "list": { @@ -73,7 +74,8 @@ }, "get": { "error": { - "failed": "無法取得工作階段" + "failed": "無法取得工作階段", + "null_id": "工作階段 ID 為空" } }, "label_one": "會議", diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index c1d289316d..259d64c910 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -22,7 +22,8 @@ }, "get": { "error": { - "failed": "Αποτυχία λήψης του πράκτορα." + "failed": "Αποτυχία λήψης του πράκτορα.", + "null_id": "Το ID του πράκτορα είναι null." } }, "list": { @@ -73,7 +74,8 @@ }, "get": { "error": { - "failed": "Αποτυχία λήψης της συνεδρίας" + "failed": "Αποτυχία λήψης της συνεδρίας", + "null_id": "Το ID της συνεδρίας είναι null" } }, "label_one": "Συνεδρία", diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 5825c72fd2..8b9b863e04 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -22,7 +22,8 @@ }, "get": { "error": { - "failed": "No se pudo obtener el agente." + "failed": "No se pudo obtener el agente.", + "null_id": "El ID del agente es nulo." } }, "list": { @@ -73,7 +74,8 @@ }, "get": { "error": { - "failed": "Error al obtener la sesión" + "failed": "Error al obtener la sesión", + "null_id": "El ID de sesión es nulo" } }, "label_one": "Sesión", diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index e82bbe99e8..f40ffc8163 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -22,7 +22,8 @@ }, "get": { "error": { - "failed": "Échec de l'obtention de l'agent." + "failed": "Échec de l'obtention de l'agent.", + "null_id": "L'ID de l'agent est nul." } }, "list": { @@ -73,7 +74,8 @@ }, "get": { "error": { - "failed": "Échec de l'obtention de la session" + "failed": "Échec de l'obtention de la session", + "null_id": "L'ID de session est nul" } }, "label_one": "Session", diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index 5600675de2..03c5218c31 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -22,7 +22,8 @@ }, "get": { "error": { - "failed": "エージェントの取得に失敗しました。" + "failed": "エージェントの取得に失敗しました。", + "null_id": "エージェント ID が null です。" } }, "list": { @@ -73,7 +74,8 @@ }, "get": { "error": { - "failed": "セッションの取得に失敗しました" + "failed": "セッションの取得に失敗しました", + "null_id": "セッション ID が null です" } }, "label_one": "セッション", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index c732b90f1a..4ea6d25f56 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -22,7 +22,8 @@ }, "get": { "error": { - "failed": "Falha ao obter o agente." + "failed": "Falha ao obter o agente.", + "null_id": "O ID do agente é nulo." } }, "list": { @@ -73,7 +74,8 @@ }, "get": { "error": { - "failed": "Falha ao obter a sessão" + "failed": "Falha ao obter a sessão", + "null_id": "O ID da sessão é nulo" } }, "label_one": "Sessão", diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 394cc9163a..8f751d8d9d 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -22,7 +22,8 @@ }, "get": { "error": { - "failed": "Не удалось получить агента." + "failed": "Не удалось получить агента.", + "null_id": "ID агента равен null." } }, "list": { @@ -73,7 +74,8 @@ }, "get": { "error": { - "failed": "Не удалось получить сеанс" + "failed": "Не удалось получить сеанс", + "null_id": "ID сессии равен null" } }, "label_one": "Сессия", diff --git a/src/renderer/src/pages/home/Chat.tsx b/src/renderer/src/pages/home/Chat.tsx index 12ba9d7573..0775a4f718 100644 --- a/src/renderer/src/pages/home/Chat.tsx +++ b/src/renderer/src/pages/home/Chat.tsx @@ -49,7 +49,8 @@ const Chat: FC = (props) => { const { isTopNavbar } = useNavbarPosition() const chatMaxWidth = useChatMaxWidth() const { chat } = useRuntime() - const { activeTopicOrSession, activeAgentId, activeSessionId } = chat + const { activeTopicOrSession, activeAgentId, activeSessionIdMap } = chat + const activeSessionId = activeAgentId ? activeSessionIdMap[activeAgentId] : null const { apiServer } = useSettings() const mainRef = React.useRef(null) @@ -147,8 +148,7 @@ const Chat: FC = (props) => { if (activeAgentId === null) { return () =>
Active Agent ID is invalid.
} - const sessionId = activeSessionId[activeAgentId] - if (!sessionId) { + if (!activeSessionId) { return () =>
Active Session ID is invalid.
} if (!apiServer.enabled) { @@ -158,18 +158,17 @@ const Chat: FC = (props) => { ) } - return () => + return () => }, [activeAgentId, activeSessionId, apiServer.enabled, t]) const SessionInputBar = useMemo(() => { if (activeAgentId === null) { return () =>
Active Agent ID is invalid.
} - const sessionId = activeSessionId[activeAgentId] - if (!sessionId) { + if (!activeSessionId) { return () =>
Active Session ID is invalid.
} - return () => + return () => }, [activeAgentId, activeSessionId]) // TODO: more info @@ -235,10 +234,8 @@ const Chat: FC = (props) => { )} {activeTopicOrSession === 'session' && !activeAgentId && } - {activeTopicOrSession === 'session' && activeAgentId && !activeSessionId[activeAgentId] && ( - - )} - {activeTopicOrSession === 'session' && activeAgentId && activeSessionId[activeAgentId] && ( + {activeTopicOrSession === 'session' && activeAgentId && !activeSessionId && } + {activeTopicOrSession === 'session' && activeAgentId && activeSessionId && ( <> diff --git a/src/renderer/src/pages/home/ChatNavbar.tsx b/src/renderer/src/pages/home/ChatNavbar.tsx index 0934754139..c44447da8f 100644 --- a/src/renderer/src/pages/home/ChatNavbar.tsx +++ b/src/renderer/src/pages/home/ChatNavbar.tsx @@ -64,6 +64,14 @@ const HeaderNavbar: FC = ({ activeAssistant, setActiveAssistant, activeTo }) } + // const handleUpdateModel = useCallback( + // async (model: ApiModel) => { + // if (!activeSession || !activeAgent) return + // return updateModel(activeSession.id, model.id, { showSuccessToast: false }) + // }, + // [activeAgent, activeSession, updateModel] + // ) + return (
diff --git a/src/renderer/src/pages/home/HomePage.tsx b/src/renderer/src/pages/home/HomePage.tsx index 32f1ec3975..d387af12ba 100644 --- a/src/renderer/src/pages/home/HomePage.tsx +++ b/src/renderer/src/pages/home/HomePage.tsx @@ -117,7 +117,7 @@ const HomePage: FC = () => { type: 'chat' }) } else if (activeTopicOrSession === 'topic') { - dispatch(setActiveAgentId('fake')) + dispatch(setActiveAgentId(null)) } }, [activeTopicOrSession, dispatch, setActiveAssistant]) diff --git a/src/renderer/src/pages/home/Tabs/AgentSettingsTab.tsx b/src/renderer/src/pages/home/Tabs/AgentSettingsTab.tsx deleted file mode 100644 index 7aab18816d..0000000000 --- a/src/renderer/src/pages/home/Tabs/AgentSettingsTab.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Button, Divider } from '@heroui/react' -import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' -import { AgentSettingsPopup } from '@renderer/pages/settings/AgentSettings' -import AdvancedSettings from '@renderer/pages/settings/AgentSettings/AdvancedSettings' -import AgentEssentialSettings from '@renderer/pages/settings/AgentSettings/AgentEssentialSettings' -import { GetAgentResponse } from '@renderer/types/agent' -import { FC } from 'react' -import { useTranslation } from 'react-i18next' - -interface Props { - agent: GetAgentResponse | undefined | null - update: ReturnType['updateAgent'] -} - -const AgentSettingsTab: FC = ({ agent, update }) => { - const { t } = useTranslation() - - const onMoreSetting = () => { - if (agent?.id) { - AgentSettingsPopup.show({ agentId: agent.id! }) - } - } - - if (!agent) { - return null - } - - return ( -
- - - - -
- ) -} - -export default AgentSettingsTab diff --git a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx index f4588f60a0..af02796d76 100644 --- a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx @@ -11,7 +11,7 @@ import { useAppDispatch } from '@renderer/store' import { addIknowAction } from '@renderer/store/runtime' import { Assistant, AssistantsSortType } from '@renderer/types' import { getErrorMessage } from '@renderer/utils' -import { FC, useCallback, useEffect, useRef, useState } from 'react' +import { FC, useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -80,12 +80,6 @@ const AssistantsTab: FC = (props) => { updateAssistants }) - useEffect(() => { - if (!agentsLoading && agents.length > 0 && !activeAgentId && apiServerConfig.enabled) { - setActiveAgentId(agents[0].id) - } - }, [agentsLoading, agents, activeAgentId, setActiveAgentId, apiServerConfig.enabled]) - const onDeleteAssistant = useCallback( (assistant: Assistant) => { const remaining = assistants.filter((a) => a.id !== assistant.id) diff --git a/src/renderer/src/pages/home/Tabs/SessionSettingsTab.tsx b/src/renderer/src/pages/home/Tabs/SessionSettingsTab.tsx new file mode 100644 index 0000000000..34b41a560c --- /dev/null +++ b/src/renderer/src/pages/home/Tabs/SessionSettingsTab.tsx @@ -0,0 +1,43 @@ +import { Button, Divider } from '@heroui/react' +import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession' +import { SessionSettingsPopup } from '@renderer/pages/settings/AgentSettings' +import AdvancedSettings from '@renderer/pages/settings/AgentSettings/AdvancedSettings' +import EssentialSettings from '@renderer/pages/settings/AgentSettings/EssentialSettings' +import { GetAgentSessionResponse } from '@renderer/types' +import { FC } from 'react' +import { useTranslation } from 'react-i18next' + +interface Props { + session: GetAgentSessionResponse | undefined | null + update: ReturnType['updateSession'] +} + +const SessionSettingsTab: FC = ({ session, update }) => { + const { t } = useTranslation() + + const onMoreSetting = () => { + if (session?.id) { + SessionSettingsPopup.show({ + agentId: session.agent_id, + sessionId: session.id + }) + } + } + + if (!session) { + return null + } + + return ( +
+ + + + +
+ ) +} + +export default SessionSettingsTab diff --git a/src/renderer/src/pages/home/Tabs/components/Agents.tsx b/src/renderer/src/pages/home/Tabs/components/Agents.tsx deleted file mode 100644 index 07154aed41..0000000000 --- a/src/renderer/src/pages/home/Tabs/components/Agents.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { Alert, Button, Spinner } from '@heroui/react' -import { AgentModal } from '@renderer/components/Popups/agent/AgentModal' -import { useAgents } from '@renderer/hooks/agents/useAgents' -import { useAgentSessionInitializer } from '@renderer/hooks/agents/useAgentSessionInitializer' -import { useRuntime } from '@renderer/hooks/useRuntime' -import { useAppDispatch } from '@renderer/store' -import { setActiveAgentId as setActiveAgentIdAction } from '@renderer/store/runtime' -import { Plus } from 'lucide-react' -import { FC, useCallback, useEffect } from 'react' -import { useTranslation } from 'react-i18next' - -import AgentItem from './AgentItem' - -interface AssistantsTabProps {} - -export const Agents: FC = () => { - const { agents, deleteAgent, isLoading, error } = useAgents() - const { t } = useTranslation() - const { chat } = useRuntime() - const { activeAgentId } = chat - const { initializeAgentSession } = useAgentSessionInitializer() - - const dispatch = useAppDispatch() - - const setActiveAgentId = useCallback( - async (id: string) => { - dispatch(setActiveAgentIdAction(id)) - // Initialize the session for this agent - await initializeAgentSession(id) - }, - [dispatch, initializeAgentSession] - ) - - useEffect(() => { - if (!isLoading && agents.length > 0 && !activeAgentId) { - setActiveAgentId(agents[0].id) - } - }, [isLoading, agents, activeAgentId, setActiveAgentId]) - - return ( - <> - {isLoading && } - {error && } - {!isLoading && - !error && - agents.map((agent) => ( - deleteAgent(agent.id)} - onPress={() => { - setActiveAgentId(agent.id) - }} - /> - ))} - e.continuePropagation()} - startContent={} - className="w-full justify-start bg-transparent text-foreground-500 hover:bg-[var(--color-list-item)]"> - {t('agent.add.title')} - - ) - }} - /> - - ) -} diff --git a/src/renderer/src/pages/home/Tabs/components/Assistants.tsx b/src/renderer/src/pages/home/Tabs/components/Assistants.tsx deleted file mode 100644 index c21ec92aeb..0000000000 --- a/src/renderer/src/pages/home/Tabs/components/Assistants.tsx +++ /dev/null @@ -1,208 +0,0 @@ -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 './AssistantItem' -import { SectionName } from './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/components/SessionItem.tsx b/src/renderer/src/pages/home/Tabs/components/SessionItem.tsx index 31d05aff44..9e16b1de64 100644 --- a/src/renderer/src/pages/home/Tabs/components/SessionItem.tsx +++ b/src/renderer/src/pages/home/Tabs/components/SessionItem.tsx @@ -33,8 +33,8 @@ interface SessionItemProps { const SessionItem: FC = ({ session, agentId, isDisabled, isLoading, onDelete, onPress }) => { const { t } = useTranslation() const { chat } = useRuntime() - const updateSession = useUpdateSession(agentId) - const activeSessionId = chat.activeSessionId[agentId] + const { updateSession } = useUpdateSession(agentId) + const activeSessionId = chat.activeSessionIdMap[agentId] const [isConfirmingDeletion, setIsConfirmingDeletion] = useState(false) const { setTimeoutTimer } = useTimer() const dispatch = useAppDispatch() diff --git a/src/renderer/src/pages/home/Tabs/components/Sessions.tsx b/src/renderer/src/pages/home/Tabs/components/Sessions.tsx index 8f7d9aa941..50020b168d 100644 --- a/src/renderer/src/pages/home/Tabs/components/Sessions.tsx +++ b/src/renderer/src/pages/home/Tabs/components/Sessions.tsx @@ -30,7 +30,7 @@ const Sessions: React.FC = ({ agentId }) => { const { agent } = useAgent(agentId) const { sessions, isLoading, error, deleteSession, createSession } = useSessions(agentId) const { chat } = useRuntime() - const { activeSessionId, sessionWaiting } = chat + const { activeSessionIdMap, sessionWaiting } = chat const dispatch = useAppDispatch() const setActiveSessionId = useCallback( @@ -75,24 +75,24 @@ const Sessions: React.FC = ({ agentId }) => { [agentId, deleteSession, dispatch, sessions, t] ) - const currentActiveSessionId = activeSessionId[agentId] + const activeSessionId = activeSessionIdMap[agentId] useEffect(() => { - if (!isLoading && sessions.length > 0 && !currentActiveSessionId) { + if (!isLoading && sessions.length > 0 && !activeSessionId) { setActiveSessionId(agentId, sessions[0].id) } - }, [isLoading, sessions, currentActiveSessionId, agentId, setActiveSessionId]) + }, [isLoading, sessions, activeSessionId, agentId, setActiveSessionId]) useEffect(() => { - if (currentActiveSessionId) { + if (activeSessionId) { dispatch( newMessagesActions.setTopicFulfilled({ - topicId: buildAgentSessionTopicId(currentActiveSessionId), + topicId: buildAgentSessionTopicId(activeSessionId), fulfilled: false }) ) } - }, [currentActiveSessionId, dispatch]) + }, [activeSessionId, dispatch]) if (isLoading) { return ( diff --git a/src/renderer/src/pages/home/Tabs/index.tsx b/src/renderer/src/pages/home/Tabs/index.tsx index 1e2559c086..c43c6d93a4 100644 --- a/src/renderer/src/pages/home/Tabs/index.tsx +++ b/src/renderer/src/pages/home/Tabs/index.tsx @@ -1,6 +1,7 @@ +import { Alert, Skeleton } from '@heroui/react' import AddAssistantPopup from '@renderer/components/Popups/AddAssistantPopup' -import { useAgent } from '@renderer/hooks/agents/useAgent' -import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' +import { useActiveSession } from '@renderer/hooks/agents/useActiveSession' +import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession' import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant' import { useRuntime } from '@renderer/hooks/useRuntime' import { useNavbarPosition, useSettings } from '@renderer/hooks/useSettings' @@ -8,13 +9,13 @@ import { useShowTopics } from '@renderer/hooks/useStore' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { Assistant, Topic } from '@renderer/types' import { Tab } from '@renderer/types/chat' -import { classNames, uuid } from '@renderer/utils' +import { classNames, getErrorMessage, uuid } from '@renderer/utils' import { FC, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -import AgentSettingsTab from './AgentSettingsTab' import Assistants from './AssistantsTab' +import SessionSettingsTab from './SessionSettingsTab' import Settings from './SettingsTab' import Topics from './TopicsTab' @@ -47,8 +48,8 @@ const HomeTabs: FC = ({ const { t } = useTranslation() const { chat } = useRuntime() const { activeTopicOrSession, activeAgentId } = chat - const { agent } = useAgent(activeAgentId) - const { updateAgent } = useUpdateAgent() + const { session, isLoading: isSessionLoading, error: sessionError } = useActiveSession() + const { updateSession } = useUpdateSession(activeAgentId) const isSessionView = activeTopicOrSession === 'session' const isTopicView = activeTopicOrSession === 'topic' @@ -125,7 +126,7 @@ const HomeTabs: FC = ({ )} - {position === 'right' && topicPosition === 'right' && isTopicView && ( + {position === 'right' && topicPosition === 'right' && ( setTab('topic')}> {t('common.topics')} @@ -154,7 +155,20 @@ const HomeTabs: FC = ({ /> )} {tab === 'settings' && isTopicView && } - {tab === 'settings' && isSessionView && } + {tab === 'settings' && isSessionView && !sessionError && ( + + + + )} + {tab === 'settings' && isSessionView && sessionError && ( +
+ +
+ )} ) diff --git a/src/renderer/src/pages/home/components/ChatNavbarContent.tsx b/src/renderer/src/pages/home/components/ChatNavbarContent.tsx index 5d89bfd94e..154226973e 100644 --- a/src/renderer/src/pages/home/components/ChatNavbarContent.tsx +++ b/src/renderer/src/pages/home/components/ChatNavbarContent.tsx @@ -1,18 +1,18 @@ import { BreadcrumbItem, Breadcrumbs, Chip, cn } from '@heroui/react' import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer' import { permissionModeCards } from '@renderer/constants/permissionModes' -import { useAgent } from '@renderer/hooks/agents/useAgent' -import { useSession } from '@renderer/hooks/agents/useSession' -import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' +import { useActiveAgent } from '@renderer/hooks/agents/useActiveAgent' +import { useActiveSession } from '@renderer/hooks/agents/useActiveSession' +import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession' import { useRuntime } from '@renderer/hooks/useRuntime' -import { ApiModel, Assistant, PermissionMode } from '@renderer/types' +import { AgentEntity, AgentSessionEntity, ApiModel, Assistant, PermissionMode } from '@renderer/types' import { formatErrorMessageWithPrefix } from '@renderer/utils/error' import { t } from 'i18next' import { FC, ReactNode, useCallback } from 'react' -import { AgentSettingsPopup } from '../../settings/AgentSettings' -import { AgentLabel } from '../../settings/AgentSettings/shared' -import SelectAgentModelButton from './SelectAgentModelButton' +import { AgentSettingsPopup, SessionSettingsPopup } from '../../settings/AgentSettings' +import { AgentLabel, SessionLabel } from '../../settings/AgentSettings/shared' +import SelectAgentBaseModelButton from './SelectAgentBaseModelButton' import SelectModelButton from './SelectModelButton' interface Props { @@ -21,41 +21,67 @@ interface Props { const ChatNavbarContent: FC = ({ assistant }) => { const { chat } = useRuntime() - const { activeTopicOrSession, activeAgentId } = chat - const sessionId = activeAgentId ? (chat.activeSessionId[activeAgentId] ?? null) : null - const { agent } = useAgent(activeAgentId) - const { updateModel } = useUpdateAgent() + const { activeTopicOrSession } = chat + const { agent: activeAgent } = useActiveAgent() + const { session: activeSession } = useActiveSession() + const { updateModel } = useUpdateSession(activeAgent?.id ?? null) const handleUpdateModel = useCallback( async (model: ApiModel) => { - if (!agent) return - return updateModel(agent.id, model.id, { showSuccessToast: false }) + if (!activeAgent || !activeSession) return + return updateModel(activeSession.id, model.id, { showSuccessToast: false }) }, - [agent, updateModel] + [activeAgent, activeSession, updateModel] ) return ( <> {activeTopicOrSession === 'topic' && } - {activeTopicOrSession === 'session' && agent && ( + {activeTopicOrSession === 'session' && activeAgent && ( - + AgentSettingsPopup.show({ agentId: agent.id })} - classNames={{ base: 'self-stretch', item: 'h-full' }}> + onPress={() => AgentSettingsPopup.show({ agentId: activeAgent.id })} + classNames={{ + base: 'self-stretch', + item: 'h-full' + }}> - - - - {activeAgentId && sessionId && ( + {activeSession && ( + + SessionSettingsPopup.show({ + agentId: activeAgent.id, + sessionId: activeSession.id + }) + } + classNames={{ + base: 'self-stretch', + item: 'h-full' + }}> + + + + + )} + {activeSession && ( - + + + )} + {activeAgent && activeSession && ( + + )} @@ -65,9 +91,7 @@ const ChatNavbarContent: FC = ({ assistant }) => { ) } -const SessionWorkspaceMeta: FC<{ agentId: string; sessionId: string }> = ({ agentId, sessionId }) => { - const { agent } = useAgent(agentId) - const { session } = useSession(agentId, sessionId) +const SessionWorkspaceMeta: FC<{ agent: AgentEntity; session: AgentSessionEntity }> = ({ agent, session }) => { if (!session || !agent) { return null } diff --git a/src/renderer/src/pages/home/components/SelectAgentModelButton.tsx b/src/renderer/src/pages/home/components/SelectAgentBaseModelButton.tsx similarity index 87% rename from src/renderer/src/pages/home/components/SelectAgentModelButton.tsx rename to src/renderer/src/pages/home/components/SelectAgentBaseModelButton.tsx index 53607cd1d1..7e21d1b47a 100644 --- a/src/renderer/src/pages/home/components/SelectAgentModelButton.tsx +++ b/src/renderer/src/pages/home/components/SelectAgentBaseModelButton.tsx @@ -12,12 +12,12 @@ import { FC } from 'react' import { useTranslation } from 'react-i18next' interface Props { - agent: AgentBaseWithId + agentBase: AgentBaseWithId onSelect: (model: ApiModel) => Promise isDisabled?: boolean } -const SelectAgentModelButton: FC = ({ agent, onSelect, isDisabled }) => { +const SelectAgentBaseModelButton: FC = ({ agentBase: agent, onSelect, isDisabled }) => { const { t } = useTranslation() const model = useApiModel({ id: agent?.model }) @@ -42,9 +42,9 @@ const SelectAgentModelButton: FC = ({ agent, onSelect, isDisabled }) => { className="nodrag rounded-2xl px-1 py-3" onPress={onSelectModel} isDisabled={isDisabled}> -
+
- + {model ? model.name : t('button.select_model')} {providerName ? ' | ' + providerName : ''}
@@ -53,4 +53,4 @@ const SelectAgentModelButton: FC = ({ agent, onSelect, isDisabled }) => { ) } -export default SelectAgentModelButton +export default SelectAgentBaseModelButton diff --git a/src/renderer/src/pages/settings/AgentSettings/AdvancedSettings.tsx b/src/renderer/src/pages/settings/AgentSettings/AdvancedSettings.tsx index d49dac8bcb..209258ce67 100644 --- a/src/renderer/src/pages/settings/AgentSettings/AdvancedSettings.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/AdvancedSettings.tsx @@ -23,7 +23,7 @@ type AdvancedSettingsProps = } | { agentBase: GetAgentSessionResponse | undefined | null - update: ReturnType + update: ReturnType['updateSession'] } const defaultConfiguration: AgentConfigurationState = AgentConfigurationSchema.parse({}) diff --git a/src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx b/src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx deleted file mode 100644 index e0329404d8..0000000000 --- a/src/renderer/src/pages/settings/AgentSettings/AgentEssentialSettings.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Avatar } from '@heroui/react' -import { getAgentTypeAvatar } from '@renderer/config/agent' -import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' -import { getAgentTypeLabel } from '@renderer/i18n/label' -import { GetAgentResponse } from '@renderer/types' -import { FC } from 'react' -import { useTranslation } from 'react-i18next' - -import { AccessibleDirsSetting } from './AccessibleDirsSetting' -import { AvatarSetting } from './AvatarSetting' -import { DescriptionSetting } from './DescriptionSetting' -import { ModelSetting } from './ModelSetting' -import { NameSetting } from './NameSetting' -import { SettingsContainer, SettingsItem, SettingsTitle } from './shared' - -// const logger = loggerService.withContext('AgentEssentialSettings') - -interface AgentEssentialSettingsProps { - agent: GetAgentResponse | undefined | null - update: ReturnType['updateAgent'] - showModelSetting?: boolean -} - -const AgentEssentialSettings: FC = ({ agent, update, showModelSetting = true }) => { - const { t } = useTranslation() - - if (!agent) return null - - return ( - - - {t('agent.type.label')} -
- - {(agent?.name ?? agent?.type) ? getAgentTypeLabel(agent.type) : ''} -
-
- - - {showModelSetting && } - - -
- ) -} - -export default AgentEssentialSettings diff --git a/src/renderer/src/pages/settings/AgentSettings/AgentSettingsPopup.tsx b/src/renderer/src/pages/settings/AgentSettings/AgentSettingsPopup.tsx index 400504f8bb..937b72ecaa 100644 --- a/src/renderer/src/pages/settings/AgentSettings/AgentSettingsPopup.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/AgentSettingsPopup.tsx @@ -6,7 +6,7 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import AdvancedSettings from './AdvancedSettings' -import AgentEssentialSettings from './AgentEssentialSettings' +import EssentialSettings from './EssentialSettings' import PromptSettings from './PromptSettings' import { AgentLabel, LeftMenu, Settings, StyledMenu, StyledModal } from './shared' import ToolingSettings from './ToolingSettings' @@ -87,7 +87,7 @@ const AgentSettingPopupContainer: React.FC = ({ tab, ag /> - {menu === 'essential' && } + {menu === 'essential' && } {menu === 'prompt' && } {menu === 'tooling' && } {menu === 'advanced' && } diff --git a/src/renderer/src/pages/settings/AgentSettings/EssentialSettings.tsx b/src/renderer/src/pages/settings/AgentSettings/EssentialSettings.tsx new file mode 100644 index 0000000000..0977e1f5cc --- /dev/null +++ b/src/renderer/src/pages/settings/AgentSettings/EssentialSettings.tsx @@ -0,0 +1,56 @@ +import { Avatar } from '@heroui/react' +import { getAgentTypeAvatar } from '@renderer/config/agent' +import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' +import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession' +import { getAgentTypeLabel } from '@renderer/i18n/label' +import { GetAgentResponse, GetAgentSessionResponse, isAgentEntity } from '@renderer/types' +import { FC } from 'react' +import { useTranslation } from 'react-i18next' + +import { AccessibleDirsSetting } from './AccessibleDirsSetting' +import { AvatarSetting } from './AvatarSetting' +import { DescriptionSetting } from './DescriptionSetting' +import { ModelSetting } from './ModelSetting' +import { NameSetting } from './NameSetting' +import { SettingsContainer, SettingsItem, SettingsTitle } from './shared' + +// const logger = loggerService.withContext('AgentEssentialSettings') + +type EssentialSettingsProps = + | { + agentBase: GetAgentResponse | undefined | null + update: ReturnType['updateAgent'] + } + | { + agentBase: GetAgentSessionResponse | undefined | null + update: ReturnType['updateSession'] + } + +const EssentialSettings: FC = ({ agentBase, update }) => { + const { t } = useTranslation() + + if (!agentBase) return null + + const isAgent = isAgentEntity(agentBase) + + return ( + + {isAgent && ( + + {t('agent.type.label')} +
+ + {(agentBase?.name ?? agentBase?.type) ? getAgentTypeLabel(agentBase.type) : ''} +
+
+ )} + {isAgent && } + + + + +
+ ) +} + +export default EssentialSettings diff --git a/src/renderer/src/pages/settings/AgentSettings/ModelSetting.tsx b/src/renderer/src/pages/settings/AgentSettings/ModelSetting.tsx index 09ee7d8235..aed615b860 100644 --- a/src/renderer/src/pages/settings/AgentSettings/ModelSetting.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/ModelSetting.tsx @@ -1,4 +1,4 @@ -import SelectAgentModelButton from '@renderer/pages/home/components/SelectAgentModelButton' +import SelectAgentBaseModelButton from '@renderer/pages/home/components/SelectAgentBaseModelButton' import { AgentBaseWithId, ApiModel, UpdateAgentBaseForm } from '@renderer/types' import { useTranslation } from 'react-i18next' @@ -21,9 +21,9 @@ export const ModelSetting: React.FC = ({ base, update, isDisa if (!base) return null return ( - + {t('common.model')} - + ) } diff --git a/src/renderer/src/pages/settings/AgentSettings/PromptSettings.tsx b/src/renderer/src/pages/settings/AgentSettings/PromptSettings.tsx index 15f2f9be60..f931950128 100644 --- a/src/renderer/src/pages/settings/AgentSettings/PromptSettings.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/PromptSettings.tsx @@ -22,7 +22,7 @@ type AgentPromptSettingsProps = } | { agentBase: AgentSessionEntity | undefined | null - update: ReturnType + update: ReturnType['updateSession'] } const PromptSettings: FC = ({ agentBase, update }) => { diff --git a/src/renderer/src/pages/settings/AgentSettings/SessionEssentialSettings.tsx b/src/renderer/src/pages/settings/AgentSettings/SessionEssentialSettings.tsx deleted file mode 100644 index 67b83dc649..0000000000 --- a/src/renderer/src/pages/settings/AgentSettings/SessionEssentialSettings.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' -import { GetAgentSessionResponse } from '@renderer/types' -import { FC } from 'react' - -import { AccessibleDirsSetting } from './AccessibleDirsSetting' -import { DescriptionSetting } from './DescriptionSetting' -import { NameSetting } from './NameSetting' -import { SettingsContainer } from './shared' - -// const logger = loggerService.withContext('AgentEssentialSettings') - -interface SessionEssentialSettingsProps { - session: GetAgentSessionResponse | undefined | null - update: ReturnType['updateAgent'] -} - -const SessionEssentialSettings: FC = ({ session, update }) => { - if (!session) return null - - return ( - - - - - - ) -} - -export default SessionEssentialSettings diff --git a/src/renderer/src/pages/settings/AgentSettings/SessionSettingsPopup.tsx b/src/renderer/src/pages/settings/AgentSettings/SessionSettingsPopup.tsx index 576f1b1005..af5b2e71d7 100644 --- a/src/renderer/src/pages/settings/AgentSettings/SessionSettingsPopup.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/SessionSettingsPopup.tsx @@ -6,8 +6,8 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import AdvancedSettings from './AdvancedSettings' +import EssentialSettings from './EssentialSettings' import PromptSettings from './PromptSettings' -import SessionEssentialSettings from './SessionEssentialSettings' import { LeftMenu, SessionLabel, Settings, StyledMenu, StyledModal } from './shared' import ToolingSettings from './ToolingSettings' @@ -30,7 +30,7 @@ const SessionSettingPopupContainer: React.FC = ({ tab const { session, isLoading, error } = useSession(agentId, sessionId) - const updateSession = useUpdateSession(agentId) + const { updateSession } = useUpdateSession(agentId) const onOk = () => { setOpen(false) @@ -89,7 +89,7 @@ const SessionSettingPopupContainer: React.FC = ({ tab /> - {menu === 'essential' && } + {menu === 'essential' && } {menu === 'prompt' && } {menu === 'tooling' && } {menu === 'advanced' && } diff --git a/src/renderer/src/pages/settings/AgentSettings/shared.tsx b/src/renderer/src/pages/settings/AgentSettings/shared.tsx index eb26fcf0a2..2443ed8f7f 100644 --- a/src/renderer/src/pages/settings/AgentSettings/shared.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/shared.tsx @@ -1,5 +1,4 @@ import { cn } from '@heroui/react' -import Ellipsis from '@renderer/components/Ellipsis' import EmojiIcon from '@renderer/components/EmojiIcon' import { getAgentTypeLabel } from '@renderer/i18n/label' import { AgentEntity, AgentSessionEntity } from '@renderer/types' @@ -35,11 +34,11 @@ export const AgentLabel: React.FC = ({ agent, classNames }) => const emoji = agent?.configuration?.avatar return ( -
+
- + {agent?.name ?? (agent?.type ? getAgentTypeLabel(agent.type) : '')} - +
) } @@ -53,7 +52,7 @@ export const SessionLabel: React.FC = ({ session, className } const displayName = session?.name ?? session?.id return ( <> - {displayName} + {displayName} ) } diff --git a/src/renderer/src/store/runtime.ts b/src/renderer/src/store/runtime.ts index fe556b9a9e..f86a79cecb 100644 --- a/src/renderer/src/store/runtime.ts +++ b/src/renderer/src/store/runtime.ts @@ -11,7 +11,7 @@ export interface ChatState { activeAgentId: string | null /** UI state. Map agent id to active session id. * null represents no active session */ - activeSessionId: Record + activeSessionIdMap: Record /** meanwhile active Assistants or Agents */ activeTopicOrSession: 'topic' | 'session' /** topic ids that are currently being renamed */ @@ -90,7 +90,7 @@ const initialState: RuntimeState = { activeTopic: null, activeAgentId: null, activeTopicOrSession: 'topic', - activeSessionId: {}, + activeSessionIdMap: {}, renamingTopics: [], newlyRenamedTopics: [], sessionWaiting: {} @@ -163,7 +163,7 @@ const runtimeSlice = createSlice({ }, setActiveSessionIdAction: (state, action: PayloadAction<{ agentId: string; sessionId: string | null }>) => { const { agentId, sessionId } = action.payload - state.chat.activeSessionId[agentId] = sessionId + state.chat.activeSessionIdMap[agentId] = sessionId }, setActiveTopicOrSessionAction: (state, action: PayloadAction<'topic' | 'session'>) => { state.chat.activeTopicOrSession = action.payload diff --git a/src/renderer/src/types/agent.ts b/src/renderer/src/types/agent.ts index 96a8e70b02..ca1e88cbd1 100644 --- a/src/renderer/src/types/agent.ts +++ b/src/renderer/src/types/agent.ts @@ -266,8 +266,12 @@ export const GetAgentSessionResponseSchema = AgentSessionEntitySchema.extend({ slash_commands: z.array(SlashCommandSchema).optional() // Array of slash commands to trigger the agent }) +export const CreateAgentSessionResponseSchema = GetAgentSessionResponseSchema + export type GetAgentSessionResponse = z.infer +export type CreateAgentSessionResponse = GetAgentSessionResponse + export const ListAgentSessionsResponseSchema = z.object({ data: z.array(AgentSessionEntitySchema), total: z.int(),