GPT对话UI
项目介绍
一个基于 GPT 的智能对话界面,提供简洁优雅的用户体验。本项目使用纯前端技术栈实现,无需后端服务器即可运行。
功能特点
- 💬 实时对话:支持与 AI 进行实时对话交互
- 🌓 主题切换:支持浅色/深色主题,自动跟随系统设置
- 📝 Markdown 渲染:AI 回复支持 Markdown 格式,包含代码高亮
- 💾 本地存储:对话历史保存在本地,刷新页面不会丢失
- 🔍 智能推荐:每次对话后自动推荐相关问题
- ⌨️ 快捷操作:支持快捷键发送消息,Shift+Enter 换行
- 📊 代码优化:支持代码块语法高亮和一键复制
- 🔧 可配置:支持配置 API 密钥
- 📱 响应式:适配不同屏幕尺寸
- 🎨 字体调节:支持动态调整界面字体大小
快速开始
- 克隆项目到本地
bash
git clone https://gitee.com/anxwefndu/gpt-chat-ui.git
- 打开项目目录
bash
cd gpt-chat-ui
- 在浏览器中打开 index.html 文件即可使用
使用说明
-
首次使用需要配置 API 密钥
- 点击左侧边栏的"配置密钥"按钮
- 输入你的 API 密钥
- 点击保存即可使用
-
基本操作
- 在输入框输入问题后点击发送或按回车键发送
- 使用 Shift + Enter 可以在输入框换行
- 点击推荐问题可以快速发送相关提问
- 可以通过左侧的滑块调整字体大小
- 点击主题按钮切换深色/浅色模式
-
历史记录
- 所有对话记录会自动保存在本地
- 可以通过"清除历史记录"按钮清空所有对话
- 刷新页面不会丢失历史记录
技术栈
- HTML5
- CSS3 (Tailwind CSS)
- JavaScript
- Marked.js (Markdown 渲染)
- Highlight.js (代码高亮)
- Font Awesome (图标)
注意事项
- 本项目需要有效的 API 密钥才能正常使用
- 建议使用现代浏览器访问以获得最佳体验
- 所有数据均存储在本地,清除浏览器数据会导致历史记录丢失
- 目前文件上传这一块还没来得及加,后续添加上该块内容
源码下载
演示截图
1.系统首页
2.使用说明
3.配色API-key
4.系统首页-暗色
核心代码
index.html
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI 智能助手</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Pacifico&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.tailwindcss.com"></script>
<script src="./resource/marked.min.js"></script>
<link rel="stylesheet" href="./resource/github.min.css">
<script src="./resource/highlight.min.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#4A90E2',
secondary: '#E3F2FD',
'primary-dark': '#2B4C7E',
'text-dark': '#E2E8F0' // 暗色主题下的主要文字颜色
},
borderRadius: {
'none': '0px',
'sm': '2px',
DEFAULT: '4px',
'md': '8px',
'lg': '12px',
'xl': '16px',
'2xl': '20px',
'3xl': '24px',
'full': '9999px',
'button': '4px'
}
}
},
darkMode: 'class' // 启用暗色模式
}
</script>
<style>
/* 保留原有样式 */
.message-bubble {
max-width: 80%;
margin: 8px 0;
padding: 12px 16px;
border-radius: 12px;
width: fit-content;
}
.user-message {
background-color: #E3F2FD;
margin-left: auto;
border-top-right-radius: 4px;
}
.ai-message {
background-color: #F5F5F5;
margin-right: auto;
border-top-left-radius: 4px;
}
/* 自定义滚动条样式 */
.custom-scrollbar::-webkit-scrollbar {
width: 8px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 暗色主题样式 */
.dark .message-bubble.ai-message {
background-color: #2D3748;
}
.dark .message-bubble.user-message {
background-color: #2B4C7E;
}
.dark .custom-scrollbar::-webkit-scrollbar-track {
background: #1A202C;
}
.dark .custom-scrollbar::-webkit-scrollbar-thumb {
background: #4A5568;
}
.dark .custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #718096;
}
input[type="range"] {
appearance: none;
width: 100%;
height: 4px;
background: #E5E7EB;
border-radius: 2px;
}
input[type="range"]::-webkit-slider-thumb {
appearance: none;
width: 16px;
height: 16px;
background: #4A90E2;
border-radius: 50%;
cursor: pointer;
}
/* 添加 Markdown 样式 */
.markdown-body {
font-size: 1em;
line-height: 1.6;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 1.5em;
margin-bottom: 0.5em;
font-weight: 600;
}
.markdown-body code {
color: #0f172a;
padding: 0.2em 0.4em;
font-size: 0.9em;
background-color: rgba(175, 184, 193, 0.2);
border-radius: 6px;
}
.markdown-body pre {
padding: 16px;
overflow: auto;
border-radius: 6px;
background-color: #f6f8fa;
margin: 1em 0;
}
.markdown-body pre code {
padding: 0;
background-color: #f6f8fa;
white-space: pre;
font-size: 0.95em;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.dark .markdown-body pre {
background-color: #f6f8fa;
}
/* 代码块内的复制按钮样式 */
.copy-button {
position: absolute;
top: 8px;
right: 8px;
padding: 4px 8px;
font-size: 12px;
color: #64748b;
background-color: #f1f5f9;
border: 1px solid #e2e8f0;
border-radius: 4px;
opacity: 0;
transition: all 0.2s ease;
}
.copy-button:hover {
color: #0f172a;
background-color: #e2e8f0;
border-color: #cbd5e1;
}
/* 暗色主题下的复制按钮 */
.dark .copy-button {
color: #94a3b8;
background-color: #1e293b;
border-color: #334155;
}
.dark .copy-button:hover {
color: #f1f5f9;
background-color: #334155;
border-color: #475569;
}
/* 鼠标悬停在代码块上时显示复制按钮 */
.markdown-body pre:hover .copy-button {
opacity: 1;
}
/* 代码高亮主题颜色优化 */
.markdown-body code {
padding: 0.2em 0.4em;
font-size: 0.95em;
background-color: #f1f5f9;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.markdown-body ul,
.markdown-body ol {
padding-left: 2em;
margin: 1em 0;
}
.markdown-body li {
margin: 0.5em 0;
}
.markdown-body p {
margin: 1em 0;
}
.markdown-body blockquote {
padding: 0 1em;
color: #57606a;
border-left: 0.25em solid #d0d7de;
margin: 1em 0;
}
.dark .markdown-body blockquote {
color: #8b949e;
border-left-color: #3b434b;
}
.markdown-body table {
border-collapse: collapse;
margin: 1em 0;
width: 100%;
}
.markdown-body table th,
.markdown-body table td {
padding: 6px 13px;
border: 1px solid #d0d7de;
}
.dark .markdown-body table th,
.dark .markdown-body table td {
border-color: #3b434b;
}
.markdown-body table tr:nth-child(2n) {
background-color: #f6f8fa;
}
.dark .markdown-body table tr:nth-child(2n) {
background-color: #161b22;
}
</style>
</head>
<body class="bg-white dark:bg-gray-900 transition-colors">
<div class="flex h-screen">
<!-- 修改侧边栏背景色,使其与主内容区一致 -->
<aside class="w-64 bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-700 flex flex-col">
<div class="p-6 border-b border-gray-200">
<h1 class="text-2xl font-['Pacifico'] text-primary">logo</h1>
</div>
<nav class="flex-1 p-6 space-y-6">
<div class="space-y-2">
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">主题模式</label>
<button
onclick="toggleTheme()"
class="flex items-center justify-between w-full px-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-700 rounded-button hover:bg-gray-100 dark:hover:bg-gray-600">
<span id="themeText">浅色模式</span>
<i class="fas fa-sun dark:fa-moon text-primary"></i>
</button>
</div>
<div class="space-y-2">
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">字体大小</label>
<div class="flex items-center space-x-2">
<span class="text-xs text-gray-500 dark:text-gray-400">小</span>
<input type="range" min="1" max="3" value="2" class="w-full">
<span class="text-xs text-gray-500 dark:text-gray-400">大</span>
</div>
</div>
<button
class="flex items-center w-full px-4 py-2 text-sm text-white bg-primary dark:bg-primary-dark rounded-button hover:bg-primary/90 dark:hover:bg-primary-dark/80 whitespace-nowrap">
<i class="fas fa-trash-alt mr-2"></i>
清除历史记录
</button>
<button onclick="showHelp()"
class="flex items-center w-full px-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-800 rounded-button hover:bg-gray-100 dark:hover:bg-gray-700 whitespace-nowrap">
<i class="fas fa-question-circle mr-2"></i>
使用帮助
</button>
<button onclick="showConfig()"
class="flex items-center w-full px-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-800 rounded-button hover:bg-gray-100 dark:hover:bg-gray-700 whitespace-nowrap">
<i class="fas fa-cog mr-2"></i>
配置密钥
</button>
</nav>
</aside>
<main class="flex-1 flex flex-col">
<div class="flex-1 overflow-y-auto p-6 space-y-4 custom-scrollbar"></div>
<div class="border-t border-gray-200 dark:border-gray-700 p-6">
<div class="relative">
<textarea
class="w-full h-24 px-4 py-3 text-gray-700 dark:text-text-dark border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 rounded-lg focus:outline-none focus:border-primary resize-none"
placeholder="输入你的问题..."></textarea>
<div class="absolute bottom-3 right-3 flex space-x-2">
<button class="p-2 text-gray-400 hover:text-primary dark:hover:text-primary">
<i class="fas fa-paperclip"></i>
</button>
<button class="px-4 py-2 bg-primary dark:bg-primary-dark text-white rounded-button hover:bg-primary/90 dark:hover:bg-primary-dark/80 whitespace-nowrap">
发送 <i class="fas fa-paper-plane ml-2"></i>
</button>
</div>
</div>
<div class="mt-2 flex flex-wrap gap-2">
<span class="px-3 py-1 text-sm text-primary dark:text-gray-300 bg-secondary dark:bg-gray-800 rounded-full cursor-pointer hover:bg-primary hover:text-white dark:hover:bg-primary-dark">
如何优化代码?
</span>
<span class="px-3 py-1 text-sm text-primary bg-secondary dark:bg-gray-700 rounded-full cursor-pointer hover:bg-primary hover:text-white">
写一篇营销文案
</span>
<span class="px-3 py-1 text-sm text-primary bg-secondary dark:bg-gray-700 rounded-full cursor-pointer hover:bg-primary hover:text-white">
数据分析方法
</span>
</div>
</div>
</main>
</div>
<script>
function toggleTheme() {
const html = document.documentElement;
const themeText = document.getElementById('themeText');
const isDark = html.classList.toggle('dark');
themeText.textContent = isDark ? '深色模式' : '浅色模式';
localStorage.setItem('theme', isDark ? 'dark' : 'light');
}
// 初始化主题
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
document.getElementById('themeText').textContent = '深色模式';
}
function callGpt(questionText, onProgress, onDone) {
const sk = loadSK();
// 获取历史消息记录
const messages = loadMessages();
// 构建消息历史
const messageHistory = messages.map(msg => ({
role: msg.role === 'user' ? 'user' : 'assistant',
content: msg.content
}));
// 添加当前问题
messageHistory.push({
role: 'user',
content: questionText
});
fetch("https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions", {
method: "post",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${sk}`
},
body: JSON.stringify({
model: "qwen-plus",
messages: messageHistory, // 使用完整的消息历史
stream: true
}),
}).then(response => {
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let fullText = "";
function read() {
reader.read().then(({done, value}) => {
if (done) {
onDone(fullText);
return;
}
const text = decoder.decode(value, {stream: true});
const chunks = text.replace("data: [DONE]", "").replace("data: ", "").split("data: ");
for (let i = 0; i < chunks.length; i++) {
try {
const obj = JSON.parse(chunks[i]);
const choices = obj.choices;
const delta = choices[0].delta;
fullText += delta.content;
// 调用回调函数,实时更新内容
onProgress(fullText);
} catch (e) {
console.log(chunks[i])
read();
}
}
read();
}).catch(error => {
console.log(error);
alert("工具处理出错");
});
}
read();
}).catch(error => {
console.log(error);
alert("工具处理出错:" + error);
});
}
// 消息记录管理
const MESSAGE_STORAGE_KEY = 'chat_messages';
function loadMessages() {
const stored = localStorage.getItem(MESSAGE_STORAGE_KEY);
return stored ? JSON.parse(stored) : [];
}
function saveMessages(messages) {
localStorage.setItem(MESSAGE_STORAGE_KEY, JSON.stringify(messages));
}
function appendMessage(role, content) {
const messages = loadMessages();
messages.push({
role,
content,
timestamp: new Date().toLocaleTimeString('zh', { hour: '2-digit', minute: '2-digit' })
});
saveMessages(messages);
renderMessages();
}
function clearMessages() {
localStorage.removeItem(MESSAGE_STORAGE_KEY);
renderMessages();
}
// 配置 marked 选项
marked.setOptions({
highlight: function(code, lang) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value;
}
return code;
},
breaks: true,
gfm: true
});
function renderMessages() {
const messagesContainer = document.querySelector('.custom-scrollbar');
const messages = loadMessages();
messagesContainer.innerHTML = messages.map(msg => `
<div class="message-bubble ${msg.role === 'user' ? 'user-message' : 'ai-message'}">
<div class="text-gray-800 dark:text-text-dark markdown-body">
${msg.role === 'user' ? msg.content : marked.parse(msg.content)}
</div>
<span class="text-xs text-gray-400 dark:text-gray-500 mt-1 block">${msg.timestamp}</span>
</div>
`).join('');
// 处理代码块的高亮和复制功能
const codeBlocks = messagesContainer.querySelectorAll('pre code');
codeBlocks.forEach(block => {
// 应用代码高亮
hljs.highlightElement(block);
// 添加复制按钮
const copyButton = document.createElement('button');
copyButton.className = 'copy-button';
copyButton.innerHTML = '<i class="fas fa-copy mr-1"></i>复制';
copyButton.onclick = async () => {
try {
await navigator.clipboard.writeText(block.textContent);
copyButton.innerHTML = '<i class="fas fa-check mr-1"></i>已复制';
setTimeout(() => {
copyButton.innerHTML = '<i class="fas fa-copy mr-1"></i>复制';
}, 2000);
} catch (err) {
console.error('复制失败:', err);
}
};
// 为代码块容器添加相对定位
const preBlock = block.parentElement;
preBlock.style.position = 'relative';
preBlock.appendChild(copyButton);
});
// 滚动到底部
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// 发送消息
function sendMessage() {
const textarea = document.querySelector('textarea');
const content = textarea.value.trim();
if (!content) return;
const sk = loadSK();
if (!sk) {
alert('请先配置 API 密钥');
showConfig();
return;
}
// 添加用户消息
appendMessage('user', content);
textarea.value = '';
// 调用 GPT
callGpt(content, (text) => {
const messages = loadMessages();
if (messages[messages.length - 1].role === 'assistant') {
messages[messages.length - 1].content = text;
} else {
messages.push({
role: 'assistant',
content: text,
timestamp: new Date().toLocaleTimeString('zh', {hour: '2-digit', minute: '2-digit'})
});
}
saveMessages(messages);
renderMessages();
}, (finalText) => {
console.log('回复完成:', finalText);
renderSuggestion();
});
}
// 事件处理
document.addEventListener('DOMContentLoaded', () => {
const textarea = document.querySelector('textarea');
const sendButton = document.querySelector('button:has(.fa-paper-plane)');
const clearButton = document.querySelector('button:has(.fa-trash-alt)');
const suggestionSpans = document.querySelectorAll('.mt-2.flex.flex-wrap.gap-2 span');
// 绑定事件
sendButton.addEventListener('click', sendMessage);
textarea.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// 快捷提问
suggestionSpans.forEach(span => {
span.addEventListener('click', () => {
textarea.value = span.textContent.trim();
sendMessage();
});
});
// 初始化消息记录
renderMessages();
// 修改清除按钮事件
clearButton.addEventListener('click', confirmClear);
renderSuggestion();
});
function showHelp() {
const helpContent = `
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg max-w-2xl mx-auto" style="z-index: 999">
<h2 class="text-xl font-bold text-gray-900 dark:text-text-dark mb-4">使用说明</h2>
<div class="space-y-3 text-gray-700 dark:text-text-dark">
<p>1. 在输入框中输入您的问题,点击发送按钮或按回车键发送</p>
<p>2. 支持快捷提问,点击下方预设的问题直接发送</p>
<p>3. 可以使用 Shift + Enter 在输入框中换行</p>
<p>4. 支持深色/浅色主题切换,可根据个人喜好选择</p>
<p>5. 支持调整字体大小,通过滑块可以设置小、中、大三种字号</p>
<p>6. 所有对话记录会保存在本地,刷新页面不会丢失</p>
<p>7. 清除历史记录会删除所有本地保存的对话</p>
<p>8. 需要配置 API 密钥才能使用对话功能</p>
<p>9. 每次对话完成后会自动推荐相关的后续问题</p>
<p>10. 支持实时显示 AI 回复内容</p>
</div>
<button onclick="closeHelp()" class="mt-6 px-4 py-2 bg-primary dark:bg-primary-dark text-white rounded-button hover:bg-primary/90 dark:hover:bg-primary-dark/80" style=" margin-left: auto; display: flex; justify-content: right">
知道了
</button>
</div>
<div class="fixed inset-0 bg-black bg-opacity-50 z-40" onclick="closeHelp()"></div>
`;
const helpDiv = document.createElement('div');
helpDiv.id = 'helpModal';
helpDiv.className = 'fixed inset-0 flex items-center justify-center z-50';
helpDiv.innerHTML = helpContent;
document.body.appendChild(helpDiv);
}
function closeHelp() {
const helpModal = document.getElementById('helpModal');
if (helpModal) {
helpModal.remove();
}
}
function confirmClear() {
const confirmContent = `
<div class="bg-white s dark:bg-gray-800 p-6 rounded-lg max-w-md mx-auto" style="z-index: 999">
<h2 class="text-xl font-bold text-gray-900 dark:text-text-dark mb-4">确认清除</h2>
<p class="text-gray-700 dark:text-text-dark">确定要清除所有对话记录吗?此操作不可恢复。</p>
<div class="mt-6 flex space-x-4" style="display: flex; justify-content: right">
<button onclick="closeConfirm()" class="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-button hover:bg-gray-200 dark:hover:bg-gray-600">
取消
</button>
<button onclick="clearAndClose()" class="px-4 py-2 bg-red-500 text-white rounded-button hover:bg-red-600">
确认清除
</button>
</div>
</div>
<div class="fixed inset-0 bg-black bg-opacity-50 z-40" onclick="closeConfirm()"></div>
`;
const confirmDiv = document.createElement('div');
confirmDiv.id = 'confirmModal';
confirmDiv.className = 'fixed inset-0 flex items-center justify-center z-50';
confirmDiv.innerHTML = confirmContent;
document.body.appendChild(confirmDiv);
}
function closeConfirm() {
const confirmModal = document.getElementById('confirmModal');
if (confirmModal) {
confirmModal.remove();
}
}
function clearAndClose() {
clearMessages();
closeConfirm();
}
function renderSuggestion() {
const messages = loadMessages();
// 获取最近的对话内容
if (messages.length > 0) {
const recentMessages = messages.slice(-3).map(msg => msg.content).join('\n');
getSuggestions(recentMessages, (suggestions) => {
const suggestionContainer = document.querySelector('.mt-2.flex.flex-wrap.gap-2');
suggestionContainer.innerHTML = suggestions.map(suggestion => `
<span class="px-3 py-1 text-sm text-primary dark:text-gray-300 bg-secondary dark:bg-gray-800 rounded-full cursor-pointer hover:bg-primary hover:text-white dark:hover:bg-primary-dark">
${suggestion}
</span>
`).join('');
// 重新绑定事件
const suggestionSpans = document.querySelectorAll('.mt-2.flex.flex-wrap.gap-2 span');
suggestionSpans.forEach(span => {
span.addEventListener('click', () => {
document.querySelector('textarea').value = span.textContent.trim();
sendMessage();
});
});
});
}
}
function getSuggestions(context, callback) {
const sk = loadSK();
if (!sk) {
callback(['如何继续优化这个方案?', '有什么相关的最佳实践?', '还有其他方面需要考虑吗?']);
return;
}
fetch("https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions", {
method: "post",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${sk}`
},
body: JSON.stringify({
model: "qwen-plus",
messages: [
{
role: "user",
content: `基于以下对话内容,推荐3个相关的后续提问,直接返回3个问题,用|||分隔:\n${context}`
}
],
stream: false
}),
}).then(response => response.json()).then(data => {
const suggestions = data.choices[0].message.content.split('|||').map(q => q.trim());
callback(suggestions);
}).catch(error => {
console.error('获取建议问题失败:', error);
// 发生错误时使用默认建议
callback(['如何继续优化这个方案?', '有什么相关的最佳实践?', '还有其他方面需要考虑吗?']);
});
}
const SK_STORAGE_KEY = 'chat_sk';
function loadSK() {
return localStorage.getItem(SK_STORAGE_KEY) || '';
}
function saveSK(sk) {
localStorage.setItem(SK_STORAGE_KEY, sk);
}
function showConfig() {
const currentSK = loadSK();
const configContent = `
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg max-w-md mx-auto" style="z-index: 999; min-width: 500px">
<h2 class="text-xl font-bold text-gray-900 dark:text-text-dark mb-4">配置 API 密钥</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">API 密钥 (SK)</label>
<input type="password" id="skInput" value="${currentSK}"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary dark:bg-gray-700 dark:text-white"
placeholder="请输入你的 API 密钥">
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
请妥善保管你的 API 密钥,不要泄露给他人。
</div>
</div>
<div class="mt-6 flex justify-end space-x-4">
<button onclick="closeConfig()" class="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-button hover:bg-gray-200 dark:hover:bg-gray-600">
取消
</button>
<button onclick="saveConfig()" class="px-4 py-2 bg-primary dark:bg-primary-dark text-white rounded-button hover:bg-primary/90 dark:hover:bg-primary-dark/80">
保存
</button>
</div>
</div>
<div class="fixed inset-0 bg-black bg-opacity-50 z-40" onclick="closeConfig()"></div>
`;
const configDiv = document.createElement('div');
configDiv.id = 'configModal';
configDiv.className = 'fixed inset-0 flex items-center justify-center z-50';
configDiv.innerHTML = configContent;
document.body.appendChild(configDiv);
}
function closeConfig() {
const configModal = document.getElementById('configModal');
if (configModal) {
configModal.remove();
}
}
function saveConfig() {
const skInput = document.getElementById('skInput');
const sk = skInput.value.trim();
if (!sk) {
alert('请输入有效的 API 密钥');
return;
}
saveSK(sk);
closeConfig();
}
const FONT_SIZE_KEY = 'chat_font_size';
function loadFontSize() {
return localStorage.getItem(FONT_SIZE_KEY) || '2';
}
function saveFontSize(size) {
localStorage.setItem(FONT_SIZE_KEY, size);
}
function updateFontSize(size) {
const html = document.documentElement;
switch(size) {
case '1':
html.style.fontSize = '14px';
break;
case '2':
html.style.fontSize = '16px';
break;
case '3':
html.style.fontSize = '18px';
break;
}
}
document.addEventListener('DOMContentLoaded', () => {
// 初始化字体大小
const fontSizeSlider = document.querySelector('input[type="range"]');
fontSizeSlider.value = loadFontSize();
updateFontSize(fontSizeSlider.value);
// 监听字体大小变化
fontSizeSlider.addEventListener('change', (e) => {
const size = e.target.value;
saveFontSize(size);
updateFontSize(size);
});
});
</script>
</body>
</html>