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(),