/**
* 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.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 = `
`;
}
/**
* 加载快捷回复
*/
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 = `
`;
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 = `
`;
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客户端初始化完成');
});