/** * AI助手Web客户端 - 前端交互逻辑 * 对接im-api后端聊天服务 * @author huazm */ // 全局变量 let currentChatId = null; let currentAppId = null; let currentAppInfo = null; let allApps = []; let isTyping = false; let abortController = null; // 用于取消fetch请求 let authToken = null; // 认证token // API配置 const API_BASE_URL = '/api/ai-assistant'; // api后端地址 /** * Token管理功能 */ // 打开Token设置模态框 function openTokenModal() { const modal = document.getElementById('token-modal'); const input = document.getElementById('token-input'); const status = document.getElementById('token-status-modal'); // 加载已保存的token const savedToken = localStorage.getItem('authToken'); if (savedToken) { input.value = savedToken; status.textContent = '已设置'; status.className = 'font-medium text-green-400'; } else { input.value = ''; status.textContent = '未设置'; status.className = 'font-medium text-slate-400'; } modal.classList.remove('hidden'); } // 关闭Token设置模态框 function closeTokenModal() { const modal = document.getElementById('token-modal'); modal.classList.add('hidden'); } // 保存Token function saveToken() { const input = document.getElementById('token-input'); const token = input.value.trim(); if (token) { // 保存到localStorage localStorage.setItem('authToken', token); authToken = token; // 更新状态显示 const statusModal = document.getElementById('token-status-modal'); statusModal.textContent = '已设置'; statusModal.className = 'font-medium text-green-400'; console.log('Token已保存'); // 关闭模态框 closeTokenModal(); // 重新加载应用列表 loadApplications(); } else { alert('请输入有效的Token'); } } // 获取认证请求头 function getAuthHeaders() { const headers = { 'Content-Type': 'application/json' }; // 从localStorage加载token const savedToken = localStorage.getItem('authToken'); if (savedToken) { authToken = savedToken; headers['Authorization'] = `Bearer ${savedToken}`; } return headers; } // 页面加载时初始化token function initToken() { const savedToken = localStorage.getItem('authToken'); if (savedToken) { authToken = savedToken; console.log('已加载保存的Token'); } } /** * 创建动态粒子背景 */ function createParticles() { const particlesContainer = document.getElementById('particles'); const particleCount = 50; for (let i = 0; i < particleCount; i++) { const particle = document.createElement('div'); particle.className = 'particle'; // 随机位置和大小 const size = Math.random() * 4 + 1; const posX = Math.random() * 100; const posY = Math.random() * 100; particle.style.width = `${size}px`; particle.style.height = `${size}px`; particle.style.left = `${posX}%`; particle.style.top = `${posY}%`; // 随机动画延迟 particle.style.animationDelay = `${Math.random() * 5}s`; particlesContainer.appendChild(particle); } } /** * 加载应用列表 */ async function loadApplications() { try { const response = await fetch(`${API_BASE_URL}/chatapp`, { method: 'GET', headers: getAuthHeaders() }); const result = await response.json(); console.log('API返回数据:', result); // 兼容两种数据格式:result.data 或 result.result const apps = result.data || result.result; if (result.code === 200 && apps) { allApps = apps; // 按sortNum倒排 allApps.sort((a, b) => { if (b.sortNum !== undefined && a.sortNum !== undefined) { return a.sortNum - b.sortNum; // 从小到大排序 } return 0; }); renderApplications(allApps); } else { showError('加载应用列表失败'); } } catch (error) { console.error('加载应用列表失败:', error); showError('加载应用列表失败: ' + error.message); } } /** * 渲染应用列表 */ function renderApplications(apps) { const appList = document.getElementById('app-list'); if (!apps || apps.length === 0) { appList.innerHTML = `

暂无可用的智能体

`; return; } appList.innerHTML = apps.map(app => `
${app.appAvatar ? `${app.appName}` : `` }

${app.appName || '未命名应用'}

${app.appDescription || app.scopeDescription || '暂无描述'}

${app.category ? ` ${app.category} ` : ''}
`).join(''); } /** * 搜索应用 */ function searchApplications(keyword) { if (!keyword.trim()) { renderApplications(allApps); return; } const filtered = allApps.filter(app => { const searchText = keyword.toLowerCase(); return (app.appName && app.appName.toLowerCase().includes(searchText)) || (app.appDescription && app.appDescription.toLowerCase().includes(searchText)) || (app.scopeDescription && app.scopeDescription.toLowerCase().includes(searchText)) || (app.category && app.category.toLowerCase().includes(searchText)); }); renderApplications(filtered); } /** * 选择应用并进入对话 */ async function selectApplication(appId) { try { // 查找应用信息 currentAppInfo = allApps.find(app => app.id === appId); if (!currentAppInfo) { showError('应用信息不存在'); return; } currentAppId = appId; currentChatId = null; // 重置聊天ID,开始新对话 // 隐藏应用选择面板,显示对话面板 document.getElementById('app-selector-panel').classList.add('hidden'); document.getElementById('chat-panel').classList.remove('hidden'); // 更新顶部应用信息 updateCurrentAppInfo(); // 清空对话内容 clearChatContainer(); // 显示欢迎消息 showWelcomeMessage(); // 加载快捷回复(如果有) loadQuickReplies(); } catch (error) { console.error('选择应用失败:', error); showError('选择应用失败: ' + error.message); } } /** * 更新当前应用信息显示 */ function updateCurrentAppInfo() { if (!currentAppInfo) return; const appAvatar = document.getElementById('current-app-avatar'); const appName = document.getElementById('current-app-name'); const appDesc = document.getElementById('current-app-desc'); if (currentAppInfo.appAvatar) { appAvatar.src = currentAppInfo.appAvatar; appAvatar.style.display = 'block'; } else { appAvatar.style.display = 'none'; } appName.textContent = currentAppInfo.appName || 'AI 助手'; appDesc.textContent = currentAppInfo.appDescription || currentAppInfo.scopeDescription || ''; } /** * 清空对话容器 */ function clearChatContainer() { const chatContainer = document.getElementById('chat-container'); chatContainer.innerHTML = ''; } /** * 显示欢迎消息 */ function showWelcomeMessage() { const chatContainer = document.getElementById('chat-container'); const welcomeMsg = currentAppInfo.conversationStarter || '你好!我是' + (currentAppInfo.appName || 'AI助手') + ',有什么可以帮助你的吗?'; chatContainer.innerHTML = `

${welcomeMsg}

`; } /** * 加载快捷回复 */ async function loadQuickReplies() { const quickReplyContainer = document.getElementById('quick-reply-container'); const quickReplyButtons = document.getElementById('quick-reply-buttons'); // 如果应用启用了推荐问题 if (currentAppInfo.enableRecommendation) { try { const response = await fetch(`${API_BASE_URL}/chatapp/${currentAppId}/getRecommendQuestion?pageSize=3¤t=1`, { method: 'GET', headers: getAuthHeaders() }); const result = await response.json(); if (result.code === 200 && result.data && result.data.records && result.data.records.length > 0) { quickReplyButtons.innerHTML = result.data.records.map(q => ` `).join(''); quickReplyContainer.classList.remove('hidden'); } else { quickReplyContainer.classList.add('hidden'); } } catch (error) { console.error('加载推荐问题失败:', error); quickReplyContainer.classList.add('hidden'); } } else { quickReplyContainer.classList.add('hidden'); } } /** * 返回应用选择 */ function backToApps() { // 取消正在进行的请求 if (abortController) { abortController.abort(); abortController = null; } // 重置状态 currentAppId = null; currentAppInfo = null; currentChatId = null; // 切换面板 document.getElementById('chat-panel').classList.add('hidden'); document.getElementById('app-selector-panel').classList.remove('hidden'); } /** * 添加消息到聊天容器 */ function addMessage(message, isUser = true) { const chatContainer = document.getElementById('chat-container'); // 移除欢迎消息 const welcomeDiv = chatContainer.querySelector('.flex.justify-center'); if (welcomeDiv) { welcomeDiv.remove(); } // 移除打字指示器 removeTypingIndicator(); const messageDiv = document.createElement('div'); messageDiv.className = `flex ${isUser ? 'justify-end' : 'justify-start'} slide-in`; const bubbleClass = isUser ? 'user-bubble' : 'ai-bubble'; const roundedClass = isUser ? 'rounded-br-md' : 'rounded-bl-md'; messageDiv.innerHTML = `

${escapeHtml(message)}

`; chatContainer.appendChild(messageDiv); scrollToBottom(); } /** * 显示打字指示器 */ function showTypingIndicator() { if (isTyping) return; isTyping = true; const chatContainer = document.getElementById('chat-container'); const typingDiv = document.createElement('div'); typingDiv.id = 'typing-indicator'; typingDiv.className = 'flex justify-start'; typingDiv.innerHTML = `
`; chatContainer.appendChild(typingDiv); scrollToBottom(); } /** * 移除打字指示器 */ function removeTypingIndicator() { const typingIndicator = document.getElementById('typing-indicator'); if (typingIndicator) { typingIndicator.remove(); isTyping = false; } } /** * 滚动到底部 */ function scrollToBottom() { const chatContainer = document.getElementById('chat-container'); chatContainer.scrollTop = chatContainer.scrollHeight; } /** * HTML转义 */ function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * 发送消息(使用SSE流式响应) */ async function sendMessage() { const input = document.getElementById('message-input'); const message = input.value.trim(); if (!message || !currentAppId) return; // 添加用户消息 addMessage(message, true); input.value = ''; // 显示打字指示器 showTypingIndicator(); try { // 构建请求体 const requestBody = { chatId: currentChatId, appId: currentAppId, equipment: 'web', messageTag: 'AI_TAG', body: { messages: [ { role: 'user', content: message } ], channel: 'web', attachmentIds: [], recommendQuestions: [], variables: {}, reasoning: currentAppInfo.reasoningEnable ? 'true' : 'false' } }; // 使用fetch进行SSE连接(支持自定义请求头) const url = `${API_BASE_URL}/chat/completions/message`; // 取消之前的请求 if (abortController) { abortController.abort(); } // 创建新的AbortController abortController = new AbortController(); // 使用fetch发送POST请求,处理流式响应 const response = await fetch(url, { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify(requestBody), signal: abortController.signal }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } let aiMessage = ''; let messageDiv = null; // 读取流式响应 const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) { break; } // 解码数据 buffer += decoder.decode(value, { stream: true }); // 按行分割 const lines = buffer.split('\n'); buffer = lines.pop() || ''; // 保留最后一个不完整的行 for (const line of lines) { if (line.startsWith('data: ')) { const data = line.substring(6).trim(); if (data === '[DONE]') { continue; } try { const json = JSON.parse(data); // 处理不同类型的事件 if (json.detailId) { // 保存chatId if (json.chatId && !currentChatId) { currentChatId = json.chatId; } // 更新AI回复 if (json.answer && json.answer.content) { aiMessage = json.answer.content; // 移除打字指示器 removeTypingIndicator(); // 更新或创建消息气泡 if (!messageDiv) { messageDiv = createAIMessageBubble(aiMessage); } else { updateAIMessageBubble(messageDiv, aiMessage); } } // 检查是否完成 if (json.status === 'FINISH' || json.status === 'ERROR') { if (json.status === 'ERROR') { showError('AI回复出错'); } break; } } } catch (parseError) { console.error('解析SSE数据失败:', parseError); } } } } // 确保移除打字指示器 removeTypingIndicator(); } catch (error) { console.error('发送消息失败:', error); removeTypingIndicator(); showError('发送消息失败: ' + error.message); } } /** * 创建AI消息气泡 */ function createAIMessageBubble(content) { const chatContainer = document.getElementById('chat-container'); const messageDiv = document.createElement('div'); messageDiv.className = 'flex justify-start slide-in'; messageDiv.innerHTML = `

${escapeHtml(content)}

`; chatContainer.appendChild(messageDiv); scrollToBottom(); return messageDiv; } /** * 更新AI消息气泡 */ function updateAIMessageBubble(messageDiv, content) { const contentElement = messageDiv.querySelector('.ai-message-content'); if (contentElement) { contentElement.innerHTML = escapeHtml(content); scrollToBottom(); } } /** * 显示错误消息 */ function showError(message) { const chatContainer = document.getElementById('chat-container'); const errorDiv = document.createElement('div'); errorDiv.className = 'flex justify-center my-4'; errorDiv.innerHTML = `
${escapeHtml(message)}
`; chatContainer.appendChild(errorDiv); scrollToBottom(); // 3秒后自动移除 setTimeout(() => { errorDiv.remove(); }, 3000); } /** * 快捷回复按钮点击 */ function sendQuickReply(text) { const input = document.getElementById('message-input'); input.value = text; sendMessage(); } /** * 快捷回复填充(不发送) */ function handleQuickReply(text) { const input = document.getElementById('message-input'); input.value = text; input.focus(); } /** * 主题切换 */ function changeTheme(theme) { const body = document.body; // 移除所有主题类 body.classList.remove('theme-dark', 'theme-blue', 'theme-purple', 'theme-green'); // 添加新主题类 body.classList.add(`theme-${theme}`); // 更新背景渐变 const gradients = { dark: 'linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%)', blue: 'linear-gradient(135deg, #0c4a6e 0%, #075985 50%, #0c4a6e 100%)', purple: 'linear-gradient(135deg, #581c87 0%, #6b21a8 50%, #581c87 100%)', green: 'linear-gradient(135deg, #064e3b 0%, #065f46 50%, #064e3b 100%)' }; body.style.background = gradients[theme] || gradients.dark; } /** * 字体切换 */ function changeFont(font) { const chatContainer = document.getElementById('chat-container'); chatContainer.style.fontFamily = font; } /** * 开始新会话 */ function startNewChat() { currentChatId = null; clearChatContainer(); showWelcomeMessage(); } /** * 页面加载完成后初始化 */ document.addEventListener('DOMContentLoaded', function() { console.log('AI助手Web客户端已加载'); // 创建粒子背景 createParticles(); // 加载应用列表 loadApplications(); // 绑定应用搜索 const searchInput = document.getElementById('app-search-input'); if (searchInput) { searchInput.addEventListener('input', function(e) { searchApplications(e.target.value); }); } // 绑定返回按钮 const backButton = document.getElementById('back-to-apps-btn'); if (backButton) { backButton.addEventListener('click', backToApps); } // 绑定发送按钮事件 const sendButton = document.getElementById('send-button'); if (sendButton) { sendButton.addEventListener('click', sendMessage); } // 绑定输入框回车事件 const messageInput = document.getElementById('message-input'); if (messageInput) { messageInput.addEventListener('keypress', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); } // 绑定字体切换 const fontSelect = document.getElementById('font-select'); if (fontSelect) { fontSelect.addEventListener('change', function() { changeFont(this.value); }); } // 初始化Token initToken(); console.log('AI助手Web客户端初始化完成'); });