| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>AI 知识库问答</title>
- <style>
- * { margin: 0; padding: 0; box-sizing: border-box; }
- body {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
- background: #f5f5f5;
- height: 100vh;
- display: flex;
- flex-direction: column;
- }
- .header {
- background: white;
- padding: 16px 24px;
- border-bottom: 1px solid #e0e0e0;
- display: flex;
- align-items: center;
- gap: 16px;
- box-shadow: 0 2px 8px rgba(0,0,0,0.05);
- }
- .header h1 { font-size: 18px; font-weight: 600; color: #333; }
- .session-info {
- font-size: 12px;
- color: #666;
- background: #f0f0f0;
- padding: 4px 10px;
- border-radius: 12px;
- }
- .header-actions { margin-left: auto; display: flex; gap: 8px; }
- .btn {
- padding: 8px 16px;
- border: 1px solid #e0e0e0;
- background: white;
- border-radius: 6px;
- cursor: pointer;
- font-size: 14px;
- transition: background 0.2s;
- }
- .btn:hover:not(:disabled) { background: #e0e0e0; }
- .btn:disabled { opacity: 0.5; cursor: not-allowed; }
- .chat-container {
- flex: 1;
- overflow-y: auto;
- padding: 24px;
- display: flex;
- flex-direction: column;
- gap: 16px;
- }
- .message {
- display: flex;
- gap: 12px;
- max-width: 800px;
- margin: 0 auto;
- width: 100%;
- }
- .message.user { flex-direction: row-reverse; }
- .avatar {
- width: 36px;
- height: 36px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 16px;
- flex-shrink: 0;
- }
- .message.user .avatar { background: #4a90e2; color: white; }
- .message.ai .avatar { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; }
- .message-content { flex: 1; min-width: 0; }
- .message-header {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-bottom: 6px;
- }
- .message.user .message-header { flex-direction: row-reverse; }
- .message-name { font-weight: 600; font-size: 13px; color: #333; }
- .message-time { font-size: 11px; color: #999; }
- .message-body {
- padding: 12px 16px;
- border-radius: 12px;
- line-height: 1.6;
- }
- .message.user .message-body { background: #4a90e2; color: white; border-bottom-right-radius: 4px; }
- .message.ai .message-body { background: white; border: 1px solid #e0e0e0; border-bottom-left-radius: 4px; }
- .message-text { word-break: break-word; }
- .message-text pre { margin: 8px 0; border-radius: 6px; overflow: hidden; background: #1e1e1e; }
- .message-text code { font-family: 'Fira Code', 'Consolas', monospace; font-size: 13px; }
- .message-text pre code { padding: 12px; display: block; overflow-x: auto; color: #d4d4d4; }
- .message-text p { margin-bottom: 8px; }
- .message-text p:last-child { margin-bottom: 0; }
- .message-text ul, .message-text ol { margin: 8px 0; padding-left: 20px; }
- .message-text li { margin-bottom: 4px; }
- .message-text code { background: rgba(0,0,0,0.06); padding: 2px 6px; border-radius: 4px; }
- .message-text pre code { background: transparent; padding: 0; }
- .ai-image {
- max-width: 100%;
- height: auto;
- border-radius: 8px;
- margin-top: 12px;
- display: block;
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
- }
- .ai-images-container {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
- gap: 12px;
- margin-top: 16px;
- padding-top: 16px;
- border-top: 1px solid #e0e0e0;
- }
- .ai-images-container .ai-image {
- margin-top: 0;
- width: 100%;
- }
- .thinking-container {
- margin-bottom: 10px;
- border: 1px solid #ffd666;
- border-radius: 8px;
- background: #fff9e6;
- overflow: hidden;
- }
- .thinking-header {
- padding: 8px 12px;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- .thinking-title {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 12px;
- font-weight: 500;
- color: #b8860b;
- }
- .thinking-icon {
- transition: transform 0.3s;
- display: inline-block;
- }
- .thinking-icon.expanded { transform: rotate(90deg); }
- .thinking-content {
- display: none;
- padding: 0 12px 12px;
- font-size: 12px;
- color: #666;
- white-space: pre-wrap;
- font-family: 'Fira Code', 'Consolas', monospace;
- }
- .thinking-content.expanded { display: block; }
- .welcome-section {
- text-align: center;
- padding: 40px 20px;
- margin: auto;
- color: #666;
- }
- .welcome-section h2 { font-size: 24px; margin-bottom: 8px; color: #333; }
- .suggestions {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- justify-content: center;
- margin-top: 16px;
- }
- .suggestion-btn {
- padding: 8px 16px;
- background: white;
- border: 1px solid #e0e0e0;
- border-radius: 20px;
- font-size: 13px;
- cursor: pointer;
- transition: all 0.2s;
- }
- .suggestion-btn:hover { background: #4a90e2; color: white; border-color: #4a90e2; }
- .typing-indicator {
- display: flex;
- gap: 4px;
- padding: 8px 0;
- }
- .typing-indicator span {
- width: 8px;
- height: 8px;
- background: #999;
- border-radius: 50%;
- animation: typing 1.4s infinite;
- }
- .typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
- .typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
- @keyframes typing {
- 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
- 30% { transform: translateY(-6px); opacity: 1; }
- }
- .streaming-cursor {
- display: inline-block;
- width: 2px;
- height: 1em;
- background: #4a90e2;
- animation: blink 0.8s infinite;
- vertical-align: middle;
- margin-left: 2px;
- }
- @keyframes blink {
- 0%, 50% { opacity: 1; }
- 51%, 100% { opacity: 0; }
- }
- .input-container {
- background: white;
- padding: 16px 24px;
- border-top: 1px solid #e0e0e0;
- }
- .input-wrapper {
- max-width: 800px;
- margin: 0 auto;
- display: flex;
- gap: 12px;
- align-items: flex-end;
- }
- .input-box {
- flex: 1;
- border: 1px solid #e0e0e0;
- border-radius: 12px;
- padding: 12px 16px;
- font-size: 15px;
- line-height: 1.5;
- resize: none;
- min-height: 48px;
- max-height: 150px;
- font-family: inherit;
- outline: none;
- transition: border-color 0.2s;
- }
- .input-box:focus { border-color: #4a90e2; }
- .send-btn {
- width: 48px;
- height: 48px;
- border: none;
- background: #4a90e2;
- color: white;
- border-radius: 12px;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- }
- .send-btn:hover:not(:disabled) { background: #357abd; }
- .send-btn:disabled { background: #ccc; cursor: not-allowed; }
- .send-btn svg { width: 20px; height: 20px; }
- </style>
- </head>
- <body>
- <div class="header">
- <h1>🤖 AI 知识库问答</h1>
- <span class="session-info" id="sessionInfo">新对话</span>
- <div class="header-actions">
- <button class="btn" id="stopBtn" onclick="stopGeneration()" style="display: none;">停止生成</button>
- <button class="btn" onclick="clearChat()">新对话</button>
- </div>
- </div>
- <div class="chat-container" id="chatContainer">
- <div class="welcome-section" id="welcomeSection">
- <h2>欢迎使用 AI 知识库问答</h2>
- <p>基于阿里云百炼平台,智能解答您的各类问题</p>
- <div class="suggestions">
- <button class="suggestion-btn" onclick="sendMessage('这个平台支持哪些功能?')">平台支持哪些功能?</button>
- <button class="suggestion-btn" onclick="sendMessage('如何开始使用?')">如何开始使用?</button>
- <button class="suggestion-btn" onclick="sendMessage('有什么使用限制吗?')">有什么使用限制吗?</button>
- </div>
- </div>
- </div>
- <div class="input-container">
- <div class="input-wrapper">
- <textarea
- class="input-box"
- id="messageInput"
- placeholder="输入您的问题..."
- rows="1"
- onkeydown="handleEnter(event)"
- oninput="autoResize(this)"
- ></textarea>
- <button class="send-btn" id="sendBtn" onclick="sendMessage()">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/>
- </svg>
- </button>
- </div>
- </div>
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/github.min.css">
- <script type="module">
- import { marked } from 'https://esm.sh/marked@12.0.2';
- import hljs from 'https://esm.sh/highlight.js@11.9.0';
- import { markedHighlight } from 'https://esm.sh/marked-highlight@2.1.0';
- marked.use(
- markedHighlight({
- langPrefix: 'hljs language-',
- highlight(code, lang) {
- const language = hljs.getLanguage(lang) ? lang : 'plaintext';
- return hljs.highlight(code, { language }).value;
- }
- })
- );
- marked.setOptions({
- breaks: true,
- gfm: true
- });
- const API_KEY = 'sk-323d807020674fd98accaf971f9b976d';
- const APP_ID = '04ce2984f5ce45838d3ba55427d62f97';
- const API_URL = `https://dashscope.aliyuncs.com/api/v1/apps/${APP_ID}/completion`;
- let sessionId = null;
- let isGenerating = false;
- let currentController = null;
- let messages = [];
- let fullThinking = '';
- let expandedThinking = new Set();
- function handleEnter(e) {
- if (e.key === 'Enter' && !e.shiftKey) {
- e.preventDefault();
- sendMessage();
- }
- }
- function autoResize(el) {
- el.style.height = 'auto';
- el.style.height = Math.min(el.scrollHeight, 150) + 'px';
- }
- function escapeHtml(text) {
- const div = document.createElement('div');
- div.textContent = text;
- return div.innerHTML;
- }
- function formatTime() {
- const now = new Date();
- return `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
- }
- function clearChat() {
- sessionId = null;
- messages = [];
- fullThinking = '';
- expandedThinking = new Set();
- document.getElementById('sessionInfo').textContent = '新对话';
- document.getElementById('chatContainer').innerHTML = `
- <div class="welcome-section" id="welcomeSection">
- <h2>欢迎使用 AI 知识库问答</h2>
- <p>基于阿里云百炼平台,智能解答您的各类问题</p>
- <div class="suggestions">
- <button class="suggestion-btn" onclick="sendMessage('这个平台支持哪些功能?')">平台支持哪些功能?</button>
- <button class="suggestion-btn" onclick="sendMessage('如何开始使用?')">如何开始使用?</button>
- <button class="suggestion-btn" onclick="sendMessage('有什么使用限制吗?')">有什么使用限制吗?</button>
- </div>
- </div>
- `;
- }
- function removeWelcome() {
- const welcome = document.getElementById('welcomeSection');
- if (welcome) welcome.remove();
- }
- function scrollToBottom() {
- const container = document.getElementById('chatContainer');
- container.scrollTop = container.scrollHeight;
- }
- async function renderMarkdown(content) {
- try {
- return await marked.parse(content);
- } catch (e) {
- return escapeHtml(content);
- }
- }
- function renderImages(urls) {
- if (!urls || urls.length === 0) return '';
- let html = '<div class="ai-images-container">';
- for (const url of urls) {
- html += `<img src="${escapeHtml(url)}" class="ai-image" alt="AI生成的图片" loading="lazy">`;
- }
- html += '</div>';
- return html;
- }
- function filterOutImages(content) {
- return content.replace(/!\[[^\]]*\]\([^)]+\)/g, '').trim();
- }
- function addUserMessage(content) {
- removeWelcome();
- const container = document.getElementById('chatContainer');
- const html = `
- <div class="message user">
- <div class="avatar">👤</div>
- <div class="message-content">
- <div class="message-header">
- <span class="message-name">我</span>
- <span class="message-time">${formatTime()}</span>
- </div>
- <div class="message-body">
- <div class="message-text">${escapeHtml(content)}</div>
- </div>
- </div>
- </div>
- `;
- container.insertAdjacentHTML('beforeend', html);
- scrollToBottom();
- }
- function addAIMessageStart() {
- removeWelcome();
- const id = 'ai-' + Date.now();
- const container = document.getElementById('chatContainer');
- const html = `
- <div class="message ai" id="${id}">
- <div class="avatar">🤖</div>
- <div class="message-content">
- <div class="message-header">
- <span class="message-name">AI 助手</span>
- <span class="message-time">${formatTime()}</span>
- </div>
- <div class="message-body">
- <div class="typing-indicator">
- <span></span><span></span><span></span>
- </div>
- </div>
- </div>
- </div>
- `;
- container.insertAdjacentHTML('beforeend', html);
- scrollToBottom();
- return id;
- }
- let currentStreamingContent = {};
- let currentStreamingHtml = {};
- let pendingUpdate = {};
- function updateAIMessage(id, thinking, thinkingExpanded, content, contentHtml) {
- const msgEl = document.getElementById(id);
- if (!msgEl) return;
- if (!currentStreamingContent[id]) {
- currentStreamingContent[id] = '';
- }
- if (content !== currentStreamingContent[id]) {
- currentStreamingContent[id] = content;
- currentStreamingHtml[id] = { thinking, thinkingExpanded, contentHtml };
- }
- if (!pendingUpdate[id]) {
- pendingUpdate[id] = true;
- requestAnimationFrame(() => {
- renderStreamingUpdate(id);
- pendingUpdate[id] = false;
- });
- }
- }
- function renderStreamingUpdate(id) {
- const msgEl = document.getElementById(id);
- if (!msgEl) return;
- const data = currentStreamingHtml[id];
- if (!data) return;
- const { thinking, thinkingExpanded, contentHtml } = data;
- const bodyEl = msgEl.querySelector('.message-body');
- if (!bodyEl) return;
- const textEl = bodyEl.querySelector('.message-text');
- if (textEl) {
- if (textEl.innerHTML !== contentHtml) {
- textEl.innerHTML = contentHtml;
- }
- }
- if (thinking !== undefined) {
- const hasThinkContainer = bodyEl.querySelector('.thinking-container');
- if (thinking && !hasThinkContainer) {
- const thinkingHtml = `
- <div class="thinking-container" onclick="toggleThinking('${id}')">
- <div class="thinking-header">
- <div class="thinking-title">
- <span class="thinking-icon ${thinkingExpanded ? 'expanded' : ''}">▶</span>
- <span>思考过程</span>
- </div>
- </div>
- <div class="thinking-content ${thinkingExpanded ? 'expanded' : ''}">${escapeHtml(thinking)}</div>
- </div>
- `;
- bodyEl.insertAdjacentHTML('afterbegin', thinkingHtml);
- } else if (hasThinkContainer) {
- const thinkContent = hasThinkContainer.querySelector('.thinking-content');
- if (thinkContent) {
- thinkContent.textContent = thinking;
- }
- }
- }
- scrollToBottom();
- }
- function finalizeAIMessage(id, thinking, content, contentHtml, imageHtml = '') {
- const msgEl = document.getElementById(id);
- if (!msgEl) return;
- delete currentStreamingContent[id];
- delete currentStreamingHtml[id];
- delete pendingUpdate[id];
- let thinkingHtml = '';
- if (thinking) {
- const expanded = expandedThinking.has(id) ? 'expanded' : '';
- thinkingHtml = `
- <div class="thinking-container" onclick="toggleThinking('${id}')">
- <div class="thinking-header">
- <div class="thinking-title">
- <span class="thinking-icon ${expanded}">▶</span>
- <span>思考过程</span>
- </div>
- </div>
- <div class="thinking-content ${expanded}">${escapeHtml(thinking)}</div>
- </div>
- `;
- }
- msgEl.querySelector('.message-body').innerHTML = thinkingHtml + `
- <div class="message-text">${contentHtml}</div>
- ${imageHtml}
- `;
- scrollToBottom();
- }
- function toggleThinking(id) {
- if (expandedThinking.has(id)) {
- expandedThinking.delete(id);
- } else {
- expandedThinking.add(id);
- }
- const msgEl = document.getElementById(id);
- if (!msgEl) return;
- const content = msgEl.querySelector('.thinking-content');
- const icon = msgEl.querySelector('.thinking-icon');
- if (content) content.classList.toggle('expanded');
- if (icon) icon.classList.toggle('expanded');
- }
- function setInputEnabled(enabled) {
- document.getElementById('messageInput').disabled = !enabled;
- document.getElementById('sendBtn').disabled = !enabled;
- document.getElementById('stopBtn').style.display = enabled ? 'none' : 'inline-block';
- isGenerating = !enabled;
- }
- function stopGeneration() {
- if (currentController) {
- currentController.abort();
- currentController = null;
- }
- setInputEnabled(true);
- }
- async function sendMessage(content) {
- content = content || document.getElementById('messageInput').value.trim();
- if (!content || isGenerating) return;
- document.getElementById('messageInput').value = '';
- document.getElementById('messageInput').style.height = 'auto';
- messages.push({ role: 'user', content });
- addUserMessage(content);
- setInputEnabled(false);
- fullThinking = '';
- currentStreamingContent = {};
- currentStreamingHtml = {};
- const aiMsgId = addAIMessageStart();
- try {
- currentController = new AbortController();
- const requestBody = {
- input: { prompt: content },
- parameters: {
- incremental_output: true,
- has_thoughts: true
- },
- debug: {}
- };
- if (sessionId) {
- requestBody.input.session_id = sessionId;
- }
- const response = await fetch(API_URL, {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${API_KEY}`,
- 'Content-Type': 'application/json',
- 'X-DashScope-SSE': 'enable'
- },
- body: JSON.stringify(requestBody),
- signal: currentController.signal
- });
- if (!response.ok) {
- const errorData = await response.json().catch(() => ({}));
- throw new Error(errorData.message || `请求失败: ${response.status}`);
- }
- let fullContent = '';
- let buffer = '';
- let thinkingContent = '';
- let hasThought = false;
- let chunkIndex = 0;
- let imageUrls = [];
- console.log('='.repeat(80));
- console.log('🚀 开始接收 AI 响应流');
- console.log('='.repeat(80));
- const reader = response.body.getReader();
- const decoder = new TextDecoder();
- while (true) {
- const { done, value } = await reader.read();
- if (done) {
- console.log('\n' + '='.repeat(80));
- console.log('✅ 响应流接收完毕');
- console.log('='.repeat(80));
- break;
- }
- chunkIndex++;
- console.log('\n' + '-'.repeat(80));
- console.log(`📦 数据块 #${chunkIndex}`);
- console.log('-'.repeat(80));
- console.log('原始二进制数据长度:', value ? value.length : 0);
- buffer += decoder.decode(value, { stream: true });
- const lines = buffer.split('\n');
- buffer = lines.pop() || '';
- console.log(`📝 分割出 ${lines.length} 行数据`);
- for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
- const line = lines[lineIndex];
- console.log(`\n ━━ 行 #${lineIndex + 1} ━━`);
- console.log(' 原始行内容:', JSON.stringify(line));
- if (line.startsWith('data:')) {
- const data = line.slice(5).trim();
- console.log(' ✨ 识别到 data: 前缀');
- console.log(' data 内容:', JSON.stringify(data));
-
- if (!data) {
- console.log(' ⚠️ data 为空,跳过');
- continue;
- }
- try {
- const json = JSON.parse(data);
- console.log('\n 🎯 完整 JSON 对象:');
- console.log(JSON.stringify(json, null, 2));
- if (json.output?.session_id) {
- sessionId = json.output.session_id;
- document.getElementById('sessionInfo').textContent = '会话ID: ' + sessionId.substring(0, 8) + '...';
- console.log('\n 🔑 会话 ID 更新:', sessionId);
- }
- if (json.output?.thoughts && json.output.thoughts.length > 0) {
- console.log('\n 💭 检测到 thoughts 字段:');
- console.log(' thoughts 数量:', json.output.thoughts.length);
- for (let i = 0; i < json.output.thoughts.length; i++) {
- const thought = json.output.thoughts[i];
- console.log(` Thought #${i + 1}:`, JSON.stringify(thought, null, 2));
- if (thought.action_type === 'mcp') {
- console.log('\n 🔧 检测到 MCP 工具调用:', thought.action_name);
- if (thought.observation) {
- try {
- const observation = JSON.parse(thought.observation);
- console.log(' 📦 observation 解析后:', JSON.stringify(observation, null, 2));
- if (observation.content && observation.content.length > 0) {
- for (const item of observation.content) {
- if (item.type === 'text') {
- try {
- const textData = JSON.parse(item.text);
- console.log(' 📦 text 解析后:', JSON.stringify(textData, null, 2));
- if (textData.results && textData.results.length > 0) {
- for (const imgUrl of textData.results) {
- console.log(' 🖼️ 发现图片 URL:', imgUrl);
- imageUrls.push(imgUrl);
- }
- }
- } catch (e) {
- console.log(' 📝 text 不是 JSON 格式,直接使用:', item.text);
- }
- }
- }
- }
- } catch (e) {
- console.log(' ⚠️ observation 解析失败:', e.message);
- }
- }
- }
- if (thought.thought) {
- thinkingContent += thought.thought;
- fullThinking = thinkingContent;
- hasThought = true;
- const html = await renderMarkdown(fullContent);
- updateAIMessage(aiMsgId, thinkingContent, true, fullContent, html + '<span class="streaming-cursor"></span>');
- }
- }
- }
- if (json.output?.text) {
- console.log('\n 📄 检测到 text 字段:');
- console.log(' text 内容:', JSON.stringify(json.output.text));
- fullContent += json.output.text;
- const html = await renderMarkdown(fullContent);
- if (hasThought) {
- updateAIMessage(aiMsgId, thinkingContent, true, fullContent, html + '<span class="streaming-cursor"></span>');
- } else {
- updateAIMessage(aiMsgId, '', false, fullContent, html + '<span class="streaming-cursor"></span>');
- }
- }
- if (json.error) {
- console.log('\n ❌ 检测到 error 字段:');
- console.log(JSON.stringify(json.error, null, 2));
- throw new Error(json.error.message || '响应错误');
- }
- console.log('\n 📊 完整对象结构分析:');
- console.log(' 顶层键:', Object.keys(json));
- if (json.output) {
- console.log(' output 键:', Object.keys(json.output));
- }
- } catch (e) {
- if (e.message !== 'Unexpected end of JSON input') {
- console.warn(' ❌ 解析错误:', e.message);
- console.warn(' 错误堆栈:', e.stack);
- } else {
- console.log(' ⏳ JSON 不完整,等待下一个数据块');
- }
- }
- } else if (line.trim() === '') {
- console.log(' 空行,跳过');
- } else {
- console.log(' ⚠️ 非 data: 开头的行,内容:', JSON.stringify(line));
- }
- }
- }
- expandedThinking.delete(aiMsgId);
- const contentWithoutImages = filterOutImages(fullContent);
- const finalHtml = await renderMarkdown(contentWithoutImages);
- const finalImageHtml = renderImages(imageUrls);
- finalizeAIMessage(aiMsgId, hasThought ? thinkingContent : '', contentWithoutImages, finalHtml, finalImageHtml);
- messages.push({ role: 'assistant', content: contentWithoutImages });
- } catch (error) {
- if (error.name === 'AbortError') {
- if (fullThinking || messages.length > 0) {
- expandedThinking.delete(aiMsgId);
- const lastContent = messages[messages.length - 1]?.content || '';
- const contentWithoutImages = filterOutImages(lastContent);
- const html = await renderMarkdown(contentWithoutImages);
- const finalImageHtml = renderImages(imageUrls);
- finalizeAIMessage(aiMsgId, fullThinking, contentWithoutImages, html, finalImageHtml);
- }
- } else {
- console.error('请求错误:', error);
- const msgEl = document.getElementById(aiMsgId);
- if (msgEl) {
- msgEl.querySelector('.message-body').innerHTML = `
- <div class="message-text" style="color: red;">❌ 错误: ${escapeHtml(error.message)}</div>
- `;
- }
- }
- }
- currentController = null;
- setInputEnabled(true);
- document.getElementById('messageInput').focus();
- }
- window.handleEnter = handleEnter;
- window.autoResize = autoResize;
- window.sendMessage = sendMessage;
- window.stopGeneration = stopGeneration;
- window.clearChat = clearChat;
- window.toggleThinking = toggleThinking;
- </script>
- </body>
- </html>
|