Files
kandev/apps/web/lib/utils/message-preview.ts
T
2026-09-18 16:30:51 +01:00

91 lines
2.4 KiB
TypeScript

export const MESSAGE_PREVIEW_MAX_LINES = 200;
export const MESSAGE_PREVIEW_MAX_CODE_UNITS = 16_000;
export type MessagePreviewLimits = {
maxLines?: number;
maxCodeUnits?: number;
};
export type MessagePreview = {
content: string;
truncated: boolean;
logicalLines: number;
codeUnits: number;
};
function isSurrogatePair(text: string, index: number): boolean {
const first = text.charCodeAt(index);
const second = text.charCodeAt(index + 1);
return first >= 0xd800 && first <= 0xdbff && second >= 0xdc00 && second <= 0xdfff;
}
function isLineBreak(text: string, index: number): boolean {
return text[index] === "\n" || text[index] === "\r";
}
function characterLengthAt(text: string, index: number): number {
if (text[index] === "\r" && text[index + 1] === "\n") return 2;
if (isSurrogatePair(text, index)) return 2;
return 1;
}
function previewResult(
source: string,
end: number,
truncated: boolean,
logicalLines: number,
): MessagePreview {
return {
content: source.slice(0, end),
truncated,
logicalLines,
codeUnits: end,
};
}
/** Select a source prefix without splitting Unicode pairs or CRLF endings. */
export function getMessagePreview(
source: string,
{
maxLines = MESSAGE_PREVIEW_MAX_LINES,
maxCodeUnits = MESSAGE_PREVIEW_MAX_CODE_UNITS,
}: MessagePreviewLimits = {},
): MessagePreview {
if (source.length === 0) return previewResult(source, 0, false, 0);
let index = 0;
let codeUnits = 0;
let completedLines = 0;
let currentLineHasContent = false;
while (index < source.length) {
const lineBreak = isLineBreak(source, index);
const characterLength = characterLengthAt(source, index);
if (codeUnits + characterLength > maxCodeUnits) {
return previewResult(source, index, true, completedLines + (currentLineHasContent ? 1 : 0));
}
if (lineBreak) {
if (completedLines >= maxLines) {
return previewResult(source, index, true, completedLines);
}
index += characterLength;
codeUnits += characterLength;
completedLines += 1;
currentLineHasContent = false;
continue;
}
if (completedLines >= maxLines) {
return previewResult(source, index, true, completedLines);
}
index += characterLength;
codeUnits += characterLength;
currentLineHasContent = true;
}
return previewResult(source, index, false, completedLines + (currentLineHasContent ? 1 : 0));
}