UNPKG

react-ai-highlight-parser

Version:

Parse and render AI streaming responses with semantic highlighting. Supports multiple modes (highlights, underline, both) and color palettes.

306 lines (300 loc) 9.47 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { HIGHLIGHT_MEANINGS: () => HIGHLIGHT_MEANINGS, HighlightRenderer: () => HighlightRenderer, HighlightSpan: () => HighlightSpan, NATURAL: () => NATURAL, PALETTES: () => PALETTES, VIBRANT: () => VIBRANT, default: () => HighlightRenderer, extractHighlightCodes: () => extractHighlightCodes, getBackgroundColor: () => getBackgroundColor, getPalette: () => getPalette, getUnderlineColor: () => getUnderlineColor, hasHighlights: () => hasHighlights, parseHighlights: () => parseHighlights, processResponse: () => processResponse, removeHighlightCodes: () => removeHighlightCodes }); module.exports = __toCommonJS(index_exports); // src/types.ts var HIGHLIGHT_MEANINGS = { "Y": "Important/Key points", "B": "Concepts/Definitions", "O": "Steps/Sequences", "G": "Success/Positive", "R": "Warnings/Errors", "P": "Examples", "L": "Data/Numbers", "GR": "Code/Technical", "H": "Emphasis/Highlights", "BR": "Context/Background" }; // src/palettes.ts var VIBRANT = { background: { "Y": "#FFF4C3", // Yellow - Important/key points "B": "#D5FEFF", // Blue - Concepts/definitions "O": "#FFD5C3", // Orange - Steps/sequences "G": "#DCFCE7", // Green - Success/positive "R": "#fee2e2", // Red - Warnings/errors "P": "#FEECFF", // Pink - Examples "L": "#E6F3FF", // Light blue - Data/numbers "GR": "#E8E6E5", // Gray - Code/technical "H": "#ede9fe", // Purple - Emphasis/highlights "BR": "#f5e8dd" // Brown - Context/background info }, underline: { "Y": "#FFC41A", "B": "#5DCFFF", "O": "#FF7744", "G": "#22C55E", "R": "#ef4444", "P": "#FC90FF", "L": "#8DC5FF", "GR": "#ACA8A4", "H": "#8b5cf6", "BR": "#92400e" } }; var NATURAL = { background: { "Y": "#F5F0E8", // Beige/cream "B": "#E8F0F4", // Steel blue light "O": "#F5E8DD", // Tan light "G": "#E8EDE6", // Sage green "R": "#F5E8EA", // Dusty rose "P": "#F0EAF5", // Lavender light "L": "#E6EEF3", // Slate light "GR": "#E8E6E5", // Gray "H": "#EAE8F0", // Soft purple "BR": "#F0E8E0" // Warm beige }, underline: { "Y": "#9A8B7A", "B": "#2C5F6F", "O": "#92400E", "G": "#6B7056", "R": "#7C2D3F", "P": "#9B8BA8", "L": "#5C7B8B", "GR": "#ACA8A4", "H": "#7C6B8A", "BR": "#8B6B47" } }; var PALETTES = { vibrant: VIBRANT, natural: NATURAL }; function getPalette(name = "vibrant") { return PALETTES[name] || PALETTES.vibrant; } function getBackgroundColor(palette, code) { return getPalette(palette).background[code] || getPalette(palette).background["Y"]; } function getUnderlineColor(palette, code) { return getPalette(palette).underline[code] || getPalette(palette).underline["O"]; } // src/parser.ts var VALID_CODES = ["Y", "B", "O", "G", "R", "P", "L", "GR", "H", "BR"]; function processResponse(text) { if (!text) return text; let processed = text; const codeBlocks = []; processed = processed.replace(/(```[\s\S]*?```)/g, (match) => { const idx = codeBlocks.length; codeBlocks.push(match); return `___CODE_BLOCK_${idx}___`; }); processed = processed.replace(/\[GREEN\]([^\[]*)\[\/GREEN\]/gi, "$1").replace(/\[RED\]([^\[]*)\[\/RED\]/gi, "$1").replace(/\[BLUE\]([^\[]*)\[\/BLUE\]/gi, "$1").replace(/\[YELLOW\]([^\[]*)\[\/YELLOW\]/gi, "$1").replace(/\[ORANGE\]([^\[]*)\[\/ORANGE\]/gi, "$1").replace(/\[PURPLE\]([^\[]*)\[\/PURPLE\]/gi, "$1"); VALID_CODES.forEach((code) => { const validPairRegex = new RegExp(`\\[${code}\\]([^\\[]*)\\[\\/${code}\\]`, "g"); const validPairs = []; let tempText = processed.replace(validPairRegex, (match) => { const idx = validPairs.length; validPairs.push(match); return `___VALID_${code}_${idx}___`; }); tempText = tempText.replace(new RegExp(`\\[${code}\\]`, "g"), "").replace(new RegExp(`\\[\\/${code}\\]`, "g"), ""); validPairs.forEach((pair, idx) => { tempText = tempText.replace(`___VALID_${code}_${idx}___`, pair); }); processed = tempText; }); codeBlocks.forEach((block, idx) => { processed = processed.replace(`___CODE_BLOCK_${idx}___`, block); }); return processed; } function removeHighlightCodes(text) { let cleaned = text; VALID_CODES.forEach((code) => { const regex = new RegExp(`\\[${code}\\]([^\\[]*)\\[\\/${code}\\]`, "g"); cleaned = cleaned.replace(regex, "$1"); }); return cleaned; } function wrapWithStyle(content, code, mode, palette) { const colors = getPalette(palette); const bgColor = colors.background[code] || colors.background["Y"]; const underlineColor = colors.underline[code] || colors.underline["O"]; if (mode === "underline") { return `<span style="text-decoration:underline ${underlineColor};text-decoration-thickness:2px;text-underline-offset:2px;text-decoration-skip-ink:none">${content}</span>`; } if (mode === "highlights") { return `<span style="background-color:${bgColor};padding:1px 3px 0 3px;border-radius:3px;display:inline">${content}</span>`; } if (mode === "both") { return `<span style="background-color:${bgColor};text-decoration:underline ${underlineColor};text-decoration-thickness:2px;text-underline-offset:2px;text-decoration-skip-ink:none;padding:1px 3px 0 3px;border-radius:3px">${content}</span>`; } return content; } function parseHighlights(text, mode = "highlights", palette = "vibrant") { if (!text || mode === "none") { return mode === "none" ? removeHighlightCodes(text) : text; } let processed = processResponse(text); processed = processed.replace(/\*\*([^*]+?)\*\*/g, "<strong>$1</strong>"); processed = processed.replace(/\*([^*]+?)\*/g, "<em>$1</em>"); processed = processed.replace(/`([^`]+)`/g, '<code style="background:#f1f1f1;padding:2px 4px;border-radius:3px;font-family:monospace;font-size:0.9em">$1</code>'); const hasHighlightCodes = new RegExp(`\\[(${VALID_CODES.join("|")})\\]`).test(processed); if (!hasHighlightCodes) { return processed; } const tokenPattern = new RegExp(`(\\[\\/?(?:${VALID_CODES.join("|")})\\])`, "g"); const tokens = processed.split(tokenPattern).filter((t) => t.length > 0); const stack = []; const output = []; for (const token of tokens) { const openMatch = token.match(new RegExp(`^\\[(${VALID_CODES.join("|")})\\]$`)); if (openMatch) { stack.push({ code: openMatch[1], startIndex: output.length }); continue; } const closeMatch = token.match(new RegExp(`^\\[\\/(${VALID_CODES.join("|")})\\]$`)); if (closeMatch) { const code = closeMatch[1]; let matchIndex = -1; for (let j = stack.length - 1; j >= 0; j--) { if (stack[j].code === code) { matchIndex = j; break; } } if (matchIndex !== -1) { const open = stack[matchIndex]; const content = output.slice(open.startIndex).join(""); output.splice(open.startIndex); output.push(wrapWithStyle(content, code, mode, palette)); stack.splice(matchIndex, 1); } continue; } output.push(token); } return output.join(""); } function hasHighlights(text) { return new RegExp(`\\[(${VALID_CODES.join("|")})\\]`).test(text); } function extractHighlightCodes(text) { const codes = /* @__PURE__ */ new Set(); VALID_CODES.forEach((code) => { if (new RegExp(`\\[${code}\\]`).test(text)) { codes.add(code); } }); return Array.from(codes); } // src/components/HighlightRenderer.tsx var import_jsx_runtime = require("react/jsx-runtime"); function HighlightRenderer({ content, mode = "highlights", palette = "vibrant", className = "" }) { const html = parseHighlights(content, mode, palette); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "div", { className, dangerouslySetInnerHTML: { __html: html } } ); } function HighlightSpan({ content, mode = "highlights", palette = "vibrant", className = "" }) { const html = parseHighlights(content, mode, palette); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "span", { className, dangerouslySetInnerHTML: { __html: html } } ); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { HIGHLIGHT_MEANINGS, HighlightRenderer, HighlightSpan, NATURAL, PALETTES, VIBRANT, extractHighlightCodes, getBackgroundColor, getPalette, getUnderlineColor, hasHighlights, parseHighlights, processResponse, removeHighlightCodes });