// Parse an assistant message into ordered segments: prose chunks and code // chunks. Handles the streaming case where the trailing fence is still open. // // Each code segment carries: { type:'code', lang, filename, code, streaming } // Each prose segment carries: { type:'prose', content } // // "streaming: true" marks a code block whose closing ``` hasn't arrived yet — // the UI uses this to show a live indicator and auto-scroll. const FENCE_OPEN_RE = /```([a-zA-Z0-9_-]*)?\n/ const FENCE_CLOSE_RE = /\n```/ const FILE_COMMENT_RE = /^\s*\/\/\s*[Ff]ile:\s*([^\n]+)\n/ function defaultFilename(lang) { const l = (lang || '').toLowerCase() if (l === 'css') return 'styles.css' if (l === 'html') return 'index.html' if (l === 'json') return 'data.json' return 'App.jsx' } export function parseMessageSegments(text) { const segments = [] if (!text) return segments let cursor = 0 while (cursor < text.length) { const rest = text.slice(cursor) const openMatch = rest.match(FENCE_OPEN_RE) if (!openMatch) { // Remaining is all prose const tail = rest if (tail.trim()) segments.push({ type: 'prose', content: tail }) break } const openIdx = openMatch.index // Prose before this code block if (openIdx > 0) { const prose = rest.slice(0, openIdx) if (prose.trim()) segments.push({ type: 'prose', content: prose }) } const lang = openMatch[1] || '' const afterOpen = rest.slice(openIdx + openMatch[0].length) const closeMatch = afterOpen.match(FENCE_CLOSE_RE) let body, streaming, advance if (closeMatch) { body = afterOpen.slice(0, closeMatch.index) streaming = false advance = openIdx + openMatch[0].length + closeMatch.index + closeMatch[0].length } else { // No closing fence yet — this block is still streaming. body = afterOpen streaming = true advance = rest.length } const fileComment = body.match(FILE_COMMENT_RE) const filename = fileComment ? fileComment[1].trim().replace(/^\//, '') : defaultFilename(lang) const code = fileComment ? body.slice(fileComment[0].length) : body segments.push({ type: 'code', lang, filename, code, streaming }) cursor += advance } return segments }