Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a7f362c88 | |||
| 15ce68cd6e | |||
| 1e5b20294c |
106
www/app.js
106
www/app.js
@@ -13,16 +13,19 @@ let conversations = []; // 对话列表
|
|||||||
let currentConversation = null; // 当前对话
|
let currentConversation = null; // 当前对话
|
||||||
let isLoading = false;
|
let isLoading = false;
|
||||||
|
|
||||||
// DOM 元素
|
// DOM 元素(初始为 null,在 openConversation 时重新获取)
|
||||||
const appContainer = document.getElementById('app');
|
let appContainer = null;
|
||||||
const messagesContainer = document.getElementById('messagesContainer');
|
let messagesContainer = null;
|
||||||
const messagesDiv = document.getElementById('messages');
|
let messagesDiv = null;
|
||||||
const userInput = document.getElementById('userInput');
|
let userInput = null;
|
||||||
const sendBtn = document.getElementById('sendBtn');
|
let sendBtn = null;
|
||||||
const welcome = document.getElementById('welcome');
|
let welcome = null;
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// 初始化 appContainer
|
||||||
|
appContainer = document.getElementById('app');
|
||||||
|
|
||||||
// 从本地存储加载对话列表
|
// 从本地存储加载对话列表
|
||||||
const saved = localStorage.getItem('conversations');
|
const saved = localStorage.getItem('conversations');
|
||||||
if (saved) {
|
if (saved) {
|
||||||
@@ -65,22 +68,20 @@ function showConversationList() {
|
|||||||
<span class="logo">🤖</span>
|
<span class="logo">🤖</span>
|
||||||
<h1>AI助手</h1>
|
<h1>AI助手</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="new-chat-btn-header" id="newChatBtn" title="新建对话">
|
||||||
|
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||||
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="list-content">
|
<div class="list-content">
|
||||||
<button class="new-chat-btn" onclick="createNewConversation()">
|
<div class="conversation-list" id="conversationList">
|
||||||
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
|
||||||
新建对话
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="conversation-list">
|
|
||||||
${conversations.length === 0
|
${conversations.length === 0
|
||||||
? '<div class="empty-list">暂无对话记录</div>'
|
? '<div class="empty-list">暂无对话记录</div>'
|
||||||
: conversations.map(conv => `
|
: conversations.map(conv => `
|
||||||
<div class="conversation-item" onclick="openConversation('${conv.id}')">
|
<div class="conversation-item" data-id="${conv.id}">
|
||||||
<div class="conv-title">${escapeHtml(conv.title)}</div>
|
<div class="conv-title">${escapeHtml(conv.title)}</div>
|
||||||
<div class="conv-meta">${conv.messages.length} 条消息 · ${formatTime(conv.updatedAt)}</div>
|
<div class="conv-meta">${conv.messages.length} 条消息 · ${formatTime(conv.updatedAt)}</div>
|
||||||
<button class="conv-delete-btn" onclick="event.stopPropagation(); deleteConversation('${conv.id}')" title="删除对话">
|
<button class="conv-delete-btn" data-id="${conv.id}" title="删除对话">
|
||||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
|
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,6 +93,29 @@ function showConversationList() {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
appContainer.innerHTML = listHtml;
|
appContainer.innerHTML = listHtml;
|
||||||
|
|
||||||
|
// 绑定事件
|
||||||
|
const newChatBtn = document.getElementById('newChatBtn');
|
||||||
|
if (newChatBtn) {
|
||||||
|
newChatBtn.addEventListener('click', createNewConversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
const conversationList = document.getElementById('conversationList');
|
||||||
|
if (conversationList) {
|
||||||
|
conversationList.addEventListener('click', (e) => {
|
||||||
|
const item = e.target.closest('.conversation-item');
|
||||||
|
const deleteBtn = e.target.closest('.conv-delete-btn');
|
||||||
|
|
||||||
|
if (deleteBtn) {
|
||||||
|
e.stopPropagation();
|
||||||
|
const id = deleteBtn.getAttribute('data-id');
|
||||||
|
deleteConversation(id);
|
||||||
|
} else if (item) {
|
||||||
|
const id = item.getAttribute('data-id');
|
||||||
|
openConversation(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建新对话
|
// 创建新对话
|
||||||
@@ -121,14 +145,14 @@ function openConversation(id) {
|
|||||||
const chatHtml = `
|
const chatHtml = `
|
||||||
<div id="chatPage">
|
<div id="chatPage">
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<button class="back-btn" onclick="showConversationList()">
|
<button class="back-btn" id="backBtn">
|
||||||
<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
|
<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="header-title">
|
<div class="header-title">
|
||||||
<span class="logo">🤖</span>
|
<span class="logo">🤖</span>
|
||||||
<h1>${escapeHtml(currentConversation.title)}</h1>
|
<h1>${escapeHtml(currentConversation.title)}</h1>
|
||||||
</div>
|
</div>
|
||||||
<button class="clear-btn" onclick="clearCurrentChat()" title="清空对话">
|
<button class="clear-btn" id="clearBtn" title="清空对话">
|
||||||
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
|
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -152,10 +176,8 @@ function openConversation(id) {
|
|||||||
id="userInput"
|
id="userInput"
|
||||||
placeholder="输入消息..."
|
placeholder="输入消息..."
|
||||||
rows="1"
|
rows="1"
|
||||||
onkeydown="handleKeyDown(event)"
|
|
||||||
oninput="autoResize(this)"
|
|
||||||
></textarea>
|
></textarea>
|
||||||
<button class="send-btn" onclick="sendMessage()" id="sendBtn">
|
<button class="send-btn" id="sendBtn">
|
||||||
<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
|
<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,9 +193,17 @@ function openConversation(id) {
|
|||||||
sendBtn = document.getElementById('sendBtn');
|
sendBtn = document.getElementById('sendBtn');
|
||||||
welcome = document.getElementById('welcome');
|
welcome = document.getElementById('welcome');
|
||||||
|
|
||||||
// 渲染消息
|
// 绑定按钮事件
|
||||||
renderMessages();
|
const backBtn = document.getElementById('backBtn');
|
||||||
userInput.focus();
|
if (backBtn) backBtn.addEventListener('click', showConversationList);
|
||||||
|
|
||||||
|
const clearBtn = document.getElementById('clearBtn');
|
||||||
|
if (clearBtn) clearBtn.addEventListener('click', clearCurrentChat);
|
||||||
|
|
||||||
|
// 绑定输入事件
|
||||||
|
userInput.addEventListener('keydown', handleKeyDown);
|
||||||
|
userInput.addEventListener('input', (e) => autoResize(e.target));
|
||||||
|
sendBtn.addEventListener('click', sendMessage);
|
||||||
|
|
||||||
// 绑定快捷按钮事件
|
// 绑定快捷按钮事件
|
||||||
document.querySelectorAll('.quick-btn').forEach(btn => {
|
document.querySelectorAll('.quick-btn').forEach(btn => {
|
||||||
@@ -183,6 +213,10 @@ function openConversation(id) {
|
|||||||
sendMessage();
|
sendMessage();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 渲染消息
|
||||||
|
renderMessages();
|
||||||
|
userInput.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除对话
|
// 删除对话
|
||||||
@@ -410,6 +444,11 @@ function clearCurrentChat() {
|
|||||||
function renderMessages() {
|
function renderMessages() {
|
||||||
if (!currentConversation) return;
|
if (!currentConversation) return;
|
||||||
|
|
||||||
|
// 根据消息数量显示/隐藏欢迎界面
|
||||||
|
if (welcome) {
|
||||||
|
welcome.style.display = currentConversation.messages.length > 0 ? 'none' : 'block';
|
||||||
|
}
|
||||||
|
|
||||||
messagesDiv.innerHTML = currentConversation.messages.map((msg, index) => {
|
messagesDiv.innerHTML = currentConversation.messages.map((msg, index) => {
|
||||||
const isUser = msg.role === 'user';
|
const isUser = msg.role === 'user';
|
||||||
const avatar = isUser ? '👤' : '🤖';
|
const avatar = isUser ? '👤' : '🤖';
|
||||||
@@ -419,17 +458,17 @@ function renderMessages() {
|
|||||||
|
|
||||||
const actions = isUser
|
const actions = isUser
|
||||||
? `<div class="message-actions">
|
? `<div class="message-actions">
|
||||||
<button class="action-btn copy-btn" onclick="copyMessage(${index})" title="复制">${copyIcon}</button>
|
<button class="action-btn copy-btn" data-index="${index}" title="复制">${copyIcon}</button>
|
||||||
<button class="action-btn delete-btn" onclick="deleteMessage(${index})" title="删除">
|
<button class="action-btn delete-btn" data-index="${index}" title="删除">
|
||||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
|
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>`
|
</div>`
|
||||||
: `<div class="message-actions">
|
: `<div class="message-actions">
|
||||||
<button class="action-btn copy-btn" onclick="copyMessage(${index})" title="复制">${copyIcon}</button>
|
<button class="action-btn copy-btn" data-index="${index}" title="复制">${copyIcon}</button>
|
||||||
<button class="action-btn regenerate-btn" onclick="regenerate(${index})" title="重新生成">
|
<button class="action-btn regenerate-btn" data-index="${index}" title="重新生成">
|
||||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
|
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="action-btn delete-btn" onclick="deleteMessage(${index})" title="删除">
|
<button class="action-btn delete-btn" data-index="${index}" title="删除">
|
||||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
|
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -445,6 +484,17 @@ function renderMessages() {
|
|||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
|
// 绑定消息操作按钮事件(事件委托)
|
||||||
|
messagesDiv.querySelectorAll('.copy-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => copyMessage(parseInt(btn.dataset.index)));
|
||||||
|
});
|
||||||
|
messagesDiv.querySelectorAll('.regenerate-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => regenerate(parseInt(btn.dataset.index)));
|
||||||
|
});
|
||||||
|
messagesDiv.querySelectorAll('.delete-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => deleteMessage(parseInt(btn.dataset.index)));
|
||||||
|
});
|
||||||
|
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,30 @@ body {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Header 中的新建对话按钮 */
|
||||||
|
.new-chat-btn-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-chat-btn-header:hover {
|
||||||
|
background: rgba(255,255,255,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-chat-btn-header:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 原样式保留(备用) */
|
||||||
.new-chat-btn {
|
.new-chat-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user