Files
chatgpt-on-wechat/channel/web/chat.html
2025-05-18 15:23:02 +08:00

851 lines
26 KiB
HTML
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.
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Assistant</title>
<link rel="icon" href="assets/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/github.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/python.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/java.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/go.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/cpp.min.js"></script>
<style>
:root {
--primary-color: #10a37f;
--primary-hover: #0d8a6c;
--bg-color: #f7f7f8;
--chat-bg: #ffffff;
--user-msg-bg: #10a37f;
--bot-msg-bg: #f7f7f8;
--border-color: #e5e5e5;
--text-color: #343541;
--text-light: #6e6e80;
--shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: var(--text-color);
background-color: var(--bg-color);
display: flex;
flex-direction: column;
height: 100vh;
margin: 0;
overflow: hidden;
}
#app-container {
display: flex;
height: 100vh;
width: 100%;
}
#sidebar {
width: 260px;
background-color: #202123;
color: white;
display: flex;
flex-direction: column;
transition: all 0.3s ease;
}
#new-chat {
margin: 15px;
padding: 12px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
display: flex;
align-items: center;
cursor: pointer;
transition: background 0.2s;
}
#new-chat:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#new-chat i {
margin-right: 10px;
}
#chat-history {
flex: 1;
overflow-y: auto;
padding: 10px 15px;
}
.history-item {
padding: 10px;
border-radius: 6px;
margin-bottom: 5px;
cursor: pointer;
display: flex;
align-items: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.history-item:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.history-item i {
margin-right: 10px;
}
#sidebar-footer {
padding: 15px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
#user-info {
display: flex;
align-items: center;
cursor: pointer;
padding: 5px;
border-radius: 6px;
}
#user-info:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#user-avatar {
width: 30px;
height: 30px;
background-color: #10a37f;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
}
#main-content {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
position: relative;
}
#chat-header {
padding: 15px 20px;
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
background-color: var(--chat-bg);
}
#menu-toggle {
display: none;
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: var(--text-color);
margin-right: 15px;
}
#header-logo {
height: 30px;
margin-right: 10px;
}
#chat-title {
font-weight: 600;
flex: 1;
}
#messages {
flex: 1;
overflow-y: auto;
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
background-color: var(--chat-bg);
}
.message-container {
display: flex;
padding: 15px 20px;
width: 100%;
max-width: 800px;
margin: 0 auto;
align-items: flex-start;
}
.bot-container {
background-color: var(--bot-msg-bg);
border-bottom: 1px solid var(--border-color);
width: 100%;
margin: 0 -20px;
padding: 20px;
}
.avatar {
width: 30px;
height: 30px;
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
flex-shrink: 0;
margin-top: 4px;
}
.bot-avatar {
background-color: #10a37f;
color: white;
}
.user-avatar {
background-color: #d9d9e3;
color: #40414f;
}
.message-content {
flex: 1;
line-height: 1.6;
padding-top: 2px;
}
.message {
width: 100%;
word-wrap: break-word;
white-space: pre-wrap;
}
.message pre {
background-color: #f0f0f0;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
}
.message code {
font-family: monospace;
background-color: rgba(0, 0, 0, 0.05);
padding: 2px 4px;
border-radius: 3px;
}
.message pre code {
background-color: transparent;
padding: 0;
}
.message blockquote {
border-left: 4px solid #ddd;
padding-left: 10px;
color: #777;
margin: 10px 0;
}
.message table {
border-collapse: collapse;
width: 100%;
margin: 10px 0;
}
.message th, .message td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
.message th {
background-color: #f2f2f2;
}
.timestamp {
font-size: 0.75rem;
color: var(--text-light);
margin-top: 5px;
}
#input-container {
padding: 15px 20px;
background-color: var(--chat-bg);
border-top: 1px solid var(--border-color);
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
#input-wrapper {
position: relative;
width: 100%;
max-width: 768px;
margin: 0 auto;
}
#input {
width: 100%;
padding: 12px 45px 12px 15px;
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 16px;
line-height: 1.5;
color: var(--text-color);
background-color: var(--chat-bg);
resize: none;
height: 52px;
max-height: 200px;
overflow-y: auto;
box-shadow: var(--shadow);
transition: border-color 0.3s;
}
#input:focus {
outline: none;
border-color: var(--primary-color);
}
#send {
position: absolute;
right: 10px;
bottom: 10px;
background-color: transparent;
border: none;
color: var(--primary-color);
font-size: 20px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 4px;
transition: background-color 0.2s;
}
#send:hover {
background-color: rgba(16, 163, 127, 0.1);
}
#send:disabled {
color: var(--border-color);
cursor: not-allowed;
}
#welcome-screen {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
text-align: center;
padding: 20px;
}
#welcome-title {
font-size: 2rem;
margin-bottom: 20px;
font-weight: 600;
}
#welcome-subtitle {
font-size: 1.2rem;
color: var(--text-light);
margin-bottom: 40px;
}
.examples-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 15px;
width: 100%;
max-width: 900px;
}
.example-card {
background-color: white;
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 15px;
cursor: pointer;
transition: all 0.2s;
}
.example-card:hover {
background-color: #f0f0f0;
}
.example-title {
font-weight: 600;
margin-bottom: 8px;
}
.example-text {
color: var(--text-light);
font-size: 0.9rem;
}
/* Responsive styles */
@media (max-width: 768px) {
#sidebar {
position: fixed;
left: -260px;
height: 100%;
z-index: 1000;
}
#sidebar.active {
left: 0;
}
#menu-toggle {
display: block;
}
.message-container {
padding: 10px;
}
.bot-container {
padding: 15px;
}
#input-container {
padding: 10px;
}
.examples-container {
grid-template-columns: 1fr;
}
#header-logo {
height: 24px;
}
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #343541;
--chat-bg: #444654;
--bot-msg-bg: #444654;
--border-color: #565869;
--text-color: #ececf1;
--text-light: #acacbe;
}
#input {
background-color: #40414f;
}
.example-card {
background-color: #40414f;
}
.example-card:hover {
background-color: #565869;
}
.message pre {
background-color: #2d2d2d;
}
.message code {
background-color: rgba(255, 255, 255, 0.1);
}
.message blockquote {
border-left-color: #555;
color: #aaa;
}
.message th, .message td {
border-color: #555;
}
.message th {
background-color: #3a3a3a;
}
.hljs {
background: #2d2d2d !important;
color: #d4d4d4 !important;
}
}
</style>
</head>
<body>
<div id="app-container">
<div id="sidebar">
<div id="new-chat">
<i class="fas fa-plus"></i>
<span>新对话</span>
</div>
<div id="chat-history">
<!-- 历史对话将在这里动态添加 -->
</div>
<div id="sidebar-footer">
<div id="user-info">
<div id="user-avatar">U</div>
<span>用户</span>
</div>
</div>
</div>
<div id="main-content">
<div id="chat-header">
<button id="menu-toggle">
<i class="fas fa-bars"></i>
</button>
<img id="header-logo" src="assets/logo.jpg" alt="AI Assistant Logo">
<div id="chat-title">AI 助手</div>
</div>
<div id="messages">
<!-- 初始欢迎界面 -->
<div id="welcome-screen">
<h1 id="welcome-title">AI 助手</h1>
<p id="welcome-subtitle">我可以回答问题、提供信息或者帮助您完成各种任务</p>
<div class="examples-container">
<div class="example-card">
<div class="example-title">解释复杂概念</div>
<div class="example-text">用简单的语言解释量子计算</div>
</div>
<div class="example-card">
<div class="example-title">创意写作</div>
<div class="example-text">写一个关于未来城市的短篇故事</div>
</div>
<div class="example-card">
<div class="example-title">编程帮助</div>
<div class="example-text">如何用Python创建一个简单的网络爬虫</div>
</div>
<div class="example-card">
<div class="example-title">生活建议</div>
<div class="example-text">推荐一些提高工作效率的方法</div>
</div>
</div>
</div>
<!-- 消息将在这里动态添加 -->
</div>
<div id="input-container">
<div id="input-wrapper">
<textarea id="input" placeholder="发送消息..." rows="1"></textarea>
<button id="send" disabled>
<i class="fas fa-paper-plane"></i>
</button>
</div>
</div>
</div>
</div>
<script>
// DOM 元素
const messagesDiv = document.getElementById('messages');
const input = document.getElementById('input');
const sendButton = document.getElementById('send');
const menuToggle = document.getElementById('menu-toggle');
const sidebar = document.getElementById('sidebar');
const welcomeScreen = document.getElementById('welcome-screen');
const exampleCards = document.querySelectorAll('.example-card');
const newChatButton = document.getElementById('new-chat');
const chatHistory = document.getElementById('chat-history');
// 在页面顶部添加这些变量和函数
let userId = 'user_' + Math.random().toString(36).substring(2, 10);
let currentSessionId = 'session_' + Date.now();
// 轮询获取消息
function pollMessages() {
fetch(`/poll/${userId}`)
.then(response => response.json())
.then(messages => {
if (messages && messages.length > 0) {
console.log('Received messages via polling:', messages);
// 隐藏欢迎屏幕
document.getElementById('welcome-screen').style.display = 'none';
// 处理每条消息
messages.forEach(message => {
addBotMessage(message.content, new Date(message.timestamp * 1000));
});
}
// 继续轮询
setTimeout(pollMessages, 1000);
})
.catch(error => {
console.error('Polling error:', error);
setTimeout(pollMessages, 3000);
});
}
// 启动轮询
document.addEventListener('DOMContentLoaded', function() {
pollMessages();
});
// 自动调整文本区域高度
input.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = (this.scrollHeight) + 'px';
// 启用/禁用发送按钮
sendButton.disabled = !this.value.trim();
});
// 处理示例卡片点击
exampleCards.forEach(card => {
card.addEventListener('click', function() {
const exampleText = this.querySelector('.example-text').textContent;
input.value = exampleText;
input.dispatchEvent(new Event('input'));
input.focus();
});
});
// 处理菜单切换
menuToggle.addEventListener('click', function() {
sidebar.classList.toggle('active');
});
// 处理新对话按钮
newChatButton.addEventListener('click', function() {
// 清空消息区域
while (messagesDiv.firstChild) {
if (messagesDiv.firstChild.id === 'welcome-screen') {
break;
}
messagesDiv.removeChild(messagesDiv.firstChild);
}
// 显示欢迎屏幕
welcomeScreen.style.display = 'flex';
// 创建新会话
currentSessionId = 'session_' + Date.now();
// 在移动设备上关闭侧边栏
if (window.innerWidth <= 768) {
sidebar.classList.remove('active');
}
// 添加到历史记录
addToHistory('新对话', currentSessionId);
});
// 添加到历史记录
function addToHistory(title, sessionId) {
const historyItem = document.createElement('div');
historyItem.className = 'history-item';
historyItem.dataset.sessionId = sessionId;
historyItem.innerHTML = `
<i class="far fa-comment"></i>
<span>${title}</span>
`;
// 点击加载对话
historyItem.addEventListener('click', function() {
// 这里可以实现加载历史对话的功能
// 在实际应用中,您需要存储和检索历史消息
// 在移动设备上关闭侧边栏
if (window.innerWidth <= 768) {
sidebar.classList.remove('active');
}
});
// 添加到历史记录顶部
if (chatHistory.firstChild) {
chatHistory.insertBefore(historyItem, chatHistory.firstChild);
} else {
chatHistory.appendChild(historyItem);
}
}
// 发送按钮点击事件
sendButton.onclick = function() {
sendMessage();
};
// 输入框按键事件
input.addEventListener('keydown', function(event) {
// Ctrl+Enter 或 Shift+Enter 添加换行
if ((event.ctrlKey || event.shiftKey) && event.key === 'Enter') {
const start = this.selectionStart;
const end = this.selectionEnd;
const value = this.value;
this.value = value.substring(0, start) + '\n' + value.substring(end);
this.selectionStart = this.selectionEnd = start + 1;
event.preventDefault();
}
// Enter 键发送消息
else if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey) {
sendMessage();
event.preventDefault();
}
});
// 发送消息函数
function sendMessage() {
const userMessage = input.value.trim();
if (userMessage) {
// 隐藏欢迎屏幕
welcomeScreen.style.display = 'none';
const timestamp = new Date();
// 添加用户消息到界面
addUserMessage(userMessage, timestamp);
// 发送到服务器
fetch('/message', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
user_id: userId,
message: userMessage,
timestamp: timestamp.toISOString(),
session_id: currentSessionId
})
}).then(response => {
if (!response.ok) {
console.error('Failed to send message');
}
}).catch(error => {
console.error('Error sending message:', error);
});
// 清空输入框并重置高度
input.value = '';
input.style.height = '52px';
sendButton.disabled = true;
// 如果这是第一条消息,添加到历史记录
const firstMessageInSession = !messagesDiv.querySelector('.message-container');
if (firstMessageInSession) {
// 使用消息的前20个字符作为标题
const title = userMessage.length > 20 ?
userMessage.substring(0, 20) + '...' :
userMessage;
addToHistory(title, currentSessionId);
}
}
}
// 格式化消息内容处理Markdown和代码高亮
function formatMessage(content) {
// 配置 marked 以使用 highlight.js
marked.setOptions({
highlight: function(code, lang) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value;
}
return hljs.highlightAuto(code).value;
},
breaks: true, // 启用换行符转换为 <br>
gfm: true // 启用 GitHub 风格的 Markdown
});
// 使用 marked 解析 Markdown
return marked.parse(content);
}
// 添加消息后应用代码高亮
function applyHighlighting() {
document.querySelectorAll('pre code').forEach((block) => {
hljs.highlightBlock(block);
});
}
// 更新添加消息的函数
function addUserMessage(content, timestamp) {
const messageContainer = document.createElement('div');
messageContainer.className = 'message-container';
messageContainer.innerHTML = `
<div class="avatar user-avatar">
<i class="fas fa-user"></i>
</div>
<div class="message-content">
<div class="message">${formatMessage(content)}</div>
<div class="timestamp">${formatTimestamp(timestamp)}</div>
</div>
`;
messagesDiv.appendChild(messageContainer);
applyHighlighting(); // 应用代码高亮
scrollToBottom();
}
// 添加机器人消息
function addBotMessage(content, timestamp) {
console.log('Adding bot message:', content, timestamp);
const botContainer = document.createElement('div');
botContainer.className = 'bot-container';
const messageContainer = document.createElement('div');
messageContainer.className = 'message-container';
// 确保时间戳是有效的 Date 对象
if (!(timestamp instanceof Date) || isNaN(timestamp)) {
timestamp = new Date();
}
messageContainer.innerHTML = `
<div class="avatar bot-avatar">
<i class="fas fa-robot"></i>
</div>
<div class="message-content">
<div class="message">${formatMessage(content)}</div>
<div class="timestamp">${formatTimestamp(timestamp)}</div>
</div>
`;
botContainer.appendChild(messageContainer);
messagesDiv.appendChild(botContainer);
applyHighlighting(); // 应用代码高亮
scrollToBottom();
}
// 格式化时间戳
function formatTimestamp(date) {
return date.toLocaleTimeString();
}
// 滚动到底部
function scrollToBottom() {
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
// 处理窗口大小变化
window.addEventListener('resize', function() {
if (window.innerWidth > 768) {
sidebar.classList.remove('active');
}
});
// 初始化
input.focus();
</script>
</body>
</html>