@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
256 lines (218 loc) • 8.99 kB
text/typescript
import isEqual from 'fast-deep-equal';
import { produce } from 'immer';
import { SWRResponse, mutate } from 'swr';
import { DeepPartial } from 'utility-types';
import { StateCreator } from 'zustand/vanilla';
import { MESSAGE_CANCEL_FLAT } from '@/const/message';
import { INBOX_SESSION_ID } from '@/const/session';
import { useClientDataSWR, useOnlyFetchOnceSWR } from '@/libs/swr';
import { agentService } from '@/services/agent';
import { sessionService } from '@/services/session';
import { AgentState } from '@/store/agent/slices/chat/initialState';
import { useSessionStore } from '@/store/session';
import { LobeAgentChatConfig, LobeAgentConfig } from '@/types/agent';
import { KnowledgeItem } from '@/types/knowledgeBase';
import { merge } from '@/utils/merge';
import { AgentStore } from '../../store';
import { agentSelectors } from './selectors';
/**
* 助手接口
*/
export interface AgentChatAction {
addFilesToAgent: (fileIds: string[], boolean?: boolean) => Promise<void>;
addKnowledgeBaseToAgent: (knowledgeBaseId: string) => Promise<void>;
internal_createAbortController: (key: keyof AgentState) => AbortController;
internal_dispatchAgentMap: (
id: string,
config: DeepPartial<LobeAgentConfig>,
actions?: string,
) => void;
internal_refreshAgentConfig: (id: string) => Promise<void>;
internal_refreshAgentKnowledge: () => Promise<void>;
internal_updateAgentConfig: (
id: string,
data: DeepPartial<LobeAgentConfig>,
signal?: AbortSignal,
) => Promise<void>;
removeFileFromAgent: (fileId: string) => Promise<void>;
removeKnowledgeBaseFromAgent: (knowledgeBaseId: string) => Promise<void>;
removePlugin: (id: string) => void;
toggleFile: (id: string, open?: boolean) => Promise<void>;
toggleKnowledgeBase: (id: string, open?: boolean) => Promise<void>;
togglePlugin: (id: string, open?: boolean) => Promise<void>;
updateAgentChatConfig: (config: Partial<LobeAgentChatConfig>) => Promise<void>;
updateAgentConfig: (config: DeepPartial<LobeAgentConfig>) => Promise<void>;
useFetchAgentConfig: (isLogin: boolean | undefined, id: string) => SWRResponse<LobeAgentConfig>;
useFetchFilesAndKnowledgeBases: () => SWRResponse<KnowledgeItem[]>;
useInitInboxAgentStore: (
isLogin: boolean | undefined,
defaultAgentConfig?: DeepPartial<LobeAgentConfig>,
) => SWRResponse<DeepPartial<LobeAgentConfig>>;
}
const FETCH_AGENT_CONFIG_KEY = 'FETCH_AGENT_CONFIG';
const FETCH_AGENT_KNOWLEDGE_KEY = 'FETCH_AGENT_KNOWLEDGE';
export const createChatSlice: StateCreator<
AgentStore,
[['zustand/devtools', never]],
[],
AgentChatAction
> = (set, get) => ({
addFilesToAgent: async (fileIds, enabled) => {
const { activeAgentId, internal_refreshAgentConfig, internal_refreshAgentKnowledge } = get();
if (!activeAgentId) return;
if (fileIds.length === 0) return;
await agentService.createAgentFiles(activeAgentId, fileIds, enabled);
await internal_refreshAgentConfig(get().activeId);
await internal_refreshAgentKnowledge();
},
addKnowledgeBaseToAgent: async (knowledgeBaseId) => {
const { activeAgentId, internal_refreshAgentConfig, internal_refreshAgentKnowledge } = get();
if (!activeAgentId) return;
await agentService.createAgentKnowledgeBase(activeAgentId, knowledgeBaseId, true);
await internal_refreshAgentConfig(get().activeId);
await internal_refreshAgentKnowledge();
},
removeFileFromAgent: async (fileId) => {
const { activeAgentId, internal_refreshAgentConfig, internal_refreshAgentKnowledge } = get();
if (!activeAgentId) return;
await agentService.deleteAgentFile(activeAgentId, fileId);
await internal_refreshAgentConfig(get().activeId);
await internal_refreshAgentKnowledge();
},
removeKnowledgeBaseFromAgent: async (knowledgeBaseId) => {
const { activeAgentId, internal_refreshAgentConfig, internal_refreshAgentKnowledge } = get();
if (!activeAgentId) return;
await agentService.deleteAgentKnowledgeBase(activeAgentId, knowledgeBaseId);
await internal_refreshAgentConfig(get().activeId);
await internal_refreshAgentKnowledge();
},
removePlugin: async (id) => {
await get().togglePlugin(id, false);
},
toggleFile: async (id, open) => {
const { activeAgentId, internal_refreshAgentConfig } = get();
if (!activeAgentId) return;
await agentService.toggleFile(activeAgentId, id, open);
await internal_refreshAgentConfig(get().activeId);
},
toggleKnowledgeBase: async (id, open) => {
const { activeAgentId, internal_refreshAgentConfig } = get();
if (!activeAgentId) return;
await agentService.toggleKnowledgeBase(activeAgentId, id, open);
await internal_refreshAgentConfig(get().activeId);
},
togglePlugin: async (id, open) => {
const originConfig = agentSelectors.currentAgentConfig(get());
const config = produce(originConfig, (draft) => {
draft.plugins = produce(draft.plugins || [], (plugins) => {
const index = plugins.indexOf(id);
const shouldOpen = open !== undefined ? open : index === -1;
if (shouldOpen) {
// 如果 open 为 true 或者 id 不存在于 plugins 中,则添加它
if (index === -1) {
plugins.push(id);
}
} else {
// 如果 open 为 false 或者 id 存在于 plugins 中,则移除它
if (index !== -1) {
plugins.splice(index, 1);
}
}
});
});
await get().updateAgentConfig(config);
},
updateAgentChatConfig: async (config) => {
const { activeId } = get();
if (!activeId) return;
await get().updateAgentConfig({ chatConfig: config });
},
updateAgentConfig: async (config) => {
const { activeId } = get();
if (!activeId) return;
const controller = get().internal_createAbortController('updateAgentConfigSignal');
await get().internal_updateAgentConfig(activeId, config, controller.signal);
},
useFetchAgentConfig: (isLogin, sessionId) =>
useClientDataSWR<LobeAgentConfig>(
isLogin ? [FETCH_AGENT_CONFIG_KEY, sessionId] : null,
([, id]: string[]) => sessionService.getSessionConfig(id),
{
onSuccess: (data) => {
get().internal_dispatchAgentMap(sessionId, data, 'fetch');
set(
{
activeAgentId: data.id,
agentConfigInitMap: { ...get().agentConfigInitMap, [sessionId]: true },
},
false,
'fetchAgentConfig',
);
},
},
),
useFetchFilesAndKnowledgeBases: () => {
return useClientDataSWR<KnowledgeItem[]>(
[FETCH_AGENT_KNOWLEDGE_KEY, get().activeAgentId],
([, id]: string[]) => agentService.getFilesAndKnowledgeBases(id),
{
fallbackData: [],
suspense: true,
},
);
},
useInitInboxAgentStore: (isLogin, defaultAgentConfig) =>
useOnlyFetchOnceSWR<DeepPartial<LobeAgentConfig>>(
!!isLogin ? 'fetchInboxAgentConfig' : null,
() => sessionService.getSessionConfig(INBOX_SESSION_ID),
{
onSuccess: (data) => {
set(
{
defaultAgentConfig: merge(get().defaultAgentConfig, defaultAgentConfig),
isInboxAgentConfigInit: true,
},
false,
'initDefaultAgent',
);
if (data) {
get().internal_dispatchAgentMap(INBOX_SESSION_ID, data, 'initInbox');
}
},
},
),
/* eslint-disable sort-keys-fix/sort-keys-fix */
internal_dispatchAgentMap: (id, config, actions) => {
const agentMap = produce(get().agentMap, (draft) => {
if (!draft[id]) {
draft[id] = config;
} else {
draft[id] = merge(draft[id], config);
}
});
if (isEqual(get().agentMap, agentMap)) return;
set({ agentMap }, false, 'dispatchAgent' + (actions ? `/${actions}` : ''));
},
internal_updateAgentConfig: async (id, data, signal) => {
const prevModel = agentSelectors.currentAgentModel(get());
// optimistic update at frontend
get().internal_dispatchAgentMap(id, data, 'optimistic_updateAgentConfig');
await sessionService.updateSessionConfig(id, data, signal);
await get().internal_refreshAgentConfig(id);
// refresh sessions to update the agent config if the model has changed
if (prevModel !== data.model) await useSessionStore.getState().refreshSessions();
},
internal_refreshAgentConfig: async (id) => {
await mutate([FETCH_AGENT_CONFIG_KEY, id]);
},
internal_refreshAgentKnowledge: async () => {
await mutate([FETCH_AGENT_KNOWLEDGE_KEY, get().activeAgentId]);
},
internal_createAbortController: (key) => {
const abortController = get()[key] as AbortController;
if (abortController) abortController.abort(MESSAGE_CANCEL_FLAT);
const controller = new AbortController();
set({ [key]: controller }, false, 'internal_createAbortController');
return controller;
},
});