/** * LLMStream - 大模型流式请求和Markdown实时渲染库 * 支持SSE流式响应、Markdown渲染、错误处理等 */ class LLMStream { constructor(options) { this.url = options.url; this.method = options.method || 'POST'; this.headers = options.headers || {}; this.body = options.body || {}; this.target = options.target; // CSS选择器 this.markdown = options.markdown !== undefined ? options.markdown : true; this.onChunk = options.onChunk; // 自定义chunk处理回调 this.onComplete = options.onComplete; // 完成回调 this.onError = options.onError; // 错误回调 this.controller = null; this.content = ''; this.targetElement = null; // 初始化目标元素 if (this.target) { this.targetElement = document.querySelector(this.target); if (!this.targetElement) { console.error(`目标元素 ${this.target} 未找到`); } } // 动态加载Markdown渲染库(如果需要) if (this.markdown && !window.marked) { this.loadMarkdownLibrary(); } } /** * 动态加载marked.js库 */ async loadMarkdownLibrary() { return new Promise((resolve, reject) => { if (window.marked) { resolve(); return; } const script = document.createElement('script'); script.src = 'https://fastly.jsdelivr.net/npm/marked/marked.min.js'; script.onload = () => { console.log('Marked.js 加载成功'); // 配置marked选项 if (window.marked) { marked.setOptions({ breaks: true, gfm: true, highlight: function(code, lang) { // 如果有highlight.js,使用它 if (window.hljs && lang) { try { return hljs.highlight(code, { language: lang }).value; } catch (e) { return code; } } return code; } }); } resolve(); }; script.onerror = reject; document.head.appendChild(script); }); } /** * 开始流式请求 */ async start() { try { // 确保Markdown库已加载 if (this.markdown && !window.marked) { await this.loadMarkdownLibrary(); } // 清空内容 this.content = ''; if (this.targetElement) { this.targetElement.innerHTML = ''; } // 创建AbortController用于取消请求 this.controller = new AbortController(); // 发起fetch请求 const response = await fetch(this.url, { method: this.method, headers: this.headers, body: JSON.stringify(this.body), signal: this.controller.signal }); if (!response.ok) { throw new Error(`HTTP错误: ${response.status} ${response.statusText}`); } // 处理流式响应 await this.handleStream(response); } catch (error) { if (error.name === 'AbortError') { console.log('请求已取消'); } else { console.error('请求错误:', error); if (this.onError) { this.onError(error); } } } } /** * 处理SSE流式响应 */ async handleStream(response) { const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); let buffer = ''; try { while (true) { const { done, value } = await reader.read(); if (done) { console.log('流式传输完成'); if (this.onComplete) { this.onComplete(this.content); } break; } // 解码数据 buffer += decoder.decode(value, { stream: true }); // 处理SSE格式的数据(data: {...}\n\n) const lines = buffer.split('\n'); buffer = lines.pop() || ''; // 保留不完整的行 for (const line of lines) { if (line.startsWith('data:')) { const data = line.slice(5).trim(); // 检查是否是结束标记 if (data === '[DONE]') { continue; } try { const json = JSON.parse(data); const delta = json.choices?.[0]?.delta?.content || ''; if (delta) { this.content += delta; this.render(this.content); // 调用自定义chunk回调 if (this.onChunk) { this.onChunk(delta, this.content); } } } catch (e) { // 忽略JSON解析错误 console.warn('JSON解析错误:', e, data); } } } } } finally { reader.releaseLock(); } } /** * 渲染内容到目标元素 */ render(content) { if (!this.targetElement) return; if (this.markdown && window.marked) { // Markdown渲染 this.targetElement.innerHTML = marked.parse(content); } else { // 纯文本渲染(保留换行) this.targetElement.textContent = content; } // 自动滚动到底部 this.targetElement.scrollTop = this.targetElement.scrollHeight; } /** * 停止流式请求 */ stop() { if (this.controller) { this.controller.abort(); console.log('已停止流式请求'); } } /** * 获取当前内容 */ getContent() { return this.content; } /** * 清空内容 */ clear() { this.content = ''; if (this.targetElement) { this.targetElement.innerHTML = ''; } } } // 导出(支持ES6模块和全局变量) if (typeof module !== 'undefined' && module.exports) { module.exports = LLMStream; } if (typeof window !== 'undefined') { window.LLMStream = LLMStream; }