/** * Apply chat-stream advisor events. Display order is first-start order: * new cards append; they are never sorted by rank or finish time. */ function trailingAdvisorStart(messages) { let i = messages.length; while (i > 0 && messages[i - 1].type === 'advisor') i -= 1; return i; } function findTrailingAdvisorIndex(messages, personaId) { const start = trailingAdvisorStart(messages); for (let i = start; i < messages.length; i += 1) { if (messages[i].persona_id === personaId) return i; } return -1; } export function applyAdvisorStreamEvent(messages, payload, { generateId, now } = {}) { const type = payload?.type; const d = payload?.data || {}; const idFactory = generateId || (() => `msg-${Date.now()}`); const timestamp = now ? now() : new Date(); if (type === 'advisor_start') { if (findTrailingAdvisorIndex(messages, d.persona_id) !== -1) { return messages; } const msg = { id: idFactory(), type: 'advisor', persona_id: d.persona_id, content: '', timestamp, advisorName: d.persona_name || d.persona_id, used_documents: false, document_chunks_used: 0, streaming: true, }; return [...messages, msg]; } if (type === 'advisor_delta') { const idx = findTrailingAdvisorIndex(messages, d.persona_id); const delta = d.delta || ''; if (idx === -1) { const created = applyAdvisorStreamEvent( messages, { type: 'advisor_start', data: d }, { generateId: idFactory, now: () => timestamp }, ); return applyAdvisorStreamEvent( created, payload, { generateId: idFactory, now: () => timestamp }, ); } const next = messages.slice(); const prev = next[idx]; next[idx] = { ...prev, content: `${prev.content || ''}${delta}`, streaming: true, }; return next; } if (type === 'advisor_done') { const idx = findTrailingAdvisorIndex(messages, d.persona_id); const doneFields = { content: d.content ?? '', advisorName: d.persona_name || d.persona_id, used_documents: d.used_documents || false, document_chunks_used: d.document_chunks_used || 0, streaming: false, }; if (idx === -1) { return [ ...messages, { id: idFactory(), type: 'advisor', persona_id: d.persona_id, timestamp, ...doneFields, }, ]; } const next = messages.slice(); next[idx] = { ...next[idx], ...doneFields }; return next; } if (type === 'advisor') { const idx = findTrailingAdvisorIndex(messages, d.persona_id); const msg = { id: idx === -1 ? idFactory() : messages[idx].id, type: 'advisor', persona_id: d.persona_id, content: d.content, timestamp: idx === -1 ? timestamp : messages[idx].timestamp, advisorName: d.persona_name || d.persona_id, used_documents: d.used_documents || false, document_chunks_used: d.document_chunks_used || 0, streaming: false, }; if (idx === -1) return [...messages, msg]; const next = messages.slice(); next[idx] = { ...messages[idx], ...msg }; return next; } return messages; } export function advisorDisplayOrder(messages) { return messages .filter((m) => m.type === 'advisor') .map((m) => m.persona_id); }