@@ -13,16 +13,19 @@ let conversations = []; // 对话列表
let currentConversation = null ; // 当前对话
let isLoading = false ;
// DOM 元素
cons t appContainer = document . getElementById ( 'app' ) ;
cons t messagesContainer = document . getElementById ( 'messagesContainer' ) ;
cons t messagesDiv = document . getElementById ( 'messages' ) ;
cons t userInput = document . getElementById ( 'userInput' ) ;
cons t sendBtn = document . getElementById ( 'sendBtn' ) ;
cons t welcome = document . getElementById ( 'welcome' ) ;
// DOM 元素(初始为 null, 在 openConversation 时重新获取)
le t appContainer = null ;
le t messagesContainer = null ;
le t messagesDiv = null ;
le t userInput = null ;
le t sendBtn = null ;
le t welcome = null ;
// 初始化
document . addEventListener ( 'DOMContentLoaded' , ( ) => {
// 初始化 appContainer
appContainer = document . getElementById ( 'app' ) ;
// 从本地存储加载对话列表
const saved = localStorage . getItem ( 'conversations' ) ;
if ( saved ) {
@@ -65,22 +68,20 @@ function showConversationList() {
<span class="logo">🤖</span>
<h1>AI助手</h1>
</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>
<div class="list-content">
<button class="new-chat-btn" onclick="createNewC onversation() ">
<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">
<div class="conversation-list" id="c onversationList ">
${ conversations . length === 0
? '<div class="empty-list">暂无对话记录</div>'
: 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-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>
</button>
</div>
@@ -92,6 +93,29 @@ function showConversationList() {
` ;
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 = `
<div id="chatPage">
<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>
</button>
<div class="header-title">
<span class="logo">🤖</span>
<h1> ${ escapeHtml ( currentConversation . title ) } </h1>
</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>
</button>
</header>
@@ -152,10 +176,8 @@ function openConversation(id) {
id="userInput"
placeholder="输入消息..."
rows="1"
onkeydown="handleKeyDown(event)"
oninput="autoResize(this)"
></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>
</button>
</div>
@@ -171,9 +193,17 @@ function openConversation(id) {
sendBtn = document . getElementById ( 'sendBtn' ) ;
welcome = document . getElementById ( 'welcome' ) ;
// 渲染消息
renderMessages ( ) ;
userInput . focus ( ) ;
// 绑定按钮事件
const backBtn = document . getElementById ( 'backBtn' ) ;
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 => {
@@ -183,6 +213,10 @@ function openConversation(id) {
sendMessage ( ) ;
} ) ;
} ) ;
// 渲染消息
renderMessages ( ) ;
userInput . focus ( ) ;
}
// 删除对话
@@ -410,6 +444,11 @@ function clearCurrentChat() {
function renderMessages ( ) {
if ( ! currentConversation ) return ;
// 根据消息数量显示/隐藏欢迎界面
if ( welcome ) {
welcome . style . display = currentConversation . messages . length > 0 ? 'none' : 'block' ;
}
messagesDiv . innerHTML = currentConversation . messages . map ( ( msg , index ) => {
const isUser = msg . role === 'user' ;
const avatar = isUser ? '👤' : '🤖' ;
@@ -419,17 +458,17 @@ function renderMessages() {
const actions = isUser
? ` <div class="message-actions">
<button class="action-btn copy-btn" onclick="copyMessage( ${ index } ) " title="复制">${ copyIcon } </button>
<button class="action-btn delete-btn" onclick="deleteMessage( ${ index } ) " title="删除">
<button class="action-btn copy-btn" data-index=" ${ index } " title="复制"> ${ copyIcon } </button>
<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>
</button>
</div> `
: ` <div class="message-actions">
<button class="action-btn copy-btn" onclick="copyMessage( ${ index } ) " title="复制">${ copyIcon } </button>
<button class="action-btn regenerate-btn" onclick="regenerate( ${ index } ) " title="重新生成">
<button class="action-btn copy-btn" data-index=" ${ index } " title="复制"> ${ copyIcon } </button>
<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>
</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>
</button>
</div> ` ;
@@ -445,6 +484,17 @@ function renderMessages() {
` ;
} ) . 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 ( ) ;
}