UNPKG

openai-cli-unofficial

Version:

A powerful OpenAI CLI Coding Agent built with TypeScript

306 lines 12.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.StreamRenderer = void 0; const chalk_1 = __importDefault(require("chalk")); const cli_highlight_1 = require("cli-highlight"); const language_1 = require("../../services/language"); /** * 流式渲染器,用于处理流式输入并进行美观输出 */ class StreamRenderer { constructor() { this.buffer = ''; this.isInCodeBlock = false; this.codeBlockLanguage = ''; this.codeBlockContent = ''; this.currentLine = ''; this.codeLineNumber = 1; // 为 cli-highlight 自定义主题,以提高在深色和浅色终端上的可读性 this.highlightTheme = { keyword: chalk_1.default.blueBright, built_in: chalk_1.default.cyanBright, type: chalk_1.default.cyanBright.dim, literal: chalk_1.default.blueBright, number: chalk_1.default.greenBright, regexp: chalk_1.default.redBright, string: chalk_1.default.redBright, subst: chalk_1.default.redBright, symbol: chalk_1.default.redBright, class: chalk_1.default.blueBright, function: chalk_1.default.yellowBright, title: chalk_1.default.blueBright, params: chalk_1.default.redBright, comment: chalk_1.default.green, doctag: chalk_1.default.green, meta: chalk_1.default.gray, 'meta-keyword': chalk_1.default.gray, 'meta-string': chalk_1.default.gray, section: chalk_1.default.blueBright, tag: chalk_1.default.gray, name: chalk_1.default.blueBright, 'builtin-name': chalk_1.default.blueBright, attr: chalk_1.default.cyanBright, attribute: chalk_1.default.cyanBright, variable: chalk_1.default.blueBright, bullet: chalk_1.default.redBright, code: chalk_1.default.redBright, emphasis: chalk_1.default.italic, strong: chalk_1.default.bold, formula: chalk_1.default.redBright, link: chalk_1.default.underline, quote: chalk_1.default.green, 'selector-attr': chalk_1.default.redBright, 'selector-class': chalk_1.default.blueBright, 'selector-id': chalk_1.default.blueBright, 'selector-pseudo': chalk_1.default.blueBright, 'selector-tag': chalk_1.default.blueBright, template_variable: chalk_1.default.blueBright, 'template-tag': chalk_1.default.blueBright, addition: chalk_1.default.green, deletion: chalk_1.default.red, default: chalk_1.default.white, }; // 支持的语言列表(常见的编程语言) this.supportedLanguages = new Set([ 'javascript', 'js', 'typescript', 'ts', 'python', 'py', 'java', 'cpp', 'c++', 'c', 'csharp', 'cs', 'php', 'ruby', 'go', 'rust', 'swift', 'kotlin', 'scala', 'sql', 'html', 'css', 'scss', 'sass', 'less', 'xml', 'json', 'yaml', 'yml', 'bash', 'sh', 'shell', 'powershell', 'dockerfile', 'makefile', 'perl', 'lua', 'r', 'matlab', 'objective-c', 'dart', 'elixir', 'erlang', 'haskell', 'clojure', 'groovy', 'actionscript' ]); } /** * 检查语言是否被支持,如果不支持则返回默认配置 */ checkLanguageSupport(language) { if (!language || language.trim() === '') { return { isSupported: false }; } const normalizedLang = language.toLowerCase().trim(); // 处理一些特殊的语言别名 const languageAliases = { 'vue': 'html', // Vue文件可以用HTML高亮作为回退 'jsx': 'javascript', 'tsx': 'typescript', 'vue.js': 'html', 'vuejs': 'html', 'svelte': 'html', 'angular': 'typescript', 'react': 'javascript' }; // 检查是否有别名映射 if (languageAliases[normalizedLang]) { return { isSupported: true, fallbackLanguage: languageAliases[normalizedLang] }; } // 检查是否直接支持 if (this.supportedLanguages.has(normalizedLang)) { return { isSupported: true }; } return { isSupported: false }; } /** * 处理流式输入的文本块 */ processChunk(chunk) { let output = ''; this.buffer += chunk; // 按换行符分割处理 const lines = this.buffer.split('\n'); // 保留最后一行(可能不完整) this.buffer = lines.pop() || ''; // 处理完整的行 for (const line of lines) { output += this.processLine(line + '\n'); } return output; } /** * 处理单行文本 */ processLine(line) { const cleanLine = line.replace('\n', ''); // 检测代码块开始/结束 if (cleanLine.startsWith('```')) { if (!this.isInCodeBlock) { // 代码块开始 this.isInCodeBlock = true; this.codeBlockLanguage = cleanLine.substring(3).trim(); this.codeBlockContent = ''; this.codeLineNumber = 1; // 显示代码块头部 const messages = language_1.languageService.getMessages(); const language = this.codeBlockLanguage || messages.main.messages.codeBlock.unknownLanguage; const terminalWidth = process.stdout.columns || 120; const languageLabel = ` ${language.toUpperCase()} `; const remainingWidth = terminalWidth - languageLabel.length; return chalk_1.default.bgBlackBright.whiteBright.bold(languageLabel) + chalk_1.default.gray('─'.repeat(Math.max(0, remainingWidth))) + '\n'; } else { // 代码块结束 this.isInCodeBlock = false; this.codeBlockContent = ''; this.codeBlockLanguage = ''; // 显示代码块底部 const terminalWidth = process.stdout.columns || 120; return chalk_1.default.gray('─'.repeat(terminalWidth)) + '\n'; } } if (this.isInCodeBlock) { // 在代码块内,添加行号和背景色 const lineNum = this.codeLineNumber.toString().padStart(3, ' '); const highlightedCode = this.highlightCodeLine(cleanLine, this.codeBlockLanguage); this.codeLineNumber++; // 获取终端宽度,默认120列 const terminalWidth = process.stdout.columns || 120; const lineNumberWidth = 5; // 行号部分宽度 " 123 " const availableWidth = terminalWidth - lineNumberWidth; // 计算实际字符长度(去除ANSI颜色代码) const stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, ''); const actualCodeLength = stripAnsi(highlightedCode).length; // 确保填充到正确宽度 const paddingNeeded = Math.max(0, availableWidth - actualCodeLength); const paddedCode = highlightedCode + ' '.repeat(paddingNeeded); // 更改为更通用的颜色方案,以兼容亮色和暗色主题 return chalk_1.default.bgBlackBright.whiteBright.bold(` ${lineNum} `) + paddedCode + '\n'; } // 普通文本行的美化处理 return this.formatRegularLine(cleanLine) + '\n'; } /** * 代码高亮(完整代码块) */ highlightCode(code, language) { const languageCheck = this.checkLanguageSupport(language); try { if (languageCheck.isSupported) { const targetLanguage = languageCheck.fallbackLanguage || language; return (0, cli_highlight_1.highlight)(code, { language: targetLanguage, theme: this.highlightTheme }); } else { // 不支持的语言使用黄色高亮 return chalk_1.default.yellow(code); } } catch (error) { // 如果高亮失败,也使用黄色高亮 return chalk_1.default.yellow(code); } } /** * 单行代码高亮 */ highlightCodeLine(codeLine, language) { const languageCheck = this.checkLanguageSupport(language); try { if (languageCheck.isSupported) { const targetLanguage = languageCheck.fallbackLanguage || language; return (0, cli_highlight_1.highlight)(codeLine, { language: targetLanguage, theme: this.highlightTheme }); } else { // 不支持的语言使用黄色高亮 return chalk_1.default.yellow(codeLine); } } catch (error) { // 如果高亮失败,也使用黄色高亮 return chalk_1.default.yellow(codeLine); } } /** * 格式化普通文本行 */ formatRegularLine(line) { const messages = language_1.languageService.getMessages(); // 处理标题 if (line.startsWith('# ')) { return chalk_1.default.bold.cyan(line); } else if (line.startsWith('## ')) { return chalk_1.default.bold.blue(line); } else if (line.startsWith('### ')) { return chalk_1.default.bold.magenta(line); } else if (line.startsWith('#### ')) { return chalk_1.default.bold.yellow(line); } // 处理列表项 if (line.match(/^[\s]*[-*+]\s/)) { return chalk_1.default.green(line); } // 处理数字列表 if (line.match(/^[\s]*\d+\.\s/)) { return chalk_1.default.cyan(line); } // 处理行内代码 line = line.replace(/`([^`]+)`/g, (match, code) => { return chalk_1.default.yellow.bgBlack(` ${code} `); }); // 处理加粗文本 line = line.replace(/\*\*([^*]+)\*\*/g, (match, text) => { return chalk_1.default.bold(text); }); // 处理斜体文本 line = line.replace(/\*([^*]+)\*/g, (match, text) => { return chalk_1.default.italic(text); }); // 处理链接 line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => { return chalk_1.default.blue.underline(text) + chalk_1.default.dim(` (${url})`); }); // 粗体和斜体 line = line.replace(/\*\*\*([^\*]+)\*\*\*/g, chalk_1.default.bold.italic('$1')); line = line.replace(/\*\*([^\*]+)\*\*/g, chalk_1.default.bold('$1')); line = line.replace(/\*([^\*]+)\*/g, chalk_1.default.italic('$1')); return line; } /** * 获取MIME类型 */ getMimeType(extension) { const mimeTypes = { '.png': 'image/png', '.jpeg': 'image/jpeg', '.jpg': 'image/jpeg', '.webp': 'image/webp', '.gif': 'image/gif', }; return mimeTypes[extension] || null; } /** * 完成处理,处理缓冲区中剩余的内容 */ finalize() { if (this.buffer.length > 0) { const result = this.processLine(this.buffer + '\n'); this.buffer = ''; return result; } // 如果还在代码块中,重置状态(因为现在是按行处理的) if (this.isInCodeBlock) { this.isInCodeBlock = false; this.codeBlockContent = ''; this.codeBlockLanguage = ''; } return ''; } /** * 重置渲染器状态 */ reset() { this.buffer = ''; this.isInCodeBlock = false; this.codeBlockLanguage = ''; this.codeBlockContent = ''; this.currentLine = ''; this.codeLineNumber = 1; } } exports.StreamRenderer = StreamRenderer; //# sourceMappingURL=stream-renderer.js.map