fix(chat): 修复重连后对话状态丢失导致消息发送失败的问题
在 WebSocket 重连场景下,服务端可能丢失 `conversationId` 状态,导致后续消息发送被拒绝。通过在客户端消息中携带 `conversationId`,并在服务端添加 fallback 恢复逻辑,确保重连后仍能正常发送消息。 同时优化了 `pendingMessage` 类型定义,支持存储待发送的图片附件,修复了延迟发送场景下图片丢失的问题。
This commit is contained in:
@@ -75,7 +75,7 @@ export function ChatInput({ onSend, disabled, connecting }: { onSend: (content:
|
|||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
const text = input.trim();
|
const text = input.trim();
|
||||||
if ((!text && images.length === 0) || disabled) return;
|
if ((!text && images.length === 0) || disabled) return;
|
||||||
const imgs = images.length > 0 ? images.map(({ data, mimeType }) => ({ data, mimeType })) : undefined;
|
const imgs = images.length > 0 ? images.map(({ data, mimeType }) => ({ type: 'image' as const, data, mimeType })) : undefined;
|
||||||
onSend(text || '(图片)', imgs);
|
onSend(text || '(图片)', imgs);
|
||||||
setInput('');
|
setInput('');
|
||||||
setImages([]);
|
setImages([]);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function ChatView() {
|
|||||||
const [manifestPath, setManifestPath] = useState<string | null>(null);
|
const [manifestPath, setManifestPath] = useState<string | null>(null);
|
||||||
const [accounts, setAccounts] = useState<Account[]>([]);
|
const [accounts, setAccounts] = useState<Account[]>([]);
|
||||||
const [quote, setQuote] = useState<string | null>(null);
|
const [quote, setQuote] = useState<string | null>(null);
|
||||||
const [pendingMessage, setPendingMessage] = useState<string | null>(null);
|
const [pendingMessage, setPendingMessage] = useState<{ content: string; images?: Array<{ type: 'image'; data: string; mimeType: string }> } | null>(null);
|
||||||
const [showScrollBtn, setShowScrollBtn] = useState(false);
|
const [showScrollBtn, setShowScrollBtn] = useState(false);
|
||||||
const creatingRef = useRef(false);
|
const creatingRef = useRef(false);
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -72,7 +72,7 @@ export function ChatView() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (conversationId && connected && pendingMessage) {
|
if (conversationId && connected && pendingMessage) {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
send(pendingMessage);
|
send(pendingMessage.content, pendingMessage.images);
|
||||||
setPendingMessage(null);
|
setPendingMessage(null);
|
||||||
}, 300);
|
}, 300);
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
@@ -108,10 +108,10 @@ export function ChatView() {
|
|||||||
}, [removeMessage]);
|
}, [removeMessage]);
|
||||||
|
|
||||||
// Delayed conversation creation
|
// Delayed conversation creation
|
||||||
const handleSendNew = useCallback(async (content: string) => {
|
const handleSendNew = useCallback(async (content: string, images?: Array<{ data: string; mimeType: string }>) => {
|
||||||
if (creatingRef.current) return;
|
if (creatingRef.current) return;
|
||||||
creatingRef.current = true;
|
creatingRef.current = true;
|
||||||
setPendingMessage(content);
|
setPendingMessage({ content, images });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newId = await createConversation(content.slice(0, 30), selectedAccountId || undefined);
|
const newId = await createConversation(content.slice(0, 30), selectedAccountId || undefined);
|
||||||
@@ -131,7 +131,7 @@ export function ChatView() {
|
|||||||
if (conversationId) {
|
if (conversationId) {
|
||||||
send(content, images);
|
send(content, images);
|
||||||
} else {
|
} else {
|
||||||
handleSendNew(content);
|
handleSendNew(content, images);
|
||||||
}
|
}
|
||||||
}, [send, quote, conversationId]);
|
}, [send, quote, conversationId]);
|
||||||
|
|
||||||
|
|||||||
@@ -156,8 +156,9 @@ export function useChat(conversationId: string | null) {
|
|||||||
}
|
}
|
||||||
}, [conversationId, connected]);
|
}, [conversationId, connected]);
|
||||||
|
|
||||||
const send = useCallback((content: string, images?: Array<{ data: string; mimeType: string }>) => {
|
const send = useCallback((content: string, images?: Array<{ type: 'image'; data: string; mimeType: string }>) => {
|
||||||
chatSocket.send('chat', { content, images });
|
const convId = activeConvRef.current;
|
||||||
|
chatSocket.send('chat', { content, images, conversationId: convId });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const stop = useCallback(() => {
|
const stop = useCallback(() => {
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ export function handleChat(ws: WebSocket) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (msg.type === 'chat') {
|
if (msg.type === 'chat') {
|
||||||
|
// Fallback: use conversationId from message if server state lost (e.g. after reconnect)
|
||||||
|
if (!conversationId && msg.conversationId) {
|
||||||
|
conversationId = msg.conversationId as string;
|
||||||
|
}
|
||||||
if (!conversationId) {
|
if (!conversationId) {
|
||||||
ws.send(JSON.stringify({ type: 'error', data: { message: '没有活跃对话,请先创建或选择一个对话' } }));
|
ws.send(JSON.stringify({ type: 'error', data: { message: '没有活跃对话,请先创建或选择一个对话' } }));
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user