| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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) { |
| |
| const tail = rest |
| if (tail.trim()) segments.push({ type: 'prose', content: tail }) |
| break |
| } |
|
|
| const openIdx = openMatch.index |
| |
| 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 { |
| |
| 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 |
| } |
|
|