fix: refine sidebar ui and conversation sorting

This commit is contained in:
JOJO 2025-11-21 22:37:55 +08:00
parent 9a83f92dd9
commit b2cfabcd1b
3 changed files with 478 additions and 110 deletions

View File

@ -170,6 +170,7 @@ async function bootstrapApp() {
loadingMoreConversations: false,
currentConversationId: null,
currentConversationTitle: '当前对话',
personalPageVisible: false,
// 搜索功能
searchQuery: '',
@ -629,6 +630,7 @@ async function bootstrapApp() {
console.log('对话已切换:', data);
this.currentConversationId = data.conversation_id;
this.currentConversationTitle = data.title || '';
this.promoteConversationToTop(data.conversation_id);
if (data.cleared) {
// 对话被清空
@ -655,6 +657,7 @@ async function bootstrapApp() {
if (data.title) {
this.currentConversationTitle = data.title;
}
this.promoteConversationToTop(convId);
const pathFragment = this.stripConversationPrefix(convId);
const currentPath = window.location.pathname.replace(/^\/+/, '');
if (data.created) {
@ -1544,6 +1547,7 @@ async function bootstrapApp() {
// 2. 更新当前对话信息
this.currentConversationId = conversationId;
this.currentConversationTitle = result.title;
this.promoteConversationToTop(conversationId);
history.pushState({ conversationId }, '', `/${this.stripConversationPrefix(conversationId)}`);
// 3. 重置UI状态
@ -1571,6 +1575,17 @@ async function bootstrapApp() {
}
},
promoteConversationToTop(conversationId) {
if (!Array.isArray(this.conversations) || !conversationId) {
return;
}
const index = this.conversations.findIndex(conv => conv && conv.id === conversationId);
if (index > 0) {
const [selected] = this.conversations.splice(index, 1);
this.conversations.unshift(selected);
}
},
// ==========================================
// 关键功能:获取并显示历史对话内容
// ==========================================
@ -2020,6 +2035,14 @@ async function bootstrapApp() {
this.sidebarCollapsed = !this.sidebarCollapsed;
},
openPersonalPage() {
this.personalPageVisible = true;
},
closePersonalPage() {
this.personalPageVisible = false;
},
async toggleThinkingMode() {
try {
const resp = await fetch('/api/thinking-mode', {

View File

@ -41,8 +41,40 @@
<div class="main-container">
<!-- 新增:对话历史侧边栏(最左侧) -->
<aside class="conversation-sidebar" :class="{ collapsed: sidebarCollapsed }">
<div class="conversation-header">
<button @click="createNewConversation" class="new-conversation-btn" v-if="!sidebarCollapsed">
<div class="conversation-header" :class="{ 'collapsed-layout': sidebarCollapsed }">
<template v-if="sidebarCollapsed">
<div class="collapsed-header-buttons">
<button type="button"
class="collapsed-control-btn conversation-menu-btn"
title="展开对话记录"
@click="toggleSidebar">
<span class="sr-only">展开对话记录</span>
<span class="chat-icon" aria-hidden="true">
<svg viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 6.5c0-1.38 1.12-2.5 2.5-2.5h13c1.38 0 2.5 1.12 2.5 2.5v8.5c0 1.38-1.12 2.5-2.5 2.5h-5.6l-3.4 3.2.6-3.2H7.5c-1.38 0-2.5-1.12-2.5-2.5V6.5z"
stroke="currentColor"
stroke-width="1.7"
stroke-linejoin="round"/>
<path d="M9 9.5h10" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>
<path d="M9 13h6" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>
</svg>
</span>
</button>
<button type="button"
class="collapsed-control-btn quick-plus-btn"
title="快捷新建对话"
@click="createNewConversation">
<span class="sr-only">新建对话</span>
<span class="pencil-icon" aria-hidden="true">
<svg class="edit-svgIcon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<path d="M410.3 231l11.3-11.3-33.9-33.9-62.1-62.1L291.7 89.8l-11.3 11.3-22.6 22.6L58.6 322.9c-10.4 10.4-18 23.3-22.2 37.4L1 480.7c-2.5 8.4-.2 17.5 6.1 23.7s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L387.7 253.7 410.3 231zM160 399.4l-9.1 22.7c-4 3.1-8.5 5.4-13.3 6.9L59.4 452l23-78.1c1.4-4.9 3.8-9.4 6.9-13.3l22.7-9.1v32c0 8.8 7.2 16 16 16h32zM362.7 18.7L348.3 33.2 325.7 55.8 314.3 67.1l33.9 33.9 62.1 62.1 33.9 33.9 11.3-11.3 22.6-22.6 14.5-14.5c25-25 25-65.5 0-90.5L453.3 18.7c-25-25-65.5-25-90.5 0zm-47.4 168l-144 144c-6.2 6.2-16.4 6.2-22.6 0s-6.2-16.4 0-22.6l144-144c6.2-6.2 16.4-6.2 22.6 0s6.2 16.4 0 22.6z"></path>
</svg>
</span>
</button>
</div>
</template>
<template v-else>
<button @click="createNewConversation" class="new-conversation-btn">
<span class="btn-icon">+</span>
<span class="btn-text">新建对话</span>
</button>
@ -50,6 +82,7 @@
<span v-if="sidebarCollapsed"></span>
<span v-else></span>
</button>
</template>
</div>
<template v-if="!sidebarCollapsed">
@ -108,6 +141,24 @@
<template v-else>
<div class="conversation-collapsed-spacer"></div>
</template>
<div class="conversation-personal-entry" :class="{ collapsed: sidebarCollapsed }">
<button type="button"
class="personal-page-btn"
:class="{ 'icon-only': sidebarCollapsed }"
title="个人页面"
@click="openPersonalPage">
<span class="sr-only">个人页面</span>
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
clip-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"></path>
</svg>
<span class="personal-label" v-if="!sidebarCollapsed">个人页面</span>
</button>
</div>
</aside>
<!-- 左侧文件树 -->
@ -127,6 +178,8 @@
</div>
</div>
</div>
<div class="sidebar-panel-card-wrapper">
<div class="sidebar-panel-card">
<div class="sidebar-header">
<div class="panel-menu-wrapper" ref="panelMenuWrapper">
<button class="sidebar-view-toggle"
@ -162,8 +215,8 @@
<span v-else>🤖 子智能体</span>
</h3>
</div>
<template v-if="panelMode === 'todo'">
<div class="todo-panel">
<div class="sidebar-panel-content">
<div v-if="panelMode === 'todo'" class="todo-panel">
<div v-if="!todoList" class="todo-empty">
暂无待办列表
</div>
@ -178,9 +231,7 @@
<div class="todo-instruction">{{ todoList.instruction }}</div>
</div>
</div>
</template>
<template v-else-if="panelMode === 'subAgents'">
<div class="sub-agent-panel">
<div v-else-if="panelMode === 'subAgents'" class="sub-agent-panel">
<div v-if="!subAgents.length" class="sub-agent-empty">
暂无运行中的子智能体
</div>
@ -190,8 +241,7 @@
:key="agent.task_id"
@click="openSubAgent(agent)">
<div class="sub-agent-header">
<span class="sub-agent-id">#{{
agent.agent_id }}</span>
<span class="sub-agent-id">#{{ agent.agent_id }}</span>
<span class="sub-agent-status" :class="agent.status">{{ agent.status }}</span>
</div>
<div class="sub-agent-summary">{{ agent.summary }}</div>
@ -201,9 +251,7 @@
</div>
</div>
</div>
</template>
<template v-else>
<div class="file-tree" @contextmenu.prevent>
<div v-else class="file-tree" @contextmenu.prevent>
<file-node
v-for="node in fileTree"
:key="node.path"
@ -214,7 +262,9 @@
@context-menu="showContextMenu"
></file-node>
</div>
</template>
</div>
</div>
</div>
</aside>
<!-- 左侧拖拽手柄 -->
@ -645,6 +695,21 @@
</div>
</aside>
</div>
<transition name="personal-page-fade">
<div class="personal-page-overlay"
v-if="personalPageVisible"
@click.self="closePersonalPage">
<div class="personal-page-card">
<h2>个人空间</h2>
<p>敬请期待,个人页面正在建设中。</p>
<button type="button"
class="personal-page-close"
@click="closePersonalPage">
返回工作区
</button>
</div>
</div>
</transition>
</template>
<div class="context-menu"
v-if="contextMenu.visible"

View File

@ -11,6 +11,7 @@
--app-bottom-inset: env(safe-area-inset-bottom, 0px);
--claude-bg: #eeece2;
--claude-panel: rgba(255, 255, 255, 0.82);
--claude-left-rail: #f7f3ea;
--claude-sidebar: rgba(255, 255, 255, 0.68);
--claude-border: rgba(118, 103, 84, 0.25);
--claude-text: #3d3929;
@ -122,22 +123,30 @@ body {
.conversation-sidebar {
width: 280px;
background: var(--claude-sidebar);
background-color: var(--claude-left-rail);
border-right: none;
flex-shrink: 0;
display: flex;
flex-direction: column;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 50;
backdrop-filter: blur(12px);
height: var(--app-viewport, 100vh) !important;
min-height: var(--app-viewport, 100vh) !important;
border-bottom: 1px solid var(--claude-border);
}
.conversation-sidebar,
.conversation-sidebar .conversation-header,
.conversation-sidebar .conversation-search,
.conversation-sidebar .conversation-list,
.conversation-sidebar .conversation-personal-entry,
.conversation-sidebar .conversation-collapsed-spacer {
background-color: var(--claude-left-rail);
}
.conversation-collapsed-spacer {
flex: 1 1 auto;
background: transparent;
background-color: inherit;
}
.conversation-sidebar.collapsed {
@ -145,14 +154,7 @@ body {
overflow: hidden;
height: var(--app-viewport, 100vh) !important;
min-height: var(--app-viewport, 100vh) !important;
}
.conversation-sidebar.collapsed .conversation-header {
justify-content: center;
}
.conversation-sidebar.collapsed .conversation-header .toggle-sidebar-btn {
margin-left: 0;
background-color: var(--claude-left-rail);
}
.conversation-header {
@ -162,7 +164,7 @@ body {
justify-content: space-between;
align-items: center;
gap: 12px;
background: rgba(255, 255, 255, 0.78);
background-color: inherit;
color: var(--claude-text);
position: sticky;
top: 0;
@ -170,6 +172,92 @@ body {
min-height: 68px;
}
.conversation-header.collapsed-layout {
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding: 16px 6px 12px;
gap: 14px;
border-bottom: none;
}
.collapsed-header-buttons {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
align-items: center;
}
.collapsed-control-btn {
width: 56px;
height: 56px;
border-radius: 28px;
border: none;
background: transparent;
color: var(--claude-text);
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
padding: 0;
box-shadow: none;
transition: transform 0.2s ease, color 0.2s ease;
}
.collapsed-control-btn:hover {
transform: translateY(-1px);
color: var(--claude-accent);
}
.conversation-menu-btn {
color: #31271d;
}
.conversation-menu-btn:hover {
color: var(--claude-accent);
}
.chat-icon {
display: inline-flex;
align-items: center;
justify-content: center;
}
.conversation-menu-btn svg {
width: 30px;
height: 30px;
}
.quick-plus-btn {
width: 48px;
height: 48px;
border-radius: 24px;
border: none;
background: transparent;
color: #2f251b;
display: inline-flex;
align-items: center;
justify-content: center;
transition: color 0.25s ease, transform 0.2s ease;
}
.quick-plus-btn:hover,
.quick-plus-btn:focus-visible {
color: var(--claude-accent);
transform: translateY(-1px);
}
.pencil-icon svg {
width: 22px;
height: 22px;
}
.pencil-icon path {
fill: currentColor;
fill-opacity: 0.92;
}
.new-conversation-btn {
flex: 1;
background: linear-gradient(135deg, var(--claude-accent) 0%, var(--claude-accent-strong) 100%);
@ -221,9 +309,100 @@ body {
background: rgba(255, 255, 255, 0.9);
}
.conversation-personal-entry {
margin-top: auto;
padding: 8px 16px 12px;
display: flex;
justify-content: center;
background-color: inherit;
}
.conversation-personal-entry.collapsed {
padding: 8px 0 10px;
}
.personal-page-btn {
border: none;
border-radius: 999px;
background: transparent;
color: var(--claude-text);
display: inline-flex;
align-items: center;
justify-content: center;
gap: 10px;
padding: 10px 18px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: color 0.2s ease, transform 0.2s ease;
}
.conversation-personal-entry:not(.collapsed) .personal-page-btn {
width: 100%;
justify-content: flex-start;
padding: 8px 12px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.4);
}
.personal-page-btn svg {
width: 20px;
height: 20px;
}
.personal-page-btn:hover,
.personal-page-btn:focus-visible {
color: var(--claude-accent);
transform: translateY(-1px);
}
.personal-page-btn:focus-visible {
outline: none;
box-shadow: 0 0 0 2px rgba(218, 119, 86, 0.15);
}
.personal-page-btn.icon-only {
width: 54px;
height: 54px;
padding: 0;
border-radius: 27px;
color: var(--claude-text);
}
.personal-page-btn.icon-only svg {
width: 26px;
height: 26px;
}
.personal-page-btn.icon-only:hover,
.personal-page-btn.icon-only:focus-visible {
color: var(--claude-accent);
transform: translateY(-1px);
}
.conversation-personal-entry:not(.collapsed) .personal-page-btn:hover,
.conversation-personal-entry:not(.collapsed) .personal-page-btn:focus-visible {
background: rgba(218, 119, 86, 0.08);
}
.personal-label {
letter-spacing: 0.02em;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
.conversation-search {
padding: 12px;
background: transparent;
background-color: inherit;
border-bottom: 1px solid var(--claude-border);
}
@ -249,7 +428,7 @@ body {
flex: 1;
overflow-y: auto;
padding: 8px 0;
background: transparent;
background-color: inherit;
}
.loading-conversations,
@ -396,7 +575,7 @@ o-conversations {
/* 拖拽手柄 */
.resize-handle {
width: 4px;
background: var(--claude-sidebar);
background: var(--claude-left-rail);
cursor: col-resize;
position: relative;
transition: background 0.2s;
@ -409,12 +588,17 @@ o-conversations {
/* 侧边栏 */
.sidebar {
background: rgba(255, 255, 255, 0.75);
background: var(--claude-left-rail);
overflow-y: auto;
flex-shrink: 0;
border-left: none;
}
.sidebar.left-sidebar {
display: flex;
flex-direction: column;
}
.sidebar-status {
padding: 18px 18px 8px;
}
@ -524,6 +708,37 @@ o-conversations {
background: rgba(255, 255, 255, 0.95);
}
.sidebar-panel-card-wrapper {
padding: 0 18px 24px;
flex: 1 1 auto;
display: flex;
min-height: 0;
}
.sidebar-panel-card {
background: var(--claude-panel);
border: 1px solid var(--claude-border);
border-radius: 18px;
box-shadow: 0 12px 30px rgba(61, 57, 41, 0.12);
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
}
.sidebar-panel-card .sidebar-header {
border-radius: 18px 18px 0 0;
}
.sidebar-panel-content {
flex: 1 1 auto;
background: var(--claude-sidebar);
padding: 6px 12px 24px;
border-radius: 0 0 18px 18px;
border-top: 1px solid rgba(118, 103, 84, 0.12);
}
.panel-menu-wrapper {
position: relative;
display: inline-flex;
@ -2232,3 +2447,68 @@ o-files {
color: var(--claude-text-secondary);
font-style: italic;
}
.personal-page-overlay {
position: fixed;
inset: 0;
background: rgba(33, 24, 14, 0.55);
backdrop-filter: blur(12px);
display: flex;
align-items: center;
justify-content: center;
z-index: 400;
padding: 20px;
}
.personal-page-card {
width: min(90vw, 420px);
background: #fffaf4;
border-radius: 26px;
border: 1px solid rgba(118, 103, 84, 0.25);
box-shadow: 0 28px 60px rgba(38, 28, 18, 0.25);
padding: 40px 48px;
text-align: center;
color: var(--claude-text);
}
.personal-page-card h2 {
font-size: 26px;
margin-bottom: 12px;
}
.personal-page-card p {
font-size: 15px;
color: var(--claude-text-secondary);
margin-bottom: 32px;
}
.personal-page-close {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10px 22px;
border-radius: 999px;
border: none;
background: linear-gradient(135deg, var(--claude-accent) 0%, var(--claude-accent-strong) 100%);
color: #fffdf8;
font-size: 14px;
font-weight: 600;
cursor: pointer;
box-shadow: 0 14px 28px rgba(189, 93, 58, 0.2);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.personal-page-close:hover {
transform: translateY(-1px);
box-shadow: 0 18px 34px rgba(189, 93, 58, 0.3);
}
.personal-page-fade-enter-active,
.personal-page-fade-leave-active {
transition: opacity 0.25s ease;
}
.personal-page-fade-enter-from,
.personal-page-fade-leave-to {
opacity: 0;
}