Files
next-tjwater-drainage-frontend/features/agent/components/streaming-token-response.tsx
T

84 lines
1.9 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";
import { MessageResponse } from "@/shared/ai-elements/message";
import {
createStreamingMarkdownBufferState,
resolveStreamingMarkdownDisplayContent,
takeNextStreamingMarkdownChunk,
} from "./streaming-token-response-state";
type StreamingTokenResponseProps = {
content: string;
streaming: boolean;
streamDone?: boolean;
className?: string;
messageId?: string;
};
const STREAMING_MARKDOWN_FRAME_MS = 45;
export function StreamingTokenResponse({
content,
streaming,
streamDone = !streaming,
className,
messageId,
}: StreamingTokenResponseProps) {
const [displayContent, setDisplayContent] = useState(() =>
streamDone ? content : ""
);
useEffect(() => {
setDisplayContent((currentDisplayContent) =>
resolveStreamingMarkdownDisplayContent(
content,
currentDisplayContent,
streamDone
)
);
}, [content, messageId, streamDone]);
useEffect(() => {
if (streamDone || !streaming) {
return;
}
const showNextChunk = () => {
setDisplayContent((currentDisplayContent) => {
const { chunk, state } = takeNextStreamingMarkdownChunk(
createStreamingMarkdownBufferState(content, currentDisplayContent)
);
if (chunk || state.displayContent !== currentDisplayContent) {
return state.displayContent;
}
return currentDisplayContent;
});
};
showNextChunk();
const timer = window.setInterval(showNextChunk, STREAMING_MARKDOWN_FRAME_MS);
return () => window.clearInterval(timer);
}, [content, streamDone, streaming]);
if (!displayContent) {
return null;
}
return (
<MessageResponse
className={cn("agent-streaming-response", className)}
isAnimating={streaming}
mode="streaming"
parseIncompleteMarkdown={true}
>
{displayContent}
</MessageResponse>
);
}