import AnsiUp from 'ansi-to-html'
const emojiMap: Record = {
':success:': '✅',
':check:': '✅',
':done:': '✅',
':error:': '❌',
':fail:': '❌',
':x:': '❌',
':warn:': '⚠️',
':warning:': '⚠️',
':info:': 'ℹ️',
':rocket:': '🚀',
':sparkles:': '✨',
':fire:': '🔥',
':bug:': '🐛',
':lock:': '🔒',
':link:': '🔗',
':memo:': '📝',
':bulb:': '💡',
':clock:': '🕒',
':finish:': '🏁',
':start:': '🛫',
':cloud:': '☁️',
':bell:': '🔔',
}
const ansiUp = new AnsiUp({
newline: false,
escapeXML: true,
stream: false,
colors: {
// Optimized 16-color palette for dark terminal background
0: '#09090b', // Black (Zinc-950)
1: '#f87171', // Red (Tailwind red-400)
2: '#4ade80', // Green (Tailwind green-400)
3: '#fbbf24', // Yellow (Tailwind amber-400)
4: '#60a5fa', // Blue (Tailwind blue-400)
5: '#c084fc', // Magenta (Tailwind purple-400)
6: '#22d3ee', // Cyan (Tailwind cyan-400)
7: '#e4e4e7', // White (Zinc-200)
8: '#71717a', // Bright Black (Zinc-500)
9: '#ef4444', // Bright Red (Tailwind red-500)
10: '#22c55e', // Bright Green (Tailwind green-500)
11: '#f59e0b', // Bright Yellow (Tailwind amber-500)
12: '#3b82f6', // Bright Blue (Tailwind blue-500)
13: '#a855f7', // Bright Magenta (Tailwind purple-500)
14: '#06b6d4', // Bright Cyan (Tailwind cyan-500)
15: '#ffffff' // Bright White
}
})
/**
* Convert ANSI escape codes to HTML and parse emoji shortcodes
*/
export function ansiToHtml(ansi: string): string {
if (!ansi) return ''
// Parse emoji shortcodes
let processed = ansi.replace(/:([a-z0-9_-]+):/g, (match) => {
return emojiMap[match] || match
})
return ansiUp.toHtml(processed)
}
/**
* Highlight keywords in HTML content while avoiding HTML tags
*/
export function highlightHtml(html: string, keyword: string): string {
if (!keyword.trim()) return html
const escaped = keyword.trim().replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
// Match keyword but not inside HTML tags
const regex = new RegExp(`(${escaped})(?![^<]*>)`, 'gi')
return html.replace(regex, '$1')
}