feat: improve chat streaming UX and add Mermaid diagram rendering (#438)
* feat: show current branch in empty chat state * fix: display current git branch for worktrees and update them with branch change * refactor: improve read tool output parsing with structured data * feat: add support for message part delta events * fix(chat): improve streaming rendering, scroll behavior, and assistant action visibility * feat: Add mermaid diagram support to chat markdown rendering * fix: update table download functionality to include success notification and remove unused MarkdownRenderer import * refactor: streamline Streamdown component props for improved readability * fix: preserve Streamdown code-block markers and use native Tauri cache clearing * feat: add context overview panel to view conversation details
This commit is contained in:
committed by
GitHub
parent
138772e66e
commit
4d71bb27eb
@@ -818,7 +818,6 @@ export const useEventStream = () => {
|
||||
type: part.type || 'text',
|
||||
} as Part;
|
||||
|
||||
// Fallback: if we see assistant parts but session.status hasn't arrived yet, mark busy.
|
||||
if (roleInfo === 'assistant') {
|
||||
const partType = (messagePart as { type?: unknown }).type;
|
||||
const partTime = (messagePart as { time?: { end?: unknown } }).time;
|
||||
@@ -863,6 +862,81 @@ export const useEventStream = () => {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'message.part.delta': {
|
||||
const sessionId = readStringProp(props, ['sessionID', 'sessionId']);
|
||||
const messageId = readStringProp(props, ['messageID', 'messageId']);
|
||||
const partId = readStringProp(props, ['partID', 'partId']);
|
||||
const field = readStringProp(props, ['field']);
|
||||
const delta = typeof props.delta === 'string' ? props.delta : null;
|
||||
|
||||
if (!sessionId || !messageId || !partId || !field || delta === null) {
|
||||
if (streamDebugEnabled()) {
|
||||
console.debug('[useEventStream] Skipping message.part.delta with missing payload', {
|
||||
sessionID: props.sessionID,
|
||||
messageID: props.messageID,
|
||||
partID: props.partID,
|
||||
field: props.field,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
lastMessageEventBySessionRef.current.set(sessionId, Date.now());
|
||||
const pendingTimer = pendingMessageStallTimersRef.current.get(sessionId);
|
||||
if (pendingTimer) {
|
||||
clearTimeout(pendingTimer);
|
||||
pendingMessageStallTimersRef.current.delete(sessionId);
|
||||
}
|
||||
|
||||
const trimmedHeadMaxId = useSessionStore.getState().sessionMemoryState.get(sessionId)?.trimmedHeadMaxId;
|
||||
if (trimmedHeadMaxId && !isIdNewer(messageId, trimmedHeadMaxId)) {
|
||||
if (streamDebugEnabled()) {
|
||||
console.debug('[useEventStream] Skipping message.part.delta for trimmed message', {
|
||||
sessionId,
|
||||
messageId,
|
||||
trimmedHeadMaxId,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const existingMessage = getMessageFromStore(sessionId, messageId);
|
||||
const existingPart = existingMessage?.parts?.find((item) => item?.id === partId);
|
||||
if (!existingPart) {
|
||||
break;
|
||||
}
|
||||
|
||||
const existingPartRecord = existingPart as Record<string, unknown>;
|
||||
const existingFieldValue = existingPartRecord[field];
|
||||
const updatedPart: Part = {
|
||||
...existingPart,
|
||||
[field]: `${typeof existingFieldValue === 'string' ? existingFieldValue : ''}${delta}`,
|
||||
} as Part;
|
||||
|
||||
let roleInfo = 'assistant';
|
||||
const existingRole = (existingMessage?.info as Record<string, unknown> | undefined)?.role;
|
||||
if (typeof existingRole === 'string') {
|
||||
roleInfo = existingRole;
|
||||
}
|
||||
|
||||
if (roleInfo === 'assistant' && delta.length > 0) {
|
||||
const currentStatus = useSessionStore.getState().sessionStatus?.get(sessionId);
|
||||
const recentlyConfirmedIdle =
|
||||
currentStatus?.type === 'idle' &&
|
||||
typeof currentStatus.confirmedAt === 'number' &&
|
||||
Date.now() - currentStatus.confirmedAt < 1200;
|
||||
if (!currentStatus || currentStatus.type === 'idle') {
|
||||
if (!recentlyConfirmedIdle) {
|
||||
updateSessionStatus(sessionId, { type: 'busy' }, 'sse:message.part.delta');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trackMessage(messageId, 'part_delta_received', { role: roleInfo, field });
|
||||
addStreamingPart(sessionId, messageId, updatedPart, roleInfo);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'message.updated': {
|
||||
const message = (typeof props.info === 'object' && props.info !== null) ? (props.info as Record<string, unknown>) : props;
|
||||
const messageExt = message as Record<string, unknown>;
|
||||
|
||||
Reference in New Issue
Block a user