Enhance LLM text streaming and message handling in backend and frontend
- Introduce event handlers in PassthroughLLMAssistantAggregator for managing LLM text streaming, including start, delta, and end events. - Implement a new method to finalize text streams, ensuring proper handling of interruptions. - Update useVoicePreview to support new message types for LLM text streaming, allowing real-time updates to chat messages. - Enhance message sorting logic to maintain order based on timestamps and sequence numbers, improving user experience during voice interactions.
This commit is contained in:
@@ -29,6 +29,9 @@ export type ChatMessage = {
|
||||
content: string;
|
||||
/** 后端给的 ISO 时间戳 */
|
||||
timestamp: string;
|
||||
sequence: number;
|
||||
turnId?: string;
|
||||
streaming?: boolean;
|
||||
};
|
||||
|
||||
// http→ws、https→wss,自动跟随 API 基址(同源反代时也对)
|
||||
@@ -59,6 +62,12 @@ function messageOrder(message: ChatMessage): number {
|
||||
return Number.isNaN(timestamp) ? Number.MAX_SAFE_INTEGER : timestamp;
|
||||
}
|
||||
|
||||
function sortMessages(messages: ChatMessage[]): ChatMessage[] {
|
||||
return messages.sort(
|
||||
(a, b) => messageOrder(a) - messageOrder(b) || a.sequence - b.sequence,
|
||||
);
|
||||
}
|
||||
|
||||
function microphoneErrorMessage(error: unknown): string {
|
||||
if (error instanceof DOMException) {
|
||||
if (error.name === "NotAllowedError") {
|
||||
@@ -303,6 +312,54 @@ export function useVoicePreview(assistantId: string | null) {
|
||||
try {
|
||||
const msg = JSON.parse(event.data);
|
||||
if (
|
||||
msg?.type === "assistant-text-start" &&
|
||||
typeof msg.turn_id === "string"
|
||||
) {
|
||||
messageSeqRef.current += 1;
|
||||
const next: ChatMessage = {
|
||||
id: `assistant-${msg.turn_id}`,
|
||||
role: "assistant",
|
||||
content: "",
|
||||
timestamp:
|
||||
typeof msg.timestamp === "string"
|
||||
? msg.timestamp
|
||||
: new Date().toISOString(),
|
||||
sequence: messageSeqRef.current,
|
||||
turnId: msg.turn_id,
|
||||
streaming: true,
|
||||
};
|
||||
setMessages((prev) => sortMessages([...prev, next]));
|
||||
} else if (
|
||||
msg?.type === "assistant-text-delta" &&
|
||||
typeof msg.turn_id === "string" &&
|
||||
typeof msg.delta === "string"
|
||||
) {
|
||||
setMessages((prev) =>
|
||||
prev.map((message) =>
|
||||
message.turnId === msg.turn_id
|
||||
? { ...message, content: message.content + msg.delta }
|
||||
: message,
|
||||
),
|
||||
);
|
||||
} else if (
|
||||
msg?.type === "assistant-text-end" &&
|
||||
typeof msg.turn_id === "string"
|
||||
) {
|
||||
setMessages((prev) =>
|
||||
prev.map((message) =>
|
||||
message.turnId === msg.turn_id
|
||||
? {
|
||||
...message,
|
||||
content:
|
||||
typeof msg.content === "string"
|
||||
? msg.content
|
||||
: message.content,
|
||||
streaming: false,
|
||||
}
|
||||
: message,
|
||||
),
|
||||
);
|
||||
} else if (
|
||||
msg?.type === "transcript" &&
|
||||
(msg.role === "user" || msg.role === "assistant") &&
|
||||
typeof msg.content === "string" &&
|
||||
@@ -317,15 +374,9 @@ export function useVoicePreview(assistantId: string | null) {
|
||||
typeof msg.timestamp === "string"
|
||||
? msg.timestamp
|
||||
: new Date().toISOString(),
|
||||
sequence: messageSeqRef.current,
|
||||
};
|
||||
setMessages((prev) =>
|
||||
[...prev, next].sort(
|
||||
(a, b) =>
|
||||
messageOrder(a) - messageOrder(b) ||
|
||||
Number(a.id.replace("msg-", "")) -
|
||||
Number(b.id.replace("msg-", "")),
|
||||
),
|
||||
);
|
||||
setMessages((prev) => sortMessages([...prev, next]));
|
||||
}
|
||||
} catch {
|
||||
/* 非 JSON / 未知消息,忽略 */
|
||||
|
||||
Reference in New Issue
Block a user