Files
InfoGenie/InfoGenie-frontend/public/toolbox/AI聊天/script.js
2025-12-13 20:53:50 +08:00

223 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 简单的静态网页调用 GitHub Models API 进行聊天
// 注意:将 token 暴露在前端存在安全风险,仅用于本地演示
const endpoint = "https://models.github.ai/inference";
let apiKey = "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"; // 注意:已硬编码,仅用于本地演示
const chatBox = document.getElementById("chat-box");
const userInput = document.getElementById("user-input");
const sendBtn = document.getElementById("send-btn");
const modelSelect = document.getElementById("model");
// 模型信息映射(中文描述与上下文上限)
const MODEL_INFO = {
"openai/gpt-4.1": {
name: "gpt-4.1",
inputTokens: "1049k",
outputTokens: "33k",
about: "在各方面优于 gpt-4o编码、指令跟随与长上下文理解均有显著提升"
},
"openai/gpt-4.1-mini": {
name: "gpt-4.1-mini",
inputTokens: "1049k",
outputTokens: "33k",
about: "在各方面优于 gpt-4o-mini在编码、指令跟随与长上下文处理上有显著提升"
},
"openai/gpt-4.1-nano": {
name: "gpt-4.1-nano",
inputTokens: "1049k",
outputTokens: "33k",
about: "在编码、指令跟随与长上下文处理上有所提升,同时具备更低延迟与成本"
},
"openai/gpt-4o": {
name: "gpt-4o",
inputTokens: "131k",
outputTokens: "16k",
about: "OpenAI 最先进的多模态 gpt-4o 家族模型,可处理文本与图像输入"
},
"openai/gpt-5": {
name: "gpt-5",
inputTokens: "200k",
outputTokens: "100k",
about: "针对逻辑密集与多步骤任务设计"
},
"deepseek-r1": {
name: "deepseek-r1",
inputTokens: "128k",
outputTokens: "4k",
about: "通过逐步训练过程在推理任务上表现出色,适用于语言、科学推理与代码生成等"
},
"deepseek-v3-0324": {
name: "deepseek-v3-0324",
inputTokens: "128k",
outputTokens: "4k",
about: "相较于 DeepSeek-V3 在关键方面显著提升,包括更强的推理能力、函数调用与代码生成表现"
},
"xai/grok-3": {
name: "grok-3",
inputTokens: "131k",
outputTokens: "4k",
about: "Grok 3 是 xAI 的首发模型,由 Colossus 在超大规模上进行预训练,在金融、医疗和法律等专业领域表现突出。"
},
"xai/grok-3-mini": {
name: "grok-3-mini",
inputTokens: "131k",
outputTokens: "4k",
about: "Grok 3 Mini 是一款轻量级模型,会在答复前进行思考。它针对数学与科学问题进行训练,特别适合逻辑类任务。"
}
};
function renderModelInfo() {
const m = MODEL_INFO[modelSelect.value];
const infoEl = document.getElementById('model-info');
if (!infoEl) return;
if (m) {
infoEl.innerHTML = `<div><span class="name">${m.name}</span> <span class="tokens">上下文 ${m.inputTokens} 输入 · ${m.outputTokens} 输出</span></div><div class="about">简介:${m.about}</div>`;
} else {
infoEl.innerHTML = `<div class="about">未配置该模型的上下文限制信息</div>`;
}
}
// Markdown 解析配置(若库已加载)
if (window.marked) {
marked.setOptions({ gfm: true, breaks: true, headerIds: true, mangle: false });
}
function renderMarkdown(text) {
try {
if (window.marked && window.DOMPurify) {
const html = marked.parse(text || '');
return DOMPurify.sanitize(html);
}
} catch (_) {}
return text || '';
}
function appendMessage(role, text){
const message = document.createElement('div');
message.className = `message ${role}`;
const bubble = document.createElement('div');
bubble.className = 'bubble';
// Markdown 渲染
bubble.innerHTML = renderMarkdown(text);
message.appendChild(bubble);
chatBox.appendChild(message);
chatBox.scrollTop = chatBox.scrollHeight;
}
function appendStreamingMessage(role){
const message = document.createElement('div');
message.className = `message ${role}`;
const bubble = document.createElement('div');
bubble.className = 'bubble';
bubble._mdBuffer = '';
bubble.innerHTML = '';
message.appendChild(bubble);
chatBox.appendChild(message);
chatBox.scrollTop = chatBox.scrollHeight;
return bubble;
}
async function sendMessage(){
const content = userInput.value.trim();
if (!content) return;
appendMessage('user', content);
userInput.value = '';
const model = modelSelect.value;
// 令牌已硬编码(本地演示),如未配置则提示
if (!apiKey) {
appendMessage('assistant', '未配置令牌');
return;
}
sendBtn.disabled = true;
const assistantBubble = appendStreamingMessage('assistant');
try {
const res = await fetch(`${endpoint}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model,
temperature: 1,
top_p: 1,
stream: true,
messages: [
{ role: 'system', content: '' },
{ role: 'user', content }
]
})
});
if (!res.ok) {
const errText = await res.text();
throw new Error(`HTTP ${res.status}: ${errText}`);
}
const reader = res.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
let doneStream = false;
while (!doneStream) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const parts = buffer.split(/\r?\n/);
buffer = parts.pop();
for (const part of parts) {
const line = part.trim();
if (line === '') continue;
if (line.startsWith('data:')) {
const payload = line.slice(5).trim();
if (payload === '[DONE]') {
doneStream = true;
break;
}
try {
const json = JSON.parse(payload);
const delta = json?.choices?.[0]?.delta?.content ?? json?.choices?.[0]?.message?.content ?? '';
if (delta) {
// 累计流式文本并增量渲染 Markdown
assistantBubble._mdBuffer = (assistantBubble._mdBuffer || '') + delta;
const safeHtml = renderMarkdown(assistantBubble._mdBuffer);
assistantBubble.innerHTML = safeHtml;
chatBox.scrollTop = chatBox.scrollHeight;
}
} catch (e) {
// 忽略无法解析的行
}
}
}
}
if (!assistantBubble._mdBuffer || !assistantBubble.textContent) {
assistantBubble.textContent = '(无内容返回)';
}
} catch (err) {
//assistantBubble.textContent = `出错了:${err.message}`;
assistantBubble.textContent = `调用次数过多或者使用人数过多,请稍后再试!`;
} finally {
sendBtn.disabled = false;
}
}
sendBtn.addEventListener('click', sendMessage);
userInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// 切换模型时更新信息面板,初次渲染一次
modelSelect.addEventListener('change', renderModelInfo);
renderModelInfo();