fix(room): clear AI stream state on room switch and hide cursor for saved chunks
- Clear activeAiStream, streamingChunks, and timers when room changes - Add showCursor prop to OrderedStreamChunks — only show cursor during active streaming, not for saved content
This commit is contained in:
parent
ab1ef0d1a7
commit
2bd40aee1b
@ -52,9 +52,12 @@ function parseSavedChunks(raw: string | null | undefined): Array<{ type: string;
|
|||||||
function OrderedStreamChunks({
|
function OrderedStreamChunks({
|
||||||
chunks,
|
chunks,
|
||||||
onMentionClick,
|
onMentionClick,
|
||||||
|
showCursor = true,
|
||||||
}: {
|
}: {
|
||||||
chunks: Array<{ type: string; content: string }>;
|
chunks: Array<{ type: string; content: string }>;
|
||||||
onMentionClick?: (type: string, id: string, label: string) => void;
|
onMentionClick?: (type: string, id: string, label: string) => void;
|
||||||
|
/** Show blinking cursor — only during active streaming */
|
||||||
|
showCursor?: boolean;
|
||||||
}) {
|
}) {
|
||||||
// Group consecutive same-type chunks (tool_call hidden)
|
// Group consecutive same-type chunks (tool_call hidden)
|
||||||
const groups: Array<{ type: 'thinking' | 'answer'; content: string }> = [];
|
const groups: Array<{ type: 'thinking' | 'answer'; content: string }> = [];
|
||||||
@ -78,8 +81,8 @@ function OrderedStreamChunks({
|
|||||||
<MessageContent key={i} content={group.content} onMentionClick={onMentionClick} />
|
<MessageContent key={i} content={group.content} onMentionClick={onMentionClick} />
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
{/* Streaming cursor */}
|
{/* Streaming cursor — only shown during active streaming */}
|
||||||
<span className="discord-streaming-cursor" />
|
{showCursor && <span className="discord-streaming-cursor" />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -415,10 +418,11 @@ export const MessageBubble = memo(function MessageBubble({
|
|||||||
onMentionClick={handleMentionClick}
|
onMentionClick={handleMentionClick}
|
||||||
/>
|
/>
|
||||||
) : parseSavedChunks(message.thinking_content) ? (
|
) : parseSavedChunks(message.thinking_content) ? (
|
||||||
/* Saved ordered chunks — render in original order */
|
/* Saved ordered chunks — render in original order (no cursor) */
|
||||||
<OrderedStreamChunks
|
<OrderedStreamChunks
|
||||||
chunks={parseSavedChunks(message.thinking_content)!}
|
chunks={parseSavedChunks(message.thinking_content)!}
|
||||||
onMentionClick={handleMentionClick}
|
onMentionClick={handleMentionClick}
|
||||||
|
showCursor={false}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
/* Legacy: aggregated thinking at top, content at bottom */
|
/* Legacy: aggregated thinking at top, content at bottom */
|
||||||
|
|||||||
@ -278,6 +278,13 @@ export function RoomProvider({
|
|||||||
setMessages([]);
|
setMessages([]);
|
||||||
setIsHistoryLoaded(false);
|
setIsHistoryLoaded(false);
|
||||||
setNextCursor(null);
|
setNextCursor(null);
|
||||||
|
// Clear AI stream state — prevents ghost "thinking..." indicator on room switch
|
||||||
|
setActiveAiStream(null);
|
||||||
|
setStreamingChunks(new Map());
|
||||||
|
streamingChunksRef.current.clear();
|
||||||
|
// Clear all streaming timers
|
||||||
|
streamingTimersRef.current.forEach((timer) => clearTimeout(timer));
|
||||||
|
streamingTimersRef.current.clear();
|
||||||
|
|
||||||
// Merge any buffered messages for the new room (Bug 3 fix)
|
// Merge any buffered messages for the new room (Bug 3 fix)
|
||||||
if (activeRoomId) {
|
if (activeRoomId) {
|
||||||
@ -1310,6 +1317,7 @@ export function RoomProvider({
|
|||||||
}, [projectName]);
|
}, [projectName]);
|
||||||
|
|
||||||
// Fetch room AI configs for @ai: mention suggestions
|
// Fetch room AI configs for @ai: mention suggestions
|
||||||
|
// Retry up to 3 times if any config has missing modelName
|
||||||
const fetchRoomAiConfigs = useCallback(async () => {
|
const fetchRoomAiConfigs = useCallback(async () => {
|
||||||
const client = wsClientRef.current;
|
const client = wsClientRef.current;
|
||||||
if (!activeRoomId || !client) {
|
if (!activeRoomId || !client) {
|
||||||
@ -1318,13 +1326,28 @@ export function RoomProvider({
|
|||||||
}
|
}
|
||||||
setAiConfigsLoading(true);
|
setAiConfigsLoading(true);
|
||||||
try {
|
try {
|
||||||
const configs = await client.aiList(activeRoomId);
|
const fetchOnce = async (): Promise<RoomAiConfig[]> => {
|
||||||
setRoomAiConfigs(
|
const configs = await client.aiList(activeRoomId);
|
||||||
configs.map((cfg) => ({
|
return configs.map((cfg) => ({
|
||||||
model: cfg.model,
|
model: cfg.model,
|
||||||
modelName: cfg.model_name,
|
modelName: cfg.model_name,
|
||||||
})),
|
}));
|
||||||
);
|
};
|
||||||
|
|
||||||
|
let configs = await fetchOnce();
|
||||||
|
let retries = 0;
|
||||||
|
const maxRetries = 3;
|
||||||
|
|
||||||
|
while (
|
||||||
|
configs.some((c) => !c.modelName) &&
|
||||||
|
retries < maxRetries
|
||||||
|
) {
|
||||||
|
retries++;
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
configs = await fetchOnce();
|
||||||
|
}
|
||||||
|
|
||||||
|
setRoomAiConfigs(configs);
|
||||||
} catch {
|
} catch {
|
||||||
setRoomAiConfigs([]);
|
setRoomAiConfigs([]);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user