mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-11 06:00:15 -07:00
115 lines
4.2 KiB
TypeScript
115 lines
4.2 KiB
TypeScript
import type { Diff as TextDiff } from '@sanity/diff-match-patch';
|
|
|
|
import { heuristicIsHtml } from './RenderHtml';
|
|
import { heuristicLegacyImageBlocks, heuristicMarkdownImageReferenceBlocks } from './RenderImage';
|
|
|
|
// Block types
|
|
export type Block = CodeBlock | DiffBlock | HtmlBlock | ImageBlock | TextBlock;
|
|
export type CodeBlock = { type: 'code'; blockTitle: string; blockCode: string; complete: boolean; };
|
|
export type DiffBlock = { type: 'diff'; textDiffs: TextDiff[] };
|
|
export type HtmlBlock = { type: 'html'; html: string; };
|
|
export type ImageBlock = { type: 'image'; url: string; alt?: string }; // Added optional alt property
|
|
export type TextBlock = { type: 'text'; content: string; }; // for Text or Markdown
|
|
|
|
|
|
export function areBlocksEqual(a: Block, b: Block): boolean {
|
|
if (a.type !== b.type)
|
|
return false;
|
|
|
|
switch (a.type) {
|
|
case 'code':
|
|
return a.blockTitle === (b as CodeBlock).blockTitle && a.blockCode === (b as CodeBlock).blockCode && a.complete === (b as CodeBlock).complete;
|
|
case 'diff':
|
|
return false; // diff blocks are never equal
|
|
case 'html':
|
|
return a.html === (b as HtmlBlock).html;
|
|
case 'image':
|
|
return a.url === (b as ImageBlock).url && a.alt === (b as ImageBlock).alt;
|
|
case 'text':
|
|
return a.content === (b as TextBlock).content;
|
|
}
|
|
}
|
|
|
|
|
|
export function parseMessageBlocks(text: string, disableParsing: boolean, forceTextDiffs?: TextDiff[]): Block[] {
|
|
if (disableParsing)
|
|
return [{ type: 'text', content: text }];
|
|
|
|
if (forceTextDiffs && forceTextDiffs.length >= 1)
|
|
return [{ type: 'diff', textDiffs: forceTextDiffs }];
|
|
|
|
// special case: this could be generated by a proxy that returns an HTML page instead of the API response
|
|
if (heuristicIsHtml(text))
|
|
return [{ type: 'html', html: text }];
|
|
|
|
// special case: markdown image references (e.g. )
|
|
const mdImageBlocks = heuristicMarkdownImageReferenceBlocks(text);
|
|
if (mdImageBlocks)
|
|
return mdImageBlocks;
|
|
|
|
// special case: legacy prodia images
|
|
const legacyImageBlocks = heuristicLegacyImageBlocks(text);
|
|
if (legacyImageBlocks)
|
|
return legacyImageBlocks;
|
|
|
|
const regexPatterns = {
|
|
// was: \w\x20\\.+-_ for tge filename, but was missing too much
|
|
// REVERTED THIS: was: (`{3,}\n?|$), but was matching backticks within blocks. so now it must end with a newline or stop
|
|
codeBlock: /`{3,}([\S\x20]+)?\n([\s\S]*?)(`{3,}\n?|$)/g,
|
|
htmlCodeBlock: /<!DOCTYPE html>([\s\S]*?)<\/html>/g,
|
|
svgBlock: /<svg (xmlns|width|viewBox)=([\s\S]*?)<\/svg>/g,
|
|
};
|
|
|
|
const blocks: Block[] = [];
|
|
let lastIndex = 0;
|
|
|
|
while (true) {
|
|
|
|
// find the first match (if any) trying all the regexes
|
|
let match: RegExpExecArray | null = null;
|
|
let matchType: keyof typeof regexPatterns | null = null;
|
|
for (const type in regexPatterns) {
|
|
const regex = regexPatterns[type as keyof typeof regexPatterns];
|
|
regex.lastIndex = lastIndex;
|
|
const currentMatch = regex.exec(text);
|
|
if (currentMatch && (match === null || currentMatch.index < match.index)) {
|
|
match = currentMatch;
|
|
matchType = type as keyof typeof regexPatterns;
|
|
}
|
|
}
|
|
if (match === null)
|
|
break;
|
|
|
|
// anything leftover before the match is text
|
|
if (match.index > lastIndex)
|
|
blocks.push({ type: 'text', content: text.slice(lastIndex, match.index) });
|
|
|
|
// add the block
|
|
switch (matchType) {
|
|
case 'codeBlock':
|
|
const blockTitle: string = (match[1] || '').trim();
|
|
const blockCode: string = match[2].trim();
|
|
const blockEnd: string = match[3];
|
|
blocks.push({ type: 'code', blockTitle, blockCode, complete: blockEnd.startsWith('```') });
|
|
break;
|
|
|
|
case 'htmlCodeBlock':
|
|
const html: string = `<!DOCTYPE html>${match[1]}</html>`;
|
|
blocks.push({ type: 'code', blockTitle: 'html', blockCode: html, complete: true });
|
|
break;
|
|
|
|
case 'svgBlock':
|
|
blocks.push({ type: 'code', blockTitle: 'svg', blockCode: match[0], complete: true });
|
|
break;
|
|
}
|
|
|
|
// advance the pointer
|
|
lastIndex = match.index + match[0].length;
|
|
}
|
|
|
|
// remainder is text
|
|
if (lastIndex < text.length)
|
|
blocks.push({ type: 'text', content: text.slice(lastIndex) });
|
|
|
|
return blocks;
|
|
} |