239 lines
6.5 KiB
JavaScript
239 lines
6.5 KiB
JavaScript
// 全局变量
|
|
let messages = [];
|
|
let currentModel = 'gpt-5.3-codex';
|
|
|
|
// 页面加载时初始化
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
await loadStatus();
|
|
await loadModels();
|
|
});
|
|
|
|
// 加载服务状态
|
|
async function loadStatus() {
|
|
try {
|
|
const response = await fetch('/health');
|
|
const data = await response.json();
|
|
|
|
if (data.status === 'ok') {
|
|
document.getElementById('serviceStatus').textContent = '运行中';
|
|
document.getElementById('accountEmail').textContent = data.token.email || data.token.account_id || '未知';
|
|
|
|
if (data.token.expired) {
|
|
const expireDate = new Date(data.token.expired);
|
|
document.getElementById('tokenExpire').textContent = expireDate.toLocaleString('zh-CN');
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('加载状态失败:', error);
|
|
document.getElementById('serviceStatus').textContent = '离线';
|
|
document.getElementById('serviceStatus').classList.remove('text-primary');
|
|
document.getElementById('serviceStatus').classList.add('text-error');
|
|
}
|
|
}
|
|
|
|
// 加载模型列表
|
|
async function loadModels() {
|
|
try {
|
|
const response = await fetch('/v1/models');
|
|
const data = await response.json();
|
|
|
|
const select = document.getElementById('modelSelect');
|
|
select.innerHTML = '';
|
|
|
|
data.data.forEach(model => {
|
|
const option = document.createElement('option');
|
|
option.value = model.id;
|
|
option.textContent = model.id;
|
|
select.appendChild(option);
|
|
});
|
|
|
|
if (data.data.length > 0) {
|
|
currentModel = data.data[0].id;
|
|
select.value = currentModel;
|
|
}
|
|
|
|
select.addEventListener('change', (e) => {
|
|
currentModel = e.target.value;
|
|
});
|
|
} catch (error) {
|
|
console.error('加载模型失败:', error);
|
|
}
|
|
}
|
|
|
|
// 发送消息
|
|
async function sendMessage() {
|
|
const input = document.getElementById('messageInput');
|
|
const message = input.value.trim();
|
|
|
|
if (!message) return;
|
|
|
|
// 添加用户消息
|
|
messages.push({ role: 'user', content: message });
|
|
appendMessage('user', message);
|
|
input.value = '';
|
|
|
|
// 显示加载状态
|
|
const loadingId = appendMessage('assistant', '思考中...', true);
|
|
|
|
try {
|
|
const response = await fetch('/v1/chat/completions', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
model: currentModel,
|
|
messages: messages,
|
|
stream: true
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
// 移除加载消息
|
|
document.getElementById(loadingId).remove();
|
|
|
|
// 处理流式响应
|
|
const reader = response.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
let assistantMessage = '';
|
|
let messageId = null;
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
|
|
const chunk = decoder.decode(value);
|
|
const lines = chunk.split('\n');
|
|
|
|
for (const line of lines) {
|
|
if (line.startsWith('data: ')) {
|
|
const data = line.slice(6);
|
|
if (data === '[DONE]') continue;
|
|
|
|
try {
|
|
const json = JSON.parse(data);
|
|
const content = json.choices[0]?.delta?.content;
|
|
|
|
if (content) {
|
|
assistantMessage += content;
|
|
|
|
if (!messageId) {
|
|
messageId = appendMessage('assistant', assistantMessage);
|
|
} else {
|
|
updateMessage(messageId, assistantMessage);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// 忽略解析错误
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 保存助手消息
|
|
if (assistantMessage) {
|
|
messages.push({ role: 'assistant', content: assistantMessage });
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('发送消息失败:', error);
|
|
document.getElementById(loadingId).remove();
|
|
appendMessage('system', '错误: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// 添加消息到聊天区域
|
|
function appendMessage(role, content, isLoading = false) {
|
|
const container = document.getElementById('chatMessages');
|
|
|
|
// 首次添加消息时清除欢迎文本
|
|
if (container.children.length === 1 && container.children[0].classList.contains('text-center')) {
|
|
container.innerHTML = '';
|
|
}
|
|
|
|
const messageId = 'msg-' + Date.now() + '-' + Math.random();
|
|
const messageDiv = document.createElement('div');
|
|
messageDiv.id = messageId;
|
|
messageDiv.className = 'chat chat-message ' + (role === 'user' ? 'chat-end' : 'chat-start');
|
|
|
|
let avatarClass = 'bg-primary';
|
|
let avatarText = 'U';
|
|
|
|
if (role === 'assistant') {
|
|
avatarClass = 'bg-secondary';
|
|
avatarText = 'AI';
|
|
} else if (role === 'system') {
|
|
avatarClass = 'bg-error';
|
|
avatarText = '!';
|
|
}
|
|
|
|
messageDiv.innerHTML = `
|
|
<div class="chat-image avatar">
|
|
<div class="w-10 rounded-full ${avatarClass} flex items-center justify-center text-white font-bold">
|
|
${avatarText}
|
|
</div>
|
|
</div>
|
|
<div class="chat-bubble ${role === 'user' ? 'chat-bubble-primary' : role === 'system' ? 'chat-bubble-error' : ''}">
|
|
${isLoading ? '<span class="loading loading-dots loading-sm"></span>' : escapeHtml(content)}
|
|
</div>
|
|
`;
|
|
|
|
container.appendChild(messageDiv);
|
|
container.scrollTop = container.scrollHeight;
|
|
|
|
return messageId;
|
|
}
|
|
|
|
// 更新消息内容
|
|
function updateMessage(messageId, content) {
|
|
const messageDiv = document.getElementById(messageId);
|
|
if (messageDiv) {
|
|
const bubble = messageDiv.querySelector('.chat-bubble');
|
|
bubble.textContent = content;
|
|
}
|
|
|
|
const container = document.getElementById('chatMessages');
|
|
container.scrollTop = container.scrollHeight;
|
|
}
|
|
|
|
// 清空聊天
|
|
function clearChat() {
|
|
messages = [];
|
|
const container = document.getElementById('chatMessages');
|
|
container.innerHTML = '<div class="text-center text-base-content/50 py-8">开始对话吧!</div>';
|
|
}
|
|
|
|
// HTML 转义
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
// 显示设置
|
|
function showSettings() {
|
|
alert('设置功能开发中...');
|
|
}
|
|
|
|
// 显示状态
|
|
async function showStatus() {
|
|
await loadStatus();
|
|
alert('状态已刷新!');
|
|
}
|
|
|
|
// 显示模型列表
|
|
async function showModels() {
|
|
try {
|
|
const response = await fetch('/v1/models');
|
|
const data = await response.json();
|
|
|
|
const modelList = data.data.map(m => m.id).join('\n');
|
|
alert('可用模型:\n\n' + modelList);
|
|
} catch (error) {
|
|
alert('获取模型列表失败: ' + error.message);
|
|
}
|
|
}
|