diff --git a/www/app.js b/www/app.js index 0a0fdfa..143aae4 100644 --- a/www/app.js +++ b/www/app.js @@ -83,18 +83,38 @@ function showConversationList() {
${conversations.length === 0 ? '
暂无对话记录
' - : conversations.map(conv => ` -
+ : sortConversations().map(conv => ` +
+ ${conv.is_pinned ? '📌' : ''}
${escapeHtml(conv.title)}
${conv.messages.length} 条消息 · ${formatTime(conv.updatedAt)}
-
`).join('') }
+ + +
+
+
+ + 重命名 +
+
+ + 分享 +
+
+ + 置顶 +
+
+ + 删除 +
+
+
`; @@ -107,21 +127,201 @@ function showConversationList() { } const conversationList = document.getElementById('conversationList'); + const actionMenu = document.getElementById('actionMenu'); + let longPressTimer = null; + let currentActionConvId = null; + 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) { + if (item) { const id = item.getAttribute('data-id'); openConversation(id); } }); + + // 长按事件 + conversationList.addEventListener('touchstart', (e) => { + const item = e.target.closest('.conversation-item'); + if (item) { + longPressTimer = setTimeout(() => { + currentActionConvId = item.getAttribute('data-id'); + showActionMenu(currentActionConvId); + }, 500); // 500ms长按 + } + }); + + conversationList.addEventListener('touchend', () => { + if (longPressTimer) { + clearTimeout(longPressTimer); + longPressTimer = null; + } + }); + + conversationList.addEventListener('touchmove', () => { + if (longPressTimer) { + clearTimeout(longPressTimer); + longPressTimer = null; + } + }); + + // 鼠标长按(PC端) + conversationList.addEventListener('mousedown', (e) => { + const item = e.target.closest('.conversation-item'); + if (item) { + longPressTimer = setTimeout(() => { + currentActionConvId = item.getAttribute('data-id'); + showActionMenu(currentActionConvId); + }, 500); + } + }); + + conversationList.addEventListener('mouseup', () => { + if (longPressTimer) { + clearTimeout(longPressTimer); + longPressTimer = null; + } + }); + + conversationList.addEventListener('mouseleave', () => { + if (longPressTimer) { + clearTimeout(longPressTimer); + longPressTimer = null; + } + }); } + + // 操作菜单事件 + if (actionMenu) { + actionMenu.addEventListener('click', (e) => { + const item = e.target.closest('.action-menu-item'); + if (item && currentActionConvId) { + const action = item.getAttribute('data-action'); + handleActionMenuAction(action, currentActionConvId); + hideActionMenu(); + } + }); + + // 点击其他地方关闭菜单 + document.addEventListener('click', (e) => { + if (actionMenu.classList.contains('show') && !actionMenu.contains(e.target)) { + hideActionMenu(); + } + }); + } +} + +// 排序对话(置顶在前) +function sortConversations() { + return [...conversations].sort((a, b) => { + // 置顶优先 + if (a.is_pinned && !b.is_pinned) return -1; + if (!a.is_pinned && b.is_pinned) return 1; + // 然后按更新时间 + return b.updatedAt - a.updatedAt; + }); +} + +// 显示操作菜单 +function showActionMenu(convId) { + const actionMenu = document.getElementById('actionMenu'); + const conv = conversations.find(c => c.id === convId); + if (!actionMenu || !conv) return; + + // 更新置顶按钮文字 + const pinText = document.getElementById('pinText'); + if (pinText) { + pinText.textContent = conv.is_pinned ? '取消置顶' : '置顶'; + } + + // 显示菜单 + actionMenu.classList.add('show'); +} + +// 隐藏操作菜单 +function hideActionMenu() { + const actionMenu = document.getElementById('actionMenu'); + if (actionMenu) { + actionMenu.classList.remove('show'); + } +} + +// 处理操作菜单动作 +function handleActionMenuAction(action, convId) { + const conv = conversations.find(c => c.id === convId); + if (!conv) return; + + switch (action) { + case 'rename': + renameConversation(convId); + break; + case 'share': + shareConversation(convId); + break; + case 'pin': + togglePinConversation(convId); + break; + case 'delete': + deleteConversation(convId); + break; + } +} + +// 重命名对话 +function renameConversation(convId) { + const conv = conversations.find(c => c.id === convId); + if (!conv) return; + + const newTitle = prompt('请输入新的对话标题:', conv.title); + if (newTitle && newTitle.trim() && newTitle !== conv.title) { + conv.title = newTitle.trim(); + conv.updatedAt = Date.now(); + saveConversations(); + showConversationList(); + showToast('已重命名'); + } +} + +// 分享对话 +function shareConversation(convId) { + const conv = conversations.find(c => c.id === convId); + if (!conv) return; + + // 构建分享内容 + const shareContent = `【${conv.title}】\n\n${conv.messages.map(m => + `${m.role === 'user' ? '👤 用户' : '🤖 AI'}: ${m.content}` + ).join('\n\n')}`; + + // 复制到剪贴板 + try { + const textarea = document.createElement('textarea'); + textarea.value = shareContent; + textarea.style.position = 'fixed'; + textarea.style.top = '0'; + textarea.style.left = '0'; + textarea.style.opacity = '0'; + document.body.appendChild(textarea); + textarea.select(); + document.execCommand('copy'); + document.body.removeChild(textarea); + showToast('对话已复制到剪贴板'); + } catch (err) { + showToast('分享失败'); + } +} + +// 置顶/取消置顶对话 +function togglePinConversation(convId) { + const conv = conversations.find(c => c.id === convId); + if (!conv) return; + + conv.is_pinned = !conv.is_pinned; + conv.updatedAt = Date.now(); + saveConversations(); + showConversationList(); + showToast(conv.is_pinned ? '已置顶' : '已取消置顶'); } // 创建新对话 diff --git a/www/index.html b/www/index.html index f511bc8..a8a62d7 100644 --- a/www/index.html +++ b/www/index.html @@ -8,12 +8,12 @@ AI助手 - +
- - + + \ No newline at end of file diff --git a/www/style.css b/www/style.css index 828c3cb..2260682 100644 --- a/www/style.css +++ b/www/style.css @@ -152,7 +152,7 @@ body { .conversation-item { display: flex; - flex-direction: column; + align-items: center; padding: 12px 16px; background: white; border: 1px solid var(--border-color); @@ -160,6 +160,12 @@ body { cursor: pointer; transition: all 0.2s; position: relative; + gap: 8px; +} + +.conversation-item.pinned { + border-color: var(--primary); + background: rgba(102, 126, 234, 0.08); } .conversation-item:hover { @@ -171,43 +177,90 @@ body { background: rgba(102, 126, 234, 0.1); } +.pin-icon { + font-size: 14px; +} + .conv-title { font-size: 16px; font-weight: 500; color: var(--text-color); - margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + flex: 1; } .conv-meta { font-size: 12px; color: var(--text-light); + white-space: nowrap; } -.conv-delete-btn { - position: absolute; - right: 12px; - top: 50%; - transform: translateY(-50%); +/* 操作菜单 */ +.action-menu { + position: fixed; + bottom: 0; + left: 0; + right: 0; background: transparent; - border: none; - color: var(--text-light); - padding: 6px; + display: none; + z-index: 1000; +} + +.action-menu.show { + display: block; +} + +.action-menu-content { + background: white; + border-radius: 16px 16px 0 0; + padding: 16px; + box-shadow: 0 -4px 20px rgba(0,0,0,0.15); + animation: slideUp 0.2s ease; +} + +@keyframes slideUp { + from { transform: translateY(100%); } + to { transform: translateY(0); } +} + +.action-menu-item { + display: flex; + align-items: center; + gap: 12px; + padding: 14px 16px; + border-radius: 10px; cursor: pointer; - opacity: 0; transition: all 0.2s; } -.conversation-item:hover .conv-delete-btn { - opacity: 1; +.action-menu-item:hover { + background: rgba(102, 126, 234, 0.1); } -.conv-delete-btn:hover { +.action-menu-item:active { + background: rgba(102, 126, 234, 0.2); +} + +.action-menu-item svg { + color: var(--primary); +} + +.action-menu-item span { + font-size: 16px; + color: var(--text-color); +} + +.action-menu-item.delete-action svg, +.action-menu-item.delete-action span { color: #e53e3e; } +.action-menu-item.delete-action:hover { + background: rgba(229, 62, 62, 0.1); +} + /* ==================== 对话页面 ==================== */ #chatPage {