Moonfanz's picture
Upload 2 files
e92e963 verified
raw
history blame
14.7 kB
<!DOCTYPE html>
<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>