fixed: chat message confusion (#782)

* fix: chat

* fix: chat

* chore: add session id

* fix: fixed incorrect taskbar icon display on linux (#783)

* fix: fixed incorrect taskbar icon display on linux

* docs: update changelog

* fix: fix data inconsistency issue on secondary pages (#784)

* chore: chat

* chore: chat

* chore: add logging message

* chore: chat

* chore: chat

* chore: add

* feat: add

* chore: chat end

* style: message width

---------

Co-authored-by: ayangweb <75017711+ayangweb@users.noreply.github.com>
Co-authored-by: medcl <m@medcl.net>
This commit is contained in:
BiggerRain
2025-07-21 21:17:20 +08:00
committed by GitHub
parent 74ed642a42
commit f1dfc5c730
15 changed files with 221 additions and 116 deletions

View File

@@ -62,6 +62,7 @@
"traptitech",
"unlisten",
"unlistener",
"unlisteners",
"unminimize",
"uuidv",
"VITE",

View File

@@ -163,6 +163,7 @@ pub async fn chat_create<R: Runtime>(
server_id: String,
message: String,
query_params: Option<HashMap<String, Value>>,
client_id: String,
) -> Result<(), String> {
let body = if !message.is_empty() {
let message = ChatRequestMessage {
@@ -202,10 +203,12 @@ pub async fn chat_create<R: Runtime>(
);
let mut lines = tokio::io::BufReader::new(reader).lines();
while let Ok(Some(line)) = lines.next_line().await {
log::debug!("Received chat stream line: {}", &line);
log::info!("client_id_create: {}", &client_id);
if let Err(err) = app_handle.emit("chat-create-stream", line) {
while let Ok(Some(line)) = lines.next_line().await {
log::info!("Received chat stream line: {}", &line);
if let Err(err) = app_handle.emit(&client_id, line) {
log::error!("Emit failed: {:?}", err);
print!("Error sending message: {:?}", err);
@@ -255,6 +258,7 @@ pub async fn chat_chat<R: Runtime>(
session_id: String,
message: String,
query_params: Option<HashMap<String, Value>>, //search,deep_thinking
client_id: String,
) -> Result<(), String> {
let body = if !message.is_empty() {
let message = ChatRequestMessage {
@@ -295,11 +299,18 @@ pub async fn chat_chat<R: Runtime>(
stream.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)),
);
let mut lines = tokio::io::BufReader::new(reader).lines();
let mut first_log = true;
log::info!("client_id: {}", &client_id);
while let Ok(Some(line)) = lines.next_line().await {
log::debug!("Received chat stream line: {}", &line);
log::info!("Received chat stream line: {}", &line);
if first_log {
log::info!("first stream line: {}", &line);
first_log = false;
}
if let Err(err) = app_handle.emit("chat-create-stream", line) {
if let Err(err) = app_handle.emit(&client_id, line) {
log::error!("Emit failed: {:?}", err);
let _ = app_handle.emit("chat-create-error", format!("Emit failed: {:?}", err));
}

View File

@@ -249,15 +249,18 @@ export function chat_create({
serverId,
message,
queryParams,
clientId,
}: {
serverId: string;
message: string;
queryParams?: Record<string, any>;
clientId: string;
}): Promise<GetResponse> {
return invokeWithErrorHandler(`chat_create`, {
serverId,
message,
queryParams,
clientId,
});
}
@@ -288,17 +291,20 @@ export function chat_chat({
sessionId,
message,
queryParams,
clientId,
}: {
serverId: string;
sessionId: string;
message: string;
queryParams?: Record<string, any>;
clientId: string;
}): Promise<string> {
return invokeWithErrorHandler(`chat_chat`, {
serverId,
sessionId,
message,
queryParams,
clientId,
});
}

View File

@@ -40,6 +40,7 @@ interface ChatAIProps {
assistantIDs?: string[];
startPage?: StartPage;
formatUrl?: (data: any) => string;
instanceId?: string;
}
export interface ChatAIRef {
@@ -66,6 +67,7 @@ const ChatAI = memo(
assistantIDs,
startPage,
formatUrl,
instanceId,
},
ref
) => {
@@ -91,7 +93,7 @@ const ChatAI = memo(
const [activeChat, setActiveChat] = useState<Chat>();
const [timedoutShow, setTimedoutShow] = useState(false);
const curIdRef = useRef("");
const curSessionIdRef = useRef("");
const [isSidebarOpenChat, setIsSidebarOpenChat] = useState(isSidebarOpen);
const [chats, setChats] = useState<Chat[]>([]);
@@ -174,18 +176,19 @@ const ChatAI = memo(
setTimedoutShow,
clearAllChunkData,
setQuestion,
curIdRef,
curSessionIdRef,
setChats,
dealMsgRef,
isChatPage,
isSearchActive,
isDeepThinkActive,
isMCPActive,
changeInput,
showChatHistory
showChatHistory,
);
const { dealMsg } = useMessageHandler(
curIdRef,
curSessionIdRef,
setCurChatEnd,
setTimedoutShow,
(chat) => cancelChat(chat || activeChat),
@@ -359,6 +362,7 @@ const ChatAI = memo(
)}
<div
data-tauri-drag-region
data-chat-instance={instanceId}
className={`flex flex-col rounded-md h-full overflow-hidden relative`}
>
<ChatHeader
@@ -392,6 +396,7 @@ const ChatAI = memo(
}
getFileUrl={getFileUrl}
formatUrl={formatUrl}
curSessionIdRef={curSessionIdRef}
/>
<Splash assistantIDs={assistantIDs} startPage={startPage} />
</>

View File

@@ -27,6 +27,7 @@ interface ChatContentProps {
handleSendMessage: (content: string, newChat?: Chat) => void;
getFileUrl: (path: string) => string;
formatUrl?: (data: any) => string;
curSessionIdRef: React.MutableRefObject<string>;
}
export const ChatContent = ({
@@ -44,7 +45,9 @@ export const ChatContent = ({
Question,
handleSendMessage,
formatUrl,
curSessionIdRef,
}: ChatContentProps) => {
console.log("curSessionIdRef", curSessionIdRef.current === activeChat?._id);
// const sessionId = useConnectStore((state) => state.currentSessionId);
const setCurrentSessionId = useConnectStore((state) => {
return state.setCurrentSessionId;
@@ -68,7 +71,7 @@ export const ChatContent = ({
useEffect(() => {
scrollToBottom();
}, [
activeChat?.id,
activeChat?._id,
query_intent?.message_chunk,
fetch_source?.message_chunk,
pick_source?.message_chunk,
@@ -122,7 +125,7 @@ export const ChatContent = ({
deep_read ||
think ||
response) &&
activeChat?._id ? (
activeChat?._id === curSessionIdRef.current ? (
<ChatMessage
key={"current"}
message={{

View File

@@ -43,17 +43,21 @@ export const QueryIntent = ({
useEffect(() => {
if (!ChunkData?.message_chunk) return;
if (!loading) {
const cleanContent = ChunkData.message_chunk.replace(/^"|"$/g, "");
const allMatches = cleanContent.match(/<JSON>([\s\S]*?)<\/JSON>/g);
if (allMatches) {
const lastMatch = allMatches[allMatches.length - 1];
const jsonString = lastMatch.replace(/<JSON>|<\/JSON>/g, "");
const data = JSON.parse(jsonString);
//console.log("QueryIntent", data);
if (data?.suggestion && getSuggestion) {
getSuggestion(data?.suggestion);
try {
const cleanContent = ChunkData.message_chunk.replace(/^"|"$/g, "");
const allMatches = cleanContent.match(/<JSON>([\s\S]*?)<\/JSON>/g);
if (allMatches) {
const lastMatch = allMatches[allMatches.length - 1];
const jsonString = lastMatch.replace(/<JSON>|<\/JSON>/g, "");
const data = JSON.parse(jsonString);
//console.log("QueryIntent", data);
if (data?.suggestion && getSuggestion) {
getSuggestion(data?.suggestion);
}
setData(data);
}
setData(data);
} catch (error) {
console.error("Failed to process message chunk in QueryIntent:", error);
}
}
}, [ChunkData?.message_chunk, loading]);
@@ -79,14 +83,22 @@ export const QueryIntent = ({
<>
<Loader className="w-4 h-4 animate-spin text-[#1990FF]" />
<span className="text-xs text-[#999999] italic">
{t(`assistant.message.steps.${ChunkData?.chunk_type || Detail.type}`)}
{t(
`assistant.message.steps.${
ChunkData?.chunk_type || Detail.type
}`
)}
</span>
</>
) : (
<>
<UnderstandIcon className="w-4 h-4 text-[#38C200]" />
<span className="text-xs text-[#999999]">
{t(`assistant.message.steps.${ChunkData?.chunk_type || Detail.type}`)}
{t(
`assistant.message.steps.${
ChunkData?.chunk_type || Detail.type
}`
)}
</span>
</>
)}

View File

@@ -29,7 +29,7 @@ export const UserMessage = ({ messageContent }: UserMessageProps) => {
return (
<div
className="flex gap-1 items-center justify-end"
className="max-w-full flex gap-1 items-center justify-end"
onMouseEnter={() => setShowCopyButton(true)}
onMouseLeave={() => setShowCopyButton(false)}
>

View File

@@ -176,13 +176,13 @@ export const ChatMessage = memo(function ChatMessage({
return (
<div
className={clsx(
"py-8 flex",
"w-full py-8 flex",
[isAssistant ? "justify-start" : "justify-end"],
rootClassName
)}
>
<div
className={`px-4 flex gap-4 ${
className={`w-full px-4 flex gap-4 ${
isAssistant ? "w-full" : "flex-row-reverse"
}`}
>

View File

@@ -271,6 +271,7 @@ function SearchChat({
<ChatAI
ref={chatAIRef}
key="ChatAI"
instanceId="search-chat"
changeInput={setInput}
isSearchActive={isSearchActive}
isDeepThinkActive={isDeepThinkActive}

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState, useRef } from "react";
import { useCallback, useEffect, useState, useRef, useMemo } from "react";
import type { Chat } from "@/types/chat";
import { useAppStore } from "@/stores/appStore";
@@ -16,9 +16,10 @@ export function useChatActions(
setTimedoutShow: (value: boolean) => void,
clearAllChunkData: () => void,
setQuestion: (value: string) => void,
curIdRef: React.MutableRefObject<string>,
curSessionIdRef: React.MutableRefObject<string>,
setChats: (chats: Chat[]) => void,
dealMsgRef: React.MutableRefObject<((msg: string) => void) | null>,
isChatPage?: boolean,
isSearchActive?: boolean,
isDeepThinkActive?: boolean,
isMCPActive?: boolean,
@@ -40,6 +41,13 @@ export function useChatActions(
const [keyword, setKeyword] = useState("");
// Add a ref at the beginning of the useChatActions function to store the listener.
const unlistenersRef = useRef<{
message?: () => void;
chatMessage?: () => void;
error?: () => void;
}>({});
const chatClose = useCallback(
async (activeChat?: Chat) => {
if (!activeChat?._id) return;
@@ -61,9 +69,16 @@ export function useChatActions(
[currentService?.id, isTauri]
);
// 1. onSelectChat
// 2. dealMsg setTimedoutShow
// 3. disabledChange Manual shutdown
const cancelChat = useCallback(
async (activeChat?: Chat) => {
setCurChatEnd(true);
// Stop listening for streaming data.
cleanupListeners();
if (!activeChat?._id) return;
let response: any;
if (isTauri) {
@@ -93,6 +108,7 @@ export function useChatActions(
async (chat: Chat, callback?: (chat: Chat) => void) => {
if (!chat?._id) return;
curSessionIdRef.current = chat?._id;
let response: any;
if (isTauri) {
if (!currentService?.id) return;
@@ -100,13 +116,13 @@ export function useChatActions(
serverId: currentService?.id,
sessionId: chat?._id,
from: 0,
size: 100,
size: 1000,
});
response = response ? JSON.parse(response) : null;
} else {
const [_error, res] = await Get(`/chat/${chat?._id}/_history`, {
from: 0,
size: 100,
size: 1000,
});
response = res;
}
@@ -134,14 +150,32 @@ export function useChatActions(
[currentService?.id, isTauri, assistantList]
);
// Modify the clientId generation logic to include the instance ID.
const clientId = useMemo(() => {
const timestamp = Date.now();
const pageType = isChatPage ? "standalone-chat" : "search-chat";
return `${pageType}-${timestamp}`;
}, [isChatPage]);
const createNewChat = useCallback(
async (value: string = "", activeChat?: Chat) => {
if (!value) return;
// 1. Set up the listener first
await setupListeners();
// 2. Update the status again
changeInput && changeInput("");
setCurChatEnd(false);
setVisibleStartPage(false);
setTimedoutShow(false);
// 3. Cleaning and preparation
await chatClose(activeChat);
clearAllChunkData();
setQuestion(value);
//console.log("sourceDataIds", sourceDataIds, MCPIds, id);
// 4. request API
const queryParams = {
search: isSearchActive,
deep_thinking: isDeepThinkActive,
@@ -150,13 +184,17 @@ export function useChatActions(
mcp_servers: MCPIds?.join(",") || "",
assistant_id: currentAssistant?._id || "",
};
if (isTauri) {
if (!currentService?.id) return;
await platformAdapter.commands("chat_create", {
serverId: currentService?.id,
message: value,
queryParams,
clientId: `chat-stream-${clientId}`,
});
console.log("_create end", value);
setCurChatEnd(true);
} else {
await streamPost({
url: "/chat/_create",
@@ -169,7 +207,6 @@ export function useChatActions(
},
});
}
console.log("_create", currentService?.id, value, queryParams);
},
[
isTauri,
@@ -179,9 +216,9 @@ export function useChatActions(
isSearchActive,
isDeepThinkActive,
isMCPActive,
curIdRef,
currentAssistant,
chatClose,
clientId,
]
);
@@ -189,7 +226,18 @@ export function useChatActions(
async (content: string, newChat: Chat) => {
if (!newChat?._id || !content) return;
// 1.
await setupListeners();
// 2.
changeInput && changeInput("");
setCurChatEnd(false);
setVisibleStartPage(false);
setTimedoutShow(false);
// 3.
clearAllChunkData();
setQuestion(content);
const queryParams = {
search: isSearchActive,
@@ -199,6 +247,7 @@ export function useChatActions(
mcp_servers: MCPIds?.join(",") || "",
assistant_id: currentAssistant?._id || "",
};
if (isTauri) {
if (!currentService?.id) return;
await platformAdapter.commands("chat_chat", {
@@ -206,7 +255,10 @@ export function useChatActions(
sessionId: newChat?._id,
queryParams,
message: content,
clientId: `chat-stream-${clientId}`,
});
console.log("chat_chat end", content);
setCurChatEnd(true);
} else {
await streamPost({
url: `/chat/${newChat?._id}/_chat`,
@@ -219,14 +271,6 @@ export function useChatActions(
},
});
}
console.log(
"chat_chat",
currentService?.id,
newChat?._id,
queryParams,
content
);
},
[
isTauri,
@@ -236,18 +280,15 @@ export function useChatActions(
isSearchActive,
isDeepThinkActive,
isMCPActive,
curIdRef,
changeInput,
currentAssistant,
clientId,
]
);
const handleSendMessage = useCallback(
async (content: string, activeChat?: Chat) => {
if (!activeChat?._id || !content) return;
setQuestion(content);
setTimedoutShow(false);
await chatHistory(activeChat, (chat) => sendMessage(content, chat));
},
@@ -257,80 +298,110 @@ export function useChatActions(
const handleChatCreateStreamMessage = useCallback(
(msg: string) => {
if (
msg.includes("_id") &&
msg.includes(`"user"`) &&
msg.includes("_source") &&
msg.includes("result")
) {
const response = JSON.parse(msg);
console.log("first", response);
let updatedChat: Chat;
if (Array.isArray(response)) {
curIdRef.current = response[0]?._id;
updatedChat = {
...updatedChatRef.current,
messages: [
...(updatedChatRef.current?.messages || []),
...(response || []),
],
};
console.log("array", updatedChat, updatedChatRef.current?.messages);
} else {
const newChat: Chat = response;
curIdRef.current = response?.payload?.id;
try {
const response = JSON.parse(msg);
console.log("first", response);
newChat._source = {
...response?.payload,
};
updatedChat = {
...newChat,
messages: [newChat],
};
let updatedChat: Chat;
if (Array.isArray(response)) {
curSessionIdRef.current = response[0]?._source?.session_id;
console.log("first-curSessionIdRef-Array", curSessionIdRef.current);
updatedChat = {
...updatedChatRef.current,
messages: [
...(updatedChatRef.current?.messages || []),
...(response || []),
],
};
console.log("array", updatedChat, updatedChatRef.current?.messages);
} else {
const newChat: Chat = response;
curSessionIdRef.current = response?.payload?.session_id;
console.log("first-curSessionIdRef", curSessionIdRef.current);
newChat._source = {
...response?.payload,
};
updatedChat = {
...newChat,
messages: [newChat],
};
}
setActiveChat(updatedChat);
return;
} catch (error) {
console.error("Failed to parse JSON:", error, "Raw message:", msg);
return;
}
changeInput && changeInput("");
setActiveChat(updatedChat);
setCurChatEnd(false);
setVisibleStartPage(false);
return;
}
dealMsgRef.current?.(msg);
},
[
curIdRef,
updatedChatRef,
changeInput,
setActiveChat,
setCurChatEnd,
setVisibleStartPage,
dealMsgRef,
]
[changeInput, setActiveChat, setCurChatEnd, setVisibleStartPage]
);
useEffect(() => {
if (!isTauri || !currentService?.id) return;
const cleanupListeners = useCallback(() => {
if (unlistenersRef.current.chatMessage) {
unlistenersRef.current.chatMessage();
}
if (unlistenersRef.current.error) {
unlistenersRef.current.error();
}
unlistenersRef.current = {};
}, []);
const unlisten_message = platformAdapter.listenEvent(
`chat-create-stream`,
const setupListeners = useCallback(async () => {
cleanupListeners();
console.log("setupListeners", clientId);
const unlisten_chat_message = await platformAdapter.listenEvent(
`chat-stream-${clientId}`,
(event) => {
const msg = event.payload as string;
//console.log("chat-create-stream", msg);
try {
console.log("msg:", JSON.parse(msg));
console.log("user:", msg.includes(`"user"`));
console.log("_source:", msg.includes("_source"));
console.log("result:", msg.includes("result"));
console.log("");
console.log("");
console.log("");
console.log("");
console.log("");
} catch (error) {
console.error("Failed to parse JSON in listener:", error);
}
handleChatCreateStreamMessage(msg);
}
);
const unlisten_error = platformAdapter.listenEvent(
const unlisten_error = await platformAdapter.listenEvent(
`chat-create-error`,
(event) => {
console.error("chat-create-error", event.payload);
}
);
return () => {
unlisten_message.then((fn) => fn());
unlisten_error.then((fn) => fn());
// Store the listener references.
unlistenersRef.current = {
chatMessage: unlisten_chat_message,
error: unlisten_error,
};
}, [currentService?.id, dealMsgRef, updatedChatRef.current]);
}, [currentService?.id, clientId, handleChatCreateStreamMessage]);
useEffect(() => {
if (!isTauri || !currentService?.id) return;
return () => {
cleanupListeners();
};
}, [currentService?.id]);
const openSessionChat = useCallback(
async (chat: Chat) => {
@@ -366,7 +437,7 @@ export function useChatActions(
response = await platformAdapter.commands("chat_history", {
serverId: currentService?.id,
from: 0,
size: 100,
size: 1000,
query: keyword,
});
@@ -374,7 +445,7 @@ export function useChatActions(
} else {
const [_error, res] = await Get(`/chat/_history`, {
from: 0,
size: 100,
size: 1000,
});
response = res;
}

View File

@@ -4,7 +4,7 @@ import type { IChunkData, Chat } from "@/types/chat";
import { useConnectStore } from "@/stores/connectStore";
export function useMessageHandler(
curIdRef: React.MutableRefObject<string>,
curSessionIdRef: React.MutableRefObject<string>,
setCurChatEnd: (value: boolean) => void,
setTimedoutShow: (value: boolean) => void,
onCancel: (chat?: Chat) => void,
@@ -41,8 +41,9 @@ export function useMessageHandler(
try {
const chunkData = JSON.parse(msg);
// console.log("chunkData", chunkData);
if (chunkData.reply_to_message !== curIdRef.current) return;
if (chunkData.session_id !== curSessionIdRef.current) return;
setLoadingStep(() => ({
query_intent: false,
@@ -55,8 +56,6 @@ export function useMessageHandler(
[chunkData.chunk_type]: true,
}));
if (chunkData.chunk_type === "query_intent") {
handlers.deal_query_intent(chunkData);
} else if (chunkData.chunk_type === "tools") {
@@ -87,7 +86,7 @@ export function useMessageHandler(
}
if (inThinkRef.current) {
handlers.deal_think({...chunkData, chunk_type: "think"});
handlers.deal_think({ ...chunkData, chunk_type: "think" });
} else {
handlers.deal_response(chunkData);
}
@@ -105,13 +104,7 @@ export function useMessageHandler(
console.error("parse error:", error);
}
},
[
onCancel,
setCurChatEnd,
setTimedoutShow,
curIdRef.current,
connectionTimeout,
]
[onCancel, setCurChatEnd, setTimedoutShow, connectionTimeout]
);
return {

View File

@@ -31,9 +31,9 @@ import { useSyncStore } from "@/hooks/useSyncStore";
import { useAppStore } from "@/stores/appStore";
import { unrequitable } from "@/utils";
interface ChatProps {}
interface StandaloneChatProps {}
export default function Chat({}: ChatProps) {
export default function StandaloneChat({}: StandaloneChatProps) {
const setIsTauri = useAppStore((state) => state.setIsTauri);
useEffect(() => {
setIsTauri(true);
@@ -77,7 +77,7 @@ export default function Chat({}: ChatProps) {
let response: any = await chat_history({
serverId: currentService?.id,
from: 0,
size: 100,
size: 1000,
query: keyword,
});
response = response ? JSON.parse(response) : null;
@@ -122,7 +122,7 @@ export default function Chat({}: ChatProps) {
serverId: currentService?.id,
sessionId: chat?._id || "",
from: 0,
size: 100,
size: 1000,
});
response = response ? JSON.parse(response) : null;
console.log("id_history", response);
@@ -304,6 +304,7 @@ export default function Chat({}: ChatProps) {
<ChatAI
ref={chatAIRef}
key="ChatAI"
instanceId="standalone-chat"
activeChatProp={activeChat}
isSearchActive={isSearchActive}
isDeepThinkActive={isDeepThinkActive}

View File

@@ -4,7 +4,7 @@ import Layout from "./layout";
import ErrorPage from "@/pages/error/index";
import DesktopApp from "@/pages/main/index";
import SettingsPage from "@/pages/settings/index";
import ChatAI from "@/pages/chat/index";
import StandaloneChat from "@/pages/chat/index";
import WebPage from "@/pages/web/index";
import CheckPage from "@/pages/check/index";
@@ -25,7 +25,7 @@ export const router = createBrowserRouter(
children: [
{ path: "/ui", element: <DesktopApp /> },
{ path: "/ui/settings", element: <SettingsPage /> },
{ path: "/ui/chat", element: <ChatAI /> },
{ path: "/ui/chat", element: <StandaloneChat /> },
{ path: "/ui/check", element: <CheckPage /> },
{ path: "/web", element: <WebPage /> },
],

View File

@@ -14,9 +14,10 @@ export interface ISource {
message?: any;
title?: string;
question?: string;
details?: any[];
details?: any[] | null;
assistant_id?: string;
assistant_item?: any;
[key: string]: any;
}
export interface Chat {
_id?: string;

View File

@@ -48,7 +48,7 @@ export interface EventPayloads {
"install-extension": void;
"uninstall-extension": void;
"config-extension": string;
"chat-create-stream": string;
[key: `chat-stream-${string}`]: string;
"chat-create-error": string;
[key: `synthesize-${string}`]: any;
}