feat(ui): refine sidebar workspace and code blocks

This commit is contained in:
JOJO 2026-05-28 03:53:49 +08:00
parent 348cad2b1a
commit 3bc6f718eb
12 changed files with 6280 additions and 4984 deletions

View File

@ -0,0 +1,150 @@
---
name: ui-aesthetic-design
description: 用于进行高质量 UI 美学设计、界面精致化、组件视觉评审与前端 demo 设计的通用设计规范。当任务涉及现代系统界面、侧边栏、资源面板、消息流、代码展示、按钮、hover/active 状态、二级菜单、展示项/可选项区分、内容对齐、图标视觉体积对齐、浅色/深色主题等 UI 美学问题时使用。
---
# UI Aesthetic Design
当设计、评审或实现 UI 改动时使用此 skill。目标不是做通用 SaaS 模板,而是形成克制、现代、精致、像成熟 AI 系统产品的界面。
## 核心审美方向
整体风格参考 ChatGPT / Claude / Linear 一类现代 AI 系统的克制感:
- 使用干净的浅色/深色表面、细边框、轻量阴影和安静的 hover 状态。
- 避免过度发光、重阴影、毛玻璃、强模糊、圆角矩形套圆角矩形、霓虹渐变和无意义装饰。
- 优先使用细线图标、浅灰 hover 底色、短而稳定的动效。
- 布局要紧凑但不拥挤,信息密度可以高,但不能乱。
- 精致感来自准确的间距、对齐、命中区域、状态反馈和微交互,而不是视觉噪音。
## 颜色与字体层级
使用少量语义化颜色:
- 背景/表面:浅色模式使用白色或近白;深色模式使用近黑或炭黑。
- 主文字:高对比,用于主要内容。
- 次文字:浅灰,用于路径、状态、元信息、辅助说明。
- 边框:中性 1px 细边框。
- Hover低透明度灰色填充。
- Active/当前项:略强的低透明度灰色,或明确但克制的选择标记。
- Danger只用于删除等破坏性操作。
字体层级:
- 主内容文字应更深、更大或更有字重。
- 次内容文字应更浅、更小。
- 不要为了“丰富界面”凭空添加解释性次级文本;只有真实数据结构里存在时才显示。
- 紧凑面板里的行默认单行显示,长文本截断;不要因为某条内容更长就撑高行高,除非产品信息结构明确需要多行。
## 对齐原则
追求**内容对齐**,不是容器对齐,也不是机械文字左对齐。
- 对齐有意义的视觉内容,而不是外层矩形框。
- 如果某一行由 SVG + 文字组成,标题或相关内容应根据 SVG 的视觉位置做对齐,而不是盲目对齐文字边缘。
- 如果下方具体内容本身没有图标,不要为了凑齐文字起点而乱加假图标。
- 按实际视觉体积对齐:不同 SVG 的 viewBox 和形状不同,真实尺寸可以不同,只要视觉大小一致。
- 不要强行把所有 SVG 设成同样宽高。
- 可以统一按钮命中区域,但图标本身大小要按视觉体积校准。
## 组件状态原则
状态设计必须反映真实语义:
- Hover只用于可交互项。
- Active/当前:只用于当前选择、当前模式或当前项。
- Disabled降低对比度并移除点击暗示。
- 展示内容:不要看起来像按钮;避免 hover 底色、阴影、指针光标。
- 可点击内容:可以使用轻量 hover 底色、指针光标和当前项标记。
不要把“有没有阴影”当成唯一状态区别。状态语义应该通过结构、标记、间距和交互暗示共同表达。
## 可选项 vs 展示项
设计前必须先判断内容类型。
### 可选项
用于用户从多个选项中选择一个,例如环境切换、资源位置切换、账号/空间切换、筛选条件切换。
- 必须有明确的“可选择”视觉语义,例如 radio 圆点、check 标记或清晰的选中行。
- 当前项必须一眼能看出来。
- 可以有浅灰 hover 底色。
- 行本身可以是可点击的。
- `默认`、`当前` 这类 badge 只是元信息,不能单独承担“这是可选项”的职责。
### 展示项
用于展示状态或列表数据,例如任务条目、命令摘要、进程摘要、通知摘要,除非真实组件会打开详情。
- 保持安静,不要做成按钮或卡片。
- 不要乱加假图标。
- 不要加卡片阴影或嵌套圆角容器。
- 如果真实产品里不是可点击项,就不要加 hover 效果。
- 紧凑面板中使用统一行高和单行截断。
- 只显示真实数据模型里存在的字段。
## 二级菜单与更多操作
二级菜单要一致、克制、可读:
- 使用紧凑浮层,带轻量边框、适中圆角和轻阴影。
- 菜单选项无论文字长短hover 背景宽度必须一样长。
- 菜单宽度由最长内容决定,所有选项填满同一宽度。
- 不要让每个选项的阴影/hover 背景长度随文字变短变长。
- 文案要短。
- 删除等破坏性项只用 danger 色区分,不要用更大尺寸或更重阴影。
- 列表行右侧 `...` 使用简单圆点视觉hover 可以只变色,不一定加背景。
## 侧边栏展开原则
消息类/导航类侧边栏可使用“遮罩露出”模型,而不是新建一个展开浮层:
- 把侧边栏理解为一张完整面板,被一个移动边界遮住。
- 收起态只露出固定图标列。
- 展开时右侧竖线向右移动,内容被裁切后逐渐露出。
- 每个动作从始至终只有一个图标,不要展开后再出现第二个图标。
- 图标右侧文字和新出现的列表内容同时执行两种效果:淡入 + 随移动边界逐渐露出。
- 展开时不应挤压主内容布局;如设计目标是覆盖/露出,就保持主布局稳定。
- 收起态 hover 只作用在图标的圆角正方形上,不要露出被裁切的大圆角整行背景。
- 展开态 hover 才可以作用于整行。
## 资源面板原则
资源面板、管理面板、辅助面板应避免旧式“圆角矩形套圆角矩形套圆角矩形”的低级感。
- 优先做窄而扁平的面板;如果它位于主导航旁边,可以让展开后的侧边栏完整覆盖它。
- 顶部标题显示当前内容类型,例如资源、环境、任务、命令、通知、日志等。不要把所有模式标题硬塞到同一种视觉层级里。
- 标题下方可放一个“模式切换按钮”,点击展开二级菜单;旁边按需放紧凑管理图标按钮。
- 不要把浅色小号“管理”文字按钮放在大标题同一行,容易层级违和;优先使用图标按钮。
- 不要无意义增加搜索框;只有真实工作流需要搜索或过滤时才加。
- 层级资源树可以使用文件/文件夹/类别图标,因为这类图标本身承载结构语义。
- 环境/空间/资源位置切换属于可选项 UI不是普通展示文本。
- 任务、命令、进程、通知等区域按真实数据字段显示:
- 任务/进程:标识、摘要、状态、可选当前步骤。
- 命令/作业:命令或作业预览、状态、返回码/结果码。
- 清单/待办:序号、标题、状态;如果存在总说明,可单独显示。
- 内容行默认统一高度,除非真实信息结构明确需要展开。
## 代码展示原则
代码块、日志块或等宽文本展示精致化时:
- 做成现代 artifact/card 感1px 细边框、较大圆角、清晰 header/body 分区。
- 避免粗边框和重嵌套面板。
- 复制/运行按钮要遵循同一套按钮系统。
- Header 里的语言名称是次级信息;代码内容才是主内容。
- padding、等宽字体、行高要统一流式输出时避免视觉抖动。
## 实现前检查清单
修改 UI 前必须:
1. 先读取真实组件、状态或数据结构,不准凭印象设计假数据。
2. 判断每个内容是可选项、可点击动作,还是展示内容。
3. 按视觉内容对齐,而不是按容器边缘或文字边缘机械对齐。
4. 复用产品已有图标资源或统一图标库,不要自己编造 SVG。
5. 统一图标命中区域,但按视觉体积调整真实图标大小。
6. 避免嵌套卡片、大阴影和假的 hover 暗示。
7. 同时检查浅色和深色模式。
8. 写 demo 时也要贴近真实产品结构和真实字段;如必须使用示例数据,要保持字段语义真实,不要写臆想说明。

200
docs/code_block_demo.html Normal file
View File

@ -0,0 +1,200 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Code Block Demo</title>
<style>
:root {
color-scheme: light;
--bg: #ffffff;
--text: #111111;
--muted: #6f6f6f;
--surface: #ffffff;
--border: rgba(0, 0, 0, 0.105);
--hover: rgba(0, 0, 0, 0.055);
--radius: 20px;
--mono: "SF Mono", "JetBrains Mono", "Menlo", "Monaco", "Consolas", monospace;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
:root[data-theme="dark"] {
color-scheme: dark;
--bg: #1a1a1a;
--text: #f3f3f3;
--muted: #9b9b9b;
--surface: #1a1a1a;
--border: rgba(255, 255, 255, 0.13);
--hover: rgba(255, 255, 255, 0.08);
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
background: var(--bg);
color: var(--text);
display: flex;
justify-content: center;
padding: 48px 24px;
}
button { font: inherit; }
.page { width: min(960px, 100%); }
.topbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 24px; }
.title { margin: 0; font-size: 30px; line-height: 1.12; letter-spacing: -0.045em; font-weight: 680; }
.subtitle { margin: 6px 0 0; color: var(--muted); font-size: 14px; line-height: 1.35; }
.theme-toggle { height: 36px; border: 1px solid var(--border); border-radius: 999px; background: transparent; color: var(--text); padding: 0 13px; cursor: pointer; }
.theme-toggle:hover { background: var(--hover); }
.message { max-width: 860px; font-size: 16px; line-height: 1.65; }
.message p { margin: 0 0 14px; }
.code-block {
margin: 16px 0 28px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
overflow: hidden;
}
.code-top {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 0 12px 0 18px;
background: var(--surface);
border-bottom: 1px solid var(--border);
}
.code-lang {
color: var(--muted);
font-size: 14px;
line-height: 1;
letter-spacing: -0.01em;
font-weight: 500;
}
.copy-btn {
width: 32px;
height: 32px;
border: 0;
border-radius: 10px;
background: transparent;
color: var(--muted);
display: grid;
place-items: center;
cursor: pointer;
transition: background 140ms ease, color 140ms ease;
}
.copy-btn:hover {
background: var(--hover);
color: var(--text);
}
.icon {
width: 18px;
height: 18px;
display: inline-block;
background: currentColor;
mask: var(--icon-url) center / contain no-repeat;
-webkit-mask: var(--icon-url) center / contain no-repeat;
}
.icon-copy { --icon-url: url("data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2024%2024%22%20width=%2248%22%20height=%2248%22%20fill=%22none%22%20stroke=%22%23000000%22%20stroke-width=%222%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%20style=%22opacity:1;%22%3E%3Crect%20width=%2214%22%20height=%2214%22%20x=%228%22%20y=%228%22%20rx=%222%22%20ry=%222%22/%3E%3Cpath%20d=%22M4%2016c-1.1%200-2-.9-2-2V4c0-1.1.9-2%202-2h10c1.1%200%202%20.9%202%202%22/%3E%3C/svg%3E"); }
.icon-check { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Cpath%20d=%22M20%206%209%2017l-5-5%22%20/%3E%0A%3C/svg%3E"); }
pre {
margin: 0;
padding: 24px 28px 28px;
background: var(--surface);
overflow: auto;
font-family: var(--mono);
font-size: 15px;
line-height: 1.64;
tab-size: 4;
}
code { font-family: inherit; color: var(--text); white-space: pre; }
.kw { color: #c13f7a; }
.fn { color: #6a45c2; }
.str { color: #08863f; }
.num { color: #8b5e00; }
.op { color: #c13f7a; }
:root[data-theme="dark"] .kw { color: #ff7ab2; }
:root[data-theme="dark"] .fn { color: #b69cff; }
:root[data-theme="dark"] .str { color: #5fd38d; }
:root[data-theme="dark"] .num { color: #e4b86a; }
</style>
</head>
<body>
<main class="page">
<header class="topbar">
<div>
<h1 class="title">代码块 Demo</h1>
<p class="subtitle">顶部更矮;顶部和代码区同色;只用一条细线分割;不添加不存在的功能。</p>
</div>
<button class="theme-toggle" id="themeToggle" type="button">深色模式</button>
</header>
<section class="message">
<p>示例代码块:</p>
<div class="code-block">
<div class="code-top">
<span class="code-lang">Python</span>
<button class="copy-btn" type="button" title="复制代码" aria-label="复制代码" data-copy="pythonCode">
<span class="icon icon-copy" aria-hidden="true"></span>
</button>
</div>
<pre><code id="pythonCode"><span class="kw">import</span> random
<span class="kw">from</span> datetime <span class="kw">import</span> datetime
quotes <span class="op">=</span> [
<span class="str">"Stay curious."</span>,
<span class="str">"Code is poetry."</span>,
<span class="str">"Small steps every day."</span>,
<span class="str">"Debugging is detective work."</span>,
]
<span class="kw">def</span> <span class="fn">random_quote</span>():
now <span class="op">=</span> datetime.now().strftime(<span class="str">"%Y-%m-%d %H:%M:%S"</span>)
quote <span class="op">=</span> random.choice(quotes)
<span class="kw">return</span> <span class="str">f"[{now}] {quote}"</span>
<span class="kw">if</span> __name__ <span class="op">==</span> <span class="str">"__main__"</span>:
<span class="kw">for</span> i <span class="kw">in</span> range(<span class="num">3</span>):
print(random_quote())</code></pre>
</div>
<div class="code-block">
<div class="code-top">
<span class="code-lang">powershell</span>
<button class="copy-btn" type="button" title="复制代码" aria-label="复制代码" data-copy="psCode">
<span class="icon icon-copy" aria-hidden="true"></span>
</button>
</div>
<pre><code id="psCode">Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -Force</code></pre>
</div>
</section>
</main>
<script>
const root = document.documentElement;
const themeToggle = document.getElementById('themeToggle');
themeToggle.addEventListener('click', () => {
const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
root.dataset.theme = next;
themeToggle.textContent = next === 'dark' ? '浅色模式' : '深色模式';
});
document.querySelectorAll('.copy-btn').forEach((button) => {
button.addEventListener('click', () => {
// file:// 预览下不调用剪贴板 API避免浏览器安全报错这里只演示点击后的视觉反馈。
const icon = button.querySelector('.icon');
icon.className = 'icon icon-check';
setTimeout(() => { icon.className = 'icon icon-copy'; }, 1000);
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,581 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Sidebar Reveal Demo</title>
<style>
:root {
color-scheme: light;
--bg: #ffffff;
--sidebar-bg: #fbfbfb;
--text: #111111;
--muted: #8f8f8f;
--border: rgba(0, 0, 0, 0.09);
--hover: rgba(0, 0, 0, 0.055);
--hover-strong: rgba(0, 0, 0, 0.095);
--active: rgba(0, 0, 0, 0.078);
--menu-bg: #ffffff;
--menu-shadow: 0 12px 28px rgba(0, 0, 0, 0.14);
--collapsed-width: 52px;
--expanded-width: 306px;
--icon-cell: 40px;
--row-height: 40px;
--row-radius: 12px;
--anim: 320ms cubic-bezier(0.22, 1, 0.36, 1);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
:root[data-theme="dark"] {
color-scheme: dark;
--bg: #1a1a1a;
--sidebar-bg: #0f0f0f;
--text: #f4f4f4;
--muted: #8f8f8f;
--border: rgba(255, 255, 255, 0.105);
--hover: rgba(255, 255, 255, 0.078);
--hover-strong: rgba(255, 255, 255, 0.13);
--active: rgba(255, 255, 255, 0.105);
--menu-bg: #202020;
--menu-shadow: 0 18px 36px rgba(0, 0, 0, 0.42);
}
* { box-sizing: border-box; }
body {
margin: 0;
height: 100vh;
background: var(--bg);
color: var(--text);
overflow: hidden;
}
button,
input {
font: inherit;
}
.app {
position: relative;
height: 100vh;
background: var(--bg);
}
.main {
min-width: 0;
height: 100vh;
margin-left: var(--collapsed-width);
display: flex;
align-items: flex-start;
justify-content: center;
padding: 28px 32px;
}
.main-title {
margin: 0;
font-size: 34px;
line-height: 1.1;
font-weight: 620;
letter-spacing: -0.04em;
}
.main-title span {
color: var(--muted);
font-weight: 430;
}
.sidebar-frame {
position: fixed;
left: 0;
top: 0;
z-index: 50;
width: var(--collapsed-width);
height: 100vh;
overflow: hidden;
background: var(--sidebar-bg);
border-right: 1px solid var(--border);
transition: width var(--anim), background 160ms ease, border-color 160ms ease;
will-change: width;
}
.sidebar-frame.open {
width: var(--expanded-width);
}
.sidebar-content {
width: var(--expanded-width);
height: 100%;
background: var(--sidebar-bg);
display: flex;
flex-direction: column;
padding: 18px 8px 14px 6px;
transition: background 160ms ease;
}
.sidebar-top {
display: flex;
flex-direction: column;
gap: 2px;
flex: 0 0 auto;
}
.sidebar-bottom {
margin-top: 8px;
flex: 0 0 auto;
}
.side-row,
.search-shell,
.conversation-row {
width: 100%;
min-height: var(--row-height);
border: 0;
border-radius: var(--row-radius);
background: transparent;
color: var(--text);
display: grid;
grid-template-columns: var(--icon-cell) minmax(0, 1fr);
align-items: center;
padding: 0;
text-align: left;
}
.side-row,
.conversation-row {
cursor: pointer;
transition: background 140ms ease, color 140ms ease;
}
.side-row:hover,
.conversation-row:hover {
background: transparent;
}
.side-icon {
width: var(--icon-cell);
height: var(--row-height);
border-radius: var(--row-radius);
display: grid;
place-items: center;
color: currentColor;
flex: 0 0 auto;
transition: background 140ms ease;
}
.sidebar-frame:not(.open) .side-row:hover .side-icon,
.sidebar-frame:not(.open) .theme-toggle:hover .side-icon,
.sidebar-frame:not(.open) .conversation-row:hover .side-icon {
background: var(--hover);
}
.sidebar-frame.open .side-row:hover,
.sidebar-frame.open .side-row.active,
.sidebar-frame.open .conversation-row:hover,
.sidebar-frame.open .conversation-row.menu-open {
background: var(--hover);
}
.side-icon svg {
display: block;
stroke: currentColor;
flex: 0 0 auto;
}
/* 保留视觉校准:按钮容器统一,图标真实尺寸不机械统一 */
.side-icon--chat svg { width: 29px; height: 29px; }
.side-icon--pencil svg { width: 22px; height: 22px; }
.side-icon--workspace svg { width: 23px; height: 23px; }
.side-icon--monitor svg { width: 23px; height: 23px; }
.side-icon--theme svg { width: 22px; height: 22px; }
.side-icon--user svg { width: 22px; height: 22px; }
.side-label,
.search-area,
.conversation-list,
.conversation-title,
.conversation-more {
opacity: 0;
transition: opacity 220ms ease;
transition-delay: 0ms;
}
.sidebar-frame.open .side-label,
.sidebar-frame.open .search-area,
.sidebar-frame.open .conversation-list,
.sidebar-frame.open .conversation-title,
.sidebar-frame.open .conversation-more {
opacity: 1;
transition-delay: 80ms;
}
.side-label {
min-width: 0;
padding-left: 8px;
padding-right: 12px;
font-size: 15px;
line-height: 1;
letter-spacing: -0.015em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.search-area {
flex: 0 0 auto;
margin-top: 10px;
}
.search-shell {
height: var(--row-height);
overflow: hidden;
transition: background 140ms ease;
}
.search-shell:focus-within,
.search-shell:hover {
background: var(--hover);
}
.search-input {
min-width: 0;
width: calc(var(--expanded-width) - var(--icon-cell) - 22px);
height: var(--row-height);
border: 0;
outline: 0;
padding: 0 12px 0 8px;
background: transparent;
color: var(--text);
font-size: 14px;
letter-spacing: -0.01em;
}
.search-input::placeholder {
color: var(--muted);
}
.conversation-list {
min-height: 0;
flex: 1 1 auto;
overflow-y: auto;
padding-top: 12px;
padding-right: 0;
scrollbar-width: thin;
}
.conversation-row {
position: relative;
grid-template-columns: minmax(0, 1fr) 40px;
min-height: var(--row-height);
margin: 0;
}
.conversation-title {
min-width: 0;
padding-left: 12px;
padding-right: 4px;
color: var(--text);
font-size: 14px;
line-height: 1.25;
letter-spacing: -0.01em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.conversation-more {
width: 30px;
height: 30px;
border: 0;
border-radius: 9px;
background: transparent;
color: var(--muted);
display: grid;
place-items: center;
cursor: pointer;
justify-self: start;
visibility: hidden;
transition: background 140ms ease, color 140ms ease, opacity 160ms ease;
}
.conversation-row:hover .conversation-more,
.conversation-row.menu-open .conversation-more {
visibility: visible;
}
.conversation-more:hover,
.conversation-row.menu-open .conversation-more {
background: transparent;
color: var(--text);
}
.conversation-more svg {
width: 22px;
height: 22px;
stroke: currentColor;
}
.context-menu {
position: fixed;
z-index: 80;
width: max-content;
min-width: 92px;
padding: 6px;
border: 1px solid var(--border);
border-radius: 14px;
background: var(--menu-bg);
box-shadow: var(--menu-shadow);
opacity: 0;
transform: translateY(-4px) scale(0.98);
pointer-events: none;
transition:
opacity 120ms ease,
transform 150ms cubic-bezier(0.22, 1, 0.36, 1),
background 160ms ease,
border-color 160ms ease;
}
.context-menu.open {
opacity: 1;
transform: translateY(0) scale(1);
pointer-events: auto;
}
.menu-button {
width: max-content;
min-width: 80px;
height: 36px;
border: 0;
border-radius: 10px;
background: transparent;
color: var(--text);
display: grid;
grid-template-columns: 24px auto;
align-items: center;
padding: 0 10px 0 8px;
text-align: left;
cursor: pointer;
font-size: 14px;
letter-spacing: -0.01em;
transition: background 140ms ease;
}
.menu-button:hover {
background: var(--hover);
}
.menu-button.danger {
color: #d6483b;
}
:root[data-theme="dark"] .menu-button.danger {
color: #ff7b72;
}
.menu-button svg {
width: 18px;
height: 18px;
stroke: currentColor;
}
.demo-hint {
position: fixed;
left: calc(var(--collapsed-width) + 20px);
bottom: 18px;
color: var(--muted);
font-size: 13px;
pointer-events: none;
}
</style>
</head>
<body>
<div class="app">
<aside class="sidebar-frame" id="sidebar" aria-label="对话侧边栏">
<div class="sidebar-content">
<div class="sidebar-top">
<button class="side-row" id="toggleSidebar" type="button" aria-label="展开对话记录" aria-expanded="false">
<span class="side-icon side-icon--chat" 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-width="1.45" stroke-linejoin="round" />
<path d="M9 9.5h10" stroke-width="1.45" stroke-linecap="round" />
<path d="M9 13h6" stroke-width="1.45" stroke-linecap="round" />
</svg>
</span>
<span class="side-label">对话记录</span>
</button>
<button class="side-row" type="button" aria-label="新建对话">
<span class="side-icon side-icon--pencil" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
<path d="m15 5 4 4" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</span>
<span class="side-label">新建对话</span>
</button>
<button class="side-row" type="button" aria-label="工作区">
<span class="side-icon side-icon--workspace" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 3 4.5 7.15v9.7L12 21l7.5-4.15v-9.7L12 3Z" stroke-width="1.55" stroke-linejoin="round" />
<path d="M4.8 7.35 12 11.5l7.2-4.15M12 11.5V21" stroke-width="1.55" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</span>
<span class="side-label">工作区</span>
</button>
<button class="side-row" type="button" aria-label="虚拟显示器">
<span class="side-icon side-icon--monitor" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3.5" y="5" width="17" height="12" rx="2" stroke-width="1.55" />
<path d="M9 21h6M12 17v4" stroke-width="1.55" stroke-linecap="round" />
</svg>
</span>
<span class="side-label">虚拟显示器</span>
</button>
</div>
<div class="search-area" aria-label="搜索对话">
<label class="search-shell">
<span class="side-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="width:22px;height:22px;stroke:currentColor">
<circle cx="11" cy="11" r="7" stroke-width="1.55" />
<path d="m20 20-3.4-3.4" stroke-width="1.55" stroke-linecap="round" />
</svg>
</span>
<input class="search-input" type="search" placeholder="搜索对话" />
</label>
</div>
<div class="conversation-list" aria-label="对话记录">
<div class="conversation-row" data-title="随意的 py 代码块">
<span class="conversation-title">随意的 py 代码块</span>
<button class="conversation-more" type="button" aria-label="更多操作">
<svg viewBox="0 0 24 24" fill="none"><path d="M6 12h.01M12 12h.01M18 12h.01" stroke-width="3" stroke-linecap="round" /></svg>
</button>
</div>
<div class="conversation-row" data-title="前端现代化设计讨论">
<span class="conversation-title">前端现代化设计讨论</span>
<button class="conversation-more" type="button" aria-label="更多操作">
<svg viewBox="0 0 24 24" fill="none"><path d="M6 12h.01M12 12h.01M18 12h.01" stroke-width="3" stroke-linecap="round" /></svg>
</button>
</div>
<div class="conversation-row" data-title="审批面板覆盖窗口方案">
<span class="conversation-title">审批面板覆盖窗口方案</span>
<button class="conversation-more" type="button" aria-label="更多操作">
<svg viewBox="0 0 24 24" fill="none"><path d="M6 12h.01M12 12h.01M18 12h.01" stroke-width="3" stroke-linecap="round" /></svg>
</button>
</div>
<div class="conversation-row" data-title="CLI 显示层细节修复">
<span class="conversation-title">CLI 显示层细节修复</span>
<button class="conversation-more" type="button" aria-label="更多操作">
<svg viewBox="0 0 24 24" fill="none"><path d="M6 12h.01M12 12h.01M18 12h.01" stroke-width="3" stroke-linecap="round" /></svg>
</button>
</div>
<div class="conversation-row" data-title="个人空间设置页重设计">
<span class="conversation-title">个人空间设置页重设计</span>
<button class="conversation-more" type="button" aria-label="更多操作">
<svg viewBox="0 0 24 24" fill="none"><path d="M6 12h.01M12 12h.01M18 12h.01" stroke-width="3" stroke-linecap="round" /></svg>
</button>
</div>
</div>
<div class="sidebar-bottom">
<button class="side-row theme-toggle" id="themeToggle" type="button" aria-label="切换深色模式">
<span class="side-icon side-icon--theme" aria-hidden="true">
<svg class="theme-icon theme-icon-light" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="4" stroke-width="1.55" />
<path d="M12 2.8v2.3M12 18.9v2.3M4.2 4.2l1.6 1.6M18.2 18.2l1.6 1.6M2.8 12h2.3M18.9 12h2.3M4.2 19.8l1.6-1.6M18.2 5.8l1.6-1.6" stroke-width="1.55" stroke-linecap="round" />
</svg>
</span>
<span class="side-label">浅色模式</span>
</button>
<button class="side-row" type="button" aria-label="个人空间">
<span class="side-icon side-icon--user" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="8" r="4" stroke-width="1.6" />
<path d="M4.5 21a7.5 7.5 0 0 1 15 0" stroke-width="1.6" stroke-linecap="round" />
</svg>
</span>
<span class="side-label">个人空间</span>
</button>
</div>
</div>
</aside>
<main class="main">
<h1 class="main-title">AI Agent <span>Auto</span></h1>
</main>
<div class="context-menu" id="contextMenu" role="menu" aria-hidden="true">
<button class="menu-button" type="button" role="menuitem">
<svg viewBox="0 0 24 24" fill="none"><rect x="8" y="8" width="11" height="11" rx="2" stroke-width="1.55"/><path d="M5 15V7a2 2 0 0 1 2-2h8" stroke-width="1.55" stroke-linecap="round"/></svg>
<span>复制</span>
</button>
<button class="menu-button danger" type="button" role="menuitem">
<svg viewBox="0 0 24 24" fill="none"><path d="M4 7h16M10 11v6M14 11v6M6 7l1 14h10l1-14M9 7V4h6v3" stroke-width="1.55" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span>删除</span>
</button>
</div>
</div>
<div class="demo-hint">点击第一个按钮折叠/露出;对话行 hover 后显示 ...;点击 ... 打开二级菜单。</div>
<script>
const sidebar = document.getElementById('sidebar');
const toggle = document.getElementById('toggleSidebar');
const themeToggle = document.getElementById('themeToggle');
const contextMenu = document.getElementById('contextMenu');
let menuOwner = null;
toggle.addEventListener('click', () => {
const isOpen = sidebar.classList.toggle('open');
toggle.setAttribute('aria-expanded', String(isOpen));
toggle.setAttribute('aria-label', isOpen ? '收起对话记录' : '展开对话记录');
if (!isOpen) closeMenu();
});
themeToggle.addEventListener('click', () => {
const root = document.documentElement;
const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
root.dataset.theme = next;
themeToggle.querySelector('.side-label').textContent = next === 'dark' ? '深色模式' : '浅色模式';
themeToggle.setAttribute('aria-label', next === 'dark' ? '切换浅色模式' : '切换深色模式');
});
function closeMenu() {
contextMenu.classList.remove('open');
contextMenu.setAttribute('aria-hidden', 'true');
if (menuOwner) menuOwner.classList.remove('menu-open');
menuOwner = null;
}
document.querySelectorAll('.conversation-more').forEach((button) => {
button.addEventListener('click', (event) => {
event.stopPropagation();
const row = button.closest('.conversation-row');
if (menuOwner === row && contextMenu.classList.contains('open')) {
closeMenu();
return;
}
closeMenu();
menuOwner = row;
row.classList.add('menu-open');
const rect = button.getBoundingClientRect();
contextMenu.style.left = '0px';
contextMenu.style.top = `${rect.bottom + 6}px`;
contextMenu.classList.add('open');
contextMenu.setAttribute('aria-hidden', 'false');
const menuWidth = contextMenu.offsetWidth;
contextMenu.style.left = `${Math.min(rect.right - menuWidth, window.innerWidth - menuWidth - 12)}px`;
});
});
document.addEventListener('click', (event) => {
if (!contextMenu.contains(event.target)) closeMenu();
});
window.addEventListener('resize', closeMenu);
</script>
</body>
</html>

View File

@ -0,0 +1,556 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Sidebar + Workspace Demo</title>
<style>
:root {
color-scheme: light;
--bg: #ffffff;
--panel-bg: #fbfbfb;
--text: #111111;
--muted: #8f8f8f;
--border: rgba(0, 0, 0, 0.09);
--hover: rgba(0, 0, 0, 0.055);
--hover-strong: rgba(0, 0, 0, 0.095);
--menu-bg: #ffffff;
--menu-shadow: 0 12px 28px rgba(0, 0, 0, 0.14);
--collapsed-width: 52px;
--expanded-width: 306px;
--workspace-width: calc(var(--expanded-width) - var(--collapsed-width));
--icon-cell: 40px;
--row-height: 40px;
--row-radius: 12px;
--anim: 320ms cubic-bezier(0.22, 1, 0.36, 1);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
:root[data-theme="dark"] {
color-scheme: dark;
--bg: #1a1a1a;
--panel-bg: #0f0f0f;
--text: #f4f4f4;
--muted: #8f8f8f;
--border: rgba(255, 255, 255, 0.105);
--hover: rgba(255, 255, 255, 0.078);
--hover-strong: rgba(255, 255, 255, 0.13);
--menu-bg: #202020;
--menu-shadow: 0 18px 36px rgba(0, 0, 0, 0.42);
}
* { box-sizing: border-box; }
body { margin: 0; height: 100vh; background: var(--bg); color: var(--text); overflow: hidden; }
button, input { font: inherit; }
button { -webkit-tap-highlight-color: transparent; }
.icon {
display: inline-block;
width: var(--icon-size, 20px);
height: var(--icon-size, 20px);
background: currentColor;
mask: var(--icon-url) center / contain no-repeat;
-webkit-mask: var(--icon-url) center / contain no-repeat;
flex: 0 0 auto;
}
.icon-chat { --icon-url: url("data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2032%2032%22%20fill=%22none%22%20stroke=%22currentColor%22%20stroke-width=%222.2%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%3E%0A%20%20%3Cpath%20d=%22M6%207c0-2%201.6-3.6%203.6-3.6h13c2%200%203.6%201.6%203.6%203.6v10c0%202-1.6%203.6-3.6%203.6h-6.2L12.4%2025l0.7-4.4H9.6c-2%200-3.6-1.6-3.6-3.6V7Z%22%20/%3E%0A%20%20%3Cpath%20d=%22M10.4%2011h11.2%22%20/%3E%0A%20%20%3Cpath%20d=%22M10.4%2015h7.2%22%20/%3E%0A%3C/svg%3E"); --icon-size: 29px; }
.icon-pencil { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Cpath%20d=%22M21.174%206.812a1%201%200%200%200-3.986-3.987L3.842%2016.174a2%202%200%200%200-.5.83l-1.321%204.352a.5.5%200%200%200%20.623.622l4.353-1.32a2%202%200%200%200%20.83-.497z%22%20/%3E%0A%20%20%3Cpath%20d=%22m15%205%204%204%22%20/%3E%0A%3C/svg%3E"); --icon-size: 22px; }
.icon-layers { --icon-url: url("data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2024%2024%22%20width=%2248%22%20height=%2248%22%20fill=%22none%22%20stroke=%22#000000%22%20stroke-width=%222%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%20style=%22opacity:1;%22%3E%3Cpath%20d=%22M12.83%202.18a2%202%200%200%200-1.66%200L2.6%206.08a1%201%200%200%200%200%201.83l8.58%203.91a2%202%200%200%200%201.66%200l8.58-3.9a1%201%200%200%200%200-1.83z%22/%3E%3Cpath%20d=%22M2%2012a1%201%200%200%200%20.58.91l8.6%203.91a2%202%200%200%200%201.65%200l8.58-3.9A1%201%200%200%200%2022%2012%22/%3E%3Cpath%20d=%22M2%2017a1%201%200%200%200%20.58.91l8.6%203.91a2%202%200%200%200%201.65%200l8.58-3.9A1%201%200%200%200%2022%2017%22/%3E%3C/svg%3E"); --icon-size: 23px; }
.icon-monitor { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Crect%20width=%2220%22%20height=%2214%22%20x=%222%22%20y=%223%22%20rx=%222%22%20/%3E%0A%20%20%3Cline%20x1=%228%22%20x2=%2216%22%20y1=%2221%22%20y2=%2221%22%20/%3E%0A%20%20%3Cline%20x1=%2212%22%20x2=%2212%22%20y1=%2217%22%20y2=%2221%22%20/%3E%0A%3C/svg%3E"); --icon-size: 23px; }
.icon-search { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Cpath%20d=%22m21%2021-4.34-4.34%22%20/%3E%0A%20%20%3Ccircle%20cx=%2211%22%20cy=%2211%22%20r=%228%22%20/%3E%0A%3C/svg%3E"); --icon-size: 22px; }
.icon-user { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Cpath%20d=%22M19%2021v-2a4%204%200%200%200-4-4H9a4%204%200%200%200-4%204v2%22%20/%3E%0A%20%20%3Ccircle%20cx=%2212%22%20cy=%227%22%20r=%224%22%20/%3E%0A%3C/svg%3E"); --icon-size: 22px; }
.icon-folder { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Cpath%20d=%22M20%2020a2%202%200%200%200%202-2V8a2%202%200%200%200-2-2h-7.9a2%202%200%200%201-1.69-.9L9.6%203.9A2%202%200%200%200%207.93%203H4a2%202%200%200%200-2%202v13a2%202%200%200%200%202%202Z%22%20/%3E%0A%3C/svg%3E"); --icon-size: 18px; }
.icon-folder-open { --icon-url: url("data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2024%2024%22%20width=%2248%22%20height=%2248%22%20fill=%22none%22%20stroke=%22#000000%22%20stroke-width=%222%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%20style=%22opacity:1;%22%3E%3Cpath%20%20%20%20%20%20d=%22m6%2014l1.5-2.9A2%202%200%200%201%209.24%2010H20a2%202%200%200%201%201.94%202.5l-1.54%206a2%202%200%200%201-1.95%201.5H4a2%202%200%200%201-2-2V5a2%202%200%200%201%202-2h3.9a2%202%200%200%201%201.69.9l.81%201.2a2%202%200%200%200%201.67.9H18a2%202%200%200%201%202%202v2%22/%3E%3C/svg%3E"); --icon-size: 18px; }
.icon-file { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Cpath%20d=%22M6%2022a2%202%200%200%201-2-2V4a2%202%200%200%201%202-2h8a2.4%202.4%200%200%201%201.704.706l3.588%203.588A2.4%202.4%200%200%201%2020%208v12a2%202%200%200%201-2%202z%22%20/%3E%0A%20%20%3Cpath%20d=%22M14%202v5a1%201%200%200%200%201%201h5%22%20/%3E%0A%3C/svg%3E"); --icon-size: 17px; }
.icon-bot { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Cpath%20d=%22M12%208V4H8%22%20/%3E%0A%20%20%3Crect%20width=%2216%22%20height=%2212%22%20x=%224%22%20y=%228%22%20rx=%222%22%20/%3E%0A%20%20%3Cpath%20d=%22M2%2014h2%22%20/%3E%0A%20%20%3Cpath%20d=%22M20%2014h2%22%20/%3E%0A%20%20%3Cpath%20d=%22M15%2013v2%22%20/%3E%0A%20%20%3Cpath%20d=%22M9%2013v2%22%20/%3E%0A%3C/svg%3E"); --icon-size: 18px; }
.icon-terminal { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Cpath%20d=%22M12%2019h8%22%20/%3E%0A%20%20%3Cpath%20d=%22m4%2017%206-6-6-6%22%20/%3E%0A%3C/svg%3E"); --icon-size: 18px; }
.icon-sticky { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Cpath%20d=%22M21%209a2.4%202.4%200%200%200-.706-1.706l-3.588-3.588A2.4%202.4%200%200%200%2015%203H5a2%202%200%200%200-2%202v14a2%202%200%200%200%202%202h14a2%202%200%200%200%202-2z%22%20/%3E%0A%20%20%3Cpath%20d=%22M15%203v5a1%201%200%200%200%201%201h5%22%20/%3E%0A%3C/svg%3E"); --icon-size: 18px; }
.icon-check { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Cpath%20d=%22M20%206%209%2017l-5-5%22%20/%3E%0A%3C/svg%3E"); --icon-size: 17px; }
.icon-clock { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Ccircle%20cx=%2212%22%20cy=%2212%22%20r=%229%22%20/%3E%0A%20%20%3Cpath%20d=%22M12%207v5l3%203%22%20/%3E%0A%3C/svg%3E"); --icon-size: 17px; }
.icon-laptop { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Cpath%20d=%22M18%205a2%202%200%200%201%202%202v8.526a2%202%200%200%200%20.212.897l1.068%202.127a1%201%200%200%201-.9%201.45H3.62a1%201%200%200%201-.9-1.45l1.068-2.127A2%202%200%200%200%204%2015.526V7a2%202%200%200%201%202-2z%22%20/%3E%0A%20%20%3Cpath%20d=%22M20.054%2015.987H3.946%22%20/%3E%0A%3C/svg%3E"); --icon-size: 18px; }
.icon-settings { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Cpath%20d=%22M9.671%204.136a2.34%202.34%200%200%201%204.659%200%202.34%202.34%200%200%200%203.319%201.915%202.34%202.34%200%200%201%202.33%204.033%202.34%202.34%200%200%200%200%203.831%202.34%202.34%200%200%201-2.33%204.033%202.34%202.34%200%200%200-3.319%201.915%202.34%202.34%200%200%201-4.659%200%202.34%202.34%200%200%200-3.32-1.915%202.34%202.34%200%200%201-2.33-4.033%202.34%202.34%200%200%200%200-3.831A2.34%202.34%200%200%201%206.35%206.051a2.34%202.34%200%200%200%203.319-1.915%22%20/%3E%0A%20%20%3Ccircle%20cx=%2212%22%20cy=%2212%22%20r=%223%22%20/%3E%0A%3C/svg%3E"); --icon-size: 18px; }
.icon-trash { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Cpath%20d=%22M19%206v14a2%202%200%200%201-2%202H7a2%202%200%200%201-2-2V6%22%20/%3E%0A%20%20%3Cpath%20d=%22M3%206h18%22%20/%3E%0A%20%20%3Cpath%20d=%22M8%206V4a2%202%200%200%201%202-2h4a2%202%200%200%201%202%202v2%22%20/%3E%0A%3C/svg%3E"); --icon-size: 18px; }
.icon-copy { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Crect%20width=%228%22%20height=%224%22%20x=%228%22%20y=%222%22%20rx=%221%22%20ry=%221%22%20/%3E%0A%20%20%3Cpath%20d=%22M16%204h2a2%202%200%200%201%202%202v14a2%202%200%200%201-2%202H6a2%202%200%200%201-2-2V6a2%202%200%200%201%202-2h2%22%20/%3E%0A%3C/svg%3E"); --icon-size: 18px; }
.icon-sun { --icon-url: url("data:image/svg+xml,%3Csvg%0A%20%20xmlns=%22http://www.w3.org/2000/svg%22%0A%20%20width=%2224%22%0A%20%20height=%2224%22%0A%20%20viewBox=%220%200%2024%2024%22%0A%20%20fill=%22none%22%0A%20%20stroke=%22currentColor%22%0A%20%20stroke-width=%222%22%0A%20%20stroke-linecap=%22round%22%0A%20%20stroke-linejoin=%22round%22%0A%3E%0A%20%20%3Cpath%20d=%22M11.017%202.814a1%201%200%200%201%201.966%200l1.051%205.558a2%202%200%200%200%201.594%201.594l5.558%201.051a1%201%200%200%201%200%201.966l-5.558%201.051a2%202%200%200%200-1.594%201.594l-1.051%205.558a1%201%200%200%201-1.966%200l-1.051-5.558a2%202%200%200%200-1.594-1.594l-5.558-1.051a1%201%200%200%201%200-1.966l5.558-1.051a2%202%200%200%200%201.594-1.594z%22%20/%3E%0A%20%20%3Cpath%20d=%22M20%202v4%22%20/%3E%0A%20%20%3Cpath%20d=%22M22%204h-4%22%20/%3E%0A%20%20%3Ccircle%20cx=%224%22%20cy=%2220%22%20r=%222%22%20/%3E%0A%3C/svg%3E"); --icon-size: 22px; }
.app { position: relative; height: 100vh; background: var(--bg); }
.main { min-width: 0; height: 100vh; margin-left: var(--expanded-width); display: flex; align-items: flex-start; justify-content: center; padding: 28px 32px; }
.main-title { margin: 0; font-size: 34px; line-height: 1.1; font-weight: 620; letter-spacing: -0.04em; }
.main-title span { color: var(--muted); font-weight: 430; }
/* 工作区:窄、扁平、无大卡片嵌套。展开的对话栏会完整盖住它。 */
.workspace-panel {
position: fixed;
left: var(--collapsed-width);
top: 0;
z-index: 20;
width: var(--workspace-width);
height: 100vh;
background: var(--panel-bg);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
padding: 18px 10px 14px;
overflow: hidden;
transition: background 160ms ease, border-color 160ms ease;
}
.workspace-title {
height: 40px;
margin: 0;
display: flex;
align-items: center;
padding: 0 0 0 16px;
font-size: 18px;
font-weight: 660;
letter-spacing: -0.035em;
white-space: nowrap;
}
.workspace-controls {
display: grid;
grid-template-columns: minmax(0, 1fr) 40px;
gap: 4px;
margin: 4px 0 10px;
}
.workspace-switch,
.workspace-manage {
height: 40px;
border: 0;
border-radius: 12px;
background: transparent;
color: var(--text);
cursor: pointer;
transition: background 140ms ease, color 140ms ease;
}
.workspace-switch:hover,
.workspace-manage:hover { background: var(--hover); }
.workspace-switch {
min-width: 0;
display: grid;
grid-template-columns: 32px minmax(0, 1fr) 18px;
align-items: center;
padding: 0 10px 0 8px;
text-align: left;
font-size: 14px;
letter-spacing: -0.012em;
}
.workspace-switch .icon { justify-self: center; color: var(--text); }
.workspace-switch-label { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chevron { width: 8px; height: 8px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(45deg) translateY(-2px); opacity: .55; justify-self: center; }
.workspace-manage { display: grid; place-items: center; color: var(--text); }
.workspace-body { min-height: 0; flex: 1 1 auto; overflow: hidden; display: flex; flex-direction: column; }
.workspace-view { display: none; min-height: 0; flex: 1 1 auto; overflow-y: auto; scrollbar-width: thin; padding-top: 2px; }
.workspace-view.active { display: block; }
.content-row {
width: 100%;
height: 36px;
border-radius: 10px;
display: grid;
align-items: center;
color: var(--text);
font-size: 13px;
letter-spacing: -0.01em;
overflow: hidden;
}
.content-row + .content-row { margin-top: 1px; }
.content-main { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.content-meta { color: var(--muted); font-size: 12px; white-space: nowrap; }
/* 文件树是真实可操作树:保留文件/文件夹图标和 hover其他内容区不做按钮感。 */
.file-row {
grid-template-columns: 20px 20px minmax(0, 1fr) auto;
gap: 4px;
padding: 0 8px 0 2px;
cursor: pointer;
transition: background 140ms ease;
}
.file-row:hover { background: var(--hover); }
.file-row .icon { color: var(--muted); justify-self: center; }
.file-arrow { width: 16px; text-align: center; color: var(--muted); font-size: 11px; }
.file-row.depth-1 { padding-left: 16px; }
.file-row.depth-2 { padding-left: 30px; }
.plain-row {
grid-template-columns: minmax(0, 1fr) auto;
gap: 8px;
padding: 0 10px;
cursor: default;
}
.option-row {
height: 44px;
grid-template-columns: 20px minmax(0, 1fr) auto;
gap: 8px;
padding: 0 10px 0 8px;
cursor: pointer;
transition: background 140ms ease, color 140ms ease;
}
.option-row:hover { background: var(--hover); }
.option-row.selected { background: var(--hover); }
.option-marker {
width: 13px;
height: 13px;
border-radius: 999px;
border: 1.5px solid var(--muted);
justify-self: center;
position: relative;
opacity: .85;
}
.option-row.selected .option-marker {
border-color: var(--text);
opacity: 1;
}
.option-row.selected .option-marker::after {
content: '';
position: absolute;
inset: 3px;
border-radius: inherit;
background: var(--text);
}
.host-copy { min-width: 0; display: grid; grid-template-rows: 18px 16px; }
.host-label { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 13px; font-weight: 560; }
.host-path { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--muted); font-size: 12px; }
.host-badges { display: inline-flex; gap: 4px; align-items: center; }
.host-badge { color: var(--muted); font-size: 11px; }
.todo-instruction {
margin-top: 8px;
padding: 0 10px;
color: var(--muted);
font-size: 12px;
line-height: 1.35;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.empty-row {
height: 36px;
display: flex;
align-items: center;
padding: 0 10px;
color: var(--muted);
font-size: 13px;
}
/* 侧边栏:同一张完整侧边栏被裁切暴露。 */
.sidebar-frame {
position: fixed;
left: 0;
top: 0;
z-index: 50;
width: var(--collapsed-width);
height: 100vh;
overflow: hidden;
background: var(--panel-bg);
border-right: 1px solid var(--border);
transition: width var(--anim), background 160ms ease, border-color 160ms ease;
will-change: width;
}
.sidebar-frame.open { width: var(--expanded-width); }
.sidebar-content { width: var(--expanded-width); height: 100%; background: var(--panel-bg); display: flex; flex-direction: column; padding: 18px 8px 14px 6px; transition: background 160ms ease; }
.sidebar-top { display: flex; flex-direction: column; gap: 2px; flex: 0 0 auto; }
.sidebar-bottom { margin-top: 8px; flex: 0 0 auto; }
.side-row,
.search-shell,
.conversation-row { width: 100%; min-height: var(--row-height); border: 0; border-radius: var(--row-radius); background: transparent; color: var(--text); display: grid; grid-template-columns: var(--icon-cell) minmax(0, 1fr); align-items: center; padding: 0; text-align: left; }
.side-row,
.conversation-row { cursor: pointer; transition: background 140ms ease, color 140ms ease; }
.side-row:hover,
.conversation-row:hover { background: transparent; }
.side-icon { width: var(--icon-cell); height: var(--row-height); border-radius: var(--row-radius); display: grid; place-items: center; color: currentColor; flex: 0 0 auto; transition: background 140ms ease; }
.sidebar-frame:not(.open) .side-row:hover .side-icon,
.sidebar-frame:not(.open) .theme-toggle:hover .side-icon { background: var(--hover); }
.sidebar-frame.open .side-row:hover,
.sidebar-frame.open .side-row.active,
.sidebar-frame.open .conversation-row:hover,
.sidebar-frame.open .conversation-row.menu-open { background: var(--hover); }
.side-label,
.search-area,
.conversation-list,
.conversation-title,
.conversation-more { opacity: 0; transition: opacity 220ms ease; transition-delay: 0ms; }
.sidebar-frame.open .side-label,
.sidebar-frame.open .search-area,
.sidebar-frame.open .conversation-list,
.sidebar-frame.open .conversation-title,
.sidebar-frame.open .conversation-more { opacity: 1; transition-delay: 80ms; }
.side-label { min-width: 0; padding-left: 8px; padding-right: 12px; font-size: 15px; line-height: 1; letter-spacing: -0.015em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.search-area { flex: 0 0 auto; margin-top: 10px; }
.search-shell { height: var(--row-height); overflow: hidden; transition: background 140ms ease; }
.search-shell:focus-within,
.search-shell:hover { background: var(--hover); }
.search-input { min-width: 0; width: calc(var(--expanded-width) - var(--icon-cell) - 22px); height: var(--row-height); border: 0; outline: 0; padding: 0 12px 0 8px; background: transparent; color: var(--text); font-size: 14px; letter-spacing: -0.01em; }
.search-input::placeholder { color: var(--muted); }
.conversation-list { min-height: 0; flex: 1 1 auto; overflow-y: auto; padding-top: 12px; padding-right: 0; scrollbar-width: thin; }
.conversation-row { position: relative; grid-template-columns: minmax(0, 1fr) 40px; min-height: var(--row-height); margin: 0; }
.conversation-title { min-width: 0; padding-left: 12px; padding-right: 4px; color: var(--text); font-size: 14px; line-height: 1.25; letter-spacing: -0.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.conversation-more { width: 30px; height: 30px; border: 0; border-radius: 9px; background: transparent; color: var(--muted); display: grid; place-items: center; cursor: pointer; justify-self: start; visibility: hidden; transition: color 140ms ease, opacity 160ms ease; }
.conversation-row:hover .conversation-more,
.conversation-row.menu-open .conversation-more { visibility: visible; }
.conversation-more:hover,
.conversation-row.menu-open .conversation-more { background: transparent; color: var(--text); }
.more-dots {
position: relative;
display: inline-block;
width: 22px;
height: 22px;
}
.more-dots::before {
content: '';
position: absolute;
left: 4px;
top: 10px;
width: 3.5px;
height: 3.5px;
border-radius: 999px;
background: currentColor;
box-shadow: 5.5px 0 0 currentColor, 11px 0 0 currentColor;
}
.floating-menu {
position: fixed;
z-index: 80;
width: max-content;
min-width: 92px;
padding: 6px;
border: 1px solid var(--border);
border-radius: 14px;
background: var(--menu-bg);
box-shadow: var(--menu-shadow);
opacity: 0;
transform: translateY(-4px) scale(0.98);
pointer-events: none;
transition: opacity 120ms ease, transform 150ms cubic-bezier(0.22, 1, 0.36, 1), background 160ms ease, border-color 160ms ease;
}
.floating-menu.open { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
.menu-button { width: max-content; min-width: 80px; height: 36px; border: 0; border-radius: 10px; background: transparent; color: var(--text); display: grid; grid-template-columns: 24px auto; gap: 4px; align-items: center; padding: 0 10px 0 8px; text-align: left; cursor: pointer; font-size: 14px; letter-spacing: -0.01em; transition: background 140ms ease; }
.menu-button:hover { background: var(--hover); }
.menu-button.danger { color: #d6483b; }
:root[data-theme="dark"] .menu-button.danger { color: #ff7b72; }
.demo-hint { position: fixed; left: calc(var(--expanded-width) + 20px); bottom: 18px; color: var(--muted); font-size: 13px; pointer-events: none; }
</style>
</head>
<body>
<div class="app">
<aside class="workspace-panel" aria-label="工作区">
<h2 class="workspace-title" id="workspaceTitle">工作区</h2>
<div class="workspace-controls">
<button class="workspace-switch" id="workspaceSwitch" type="button" aria-haspopup="menu" aria-expanded="false">
<span class="icon icon-folder-open" id="workspaceSwitchIcon" aria-hidden="true"></span>
<span class="workspace-switch-label" id="workspaceSwitchLabel">工作区文件</span>
<span class="chevron" aria-hidden="true"></span>
</button>
<button class="workspace-manage" type="button" aria-label="管理工作区">
<span class="icon icon-folder" aria-hidden="true"></span>
</button>
</div>
<div class="workspace-body">
<div class="workspace-view active" data-view="files">
<div class="content-row file-row"><span class="file-arrow"></span><span class="icon icon-folder-open"></span><span class="content-main">static</span><span class="content-meta"></span></div>
<div class="content-row file-row depth-1"><span class="file-arrow"></span><span class="icon icon-file"></span><span class="content-main">App.vue</span><span class="content-meta"></span></div>
<div class="content-row file-row depth-1"><span class="file-arrow"></span><span class="icon icon-folder-open"></span><span class="content-main">components</span><span class="content-meta"></span></div>
<div class="content-row file-row depth-2"><span class="file-arrow"></span><span class="icon icon-file"></span><span class="content-main">ConversationSidebar.vue</span><span class="content-meta"></span></div>
<div class="content-row file-row depth-2"><span class="file-arrow"></span><span class="icon icon-file"></span><span class="content-main">LeftPanel.vue</span><span class="content-meta"></span></div>
<div class="content-row file-row"><span class="file-arrow"></span><span class="icon icon-folder"></span><span class="content-main">server</span><span class="content-meta"></span></div>
<div class="content-row file-row"><span class="file-arrow"></span><span class="icon icon-file"></span><span class="content-main">main.py</span><span class="content-meta"></span></div>
</div>
<div class="workspace-view" data-view="host">
<div class="content-row option-row selected"><span class="option-marker" aria-hidden="true"></span><span class="host-copy"><span class="host-label">agents</span><span class="host-path">/Users/jojo/Desktop/agents/正在修复中/agents</span></span><span class="host-badges"><span class="host-badge">默认</span><span class="host-badge">当前</span></span></div>
<div class="content-row option-row"><span class="option-marker" aria-hidden="true"></span><span class="host-copy"><span class="host-label">前端工作区</span><span class="host-path">/Users/jojo/Desktop/frontend</span></span><span class="host-badges"></span></div>
<div class="content-row option-row"><span class="option-marker" aria-hidden="true"></span><span class="host-copy"><span class="host-label">未配置路径</span><span class="host-path">(未配置路径)</span></span><span class="host-badges"></span></div>
</div>
<div class="workspace-view" data-view="agents">
<div class="content-row plain-row"><span class="content-main">#1 正在整理侧边栏与工作区样式</span><span class="content-meta">running</span></div>
<div class="content-row plain-row"><span class="content-main">#2 等待下一轮验证</span><span class="content-meta">idle</span></div>
<div class="content-row plain-row"><span class="content-main">当前read_file</span><span class="content-meta"></span></div>
</div>
<div class="workspace-view" data-view="commands">
<div class="content-row plain-row"><span class="content-main">npm --prefix cli run typecheck</span><span class="content-meta">completed</span></div>
<div class="content-row plain-row"><span class="content-main">python -m unittest test.test_server_refactor_smoke</span><span class="content-meta">running</span></div>
<div class="content-row plain-row"><span class="content-main">状态码:--</span><span class="content-meta"></span></div>
</div>
<div class="workspace-view" data-view="todos">
<div class="content-row plain-row"><span class="content-main">task1确定侧边栏动画模型</span><span class="content-meta">完成</span></div>
<div class="content-row plain-row"><span class="content-main">task2工作区视觉改造</span><span class="content-meta">未完成</span></div>
<div class="content-row plain-row"><span class="content-main">task3代码块精致化</span><span class="content-meta">未完成</span></div>
<div class="todo-instruction">指令:按真实数据结构设计显示区域,不添加额外说明卡片。</div>
</div>
</div>
</aside>
<aside class="sidebar-frame" id="sidebar" aria-label="对话侧边栏">
<div class="sidebar-content">
<div class="sidebar-top">
<button class="side-row" id="toggleSidebar" type="button" aria-label="展开对话记录" aria-expanded="false">
<span class="side-icon"><span class="icon icon-chat" aria-hidden="true"></span></span>
<span class="side-label">对话记录</span>
</button>
<button class="side-row" type="button" aria-label="新建对话"><span class="side-icon"><span class="icon icon-pencil"></span></span><span class="side-label">新建对话</span></button>
<button class="side-row" type="button" aria-label="工作区"><span class="side-icon"><span class="icon icon-layers"></span></span><span class="side-label">工作区</span></button>
<button class="side-row" type="button" aria-label="虚拟显示器"><span class="side-icon"><span class="icon icon-monitor"></span></span><span class="side-label">虚拟显示器</span></button>
</div>
<div class="search-area" aria-label="搜索对话"><label class="search-shell"><span class="side-icon"><span class="icon icon-search"></span></span><input class="search-input" type="search" placeholder="搜索对话" /></label></div>
<div class="conversation-list" aria-label="对话记录">
<div class="conversation-row"><span class="conversation-title">随意的 py 代码块</span><button class="conversation-more" type="button" aria-label="更多操作"><span class="more-dots" aria-hidden="true"></span></button></div>
<div class="conversation-row"><span class="conversation-title">前端现代化设计讨论</span><button class="conversation-more" type="button" aria-label="更多操作"><span class="more-dots" aria-hidden="true"></span></button></div>
<div class="conversation-row"><span class="conversation-title">审批面板覆盖窗口方案</span><button class="conversation-more" type="button" aria-label="更多操作"><span class="more-dots" aria-hidden="true"></span></button></div>
<div class="conversation-row"><span class="conversation-title">CLI 显示层细节修复</span><button class="conversation-more" type="button" aria-label="更多操作"><span class="more-dots" aria-hidden="true"></span></button></div>
<div class="conversation-row"><span class="conversation-title">个人空间设置页重设计</span><button class="conversation-more" type="button" aria-label="更多操作"><span class="more-dots" aria-hidden="true"></span></button></div>
</div>
<div class="sidebar-bottom">
<button class="side-row theme-toggle" id="themeToggle" type="button" aria-label="切换深色模式"><span class="side-icon"><span class="icon icon-sun"></span></span><span class="side-label">浅色模式</span></button>
<button class="side-row" type="button" aria-label="个人空间"><span class="side-icon"><span class="icon icon-user"></span></span><span class="side-label">个人空间</span></button>
</div>
</div>
</aside>
<main class="main"><h1 class="main-title">AI Agent <span>Auto</span></h1></main>
<div class="floating-menu" id="conversationMenu" role="menu" aria-hidden="true">
<button class="menu-button" type="button" role="menuitem"><span class="icon icon-copy"></span><span>复制</span></button>
<button class="menu-button danger" type="button" role="menuitem"><span class="icon icon-trash"></span><span>删除</span></button>
</div>
<div class="floating-menu" id="workspaceMenu" role="menu" aria-hidden="true">
<button class="menu-button" type="button" data-view-target="files"><span class="icon icon-folder-open"></span><span>工作区文件</span></button>
<button class="menu-button" type="button" data-view-target="host"><span class="icon icon-laptop"></span><span>宿主机工作区</span></button>
<button class="menu-button" type="button" data-view-target="agents"><span class="icon icon-bot"></span><span>子智能体</span></button>
<button class="menu-button" type="button" data-view-target="commands"><span class="icon icon-terminal"></span><span>后台指令</span></button>
<button class="menu-button" type="button" data-view-target="todos"><span class="icon icon-sticky"></span><span>待办事项</span></button>
</div>
</div>
<div class="demo-hint">工作区为扁平窄面板;展开侧边栏会完整盖住工作区。</div>
<script>
const sidebar = document.getElementById('sidebar');
const toggle = document.getElementById('toggleSidebar');
const themeToggle = document.getElementById('themeToggle');
const conversationMenu = document.getElementById('conversationMenu');
const workspaceMenu = document.getElementById('workspaceMenu');
const workspaceSwitch = document.getElementById('workspaceSwitch');
const workspaceTitle = document.getElementById('workspaceTitle');
const workspaceSwitchLabel = document.getElementById('workspaceSwitchLabel');
const workspaceSwitchIcon = document.getElementById('workspaceSwitchIcon');
let conversationOwner = null;
const viewMeta = {
files: { title: '工作区', label: '工作区文件', icon: 'icon-folder-open' },
host: { title: '宿主机工作区', label: '宿主机工作区', icon: 'icon-laptop' },
agents: { title: '子智能体', label: '子智能体', icon: 'icon-bot' },
commands: { title: '后台指令', label: '后台指令', icon: 'icon-terminal' },
todos: { title: '待办事项', label: '待办事项', icon: 'icon-sticky' }
};
toggle.addEventListener('click', () => {
const isOpen = sidebar.classList.toggle('open');
toggle.setAttribute('aria-expanded', String(isOpen));
toggle.setAttribute('aria-label', isOpen ? '收起对话记录' : '展开对话记录');
if (!isOpen) closeConversationMenu();
});
themeToggle.addEventListener('click', () => {
const root = document.documentElement;
const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
root.dataset.theme = next;
themeToggle.querySelector('.side-label').textContent = next === 'dark' ? '深色模式' : '浅色模式';
});
function positionMenu(menu, anchor) {
const rect = anchor.getBoundingClientRect();
menu.style.left = '0px';
menu.style.top = `${rect.bottom + 6}px`;
menu.classList.add('open');
menu.setAttribute('aria-hidden', 'false');
const width = menu.offsetWidth;
menu.style.left = `${Math.min(rect.right - width, window.innerWidth - width - 12)}px`;
}
function closeConversationMenu() {
conversationMenu.classList.remove('open');
conversationMenu.setAttribute('aria-hidden', 'true');
if (conversationOwner) conversationOwner.classList.remove('menu-open');
conversationOwner = null;
}
function closeWorkspaceMenu() {
workspaceMenu.classList.remove('open');
workspaceMenu.setAttribute('aria-hidden', 'true');
workspaceSwitch.setAttribute('aria-expanded', 'false');
}
document.querySelectorAll('.conversation-more').forEach((button) => {
button.addEventListener('click', (event) => {
event.stopPropagation();
closeWorkspaceMenu();
const row = button.closest('.conversation-row');
if (conversationOwner === row && conversationMenu.classList.contains('open')) {
closeConversationMenu();
return;
}
closeConversationMenu();
conversationOwner = row;
row.classList.add('menu-open');
positionMenu(conversationMenu, button);
});
});
workspaceSwitch.addEventListener('click', (event) => {
event.stopPropagation();
closeConversationMenu();
if (workspaceMenu.classList.contains('open')) {
closeWorkspaceMenu();
} else {
workspaceSwitch.setAttribute('aria-expanded', 'true');
positionMenu(workspaceMenu, workspaceSwitch);
}
});
workspaceMenu.querySelectorAll('[data-view-target]').forEach((button) => {
button.addEventListener('click', () => {
const view = button.dataset.viewTarget;
document.querySelectorAll('.workspace-view').forEach((node) => node.classList.toggle('active', node.dataset.view === view));
const meta = viewMeta[view];
workspaceTitle.textContent = meta.title;
workspaceSwitchLabel.textContent = meta.label;
workspaceSwitchIcon.className = `icon ${meta.icon}`;
closeWorkspaceMenu();
});
});
document.addEventListener('click', (event) => {
if (!conversationMenu.contains(event.target)) closeConversationMenu();
if (!workspaceMenu.contains(event.target) && !workspaceSwitch.contains(event.target)) closeWorkspaceMenu();
});
window.addEventListener('resize', () => {
closeConversationMenu();
closeWorkspaceMenu();
});
</script>
</body>
</html>

View File

@ -226,7 +226,10 @@
>
默认工作区
</span>
<span v-if="item.workspace_id === hostWorkspaceId" class="host-workspace-badge current">
<span
v-if="item.workspace_id === hostWorkspaceId"
class="host-workspace-badge current"
>
当前
</span>
</span>
@ -237,7 +240,9 @@
</button>
</div>
<div v-else class="file-tree-empty">暂无工作区点击右侧 + 新建</div>
<div class="host-workspace-hint" v-if="hostWorkspaceSwitching">正在切换请稍候</div>
<div class="host-workspace-hint" v-if="hostWorkspaceSwitching">
正在切换请稍候
</div>
</div>
<div v-else class="file-tree-empty">{{ fileTreeMessage }}</div>
</template>

View File

@ -1,49 +1,43 @@
<template>
<aside class="conversation-sidebar" :class="{ collapsed, 'monitor-mode': monitorModeActive }">
<div class="conversation-header" :class="{ 'collapsed-layout': collapsed }">
<template v-if="collapsed">
<div class="collapsed-header-buttons">
<div class="conversation-sidebar-inner">
<div class="conversation-primary-actions">
<button
type="button"
class="collapsed-control-btn conversation-menu-btn"
title="展开对话记录"
class="sidebar-nav-row conversation-menu-btn"
:title="collapsed ? '展开对话记录' : '收起对话记录'"
@click="$emit('toggle')"
>
<span class="sr-only">展开对话记录</span>
<span class="chat-icon" data-tutorial="conversation-menu" aria-hidden="true">
<slot name="collapsed-chat-icon">
<span
class="sidebar-nav-icon chat-icon"
data-tutorial="conversation-menu"
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-width="1.45"
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-width="1.45"
stroke-linecap="round"
/>
<path d="M9 13h6" stroke="currentColor" stroke-width="1.45" stroke-linecap="round" />
</svg>
</slot>
</span>
<span class="sidebar-nav-label">对话记录</span>
</button>
<button
type="button"
class="collapsed-control-btn quick-plus-btn"
title="快捷新建对话"
@click="$emit('create')"
<button type="button" class="sidebar-nav-row" title="新建对话" @click="$emit('create')">
<span
class="sidebar-nav-icon pencil-icon"
data-tutorial="quick-new-conversation"
aria-hidden="true"
>
<span class="sr-only">新建对话</span>
<span class="pencil-icon" data-tutorial="quick-new-conversation" aria-hidden="true">
<slot name="collapsed-create-icon">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
@ -51,131 +45,62 @@
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.8"
stroke-width="1.6"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"
fill="none"
/>
<path d="m15 5 4 4" fill="none" />
<path d="m15 5 4 4" />
</svg>
</slot>
</span>
<span class="sidebar-nav-label">新建对话</span>
</button>
<button
type="button"
class="collapsed-control-btn workspace-control-btn"
class="sidebar-nav-row workspace-control-btn"
:class="{ active: workspaceCollapsed }"
:title="workspaceCollapsed ? '显示工作区' : '隐藏工作区'"
@click="$emit('toggle-workspace')"
>
<span class="sr-only">{{ workspaceCollapsed ? '显示工作区' : '隐藏工作区' }}</span>
<span
class="icon icon-md"
data-tutorial="workspace-toggle"
aria-hidden="true"
:style="iconStyle('layers')"
></span>
<span class="sidebar-nav-icon" data-tutorial="workspace-toggle" aria-hidden="true">
<span class="icon icon-md" :style="iconStyle('layers')"></span>
</span>
<span class="sidebar-nav-label">工作区</span>
</button>
<button
type="button"
class="collapsed-control-btn monitor-mode-btn"
class="sidebar-nav-row monitor-mode-btn"
:class="{ active: displayMode === 'monitor', blocked: displayModeDisabled }"
@click="$emit('toggle-display-mode')"
:aria-disabled="displayModeDisabled"
:title="displayMode === 'monitor' ? '退出显示器' : '虚拟显示器'"
@click="$emit('toggle-display-mode')"
>
<span class="sr-only">{{
displayMode === 'monitor' ? '退出显示器' : '虚拟显示器'
}}</span>
<span
class="icon icon-md"
data-tutorial="monitor-toggle"
aria-hidden="true"
:style="iconStyle('monitor')"
></span>
<span class="sidebar-nav-icon" data-tutorial="monitor-toggle" aria-hidden="true">
<span class="icon icon-md" :style="iconStyle('monitor')"></span>
</span>
<span class="sidebar-nav-label">虚拟显示器</span>
</button>
</div>
</template>
<template v-else>
<button
class="new-conversation-btn"
:class="{ 'icon-only': monitorModeActive }"
:title="monitorModeActive ? '新建对话' : undefined"
@click="$emit('create')"
>
<template v-if="monitorModeActive">
<span
class="monitor-icon-slot icon icon-md"
:style="iconStyle('pencil')"
aria-hidden="true"
></span>
<span class="sr-only">新建对话</span>
</template>
<template v-else>
<span class="btn-icon">+</span>
<span class="btn-text">新建对话</span>
</template>
</button>
<button
v-if="resolvedShowCollapseButton"
class="toggle-sidebar-btn"
data-tutorial="conversation-collapse"
:class="{ 'icon-only': monitorModeActive }"
:title="monitorModeActive ? (collapsed ? '展开对话记录' : '折叠对话记录') : undefined"
@click="$emit('toggle')"
>
<template v-if="resolvedCollapseButtonVariant === 'toggle'">
<template v-if="monitorModeActive">
<span
class="monitor-icon-slot icon icon-md"
:style="iconStyle(collapsed ? 'menu' : 'x')"
aria-hidden="true"
></span>
<span class="sr-only">{{ collapsed ? '展开对话记录' : '折叠对话记录' }}</span>
</template>
<template v-else>
<span
v-if="collapsed"
class="icon icon-md"
:style="iconStyle('menu')"
aria-hidden="true"
></span>
<span v-else class="toggle-arrow" aria-hidden="true"></span>
</template>
</template>
<template v-else>
<template v-if="monitorModeActive">
<span
class="monitor-icon-slot icon icon-md"
:style="iconStyle('x')"
aria-hidden="true"
></span>
<span class="sr-only">关闭侧边栏</span>
</template>
<template v-else>
<span class="toggle-close" aria-hidden="true">×</span>
</template>
</template>
</button>
</template>
</div>
<template v-if="!collapsed">
<div class="conversation-search">
<div class="search-input-wrap">
<label class="search-input-wrap">
<span class="sidebar-nav-icon search-inline-icon" aria-hidden="true">
<span class="icon icon-sm" :style="iconStyle('search')"></span>
</span>
<input
class="search-input"
:value="searchQuery"
placeholder="搜索对话..."
placeholder="搜索对话"
@input="$emit('search', ($event.target as HTMLInputElement).value)"
@keydown.enter.prevent="
$emit('search-submit', ($event.target as HTMLInputElement).value)
"
/>
</div>
</label>
</div>
<div class="conversation-list">
@ -194,33 +119,53 @@
:key="conv.id"
class="conversation-item"
:class="{ active: conv.id === currentConversationId }"
@click="$emit('select', conv.id)"
@click="
openActionMenuId = null;
$emit('select', conv.id);
"
>
<div class="conversation-title">{{ conv.title }}</div>
<div class="conversation-meta">
<span class="conversation-time">{{ formatTime(conv.updated_at) }}</span>
<span class="conversation-counts">
{{ conv.total_messages || 0 }}条消息
<span v-if="(conv.total_tools || 0) > 0"> · {{ conv.total_tools }}工具</span>
</span>
<div
class="conversation-action-wrap"
:class="{ 'menu-open': openActionMenuId === conv.id }"
@click.stop
>
<button
type="button"
class="conversation-more-btn"
title="更多操作"
aria-label="更多操作"
:aria-expanded="openActionMenuId === conv.id"
@click="toggleActionMenu(conv.id)"
>
<span aria-hidden="true"></span>
</button>
<div
class="conversation-actions-menu"
:class="{ open: openActionMenuId === conv.id }"
>
<button
type="button"
@click="
openActionMenuId = null;
$emit('duplicate', conv.id);
"
>
<span class="icon icon-sm" :style="iconStyle('copy')" aria-hidden="true"></span>
<span>复制</span>
</button>
<button
type="button"
class="danger"
@click="
openActionMenuId = null;
$emit('delete', conv.id);
"
>
<span class="icon icon-sm" :style="iconStyle('trash')" aria-hidden="true"></span>
<span>删除</span>
</button>
</div>
<div class="conversation-actions" @click.stop>
<button
type="button"
class="conversation-action-btn delete-btn"
title="删除对话"
@click="$emit('delete', conv.id)"
>
×
</button>
<button
type="button"
class="conversation-action-btn copy-btn"
title="复制对话"
@click="$emit('duplicate', conv.id)"
>
</button>
</div>
</div>
</div>
@ -254,43 +199,35 @@
<div v-else-if="displayConversations.length" class="search-done">已全部搜索</div>
</div>
</div>
</template>
<template v-else>
<div class="conversation-collapsed-spacer"></div>
</template>
<div class="conversation-personal-entry" :class="{ collapsed, active: personalPageVisible }">
<div class="conversation-personal-entry" :class="{ active: personalPageVisible }">
<button
type="button"
class="personal-page-btn"
class="sidebar-nav-row personal-page-btn"
data-tutorial="open-personal-space"
:class="{ 'icon-only': collapsed }"
title="个人页面"
title="个人空间"
@click="$emit('personal')"
>
<span class="sr-only">个人页面</span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<span class="sidebar-nav-icon" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<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="!collapsed">个人空间</span>
</span>
<span class="sidebar-nav-label personal-label">个人空间</span>
</button>
</div>
</div>
</aside>
</template>
<script setup lang="ts">
defineOptions({ name: 'ConversationSidebar' });
import { computed } from 'vue';
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useUiStore } from '@/stores/ui';
import { useConversationStore } from '@/stores/conversation';
@ -325,6 +262,7 @@ defineEmits<{
(event: 'delete', id: string): void;
(event: 'duplicate', id: string): void;
(event: 'toggle-workspace'): void;
(event: 'toggle-display-mode'): void;
}>();
const uiStore = useUiStore();
@ -350,15 +288,30 @@ const {
} = storeToRefs(conversationStore);
const { visible: personalPageVisible } = storeToRefs(personalizationStore);
const formatTime = (value: unknown) => (props.formatTime ? props.formatTime(value) : String(value));
const iconStyle = (key: string) => (props.iconStyle ? props.iconStyle(key) : {});
const resolvedShowCollapseButton = computed(() => props.showCollapseButton);
const resolvedCollapseButtonVariant = computed(() => props.collapseButtonVariant);
const displayMode = computed(() => props.displayMode);
const monitorModeActive = computed(
() => displayMode.value === 'monitor' || uiChatDisplayMode.value === 'monitor'
);
const displayModeDisabled = computed(() => props.displayModeDisabled);
const openActionMenuId = ref<string | null>(null);
const toggleActionMenu = (conversationId: string) => {
openActionMenuId.value = openActionMenuId.value === conversationId ? null : conversationId;
};
const closeActionMenu = () => {
openActionMenuId.value = null;
};
onMounted(() => {
document.addEventListener('click', closeActionMenu);
});
onBeforeUnmount(() => {
document.removeEventListener('click', closeActionMenu);
});
const displayConversations = computed(() =>
searchActive.value ? searchResults.value : conversations.value
);

View File

@ -41,8 +41,18 @@
bottom: -8px;
height: 16px;
background: var(--chat-surface-color);
mask-image: linear-gradient(180deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.6) 55%, rgba(0, 0, 0, 0) 100%);
-webkit-mask-image: linear-gradient(180deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.6) 55%, rgba(0, 0, 0, 0) 100%);
mask-image: linear-gradient(
180deg,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 0.6) 55%,
rgba(0, 0, 0, 0) 100%
);
-webkit-mask-image: linear-gradient(
180deg,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 0.6) 55%,
rgba(0, 0, 0, 0) 100%
);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
pointer-events: none;
@ -53,7 +63,7 @@
font-size: 15px;
line-height: 1.4;
letter-spacing: 0.02em;
font-family: 'Iowan Old Style', ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
font-family: 'Iowan Old Style', ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
max-width: 60%;
color: #0f172a;
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.12);
@ -132,7 +142,9 @@
.scroll-to-bottom-fade-enter-active,
.scroll-to-bottom-fade-leave-active {
transition: opacity 0.2s ease, transform 0.2s ease;
transition:
opacity 0.2s ease,
transform 0.2s ease;
}
.scroll-to-bottom-fade-enter-from,
@ -180,7 +192,6 @@
.assistant-message .message-text {
padding: 16px 8px !important;
}
}
/* 额外的手机端样式,不依赖 mobile 类 */
@ -205,7 +216,7 @@
font-weight: 600;
font-size: 15px;
line-height: 1.35;
font-family: 'Iowan Old Style', ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
font-family: 'Iowan Old Style', ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
cursor: pointer;
transition: color 0.15s ease;
}
@ -364,7 +375,9 @@
.header-menu-enter-active,
.header-menu-leave-active {
transition: all 0.16s ease, opacity 0.16s ease;
transition:
all 0.16s ease,
opacity 0.16s ease;
transform-origin: top left;
}
@ -1010,7 +1023,10 @@ body[data-theme='dark'] .more-icon {
font-size: 15px;
line-height: 1;
cursor: pointer;
transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease;
transition:
color 0.18s ease,
border-color 0.18s ease,
background 0.18s ease;
}
.chat-inline-card__refresh-btn:hover {
@ -1054,12 +1070,16 @@ body[data-theme='dark'] .more-icon {
}
/* 兼容旧类名(可逐步移除) */
.chat-inline-image__error { @extend .chat-inline-card__error; }
.chat-inline-image figcaption { @extend .chat-inline-card__caption; }
.chat-inline-image__error {
@extend .chat-inline-card__error;
}
.chat-inline-image figcaption {
@extend .chat-inline-card__caption;
}
/* show_html 在 streaming 重建期间会短暂回到原始标签,需提供稳定占位避免 scrollHeight 抖动 */
show_html:not([data-rendered="1"]),
show-html:not([data-rendered="1"]) {
show_html:not([data-rendered='1']),
show-html:not([data-rendered='1']) {
display: block;
width: 100%;
max-width: 100%;
@ -1073,16 +1093,26 @@ show-html:not([data-rendered="1"]) {
box-shadow: var(--claude-shadow);
}
show_html:not([data-rendered="1"])[ratio="1:1"],
show-html:not([data-rendered="1"])[ratio="1:1"] { aspect-ratio: 1 / 1; }
show_html:not([data-rendered="1"])[ratio="16:9"],
show-html:not([data-rendered="1"])[ratio="16:9"] { aspect-ratio: 16 / 9; }
show_html:not([data-rendered="1"])[ratio="9:16"],
show-html:not([data-rendered="1"])[ratio="9:16"] { aspect-ratio: 9 / 16; }
show_html:not([data-rendered="1"])[ratio="4:3"],
show-html:not([data-rendered="1"])[ratio="4:3"] { aspect-ratio: 4 / 3; }
show_html:not([data-rendered="1"])[ratio="3:4"],
show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
show_html:not([data-rendered='1'])[ratio='1:1'],
show-html:not([data-rendered='1'])[ratio='1:1'] {
aspect-ratio: 1 / 1;
}
show_html:not([data-rendered='1'])[ratio='16:9'],
show-html:not([data-rendered='1'])[ratio='16:9'] {
aspect-ratio: 16 / 9;
}
show_html:not([data-rendered='1'])[ratio='9:16'],
show-html:not([data-rendered='1'])[ratio='9:16'] {
aspect-ratio: 9 / 16;
}
show_html:not([data-rendered='1'])[ratio='4:3'],
show-html:not([data-rendered='1'])[ratio='4:3'] {
aspect-ratio: 4 / 3;
}
show_html:not([data-rendered='1'])[ratio='3:4'],
show-html:not([data-rendered='1'])[ratio='3:4'] {
aspect-ratio: 3 / 4;
}
.text-output .text-content {
padding: 0 20px 0 15px;
@ -1092,9 +1122,13 @@ show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
}
.text-output pre {
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Consolas', 'Menlo', 'Monaco', 'Courier New', monospace !important;
font-family:
'JetBrains Mono', 'SF Mono', 'Fira Code', 'Consolas', 'Menlo', 'Monaco', 'Courier New',
monospace !important;
font-variant-ligatures: none;
font-feature-settings: 'liga' 0, 'calt' 0;
font-feature-settings:
'liga' 0,
'calt' 0;
line-height: 1.55;
overflow-x: auto;
max-width: 100%;
@ -1103,9 +1137,13 @@ show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
}
.text-output pre code {
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Consolas', 'Menlo', 'Monaco', 'Courier New', monospace !important;
font-family:
'JetBrains Mono', 'SF Mono', 'Fira Code', 'Consolas', 'Menlo', 'Monaco', 'Courier New',
monospace !important;
font-variant-ligatures: none;
font-feature-settings: 'liga' 0, 'calt' 0;
font-feature-settings:
'liga' 0,
'calt' 0;
line-height: 1.55;
text-shadow: none;
animation: none !important; /* 防止外部动画样式影响代码块,导致视觉抖动 */
@ -1241,17 +1279,19 @@ show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
}
.code-block-wrapper {
border: 2px solid var(--claude-border);
border-radius: 12px;
border: 1px solid var(--claude-border);
border-radius: 20px;
overflow: hidden;
margin: 16px 0;
background: var(--theme-surface-soft);
background: var(--theme-surface-strong, #ffffff);
min-height: 80px;
box-shadow: none;
}
.code-block-header {
background: var(--theme-chip-bg);
padding: 10px 16px;
min-height: 44px;
background: var(--theme-surface-strong, #ffffff);
padding: 0 12px 0 18px;
display: flex;
justify-content: space-between;
align-items: center;
@ -1260,8 +1300,10 @@ show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
.code-language {
color: var(--claude-text-secondary);
font-size: 13px;
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
font-size: 14px;
line-height: 1;
letter-spacing: -0.01em;
font-family: inherit;
font-weight: 500;
}
@ -1270,34 +1312,53 @@ show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
height: 32px;
border: none;
padding: 0;
border-radius: 50%;
border-radius: 10px;
background-color: transparent;
background-image: url('/static/icons/copy.svg');
background-repeat: no-repeat;
background-position: center;
background-size: 20px;
color: var(--claude-text-secondary);
cursor: pointer;
transition: all 0.2s ease;
opacity: 0.9;
transition:
background-color 140ms ease,
color 140ms ease;
position: relative;
}
.copy-code-btn::before {
content: '';
width: 18px;
height: 18px;
display: block;
margin: auto;
background: currentColor;
mask: url('/static/icons/copy.svg') center / contain no-repeat;
-webkit-mask: url('/static/icons/copy.svg') center / contain no-repeat;
}
.copy-code-btn:hover {
opacity: 0.7;
background-color: var(--theme-tab-active, rgba(0, 0, 0, 0.055));
color: var(--claude-text);
}
.copy-code-btn.copied {
background-image: url('/static/icons/check.svg');
.copy-code-btn.copied::before {
mask-image: url('/static/icons/check.svg');
-webkit-mask-image: url('/static/icons/check.svg');
}
.code-block-wrapper pre {
background: #ffffff !important;
padding: 16px !important;
background: var(--theme-surface-strong, #ffffff) !important;
padding: 24px 28px 28px !important;
margin: 0 !important;
border-radius: 0 !important;
border: none !important;
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Consolas', 'Menlo', 'Monaco', 'Courier New', monospace !important;
font-family:
'JetBrains Mono', 'SF Mono', 'Fira Code', 'Consolas', 'Menlo', 'Monaco', 'Courier New',
monospace !important;
font-variant-ligatures: none;
font-feature-settings: 'liga' 0, 'calt' 0;
line-height: 1.55;
font-feature-settings:
'liga' 0,
'calt' 0;
font-size: 15px;
line-height: 1.64;
overflow-x: auto;
max-width: 100%;
scrollbar-gutter: stable;
@ -1307,11 +1368,15 @@ show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
.code-block-wrapper pre code {
background: transparent !important;
padding: 0 !important;
color: #000000;
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Consolas', 'Menlo', 'Monaco', 'Courier New', monospace !important;
color: var(--claude-text);
font-family:
'JetBrains Mono', 'SF Mono', 'Fira Code', 'Consolas', 'Menlo', 'Monaco', 'Courier New',
monospace !important;
font-variant-ligatures: none;
font-feature-settings: 'liga' 0, 'calt' 0;
line-height: 1.55;
font-feature-settings:
'liga' 0,
'calt' 0;
line-height: 1.64;
text-shadow: none;
animation: none !important; /* 避免被其他动画样式污染导致行距抖动 */
}
@ -1326,6 +1391,11 @@ show-html:not([data-rendered="1"])[ratio="3:4"] { aspect-ratio: 3 / 4; }
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}

View File

@ -8,7 +8,9 @@
justify-content: center;
z-index: 400;
padding: 20px;
transition: opacity 0.25s ease, backdrop-filter 0.25s ease;
transition:
opacity 0.25s ease,
backdrop-filter 0.25s ease;
will-change: opacity, backdrop-filter;
}
@ -64,7 +66,9 @@
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;
transition:
transform 0.2s ease,
box-shadow 0.2s ease;
}
.personal-page-close:hover {
@ -82,7 +86,9 @@
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s ease, box-shadow 0.2s ease;
transition:
background 0.2s ease,
box-shadow 0.2s ease;
}
.personal-page-logout:hover {
@ -147,7 +153,7 @@
}
.switch-slider::before {
content: "";
content: '';
position: absolute;
left: 4px;
top: 4px;
@ -179,7 +185,10 @@
border: 1px solid var(--theme-control-border);
background: var(--theme-surface-muted);
cursor: pointer;
transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
transition:
border-color 0.2s ease,
box-shadow 0.2s ease,
background 0.2s ease;
position: relative;
}
@ -224,7 +233,10 @@
stroke-width: 5;
stroke-linecap: round;
stroke-linejoin: round;
transition: stroke-dasharray 0.5s ease, stroke-dashoffset 0.5s ease, stroke 0.2s ease;
transition:
stroke-dasharray 0.5s ease,
stroke-dashoffset 0.5s ease,
stroke 0.2s ease;
stroke-dasharray: 241 9999999;
stroke-dashoffset: 0;
}
@ -286,7 +298,10 @@
background: transparent;
color: var(--claude-text-secondary);
cursor: pointer;
transition: background 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
transition:
background 0.2s ease,
color 0.2s ease,
box-shadow 0.2s ease;
}
.personal-tab-desc {
@ -851,9 +866,15 @@
border-radius: 32px;
min-height: 220px;
overflow: hidden;
background: radial-gradient(circle at 20% 20%, rgba(255, 255, 255, 0.65), rgba(104, 147, 255, 0.2))
background: radial-gradient(
circle at 20% 20%,
rgba(255, 255, 255, 0.65),
rgba(104, 147, 255, 0.2)
)
rgba(28, 27, 41, 0.95);
box-shadow: 0 25px 60px rgba(24, 20, 37, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.4);
box-shadow:
0 25px 60px rgba(24, 20, 37, 0.45),
inset 0 1px 0 rgba(255, 255, 255, 0.4);
}
.experiment-visual::after {
@ -878,7 +899,8 @@
.experiment-grid {
position: absolute;
inset: 0;
background-image: linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px),
background-image:
linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px);
background-size: 32px 32px;
opacity: 0.4;
@ -997,7 +1019,7 @@
}
.liquid-glass-panel::before {
content: "";
content: '';
position: absolute;
inset: -15%;
background:
@ -1009,7 +1031,7 @@
}
.liquid-glass-panel::after {
content: "";
content: '';
position: absolute;
inset: 0;
background:
@ -1080,7 +1102,9 @@
background: linear-gradient(135deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.12));
backdrop-filter: blur(18px);
cursor: pointer;
transition: transform 0.15s ease, background 0.2s ease;
transition:
transform 0.15s ease,
background 0.2s ease;
}
.liquid-glass-btn:hover:not(:disabled) {
@ -1137,7 +1161,10 @@
padding: 16px;
text-align: left;
cursor: pointer;
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
transition:
border-color 0.2s ease,
box-shadow 0.2s ease,
transform 0.2s ease;
width: 100%;
display: flex;
flex-direction: column;
@ -1359,7 +1386,10 @@
gap: 10px;
text-align: left;
cursor: pointer;
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
transition:
border-color 0.2s ease,
box-shadow 0.2s ease,
transform 0.2s ease;
}
.theme-card:hover {
@ -1461,8 +1491,18 @@
bottom: -8px;
height: 16px;
background: var(--chat-surface-color, #ffffff);
mask-image: linear-gradient(180deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.6) 55%, rgba(0, 0, 0, 0) 100%);
-webkit-mask-image: linear-gradient(180deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.6) 55%, rgba(0, 0, 0, 0) 100%);
mask-image: linear-gradient(
180deg,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 0.6) 55%,
rgba(0, 0, 0, 0) 100%
);
-webkit-mask-image: linear-gradient(
180deg,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 0.6) 55%,
rgba(0, 0, 0, 0) 100%
);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
pointer-events: none;
@ -1597,7 +1637,7 @@
font-size: 14px;
font-weight: 700;
color: #0f172a;
font-family: 'Iowan Old Style', ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
font-family: 'Iowan Old Style', ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@ -1627,7 +1667,7 @@
font-weight: 600;
font-size: 14px;
line-height: 1.35;
font-family: 'Iowan Old Style', ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
font-family: 'Iowan Old Style', ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
cursor: pointer;
transition: color 0.15s ease;
flex-shrink: 0;
@ -1699,7 +1739,9 @@
.mobile-panel-menu-enter-active,
.mobile-panel-menu-leave-active {
transition: opacity 0.2s ease, transform 0.2s ease;
transition:
opacity 0.2s ease,
transform 0.2s ease;
}
.mobile-panel-menu-enter-from,
@ -1897,7 +1939,9 @@
.desktop-approval-fade-enter-active .desktop-approval-sheet,
.desktop-approval-fade-leave-active .desktop-approval-sheet {
transition: transform 0.22s ease, opacity 0.22s ease;
transition:
transform 0.22s ease,
opacity 0.22s ease;
}
.desktop-approval-fade-enter-from .desktop-approval-sheet,
@ -1925,7 +1969,9 @@
justify-content: center;
padding: 20px;
z-index: 2200;
transition: opacity 0.25s ease, backdrop-filter 0.25s ease;
transition:
opacity 0.25s ease,
backdrop-filter 0.25s ease;
will-change: opacity, backdrop-filter;
}
@ -1936,7 +1982,9 @@
border-radius: 18px;
padding: 24px;
box-shadow: var(--theme-shadow-soft);
transition: transform 0.25s ease, opacity 0.25s ease;
transition:
transform 0.25s ease,
opacity 0.25s ease;
}
.confirm-title {
@ -2005,7 +2053,9 @@
justify-content: center;
padding: 20px;
z-index: 2200;
transition: opacity 0.25s ease, backdrop-filter 0.25s ease;
transition:
opacity 0.25s ease,
backdrop-filter 0.25s ease;
}
.subagent-activity-modal {
@ -2071,7 +2121,9 @@
font-size: 12px;
padding: 4px 10px;
cursor: pointer;
transition: background 0.2s ease, border-color 0.2s ease;
transition:
background 0.2s ease,
border-color 0.2s ease;
}
.subagent-stop-btn:hover:not(:disabled) {
@ -2181,10 +2233,11 @@
overflow: auto;
}
.confirm-dialog-fade-enter-active,
.confirm-dialog-fade-leave-active {
transition: opacity 0.25s ease, backdrop-filter 0.25s ease;
transition:
opacity 0.25s ease,
backdrop-filter 0.25s ease;
}
.confirm-dialog-fade-enter-from,
@ -2195,7 +2248,9 @@
.confirm-dialog-fade-enter-active .confirm-modal,
.confirm-dialog-fade-leave-active .confirm-modal {
transition: transform 0.25s ease, opacity 0.25s ease;
transition:
transform 0.25s ease,
opacity 0.25s ease;
}
.confirm-dialog-fade-enter-from .confirm-modal,
@ -2509,7 +2564,9 @@
.personal-page-vertical-enter-active,
.personal-page-vertical-leave-active {
transition: opacity 0.35s ease, transform 0.35s ease;
transition:
opacity 0.35s ease,
transform 0.35s ease;
}
.personal-page-vertical-enter-from {
@ -2524,7 +2581,9 @@
.personal-page-fade-enter-active,
.personal-page-fade-leave-active {
transition: opacity 0.25s ease, backdrop-filter 0.25s ease;
transition:
opacity 0.25s ease,
backdrop-filter 0.25s ease;
}
.personal-page-fade-enter-from,
@ -2535,7 +2594,9 @@
.personal-page-overlay.personal-page-fade-enter-active .personal-page-card,
.personal-page-overlay.personal-page-fade-leave-active .personal-page-card {
transition: transform 0.25s ease, opacity 0.25s ease;
transition:
transform 0.25s ease,
opacity 0.25s ease;
}
.personal-page-overlay.personal-page-fade-enter-from .personal-page-card,
@ -2547,7 +2608,9 @@
/* 通用覆盖层淡入动画(复用个人空间动效) */
.overlay-fade-enter-active,
.overlay-fade-leave-active {
transition: opacity 0.25s ease, backdrop-filter 0.25s ease;
transition:
opacity 0.25s ease,
backdrop-filter 0.25s ease;
}
.overlay-fade-enter-from,
.overlay-fade-leave-to {
@ -2556,7 +2619,9 @@
}
.overlay-fade-enter-active > *:first-child,
.overlay-fade-leave-active > *:first-child {
transition: transform 0.25s ease, opacity 0.25s ease;
transition:
transform 0.25s ease,
opacity 0.25s ease;
}
.overlay-fade-enter-from > *:first-child,
.overlay-fade-leave-to > *:first-child {
@ -2625,7 +2690,9 @@
padding: 8px 14px;
font-size: 13px;
cursor: pointer;
transition: transform 0.18s ease, box-shadow 0.18s ease;
transition:
transform 0.18s ease,
box-shadow 0.18s ease;
}
.loading-refresh-btn:hover {
@ -2722,7 +2789,8 @@
}
@keyframes box1 {
0%, 50% {
0%,
50% {
transform: translate(100%, 0);
}
@ -2746,7 +2814,8 @@
}
@keyframes box3 {
0%, 50% {
0%,
50% {
transform: translate(100%, 100%);
}
@ -2815,23 +2884,6 @@ body[data-theme='light'] {
background: #ececec;
}
.personal-page-btn {
background: #f5f5f5;
}
.conversation-personal-entry:not(.collapsed) .personal-page-btn {
background: #f5f5f5;
}
.conversation-personal-entry:not(.collapsed) .personal-page-btn:hover,
.conversation-personal-entry:not(.collapsed) .personal-page-btn:focus-visible {
background: #ececec;
}
.code-block-header {
background: #f5f5f5;
}
/* 机器人图标保持黑色 */
.status-logo {
color: #0d0d0d;
@ -2850,7 +2902,9 @@ body[data-theme='light'] {
}
.mode-indicator:focus-visible {
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.12);
box-shadow:
0 0 0 2px rgba(0, 0, 0, 0.1),
0 2px 4px rgba(0, 0, 0, 0.12);
}
.mode-indicator .icon {
@ -2901,60 +2955,6 @@ body[data-theme='light'] {
color: #676767 !important;
}
/* 对话记录栏 - 统一使用浅灰色背景 */
.new-conversation-btn {
background: #ececec !important;
color: #0d0d0d !important;
border-color: rgba(0, 0, 0, 0.1) !important;
box-shadow: none !important;
}
.new-conversation-btn:hover {
background: #e0e0e0 !important;
box-shadow: none !important;
}
.conversation-item {
background: #ffffff !important;
box-shadow: none !important;
}
.conversation-item:hover {
background: #f5f5f5 !important;
box-shadow: none !important;
}
.conversation-item.active {
background: #f5f5f5 !important;
box-shadow: none !important;
}
.toggle-sidebar-btn {
background: #ececec !important;
border-color: rgba(0, 0, 0, 0.1) !important;
}
.toggle-sidebar-btn:hover {
background: #e0e0e0 !important;
}
.search-input {
background: #ececec !important;
}
.search-input:focus {
background: #e0e0e0 !important;
}
.conversation-personal-entry:not(.collapsed) .personal-page-btn {
background: #ececec !important;
}
.conversation-personal-entry:not(.collapsed) .personal-page-btn:hover,
.conversation-personal-entry:not(.collapsed) .personal-page-btn:focus-visible {
background: #e0e0e0 !important;
}
/* 删除按钮 - 使用深灰色 */
.conversation-action-btn.delete-btn {
background: #6b6b6b !important;
@ -3073,27 +3073,6 @@ body[data-theme='dark'] {
background: #353535;
}
.personal-page-btn {
background: #2a2a2a;
}
.conversation-personal-entry:not(.collapsed) .personal-page-btn {
background: #2a2a2a;
}
.conversation-personal-entry:not(.collapsed) .personal-page-btn:hover,
.conversation-personal-entry:not(.collapsed) .personal-page-btn:focus-visible {
background: #353535;
}
.code-block-header {
background: #2a2a2a;
}
.copy-code-btn {
filter: brightness(0) invert(1);
}
/* 机器人图标保持白色 */
.status-logo {
color: #ffffff;
@ -3112,7 +3091,9 @@ body[data-theme='dark'] {
}
.mode-indicator:focus-visible {
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1), 0 3px 6px rgba(0, 0, 0, 0.4);
box-shadow:
0 0 0 2px rgba(255, 255, 255, 0.1),
0 3px 6px rgba(0, 0, 0, 0.4);
}
.mode-indicator .icon {
@ -3128,29 +3109,6 @@ body[data-theme='dark'] {
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4);
}
/* 对话侧边栏图标颜色 */
.conversation-header button,
.workspace-immersive-btn {
color: #ffffff;
}
.conversation-header button svg,
.workspace-immersive-btn svg {
stroke: #ffffff;
fill: none;
}
/* 个人空间按钮 - 移除圆形外框 */
.personal-page-btn.icon-only {
background: transparent;
border: none;
}
.personal-page-btn.icon-only:hover {
background: transparent;
border: none;
}
/* 个人空间选择栏 - 文字颜色 */
.run-mode-title {
color: #ffffff !important;
@ -3196,60 +3154,6 @@ body[data-theme='dark'] {
color: #ffffff !important;
}
/* 对话记录栏 - 统一使用深灰色背景 */
.new-conversation-btn {
background: #2a2a2a !important;
color: #ffffff !important;
border-color: rgba(255, 255, 255, 0.1) !important;
box-shadow: none !important;
}
.new-conversation-btn:hover {
background: #3a3a3a !important;
box-shadow: none !important;
}
.conversation-item {
background: #2a2a2a !important;
box-shadow: none !important;
}
.conversation-item:hover {
background: #3a3a3a !important;
box-shadow: none !important;
}
.conversation-item.active {
background: #3a3a3a !important;
box-shadow: none !important;
}
.toggle-sidebar-btn {
background: #2a2a2a !important;
border-color: rgba(255, 255, 255, 0.1) !important;
}
.toggle-sidebar-btn:hover {
background: #3a3a3a !important;
}
.search-input {
background: #2a2a2a !important;
}
.search-input:focus {
background: #3a3a3a !important;
}
.conversation-personal-entry:not(.collapsed) .personal-page-btn {
background: #2a2a2a !important;
}
.conversation-personal-entry:not(.collapsed) .personal-page-btn:hover,
.conversation-personal-entry:not(.collapsed) .personal-page-btn:focus-visible {
background: #3a3a3a !important;
}
/* 删除按钮 - 使用浅灰色 */
.conversation-action-btn.delete-btn {
background: #6b6b6b !important;
@ -3795,8 +3699,14 @@ body[data-theme='dark'] {
}
@keyframes tutorialClickPulse {
0% { opacity: 1; transform: scale(0.6); }
100% { opacity: 0; transform: scale(1.8); }
0% {
opacity: 1;
transform: scale(0.6);
}
100% {
opacity: 0;
transform: scale(1.8);
}
}
.tutorial-popover {

View File

@ -16,6 +16,7 @@
display: flex;
align-items: stretch;
flex-shrink: 0;
margin-left: 52px;
}
/* 侧边栏 */
@ -143,7 +144,10 @@
background: linear-gradient(135deg, var(--mode-indicator-color-1), var(--mode-indicator-color-2));
color: #fffef8;
box-shadow: 0 8px 20px var(--mode-indicator-shadow);
transition: background 0.35s ease, box-shadow 0.35s ease, transform 0.25s ease;
transition:
background 0.35s ease,
box-shadow 0.35s ease,
transform 0.25s ease;
}
.mode-indicator:hover {
@ -151,7 +155,9 @@
}
.mode-indicator:focus-visible {
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.8), 0 8px 20px var(--mode-indicator-shadow);
box-shadow:
0 0 0 2px rgba(255, 255, 255, 0.8),
0 8px 20px var(--mode-indicator-shadow);
}
.mode-indicator.fast {
@ -182,13 +188,15 @@
height: 12px;
border-radius: 6px;
background: #e34d4d;
transition: background 0.2s ease, box-shadow 0.2s ease;
transition:
background 0.2s ease,
box-shadow 0.2s ease;
position: relative;
overflow: visible;
}
.connection-dot::after {
content: "";
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
@ -206,7 +214,9 @@
.mode-icon-enter-active,
.mode-icon-leave-active {
transition: opacity 0.2s ease, transform 0.2s ease;
transition:
opacity 0.2s ease,
transform 0.2s ease;
}
.mode-icon-enter-from,
@ -258,7 +268,9 @@
border-radius: 6px;
font-size: 13px;
cursor: pointer;
transition: background 0.2s, border-color 0.2s;
transition:
background 0.2s,
border-color 0.2s;
}
.sidebar-manage-btn:hover {
@ -443,9 +455,11 @@
padding: 12px 14px;
background: rgba(255, 255, 255, 0.92);
cursor: pointer;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
transition:
border-color 0.2s ease,
box-shadow 0.2s ease;
[data-theme="dark"] & {
[data-theme='dark'] & {
background: rgba(30, 30, 30, 0.6);
border-color: rgba(255, 255, 255, 0.1);
}
@ -455,7 +469,7 @@
border-color: #6c5ce7;
box-shadow: 0 6px 16px rgba(108, 92, 231, 0.15);
[data-theme="dark"] & {
[data-theme='dark'] & {
border-color: #8b7ce7;
box-shadow: 0 6px 16px rgba(139, 124, 231, 0.25);
}
@ -517,7 +531,7 @@
border: 1px dashed var(--claude-border);
text-align: center;
[data-theme="dark"] & {
[data-theme='dark'] & {
background: rgba(20, 20, 20, 0.9);
border-color: rgba(255, 255, 255, 0.12);
}
@ -604,7 +618,9 @@
text-align: left;
color: var(--claude-text);
cursor: pointer;
transition: border-color 0.2s ease, transform 0.2s ease;
transition:
border-color 0.2s ease,
transform 0.2s ease;
&:hover:not(:disabled) {
border-color: var(--claude-accent);
@ -664,18 +680,18 @@
}
}
[data-theme="dark"] .host-workspace-header-box {
[data-theme='dark'] .host-workspace-header-box {
background: rgba(30, 30, 30, 0.78);
border-color: rgba(160, 160, 160, 0.45);
}
[data-theme="dark"] .host-workspace-card {
[data-theme='dark'] .host-workspace-card {
&.active {
background: rgba(255, 255, 255, 0.08);
}
}
[data-theme="dark"] .host-workspace-badge {
[data-theme='dark'] .host-workspace-badge {
&.default {
color: #f7c56f;
border-color: rgba(247, 197, 111, 0.35);
@ -750,7 +766,9 @@
color: inherit;
cursor: pointer;
border-radius: 8px;
transition: background 0.2s ease, transform 0.2s ease;
transition:
background 0.2s ease,
transform 0.2s ease;
text-align: left;
font-family: inherit;
}
@ -843,3 +861,78 @@
cursor: not-allowed;
background: transparent;
}
/* Targeted refinement: keep workspace content/layout, only polish switcher/menu/manage controls. */
.sidebar-manage-btn {
height: 32px;
border: 0;
border-radius: 10px;
background: transparent;
color: var(--claude-text);
padding: 0 11px;
font-size: 13px;
cursor: pointer;
transition:
background 140ms ease,
color 140ms ease;
}
.sidebar-manage-btn:hover {
background: var(--theme-tab-active, rgba(0, 0, 0, 0.055));
border-color: transparent;
}
.sidebar-view-toggle {
width: 32px;
height: 32px;
border-radius: 10px;
border: 0;
background: transparent;
color: var(--claude-text);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition:
background 140ms ease,
color 140ms ease;
}
.sidebar-view-toggle:hover {
background: var(--theme-tab-active, rgba(0, 0, 0, 0.055));
}
.panel-menu {
left: calc(100% + 8px);
top: 0;
display: flex;
gap: 4px;
background: var(--theme-surface-strong, #ffffff);
border: 1px solid var(--claude-border);
border-radius: 14px;
padding: 6px;
box-shadow: var(--theme-shadow-soft);
z-index: 20;
}
.panel-menu button {
width: 34px;
height: 34px;
border: 0;
background: transparent;
cursor: pointer;
padding: 0;
border-radius: 10px;
color: var(--claude-text);
display: inline-flex;
align-items: center;
justify-content: center;
transition:
background 140ms ease,
color 140ms ease;
}
.panel-menu button:hover,
.panel-menu button.active {
background: var(--theme-tab-active, rgba(0, 0, 0, 0.055));
}

File diff suppressed because it is too large Load Diff

View File

@ -3,10 +3,6 @@
/* ========================================= */
@media (max-width: 1200px) {
.conversation-sidebar {
width: 260px;
}
.left-sidebar,
.right-sidebar {
width: 300px !important;

View File

@ -10,6 +10,7 @@ export const ICONS = Object.freeze({
checkbox: '/static/icons/checkbox.svg',
circleAlert: '/static/icons/circle-alert.svg',
clipboard: '/static/icons/clipboard.svg',
copy: '/static/icons/copy.svg',
clock: '/static/icons/clock.svg',
eye: '/static/icons/eye.svg',
file: '/static/icons/file.svg',