不知名提交
This commit is contained in:
42
InfoGenie-frontend/public/toolbox/AI聊天/index.html
Normal file
42
InfoGenie-frontend/public/toolbox/AI聊天/index.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<!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>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="chat-container">
|
||||
<div class="warning-banner" role="alert">⚠️ 重要提示:本AI聊天功能为免费体验服务,旨在展示不同大语言模型的交互能力。请注意:1.不支持上下文记忆功能;2.每分钟调用次数有限制;3.使用人数过多会出现调用失败情况</div>
|
||||
<div class="chat-header">
|
||||
<h2>AI 聊天</h2>
|
||||
<div class="model-selector">
|
||||
<label for="model">选择模型: </label>
|
||||
<select id="model">
|
||||
<option value="openai/gpt-4.1">gpt-4.1</option>
|
||||
<option value="openai/gpt-4.1-mini">gpt-4.1-mini</option>
|
||||
<option value="openai/gpt-4.1-nano">gpt-4.1-nano</option>
|
||||
<option value="openai/gpt-4o">gpt-4o</option>
|
||||
<option value="openai/gpt-5">gpt-5</option>
|
||||
<option value="deepseek-r1">deepseek-r1</option>
|
||||
<option value="deepseek-v3-0324">deepseek-v3-0324</option>
|
||||
<option value="xai/grok-3">grok-3</option>
|
||||
<option value="xai/grok-3-mini">grok-3-mini</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="model-info" class="model-info"></div>
|
||||
<div class="chat-box" id="chat-box">
|
||||
<!-- 消息将在这里显示 -->
|
||||
</div>
|
||||
<div class="chat-input">
|
||||
<input type="text" id="user-input" placeholder="输入你的消息...">
|
||||
<button id="send-btn">发送</button>
|
||||
</div>
|
||||
</div>
|
||||
<script src="marked.min.js"></script>
|
||||
<script src="purify.min.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
69
InfoGenie-frontend/public/toolbox/AI聊天/marked.min.js
vendored
Normal file
69
InfoGenie-frontend/public/toolbox/AI聊天/marked.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
3
InfoGenie-frontend/public/toolbox/AI聊天/purify.min.js
vendored
Normal file
3
InfoGenie-frontend/public/toolbox/AI聊天/purify.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
223
InfoGenie-frontend/public/toolbox/AI聊天/script.js
Normal file
223
InfoGenie-frontend/public/toolbox/AI聊天/script.js
Normal file
@@ -0,0 +1,223 @@
|
||||
// 简单的静态网页调用 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();
|
||||
172
InfoGenie-frontend/public/toolbox/AI聊天/style.css
Normal file
172
InfoGenie-frontend/public/toolbox/AI聊天/style.css
Normal file
@@ -0,0 +1,172 @@
|
||||
/* 淡绿色淡黄绿色渐变清新柔和风格,移动端适配 */
|
||||
:root {
|
||||
--bg-start: #d9f7be; /* 淡绿 */
|
||||
--bg-end: #f4f9d2; /* 淡黄绿 */
|
||||
--primary: #4caf50; /* 绿色强调 */
|
||||
--secondary: #8bc34a;
|
||||
--text: #2f3b2f;
|
||||
--muted: #6b7a6b;
|
||||
--white: #ffffff;
|
||||
--shadow: rgba(76, 175, 80, 0.2);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: linear-gradient(135deg, var(--bg-start), var(--bg-end));
|
||||
color: var(--text);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
width: 100%;
|
||||
max-width: 720px;
|
||||
background: var(--white);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 30px var(--shadow);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部警示通知样式 */
|
||||
.warning-banner {
|
||||
padding: 10px 16px;
|
||||
background: #fff8d6; /* 柔和黄色背景 */
|
||||
border: 1px solid #ffec99; /* 黄色边框 */
|
||||
border-left: 4px solid #faad14; /* 左侧强调条 */
|
||||
color: #5c4a00; /* 深色文字保证可读性 */
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.warning-banner { font-size: 13px; padding: 8px 12px; }
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
background: linear-gradient(135deg, #eafbe6, #f9ffe6);
|
||||
border-bottom: 1px solid #e1f3d8;
|
||||
}
|
||||
|
||||
.chat-header h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.model-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.model-selector select {
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #cfe8c9;
|
||||
border-radius: 8px;
|
||||
background: #f7fff2;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.chat-box {
|
||||
height: 60vh;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
background: #fbfff5;
|
||||
}
|
||||
|
||||
.message { display: flex; margin-bottom: 12px; gap: 8px; }
|
||||
.message .bubble {
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
max-width: 85%;
|
||||
box-shadow: 0 2px 10px var(--shadow);
|
||||
}
|
||||
.message.user .bubble {
|
||||
background: #e2f7d8;
|
||||
align-self: flex-end;
|
||||
}
|
||||
.message.assistant .bubble {
|
||||
background: #fffef0;
|
||||
}
|
||||
.message.user { justify-content: flex-end; }
|
||||
.message.assistant { justify-content: flex-start; }
|
||||
|
||||
.chat-input {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
border-top: 1px solid #e1f3d8;
|
||||
background: #fafff0;
|
||||
}
|
||||
|
||||
#user-input {
|
||||
flex: 1;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #cfe8c9;
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
#send-btn {
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, var(--secondary), var(--primary));
|
||||
color: var(--white);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
#send-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||
|
||||
/* 模型信息面板样式 */
|
||||
.model-info {
|
||||
padding: 10px 16px;
|
||||
background: #f9fff0;
|
||||
border-bottom: 1px solid #e1f3d8;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
.model-info .name { color: var(--primary); font-weight: 600; }
|
||||
.model-info .tokens { color: var(--text); font-weight: 600; margin-left: 8px; }
|
||||
.model-info .about { margin-top: 6px; line-height: 1.5; }
|
||||
|
||||
/* 移动端优化 */
|
||||
@media (max-width: 480px) {
|
||||
.chat-box { height: 62vh; }
|
||||
#user-input { font-size: 15px; }
|
||||
#send-btn { padding: 10px 14px; }
|
||||
.model-info { font-size: 13px; padding: 8px 12px; }
|
||||
}
|
||||
|
||||
/* 全局隐藏滚动条,保留滚动效果 */
|
||||
html, body {
|
||||
-ms-overflow-style: none; /* IE/旧版 Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
html::-webkit-scrollbar,
|
||||
body::-webkit-scrollbar { display: none; } /* Chrome/Safari/新 Edge */
|
||||
|
||||
/* 隐藏所有元素滚动条(覆盖常见浏览器) */
|
||||
* {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
*::-webkit-scrollbar { display: none; }
|
||||
|
||||
/* 保持聊天框可滚动并优化移动端滚动体验 */
|
||||
.chat-box { -webkit-overflow-scrolling: touch; }
|
||||
|
||||
/* 代码块允许横向滚动但隐藏滚动条 */
|
||||
.message .bubble pre { overflow-x: auto; }
|
||||
.message .bubble pre { scrollbar-width: none; -ms-overflow-style: none; }
|
||||
.message .bubble pre::-webkit-scrollbar { display: none; }
|
||||
Reference in New Issue
Block a user