Spaces:
Sleeping
Sleeping
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Custom-GPT</title> | |
<link href="{{ url_for('static', filename='css/output.css') }}" rel="stylesheet"> | |
<link href="{{ url_for('static', filename='css/dracula.css') }}" rel="stylesheet"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/highlight.min.js"></script> | |
<script src="{{ url_for('static', filename='js/clipboard.min.js') }}"></script> | |
<script src="{{ url_for('static', filename='js/markdown-it.min.js') }}"></script> | |
<script src="{{ url_for('static', filename='js/markdown-renderer.js') }}"></script> | |
<style> | |
.left-controls { | |
position: absolute; | |
left: 20px; | |
top: 76px; | |
width: 250px; | |
} | |
.code-block { | |
margin-top: 0px; | |
} | |
.chat-container { | |
position: relative; | |
max-width: 3xl; | |
margin: 0 auto; | |
height: calc(100vh - 40px); | |
display: flex; | |
flex-direction: column; | |
} | |
#chat-history { | |
flex-grow: 1; | |
overflow-y: auto; | |
} | |
#user-input { | |
min-height: 80px; | |
max-height: 120px; | |
resize: none; | |
overflow-y: auto; | |
} | |
#user-input::-webkit-scrollbar, | |
#chat-history::-webkit-scrollbar { | |
display: none; | |
} | |
#chat-history { | |
max-width: 100%; | |
word-wrap: break-word; | |
overflow-wrap: break-word; | |
} | |
#chat-history pre { | |
max-width: 100%; | |
white-space: pre-wrap; | |
overflow-x: auto; | |
} | |
#chat-history code { | |
white-space: pre-wrap; | |
word-break: break-all; | |
} | |
.code-language { | |
font-weight: bold; | |
} | |
</style> | |
</head> | |
<body class="bg-gradient-to-br from-purple-100 to-blue-100 min-h-screen font-['Inter']"> | |
<div class="left-controls container"> | |
<div class="item"> | |
<select id="preset-selector" | |
class="p-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-300 focus:outline-none"> | |
</select> | |
</div> | |
<div class="item"> | |
<button id="add-preset-btn" | |
class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg transition duration-300 ease-in-out"> | |
添加新预设 | |
</button> | |
</div> | |
<div class="item"> | |
<button id="delete-preset-btn" | |
class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg transition duration-300 ease-in-out"> | |
删除该预设 | |
</button> | |
</div> | |
</div> | |
<div class="flex justify-center items-start min-h-screen"> | |
<div class="chat-container container p-4 max-w-3xl flex flex-col h-screen"> | |
<h1 class="text-3xl font-bold mb-6 text-center text-gray-800">Custom-GPT</h1> | |
<div id="chat-history" class="bg-white p-6 rounded-xl shadow-lg mb-6 flex-grow overflow-y-auto"> | |
</div> | |
<div class="flex shadow-lg rounded-xl overflow-hidden min-h-[80px]"> | |
<textarea id="user-input" | |
class="flex-grow p-4 border-none focus:ring-2 focus:ring-blue-300 focus:outline-none resize-y" | |
placeholder="输入您的消息..."></textarea> | |
<div class="flex items-center px-2 bg-white"> | |
<button id="send-btn" | |
class="bg-blue-500 hover:bg-blue-600 text-white w-12 h-12 rounded-lg transition duration-300 ease-in-out flex items-center justify-center"> | |
发送 | |
</button> | |
</div> | |
</div> | |
<button id="clear-btn" | |
class="mt-6 bg-red-500 hover:bg-red-600 text-white p-3 rounded-xl w-full transition duration-300 ease-in-out"> | |
清除聊天记录 | |
</button> | |
</div> | |
</div> | |
<script> | |
const defaultPresets = ["默认", "文章润色", "翻译", "辩论"]; | |
const chatHistory = document.getElementById('chat-history'); | |
const userInput = document.getElementById('user-input'); | |
const sendBtn = document.getElementById('send-btn'); | |
const clearBtn = document.getElementById('clear-btn'); | |
const presetSelector = document.getElementById('preset-selector'); | |
const addPresetBtn = document.getElementById('add-preset-btn'); | |
const deletePresetBtn = document.getElementById('delete-preset-btn'); | |
let history = []; | |
let currentUserMessage = []; | |
// Load presets | |
async function loadPresets() { | |
const response = await fetch('/presets'); | |
const customPresets = await response.json(); | |
customPresets.forEach(preset => { | |
const option = document.createElement('option'); | |
option.value = preset.id; | |
option.textContent = preset.name; | |
presetSelector.appendChild(option); | |
}); | |
} | |
// Add custom preset | |
addPresetBtn.addEventListener('click', async () => { | |
const presetName = prompt('输入预设名字:'); | |
const presetContent = prompt('输入预设内容:'); | |
if (presetName && presetContent) { | |
const response = await fetch('/add_preset', { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json' }, | |
body: JSON.stringify({ name: presetName, content: presetContent }) | |
}); | |
const result = await response.json(); | |
if (result.status === 'success') { | |
const option = document.createElement('option'); | |
option.value = result.id; | |
option.textContent = presetName; | |
presetSelector.appendChild(option); | |
} | |
} | |
}); | |
deletePresetBtn.addEventListener('click', async () => { | |
const selectedPreset = presetSelector.value; | |
if (selectedPreset && !defaultPresets.includes(selectedPreset)) { | |
const response = await fetch('/delete_preset', { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json' }, | |
body: JSON.stringify({ id: selectedPreset }) | |
}); | |
const result = await response.json(); | |
if (result.status === 'success') { | |
presetSelector.removeChild(presetSelector.querySelector(`option[value="${selectedPreset}"]`)); | |
} | |
} | |
}); | |
// 页面加载时获取历史记录 | |
async function loadHistory() { | |
try { | |
const response = await fetch('/history'); | |
const data = await response.json(); | |
history = data; | |
// 显示历史消息 | |
history.forEach(msg => { | |
addMessage(msg.role, msg.parts); | |
}); | |
} catch (error) { | |
console.error('Failed to load chat history:', error); | |
} | |
} | |
// 页面加载完成后立即加载历史记录 | |
document.addEventListener('DOMContentLoaded', () => { | |
loadHistory(); | |
loadPresets(); | |
}); | |
async function uploadFile(file) { | |
const formData = new FormData(); | |
formData.append('file', file); | |
try { | |
const response = await fetch('/upload', { | |
method: 'POST', | |
body: formData | |
}); | |
const result = await response.json(); | |
if (result.success) { | |
currentUserMessage.push(result.gemini_uri); | |
// 添加文件预览 | |
} | |
} catch (error) { | |
console.error('Upload failed:', error); | |
} | |
} | |
function addMessage(role, parts) { | |
const messageDiv = document.createElement('div'); | |
messageDiv.className = `mb-4 ${role === 'user' ? 'text-right' : 'text-left'} w-full`; | |
console.log(parts); | |
const messageContent = ` | |
<div class="inline-block break-words"> | |
<span class="prose inline-block w-full p-3 rounded-lg ${role === 'user' | |
? 'bg-gradient-to-b from-blue-400 via-blue-500 to-blue-600 text-white rounded-br-none' | |
: 'bg-gradient-to-b from-yellow-100 via-yellow-200 to-yellow-300 text-gray-800 rounded-bl-none' | |
}">${role === 'user' ? parts[parts.length - 1] : MarkdownRenderer.render(parts[parts.length - 1])}</span> | |
</div> | |
`; | |
messageDiv.innerHTML = messageContent; | |
chatHistory.appendChild(messageDiv); | |
chatHistory.scrollTop = chatHistory.scrollHeight; | |
} | |
function adjustTextareaHeight() { | |
userInput.style.height = 'auto'; | |
userInput.style.height = (userInput.scrollHeight) + 'px'; | |
} | |
// Event listener for input changes | |
userInput.addEventListener('input', adjustTextareaHeight); | |
// Event listener for keydown events | |
userInput.addEventListener('keydown', (e) => { | |
if (e.key === 'Enter' && !e.shiftKey) { | |
e.preventDefault(); | |
sendMessage(); | |
} | |
}); | |
userInput.addEventListener('paste', async function (e) { | |
const items = e.clipboardData.items; | |
for (let item of items) { | |
if (item.kind === 'file') { | |
const file = item.getAsFile(); | |
await uploadFile(file); | |
} | |
} | |
}); | |
// Modify sendMessage function | |
async function sendMessage() { | |
// 打字机效果的配置参数 | |
const TYPING_CONFIG = { | |
SPEED: 10, // 打字速度(毫秒/字符) | |
CHUNK_SIZE: 4, // 每次打字的字符数 | |
SCROLL_BEHAVIOR: 'smooth' // 滚动行为:'smooth' 或 'auto' | |
}; | |
currentUserMessage.push(userInput.value.trim()); | |
const Message = {role: 'user', parts: currentUserMessage } | |
console.log("Sending message:", Message); | |
if (!currentUserMessage.length) return; | |
history.push(Message); | |
addMessage('user', currentUserMessage); | |
userInput.value = ''; | |
currentUserMessage = []; | |
adjustTextareaHeight(); | |
const selectedPreset = presetSelector.value; | |
try { | |
const response = await fetch('/chat', { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json' }, | |
body: JSON.stringify({ userMessage: Message, preset: selectedPreset }) | |
}); | |
const reader = response.body.getReader(); | |
const decoder = new TextDecoder(); | |
let aiResponse = ''; | |
let displayedResponse = ''; | |
let aiMessageDiv = null; | |
// 打字效果函数 | |
const typeWriter = (text, index, callback) => { | |
if (index < text.length) { | |
displayedResponse = text.substring(0, index + TYPING_CONFIG.CHUNK_SIZE); if (aiMessageDiv) { | |
aiMessageDiv.innerHTML = `<span | |
class="prose inline-block p-2 rounded-lg bg-gradient-to-b from-green-200 via-green-250 to-green-300"> | |
${MarkdownRenderer.render(displayedResponse)}</span>`; | |
chatHistory.scrollTop = chatHistory.scrollHeight; | |
chatHistory.style.scrollBehavior = TYPING_CONFIG.SCROLL_BEHAVIOR; | |
} | |
setTimeout(() => typeWriter(text, index + TYPING_CONFIG.CHUNK_SIZE, callback), TYPING_CONFIG.SPEED); | |
} else { | |
callback(); | |
} | |
}; | |
while (true) { | |
const { done, value } = await reader.read(); | |
if (done) break; | |
const chunk = decoder.decode(value); | |
aiResponse += chunk; | |
if (!aiMessageDiv) { | |
aiMessageDiv = document.createElement('div'); | |
aiMessageDiv.className = 'mb-2 text-left'; | |
chatHistory.appendChild(aiMessageDiv); | |
} | |
await new Promise(resolve => { | |
typeWriter(aiResponse, displayedResponse.length, resolve); | |
}); | |
} | |
history.push(currentUserMessage); | |
history.push({ role: 'model', part: [aiResponse] }); | |
} catch (error) { | |
console.error('Error sending message:', error); | |
addMessage('model', 'Sorry, something went wrong. Please try again.'); | |
} | |
} | |
// 清除聊天记录 | |
async function clearChat() { | |
try { | |
const response = await fetch('/clear_history', { | |
method: 'POST' | |
}); | |
const result = await response.json(); | |
if (result.status === 'success') { | |
chatHistory.innerHTML = ''; | |
history = []; | |
} else { | |
console.error('Failed to clear chat history:', result.message); | |
} | |
} catch (error) { | |
console.error('Error clearing chat:', error); | |
} | |
} | |
sendBtn.addEventListener('click', sendMessage); | |
clearBtn.addEventListener('click', clearChat); | |
// 防止输入框在移动设备上弹出键盘时影响布局 | |
const viewport = document.querySelector('meta[name=viewport]'); | |
viewport.setAttribute('content', viewport.content + ', height=' + window.innerHeight); | |
</script> | |
</body> | |
</html> |