@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.
172 lines (144 loc) • 5.24 kB
text/typescript
import { crawlResultsPrompt } from '@lobechat/prompts';
import { CreateMessageParams, SEARCH_SEARXNG_NOT_CONFIG, SearchQuery } from '@lobechat/types';
import { nanoid } from '@lobechat/utils';
import { StateCreator } from 'zustand/vanilla';
import { searchService } from '@/services/search';
import { chatSelectors } from '@/store/chat/selectors';
import { ChatStore } from '@/store/chat/store';
import { WebBrowsingExecutionRuntime } from '@/tools/web-browsing/ExecutionRuntime';
export interface SearchAction {
crawlMultiPages: (
id: string,
params: { urls: string[] },
aiSummary?: boolean,
) => Promise<boolean | undefined>;
crawlSinglePage: (
id: string,
params: { url: string },
aiSummary?: boolean,
) => Promise<boolean | undefined>;
saveSearchResult: (id: string) => Promise<void>;
search: (id: string, data: SearchQuery, aiSummary?: boolean) => Promise<void | boolean>;
togglePageContent: (url: string) => void;
toggleSearchLoading: (id: string, loading: boolean) => void;
/**
* 重新发起搜索
* @description 会更新插件的 arguments 参数,然后再次搜索
*/
triggerSearchAgain: (
id: string,
data: SearchQuery,
options?: { aiSummary: boolean },
) => Promise<void>;
}
const runtime = new WebBrowsingExecutionRuntime({ searchService });
export const searchSlice: StateCreator<
ChatStore,
[['zustand/devtools', never]],
[],
SearchAction
> = (set, get) => ({
crawlMultiPages: async (id, params, aiSummary = true) => {
const { internal_updateMessageContent } = get();
get().toggleSearchLoading(id, true);
try {
const { content, success, error, state } = await runtime.crawlMultiPages(params);
await internal_updateMessageContent(id, content);
if (success) {
await get().updatePluginState(id, state);
} else {
await get().internal_updatePluginError(id, error);
}
get().toggleSearchLoading(id, false);
// Convert to XML format to save tokens
// if aiSummary is true, then trigger ai message
return aiSummary;
} catch (e) {
const err = e as Error;
console.error(e);
const content = [{ errorMessage: err.message, errorType: err.name }];
const xmlContent = crawlResultsPrompt(content);
await internal_updateMessageContent(id, xmlContent);
}
},
crawlSinglePage: async (id, params, aiSummary) => {
const { crawlMultiPages } = get();
return await crawlMultiPages(id, { urls: [params.url] }, aiSummary);
},
saveSearchResult: async (id) => {
const message = chatSelectors.getMessageById(id)(get());
if (!message || !message.plugin) return;
const { internal_addToolToAssistantMessage, internal_createMessage, openToolUI } = get();
// 1. 创建一个新的 tool call message
const newToolCallId = `tool_call_${nanoid()}`;
const toolMessage: CreateMessageParams = {
content: message.content,
id: undefined,
parentId: message.parentId,
plugin: message.plugin,
pluginState: message.pluginState,
role: 'tool',
sessionId: get().activeId,
tool_call_id: newToolCallId,
topicId: get().activeTopicId,
};
const addToolItem = async () => {
if (!message.parentId || !message.plugin) return;
await internal_addToolToAssistantMessage(message.parentId, {
id: newToolCallId,
...message.plugin,
});
};
const [newMessageId] = await Promise.all([
// 1. 添加 tool message
internal_createMessage(toolMessage),
// 2. 将这条 tool call message 插入到 ai 消息的 tools 中
addToolItem(),
]);
if (!newMessageId) return;
// 将新创建的 tool message 激活
openToolUI(newMessageId, message.plugin.identifier);
},
search: async (id, params, aiSummary = true) => {
get().toggleSearchLoading(id, true);
const { content, success, error, state } = await runtime.search(params);
if (success) {
await get().updatePluginState(id, state);
} else {
if ((error as Error).message === SEARCH_SEARXNG_NOT_CONFIG) {
await get().internal_updateMessagePluginError(id, {
body: {
provider: 'searxng',
},
message: 'SearXNG is not configured',
type: 'PluginSettingsInvalid',
});
} else {
await get().internal_updateMessagePluginError(id, {
body: error,
message: (error as Error).message,
type: 'PluginServerError',
});
}
}
get().toggleSearchLoading(id, false);
await get().internal_updateMessageContent(id, content);
// 如果 aiSummary 为 true,则会自动触发总结
return aiSummary;
},
togglePageContent: (url) => {
set({ activePageContentUrl: url });
},
toggleSearchLoading: (id, loading) => {
set(
{ searchLoading: { ...get().searchLoading, [id]: loading } },
false,
`toggleSearchLoading/${loading ? 'start' : 'end'}`,
);
},
triggerSearchAgain: async (id, data, options) => {
get().toggleSearchLoading(id, true);
await get().updatePluginArguments(id, data);
await get().search(id, data, options?.aiSummary);
},
});