mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-03-19 13:28:11 +08:00
feat: web ui channel update
This commit is contained in:
@@ -190,12 +190,19 @@
|
||||
|
||||
.bot-container {
|
||||
background-color: var(--bot-msg-bg);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
/* border-bottom: 1px solid var(--border-color); */
|
||||
width: 100%;
|
||||
margin: 0 -20px;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.user-container {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
@@ -222,12 +229,30 @@
|
||||
flex: 1;
|
||||
line-height: 1.6;
|
||||
padding-top: 2px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.message {
|
||||
width: 100%;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.message p {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.message p:empty {
|
||||
margin: 0;
|
||||
line-height: 0.7em;
|
||||
}
|
||||
|
||||
.message p:empty::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
height: 0.7em;
|
||||
}
|
||||
|
||||
.message pre {
|
||||
@@ -319,8 +344,9 @@
|
||||
|
||||
#send {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
height: 100%;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: var(--primary-color);
|
||||
@@ -330,7 +356,6 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
@@ -484,6 +509,68 @@
|
||||
color: #d4d4d4 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.typing-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-left: 0;
|
||||
justify-content: flex-start;
|
||||
width: auto;
|
||||
position: relative;
|
||||
top: -5px;
|
||||
left: -10px;
|
||||
}
|
||||
|
||||
.typing-indicator span {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
margin: 0 2px;
|
||||
background-color: var(--text-light);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.typing-indicator span:nth-child(1) {
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
.typing-indicator span:nth-child(2) {
|
||||
animation: pulse 1s infinite 0.2s;
|
||||
}
|
||||
|
||||
.typing-indicator span:nth-child(3) {
|
||||
animation: pulse 1s infinite 0.4s;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 0.4;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
100% {
|
||||
opacity: 0.4;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.history-divider {
|
||||
padding: 10px;
|
||||
color: var(--text-light);
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.history-item.active {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -530,12 +617,12 @@
|
||||
</div>
|
||||
<div class="example-card">
|
||||
<div class="example-title">编程帮助</div>
|
||||
<div class="example-text">如何用Python创建一个简单的网络爬虫?</div>
|
||||
<div class="example-text">如何用Python写一个简单的网络爬虫</div>
|
||||
</div>
|
||||
<div class="example-card">
|
||||
<!-- <div class="example-card">
|
||||
<div class="example-title">生活建议</div>
|
||||
<div class="example-text">推荐一些提高工作效率的方法</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- 消息将在这里动态添加 -->
|
||||
@@ -564,41 +651,10 @@
|
||||
const newChatButton = document.getElementById('new-chat');
|
||||
const chatHistory = document.getElementById('chat-history');
|
||||
|
||||
// 在页面顶部添加这些变量和函数
|
||||
// 简化变量,只保留用户ID
|
||||
let userId = 'user_' + Math.random().toString(36).substring(2, 10);
|
||||
let currentSessionId = 'session_' + Date.now();
|
||||
let currentSessionId = 'default_session'; // 使用固定会话ID
|
||||
|
||||
// 轮询获取消息
|
||||
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';
|
||||
@@ -623,60 +679,127 @@
|
||||
sidebar.classList.toggle('active');
|
||||
});
|
||||
|
||||
// 处理新对话按钮
|
||||
// 处理新对话按钮 - 创建新的用户ID和清空当前对话
|
||||
newChatButton.addEventListener('click', function() {
|
||||
// 生成新的用户ID
|
||||
userId = 'user_' + Math.random().toString(36).substring(2, 10);
|
||||
console.log('New conversation started with user ID:', userId);
|
||||
|
||||
// 清空聊天记录
|
||||
clearChat();
|
||||
});
|
||||
|
||||
// 清空聊天记录并显示欢迎屏幕
|
||||
function clearChat() {
|
||||
// 清空消息区域
|
||||
while (messagesDiv.firstChild) {
|
||||
if (messagesDiv.firstChild.id === 'welcome-screen') {
|
||||
break;
|
||||
}
|
||||
messagesDiv.removeChild(messagesDiv.firstChild);
|
||||
}
|
||||
messagesDiv.innerHTML = '';
|
||||
|
||||
// 显示欢迎屏幕
|
||||
welcomeScreen.style.display = 'flex';
|
||||
// 创建欢迎屏幕
|
||||
const newWelcomeScreen = document.createElement('div');
|
||||
newWelcomeScreen.id = 'welcome-screen';
|
||||
newWelcomeScreen.innerHTML = `
|
||||
<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>
|
||||
`;
|
||||
|
||||
// 创建新会话
|
||||
currentSessionId = 'session_' + Date.now();
|
||||
// 设置样式
|
||||
newWelcomeScreen.style.display = 'flex';
|
||||
newWelcomeScreen.style.flexDirection = 'column';
|
||||
newWelcomeScreen.style.alignItems = 'center';
|
||||
newWelcomeScreen.style.justifyContent = 'center';
|
||||
newWelcomeScreen.style.height = '100%';
|
||||
newWelcomeScreen.style.textAlign = 'center';
|
||||
newWelcomeScreen.style.padding = '20px';
|
||||
|
||||
// 添加到DOM
|
||||
messagesDiv.appendChild(newWelcomeScreen);
|
||||
|
||||
// 绑定示例卡片事件
|
||||
newWelcomeScreen.querySelectorAll('.example-card').forEach(card => {
|
||||
card.addEventListener('click', function() {
|
||||
const exampleText = this.querySelector('.example-text').textContent;
|
||||
input.value = exampleText;
|
||||
input.dispatchEvent(new Event('input'));
|
||||
input.focus();
|
||||
});
|
||||
});
|
||||
|
||||
// 清空localStorage中的消息 - 使用用户ID作为键
|
||||
localStorage.setItem(`chatMessages_${userId}`, JSON.stringify([]));
|
||||
|
||||
// 在移动设备上关闭侧边栏
|
||||
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);
|
||||
// 从localStorage加载消息 - 使用用户ID作为键
|
||||
function loadMessagesFromLocalStorage() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(`chatMessages_${userId}`) || '[]');
|
||||
} catch (error) {
|
||||
console.error('Error loading messages from localStorage:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// 保存消息到localStorage - 使用用户ID作为键
|
||||
function saveMessageToLocalStorage(message) {
|
||||
try {
|
||||
const messages = loadMessagesFromLocalStorage();
|
||||
messages.push(message);
|
||||
localStorage.setItem(`chatMessages_${userId}`, JSON.stringify(messages));
|
||||
} catch (error) {
|
||||
console.error('Error saving message to localStorage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化代码
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 移除原始欢迎屏幕
|
||||
const originalWelcomeScreen = document.getElementById('welcome-screen');
|
||||
if (originalWelcomeScreen) {
|
||||
originalWelcomeScreen.remove();
|
||||
}
|
||||
|
||||
// 清空消息区域,确保不会重复显示消息
|
||||
messagesDiv.innerHTML = '';
|
||||
|
||||
// 加载消息
|
||||
const messages = loadMessagesFromLocalStorage();
|
||||
|
||||
if (messages.length === 0) {
|
||||
// 如果没有消息,显示欢迎屏幕
|
||||
clearChat();
|
||||
} else {
|
||||
// 显示现有消息
|
||||
messages.forEach(msg => {
|
||||
if (msg.role === 'user') {
|
||||
// 使用不保存到localStorage的版本显示消息
|
||||
displayUserMessage(msg.content, new Date(msg.timestamp));
|
||||
} else if (msg.role === 'assistant') {
|
||||
// 使用不保存到localStorage的版本显示消息
|
||||
displayBotMessage(msg.content, new Date(msg.timestamp));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 发送按钮点击事件
|
||||
sendButton.onclick = function() {
|
||||
sendMessage();
|
||||
@@ -707,14 +830,20 @@
|
||||
const userMessage = input.value.trim();
|
||||
if (userMessage) {
|
||||
// 隐藏欢迎屏幕
|
||||
welcomeScreen.style.display = 'none';
|
||||
const welcomeScreenElement = document.getElementById('welcome-screen');
|
||||
if (welcomeScreenElement) {
|
||||
welcomeScreenElement.remove();
|
||||
}
|
||||
|
||||
const timestamp = new Date();
|
||||
|
||||
// 添加用户消息到界面
|
||||
addUserMessage(userMessage, timestamp);
|
||||
|
||||
// 发送到服务器
|
||||
// 添加一个等待中的机器人消息
|
||||
const loadingContainer = addLoadingMessage();
|
||||
|
||||
// 发送到服务器并等待响应
|
||||
fetch('/message', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -726,80 +855,194 @@
|
||||
timestamp: timestamp.toISOString(),
|
||||
session_id: currentSessionId
|
||||
})
|
||||
}).then(response => {
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
console.error('Failed to send message');
|
||||
throw new Error('Failed to send message');
|
||||
}
|
||||
}).catch(error => {
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
// 移除加载消息
|
||||
if (loadingContainer.parentNode) {
|
||||
messagesDiv.removeChild(loadingContainer);
|
||||
}
|
||||
|
||||
// 添加AI回复
|
||||
if (data.reply) {
|
||||
addBotMessage(data.reply, new Date());
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error sending message:', error);
|
||||
// 移除加载消息
|
||||
if (loadingContainer.parentNode) {
|
||||
messagesDiv.removeChild(loadingContainer);
|
||||
}
|
||||
// 显示错误消息
|
||||
addBotMessage("抱歉,发生了错误,请稍后再试。", new Date());
|
||||
});
|
||||
|
||||
// 清空输入框并重置高度
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加加载中的消息
|
||||
function addLoadingMessage() {
|
||||
const botContainer = document.createElement('div');
|
||||
botContainer.className = 'bot-container loading-container';
|
||||
|
||||
const messageContainer = document.createElement('div');
|
||||
messageContainer.className = 'message-container';
|
||||
|
||||
messageContainer.innerHTML = `
|
||||
<div class="avatar bot-avatar">
|
||||
<i class="fas fa-robot"></i>
|
||||
</div>
|
||||
<div class="message-content">
|
||||
<div class="message">
|
||||
<div class="typing-indicator">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
botContainer.appendChild(messageContainer);
|
||||
messagesDiv.appendChild(botContainer);
|
||||
scrollToBottom();
|
||||
|
||||
return botContainer;
|
||||
}
|
||||
|
||||
// 格式化消息内容(处理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;
|
||||
highlight: function(code, language) {
|
||||
if (language && hljs.getLanguage(language)) {
|
||||
try {
|
||||
return hljs.highlight(code, { language: language }).value;
|
||||
} catch (e) {
|
||||
console.error('Error highlighting code:', e);
|
||||
return code;
|
||||
}
|
||||
}
|
||||
return hljs.highlightAuto(code).value;
|
||||
return code;
|
||||
},
|
||||
breaks: true, // 启用换行符转换为 <br>
|
||||
gfm: true // 启用 GitHub 风格的 Markdown
|
||||
breaks: true, // 启用换行符转换为 <br>
|
||||
gfm: true, // 启用 GitHub 风格的 Markdown
|
||||
headerIds: true, // 为标题生成ID
|
||||
mangle: false, // 不转义内联HTML
|
||||
sanitize: false, // 不净化输出
|
||||
smartLists: true, // 使用更智能的列表行为
|
||||
smartypants: false, // 不使用更智能的标点符号
|
||||
xhtml: false // 不使用自闭合标签
|
||||
});
|
||||
|
||||
// 使用 marked 解析 Markdown
|
||||
return marked.parse(content);
|
||||
try {
|
||||
// 使用 marked 解析 Markdown
|
||||
const parsed = marked.parse(content);
|
||||
return parsed;
|
||||
} catch (e) {
|
||||
console.error('Error parsing markdown:', e);
|
||||
// 如果解析失败,至少确保换行符正确显示
|
||||
return content.replace(/\n/g, '<br>');
|
||||
}
|
||||
}
|
||||
|
||||
// 添加消息后应用代码高亮
|
||||
function applyHighlighting() {
|
||||
document.querySelectorAll('pre code').forEach((block) => {
|
||||
hljs.highlightBlock(block);
|
||||
try {
|
||||
document.querySelectorAll('pre code').forEach((block) => {
|
||||
// 手动应用高亮
|
||||
const language = block.className.replace('language-', '');
|
||||
if (language && hljs.getLanguage(language)) {
|
||||
try {
|
||||
hljs.highlightBlock(block);
|
||||
} catch (e) {
|
||||
console.error('Error highlighting block:', e);
|
||||
}
|
||||
} else {
|
||||
hljs.highlightAuto(block);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error applying code highlighting:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加用户消息的函数 (保存到localStorage)
|
||||
function addUserMessage(content, timestamp) {
|
||||
// 显示消息
|
||||
displayUserMessage(content, timestamp);
|
||||
|
||||
// 保存到localStorage
|
||||
saveMessageToLocalStorage({
|
||||
role: 'user',
|
||||
content: content,
|
||||
timestamp: timestamp.getTime()
|
||||
});
|
||||
}
|
||||
|
||||
// 更新添加消息的函数
|
||||
function addUserMessage(content, timestamp) {
|
||||
// 添加机器人消息的函数 (保存到localStorage)
|
||||
function addBotMessage(content, timestamp) {
|
||||
// 显示消息
|
||||
displayBotMessage(content, timestamp);
|
||||
|
||||
// 保存到localStorage
|
||||
saveMessageToLocalStorage({
|
||||
role: 'assistant',
|
||||
content: content,
|
||||
timestamp: timestamp.getTime()
|
||||
});
|
||||
}
|
||||
|
||||
// 只显示用户消息而不保存到localStorage
|
||||
function displayUserMessage(content, timestamp) {
|
||||
const userContainer = document.createElement('div');
|
||||
userContainer.className = 'user-container';
|
||||
|
||||
const messageContainer = document.createElement('div');
|
||||
messageContainer.className = 'message-container';
|
||||
|
||||
// 安全地格式化消息
|
||||
let formattedContent;
|
||||
try {
|
||||
formattedContent = formatMessage(content);
|
||||
} catch (e) {
|
||||
console.error('Error formatting user message:', e);
|
||||
formattedContent = `<p>${content.replace(/\n/g, '<br>')}</p>`;
|
||||
}
|
||||
|
||||
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="message">${formattedContent}</div>
|
||||
<div class="timestamp">${formatTimestamp(timestamp)}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
messagesDiv.appendChild(messageContainer);
|
||||
applyHighlighting(); // 应用代码高亮
|
||||
userContainer.appendChild(messageContainer);
|
||||
messagesDiv.appendChild(userContainer);
|
||||
|
||||
// 应用代码高亮
|
||||
setTimeout(() => {
|
||||
applyHighlighting();
|
||||
}, 0);
|
||||
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
// 添加机器人消息
|
||||
function addBotMessage(content, timestamp) {
|
||||
console.log('Adding bot message:', content, timestamp);
|
||||
|
||||
// 只显示机器人消息而不保存到localStorage
|
||||
function displayBotMessage(content, timestamp) {
|
||||
const botContainer = document.createElement('div');
|
||||
botContainer.className = 'bot-container';
|
||||
|
||||
@@ -811,19 +1054,64 @@
|
||||
timestamp = new Date();
|
||||
}
|
||||
|
||||
// 安全地格式化消息
|
||||
let formattedContent;
|
||||
try {
|
||||
formattedContent = formatMessage(content);
|
||||
} catch (e) {
|
||||
console.error('Error formatting bot message:', e);
|
||||
formattedContent = `<p>${content.replace(/\n/g, '<br>')}</p>`;
|
||||
}
|
||||
|
||||
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="message">${formattedContent}</div>
|
||||
<div class="timestamp">${formatTimestamp(timestamp)}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
botContainer.appendChild(messageContainer);
|
||||
messagesDiv.appendChild(botContainer);
|
||||
applyHighlighting(); // 应用代码高亮
|
||||
|
||||
// 使用setTimeout确保DOM已更新,并延长等待时间
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// 直接对新添加的消息应用高亮
|
||||
const codeBlocks = botContainer.querySelectorAll('pre code');
|
||||
codeBlocks.forEach(block => {
|
||||
// 确保代码块有正确的类
|
||||
if (!block.classList.contains('hljs')) {
|
||||
block.classList.add('hljs');
|
||||
}
|
||||
|
||||
// 尝试获取语言
|
||||
let language = '';
|
||||
block.classList.forEach(cls => {
|
||||
if (cls.startsWith('language-')) {
|
||||
language = cls.replace('language-', '');
|
||||
}
|
||||
});
|
||||
|
||||
// 应用高亮
|
||||
if (language && hljs.getLanguage(language)) {
|
||||
try {
|
||||
hljs.highlightBlock(block);
|
||||
} catch (e) {
|
||||
console.error('Error highlighting specific language:', e);
|
||||
hljs.highlightAuto(block);
|
||||
}
|
||||
} else {
|
||||
hljs.highlightAuto(block);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error in delayed highlighting:', e);
|
||||
}
|
||||
}, 100); // 增加延迟以确保DOM完全更新
|
||||
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import sys
|
||||
import time
|
||||
import web
|
||||
import json
|
||||
from queue import Queue
|
||||
from queue import Queue, Empty
|
||||
from bridge.context import *
|
||||
from bridge.reply import Reply, ReplyType
|
||||
from channel.chat_channel import ChatChannel, check_prefix
|
||||
@@ -58,91 +58,29 @@ class WebChannel(ChatChannel):
|
||||
logger.warning(f"Web channel doesn't support {reply.type} yet")
|
||||
return
|
||||
|
||||
if reply.type == ReplyType.IMAGE:
|
||||
from PIL import Image
|
||||
|
||||
image_storage = reply.content
|
||||
image_storage.seek(0)
|
||||
img = Image.open(image_storage)
|
||||
print("<IMAGE>")
|
||||
img.show()
|
||||
elif reply.type == ReplyType.IMAGE_URL:
|
||||
import io
|
||||
|
||||
import requests
|
||||
from PIL import Image
|
||||
|
||||
img_url = reply.content
|
||||
pic_res = requests.get(img_url, stream=True)
|
||||
image_storage = io.BytesIO()
|
||||
for block in pic_res.iter_content(1024):
|
||||
image_storage.write(block)
|
||||
image_storage.seek(0)
|
||||
img = Image.open(image_storage)
|
||||
print(img_url)
|
||||
img.show()
|
||||
else:
|
||||
print(reply.content)
|
||||
|
||||
# 获取用户ID
|
||||
user_id = context.get("receiver", None)
|
||||
if not user_id:
|
||||
logger.error("No receiver found in context, cannot send message")
|
||||
return
|
||||
|
||||
# 确保用户有对应的消息队列
|
||||
if user_id not in self.message_queues:
|
||||
self.message_queues[user_id] = Queue()
|
||||
logger.debug(f"Created message queue for user {user_id}")
|
||||
|
||||
# 将消息放入对应用户的队列
|
||||
message_data = {
|
||||
"type": str(reply.type),
|
||||
"content": reply.content,
|
||||
"timestamp": time.time() # 使用 Unix 时间戳
|
||||
}
|
||||
self.message_queues[user_id].put(message_data)
|
||||
logger.debug(f"Message queued for user {user_id}: {reply.content[:30]}...")
|
||||
# 检查是否有响应队列
|
||||
response_queue = context.get("response_queue", None)
|
||||
if response_queue:
|
||||
# 直接将响应放入队列
|
||||
response_data = {
|
||||
"type": str(reply.type),
|
||||
"content": reply.content,
|
||||
"timestamp": time.time()
|
||||
}
|
||||
response_queue.put(response_data)
|
||||
logger.debug(f"Response sent to queue for user {user_id}")
|
||||
else:
|
||||
logger.warning(f"No response queue found for user {user_id}, response dropped")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in send method: {e}")
|
||||
|
||||
def sse_handler(self, user_id):
|
||||
"""
|
||||
Handle Server-Sent Events (SSE) for real-time communication.
|
||||
"""
|
||||
web.header('Content-Type', 'text/event-stream')
|
||||
web.header('Cache-Control', 'no-cache')
|
||||
web.header('Connection', 'keep-alive')
|
||||
|
||||
logger.debug(f"SSE connection established for user {user_id}")
|
||||
|
||||
# 确保用户有消息队列
|
||||
if user_id not in self.message_queues:
|
||||
self.message_queues[user_id] = Queue()
|
||||
logger.debug(f"Created new message queue for user {user_id}")
|
||||
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
# 发送心跳
|
||||
yield f": heartbeat\n\n"
|
||||
|
||||
# 非阻塞方式获取消息
|
||||
if user_id in self.message_queues and not self.message_queues[user_id].empty():
|
||||
message = self.message_queues[user_id].get_nowait()
|
||||
logger.debug(f"Sending message to user {user_id}: {message}")
|
||||
data = json.dumps(message)
|
||||
yield f"data: {data}\n\n"
|
||||
logger.debug(f"Message sent to user {user_id}")
|
||||
time.sleep(0.5)
|
||||
except Exception as e:
|
||||
logger.error(f"SSE Error for user {user_id}: {str(e)}")
|
||||
break
|
||||
finally:
|
||||
# 清理资源
|
||||
logger.debug(f"SSE connection closed for user {user_id}")
|
||||
|
||||
def post_message(self):
|
||||
"""
|
||||
Handle incoming messages from users via POST request.
|
||||
@@ -167,7 +105,7 @@ class WebChannel(ChatChannel):
|
||||
msg_id=msg_id,
|
||||
content=prompt,
|
||||
from_user_id=user_id,
|
||||
to_user_id="Chatgpt", # 明确指定接收者
|
||||
to_user_id="Chatgpt",
|
||||
other_user_id=user_id
|
||||
)
|
||||
|
||||
@@ -175,13 +113,24 @@ class WebChannel(ChatChannel):
|
||||
if not context:
|
||||
return json.dumps({"status": "error", "message": "Failed to process message"})
|
||||
|
||||
# 创建一个响应队列
|
||||
response_queue = Queue()
|
||||
|
||||
# 确保上下文包含必要的信息
|
||||
context["isgroup"] = False
|
||||
context["receiver"] = user_id # 添加接收者信息,用于send方法中识别用户
|
||||
context["session_id"] = session_id # 添加会话ID
|
||||
context["receiver"] = user_id
|
||||
context["session_id"] = user_id
|
||||
context["response_queue"] = response_queue
|
||||
|
||||
# 发送消息到处理队列
|
||||
self.produce(context)
|
||||
return json.dumps({"status": "success", "message": "Message received"})
|
||||
|
||||
# 等待响应,最多等待30秒
|
||||
try:
|
||||
response = response_queue.get(timeout=30)
|
||||
return json.dumps({"status": "success", "reply": response["content"]})
|
||||
except Empty:
|
||||
return json.dumps({"status": "error", "message": "Response timeout"})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing message: {e}")
|
||||
@@ -203,31 +152,14 @@ class WebChannel(ChatChannel):
|
||||
logger.info(f"Created static directory: {static_dir}")
|
||||
|
||||
urls = (
|
||||
'/sse/(.+)', 'SSEHandler',
|
||||
'/poll/(.+)', 'PollHandler',
|
||||
'/message', 'MessageHandler',
|
||||
'/chat', 'ChatHandler',
|
||||
'/assets/(.*)', 'AssetsHandler', # 匹配 /static/任何路径
|
||||
'/assets/(.*)', 'AssetsHandler', # 匹配 /assets/任何路径
|
||||
)
|
||||
port = conf().get("web_port", 9899)
|
||||
app = web.application(urls, globals(), autoreload=False)
|
||||
web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port))
|
||||
|
||||
def poll_messages(self, user_id):
|
||||
"""Poll for new messages."""
|
||||
messages = []
|
||||
|
||||
if user_id in self.message_queues:
|
||||
while not self.message_queues[user_id].empty():
|
||||
messages.append(self.message_queues[user_id].get_nowait())
|
||||
|
||||
return json.dumps(messages)
|
||||
|
||||
|
||||
class SSEHandler:
|
||||
def GET(self, user_id):
|
||||
return WebChannel().sse_handler(user_id)
|
||||
|
||||
|
||||
class MessageHandler:
|
||||
def POST(self):
|
||||
@@ -242,13 +174,6 @@ class ChatHandler:
|
||||
return f.read()
|
||||
|
||||
|
||||
# 添加轮询处理器
|
||||
class PollHandler:
|
||||
def GET(self, user_id):
|
||||
web.header('Content-Type', 'application/json')
|
||||
return WebChannel().poll_messages(user_id)
|
||||
|
||||
|
||||
class AssetsHandler:
|
||||
def GET(self, file_path): # 修改默认参数
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user