Files
coco-app/src/hooks/useStreamChat.ts
BiggerRain 4e58bc4b2c fix: duplicate chat content (#916)
* style: add dark drop shadow to images

* docs: add release note

* style: add dark

* fix: duplicate chat content

* docs: add release note

* chore: update history list
2025-10-11 10:33:09 +08:00

142 lines
3.7 KiB
TypeScript

import { useAsyncEffect, useMount, useReactive, useUnmount } from "ahooks";
import { noop } from "lodash-es";
import { useRef, useState } from "react";
import { useAppStore } from "@/stores/appStore";
import { EventPayloads } from "@/types/platform";
import platformAdapter from "@/utils/platformAdapter";
import useMessageChunkData from "./useMessageChunkData";
import { nanoid } from "nanoid";
interface Options {
message: string;
clientId: keyof EventPayloads;
server?: any;
assistant?: any;
setVisible: (visible: boolean) => void;
}
interface State {
sessionId?: string;
isTyping?: boolean;
messageId: string;
}
export const useStreamChat = (options: Options) => {
const { message, clientId, server, assistant, setVisible } = options;
const unlistenRef = useRef<() => void>(noop);
const { addError } = useAppStore();
const state = useReactive<State>({
isTyping: true,
messageId: nanoid(),
});
const [loadingStep, setLoadingStep] = useState<Record<string, boolean>>({
query_intent: false,
tools: false,
fetch_source: false,
pick_source: false,
deep_read: false,
think: false,
response: false,
});
const {
data: chunkData,
handlers,
clearAllChunkData,
} = useMessageChunkData();
useMount(async () => {
try {
unlistenRef.current = await platformAdapter.listenEvent(
clientId,
({ payload }) => {
//console.log(clientId, JSON.parse(payload));
const chunkData = JSON.parse(payload);
if (chunkData?._id) {
state.sessionId = chunkData._id;
return;
}
if (state.sessionId !== chunkData.session_id) {
return;
}
// If the chunk data does not contain a message_chunk, we ignore it
if (chunkData.message_chunk.trim()) {
setVisible(true);
}
state.isTyping = true;
setLoadingStep(() => ({
query_intent: false,
tools: false,
fetch_source: false,
pick_source: false,
deep_read: false,
think: false,
response: false,
[chunkData.chunk_type]: true,
}));
if (chunkData.chunk_type === "query_intent") {
handlers.deal_query_intent(chunkData);
} else if (chunkData.chunk_type === "tools") {
handlers.deal_tools(chunkData);
} else if (chunkData.chunk_type === "fetch_source") {
handlers.deal_fetch_source(chunkData);
} else if (chunkData.chunk_type === "pick_source") {
handlers.deal_pick_source(chunkData);
} else if (chunkData.chunk_type === "deep_read") {
handlers.deal_deep_read(chunkData);
} else if (chunkData.chunk_type === "think") {
handlers.deal_think(chunkData);
} else if (chunkData.chunk_type === "response") {
handlers.deal_response(chunkData);
} else if (chunkData.chunk_type === "reply_end") {
console.log("AI finished output");
state.isTyping = false;
return;
}
}
);
} catch (error) {
addError(String(error));
}
});
useAsyncEffect(async () => {
if (!message || !server || !assistant) return;
await clearAllChunkData();
state.messageId = nanoid();
try {
await platformAdapter.invokeBackend("ask_ai", {
message,
clientId,
serverId: server.id,
assistantId: assistant.id,
});
} catch (error) {
addError(String(error));
}
}, [message, server, assistant]);
useUnmount(() => {
unlistenRef.current();
});
return {
...state,
chunkData,
loadingStep,
};
};