mirror of
https://github.com/harivansh-afk/clanker-agent.git
synced 2026-04-15 04:03:27 +00:00
1 line
No EOL
7.6 KiB
Text
1 line
No EOL
7.6 KiB
Text
{"version":3,"file":"ansi-to-html.d.ts","sourceRoot":"","sources":["../../../src/core/export-html/ansi-to-html.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA8LH;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsD/C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAMvD","sourcesContent":["/**\n * ANSI escape code to HTML converter.\n *\n * Converts terminal ANSI color/style codes to HTML with inline styles.\n * Supports:\n * - Standard foreground colors (30-37) and bright variants (90-97)\n * - Standard background colors (40-47) and bright variants (100-107)\n * - 256-color palette (38;5;N and 48;5;N)\n * - RGB true color (38;2;R;G;B and 48;2;R;G;B)\n * - Text styles: bold (1), dim (2), italic (3), underline (4)\n * - Reset (0)\n */\n\n// Standard ANSI color palette (0-15)\nconst ANSI_COLORS = [\n \"#000000\", // 0: black\n \"#800000\", // 1: red\n \"#008000\", // 2: green\n \"#808000\", // 3: yellow\n \"#000080\", // 4: blue\n \"#800080\", // 5: magenta\n \"#008080\", // 6: cyan\n \"#c0c0c0\", // 7: white\n \"#808080\", // 8: bright black\n \"#ff0000\", // 9: bright red\n \"#00ff00\", // 10: bright green\n \"#ffff00\", // 11: bright yellow\n \"#0000ff\", // 12: bright blue\n \"#ff00ff\", // 13: bright magenta\n \"#00ffff\", // 14: bright cyan\n \"#ffffff\", // 15: bright white\n];\n\n/**\n * Convert 256-color index to hex.\n */\nfunction color256ToHex(index: number): string {\n // Standard colors (0-15)\n if (index < 16) {\n return ANSI_COLORS[index];\n }\n\n // Color cube (16-231): 6x6x6 = 216 colors\n if (index < 232) {\n const cubeIndex = index - 16;\n const r = Math.floor(cubeIndex / 36);\n const g = Math.floor((cubeIndex % 36) / 6);\n const b = cubeIndex % 6;\n const toComponent = (n: number) => (n === 0 ? 0 : 55 + n * 40);\n const toHex = (n: number) => toComponent(n).toString(16).padStart(2, \"0\");\n return `#${toHex(r)}${toHex(g)}${toHex(b)}`;\n }\n\n // Grayscale (232-255): 24 shades\n const gray = 8 + (index - 232) * 10;\n const grayHex = gray.toString(16).padStart(2, \"0\");\n return `#${grayHex}${grayHex}${grayHex}`;\n}\n\n/**\n * Escape HTML special characters.\n */\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\ninterface TextStyle {\n fg: string | null;\n bg: string | null;\n bold: boolean;\n dim: boolean;\n italic: boolean;\n underline: boolean;\n}\n\nfunction createEmptyStyle(): TextStyle {\n return {\n fg: null,\n bg: null,\n bold: false,\n dim: false,\n italic: false,\n underline: false,\n };\n}\n\nfunction styleToInlineCSS(style: TextStyle): string {\n const parts: string[] = [];\n if (style.fg) parts.push(`color:${style.fg}`);\n if (style.bg) parts.push(`background-color:${style.bg}`);\n if (style.bold) parts.push(\"font-weight:bold\");\n if (style.dim) parts.push(\"opacity:0.6\");\n if (style.italic) parts.push(\"font-style:italic\");\n if (style.underline) parts.push(\"text-decoration:underline\");\n return parts.join(\";\");\n}\n\nfunction hasStyle(style: TextStyle): boolean {\n return (\n style.fg !== null ||\n style.bg !== null ||\n style.bold ||\n style.dim ||\n style.italic ||\n style.underline\n );\n}\n\n/**\n * Parse ANSI SGR (Select Graphic Rendition) codes and update style.\n */\nfunction applySgrCode(params: number[], style: TextStyle): void {\n let i = 0;\n while (i < params.length) {\n const code = params[i];\n\n if (code === 0) {\n // Reset all\n style.fg = null;\n style.bg = null;\n style.bold = false;\n style.dim = false;\n style.italic = false;\n style.underline = false;\n } else if (code === 1) {\n style.bold = true;\n } else if (code === 2) {\n style.dim = true;\n } else if (code === 3) {\n style.italic = true;\n } else if (code === 4) {\n style.underline = true;\n } else if (code === 22) {\n // Reset bold/dim\n style.bold = false;\n style.dim = false;\n } else if (code === 23) {\n style.italic = false;\n } else if (code === 24) {\n style.underline = false;\n } else if (code >= 30 && code <= 37) {\n // Standard foreground colors\n style.fg = ANSI_COLORS[code - 30];\n } else if (code === 38) {\n // Extended foreground color\n if (params[i + 1] === 5 && params.length > i + 2) {\n // 256-color: 38;5;N\n style.fg = color256ToHex(params[i + 2]);\n i += 2;\n } else if (params[i + 1] === 2 && params.length > i + 4) {\n // RGB: 38;2;R;G;B\n const r = params[i + 2];\n const g = params[i + 3];\n const b = params[i + 4];\n style.fg = `rgb(${r},${g},${b})`;\n i += 4;\n }\n } else if (code === 39) {\n // Default foreground\n style.fg = null;\n } else if (code >= 40 && code <= 47) {\n // Standard background colors\n style.bg = ANSI_COLORS[code - 40];\n } else if (code === 48) {\n // Extended background color\n if (params[i + 1] === 5 && params.length > i + 2) {\n // 256-color: 48;5;N\n style.bg = color256ToHex(params[i + 2]);\n i += 2;\n } else if (params[i + 1] === 2 && params.length > i + 4) {\n // RGB: 48;2;R;G;B\n const r = params[i + 2];\n const g = params[i + 3];\n const b = params[i + 4];\n style.bg = `rgb(${r},${g},${b})`;\n i += 4;\n }\n } else if (code === 49) {\n // Default background\n style.bg = null;\n } else if (code >= 90 && code <= 97) {\n // Bright foreground colors\n style.fg = ANSI_COLORS[code - 90 + 8];\n } else if (code >= 100 && code <= 107) {\n // Bright background colors\n style.bg = ANSI_COLORS[code - 100 + 8];\n }\n // Ignore unrecognized codes\n\n i++;\n }\n}\n\n// Match ANSI escape sequences: ESC[ followed by params and ending with 'm'\nconst ANSI_REGEX = /\\x1b\\[([\\d;]*)m/g;\n\n/**\n * Convert ANSI-escaped text to HTML with inline styles.\n */\nexport function ansiToHtml(text: string): string {\n const style = createEmptyStyle();\n let result = \"\";\n let lastIndex = 0;\n let inSpan = false;\n\n // Reset regex state\n ANSI_REGEX.lastIndex = 0;\n\n let match = ANSI_REGEX.exec(text);\n while (match !== null) {\n // Add text before this escape sequence\n const beforeText = text.slice(lastIndex, match.index);\n if (beforeText) {\n result += escapeHtml(beforeText);\n }\n\n // Parse SGR parameters\n const paramStr = match[1];\n const params = paramStr\n ? paramStr.split(\";\").map((p) => parseInt(p, 10) || 0)\n : [0];\n\n // Close existing span if we have one\n if (inSpan) {\n result += \"</span>\";\n inSpan = false;\n }\n\n // Apply the codes\n applySgrCode(params, style);\n\n // Open new span if we have any styling\n if (hasStyle(style)) {\n result += `<span style=\"${styleToInlineCSS(style)}\">`;\n inSpan = true;\n }\n\n lastIndex = match.index + match[0].length;\n match = ANSI_REGEX.exec(text);\n }\n\n // Add remaining text\n const remainingText = text.slice(lastIndex);\n if (remainingText) {\n result += escapeHtml(remainingText);\n }\n\n // Close any open span\n if (inSpan) {\n result += \"</span>\";\n }\n\n return result;\n}\n\n/**\n * Convert array of ANSI-escaped lines to HTML.\n * Each line is wrapped in a div element.\n */\nexport function ansiLinesToHtml(lines: string[]): string {\n return lines\n .map(\n (line) => `<div class=\"ansi-line\">${ansiToHtml(line) || \" \"}</div>`,\n )\n .join(\"\\n\");\n}\n"]} |