chore: import original project baseline

Import the pre-repair source tree as the history baseline.
Runtime data (data/), virtualenvs, bytecode caches and logs are
gitignored so local secrets and user state stay out of the repo.
This commit is contained in:
2026-09-17 16:40:01 +08:00
commit a7412824e0
124 changed files with 26747 additions and 0 deletions
+2111
View File
File diff suppressed because it is too large Load Diff
+3
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
+1213
View File
File diff suppressed because one or more lines are too long
+82
View File
@@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Markdown 库 -->
<script src="marked.min.js"></script>
<!-- 代码高亮 -->
<link rel="stylesheet" href="highlight/atom-one-dark.min.css">
<script src="highlight/highlight.min.js"></script>
<!-- 🌟 HTML 消毒(防 XSS / 防 HTML 穿透的纵深防御) -->
<script src="dompurify.min.js"></script>
<!-- 🌟 KaTeX 公式渲染(本地离线资产;须在 app.js 之前) -->
<link rel="stylesheet" href="katex/katex.min.css">
<script src="katex/katex.min.js"></script>
<!-- 自定义样式 -->
<link rel="stylesheet" href="style.css">
<!-- 🌟 Qt WebChannel 支持(用于前端与 Python 通信) -->
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
</head>
<body>
<!--🌟 会话加载界面(进入新会话时的统一加载:居中图标 + 从左到右扫描条) -->
<div class="session-loading" id="session-loading" style="display:none;">
<div class="session-loading-box">
<img class="session-loading-icon" src="../../svg/main.svg" alt="haocode">
<div class="session-loading-bar">
<div class="session-loading-bar-fill"></div>
</div>
</div>
</div>
<!--🌟 欢迎界面 -->
<div class="welcome-screen">
<div class="welcome-content">
<img class="welcome-icon" src="../../svg/main.svg" alt="haocode">
<h1 class="brand-name">haocode</h1>
<p class="brand-tagline">探索未至之境</p>
</div>
</div>
<!-- 聊天容器 -->
<div id="chat-container"></div>
<div id="scroll-anchor" style="height: 1px; margin-bottom: 150px;"></div>
<!-- 🌟 桥接初始化脚本(必须在 app.js 之前执行)
双协议:WebView2window.chrome.webview.postMessage/ QtWebEngineQWebChannel -->
<script>
document.addEventListener("DOMContentLoaded", function () {
if (window.chrome && window.chrome.webview) {
// ---- WebView2 路径:JSON 消息协议,方法名与 QWebChannel slot 一一对应 ----
const post = (m, a) => {
try { window.chrome.webview.postMessage({ m: m, a: a }); } catch (e) { console.warn("[JS] postMessage 失败:", e); }
};
window.bridge = {
onRegenerateClicked: (x) => post("onRegenerateClicked", [x]),
onBranchSwitch: (x, d) => post("onBranchSwitch", [x, d]),
onDeleteMessageClicked: (x) => post("onDeleteMessageClicked", [x]),
onAttachmentClicked: (x) => post("onAttachmentClicked", [x]),
onScrollChanged: (y, h, c) => post("onScrollChanged", [y, h, c])
};
console.log("[JS] WebView2 桥接就绪,window.bridge 可用");
} else {
// ---- QtWebEngine 路径:原有 QWebChannel ----
new QWebChannel(qt.webChannelTransport, function (channel) {
window.bridge = channel.objects.bridge;
console.log("[JS] QWebChannel 已连接,window.bridge 就绪");
});
}
});
</script>
<!-- 主逻辑脚本 -->
<script src="app.js"></script>
</body>
</html>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+6
View File
File diff suppressed because one or more lines are too long
+902
View File
@@ -0,0 +1,902 @@
/* ==================== 1. 全局 ==================== */
body, html {
margin: 0; padding: 0;
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
background-color: #ffffff;
color: #333;
overflow-x: hidden;
/* 🌟 不再给 html/body 设 height:100%(会让 body 退化为内部滚动容器,
导致 window.scrollY/scrollTo 失效;视口高度由 #chat-container 的 min-height:100vh 承担) */
}
#chat-container {
max-width: 95%;
margin: 0 auto;
padding: 20px 20px 20px 20px;
min-height: 100vh; /* 🌟 至少占满整个视口高度 */
}
/* ==================== 隐藏 WebEngine 原生滚动条 ==================== */
/* 改用右侧自定义 Qt 滚动条(WebScrollBar),避免内置滚动条高速滑动时的重绘延迟观感 */
::-webkit-scrollbar {
width: 0px;
height: 0px;
background: transparent;
}
/* ==================== 会话加载界面 ==================== */
.session-loading {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
background-color: #ffffff;
opacity: 1;
transition: opacity 0.45s ease; /* 结束后的渐变退场 */
}
.session-loading.hide {
opacity: 0;
pointer-events: none;
}
.session-loading-box {
display: flex;
flex-direction: column;
align-items: center;
}
.session-loading-icon {
width: 72px;
height: 72px;
}
.session-loading-bar {
width: 190px;
height: 4px;
margin-top: 26px;
background: #eceef2;
border-radius: 2px;
overflow: hidden;
position: relative;
}
.session-loading-bar-fill {
position: absolute;
top: 0; left: 0;
height: 100%;
width: 36%;
border-radius: 2px;
background: linear-gradient(90deg, rgba(59,130,246,0) 0%, #3b82f6 50%, rgba(59,130,246,0) 100%);
animation: loadingSweep 1.2s ease-in-out infinite; /* 从左到右扫描,非进度条 */
}
@keyframes loadingSweep {
0% { transform: translateX(-110%); }
100% { transform: translateX(300%); }
}
/* ==================== 欢迎界面 ==================== */
.welcome-screen {
display: flex;
justify-content: center;
align-items: center;
position: fixed; /* 🌟 固定定位,脱离文档流 */
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 60px 20px;
pointer-events: none; /* 🌟 允许点击穿透到下方的输入框 */
}
.welcome-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
pointer-events: auto; /* 🌟 但内容本身可以交互 */
/* 旧的渐入动画已移除:会话切换统一由加载界面完成过渡 */
}
/* 欢迎页图标:main.svg */
.welcome-icon {
width: 56px;
height: 56px;
margin-bottom: 12px;
}
.brand-name {
font-size: 40px;
font-weight: 700;
margin: 0;
letter-spacing: 6px;
font-family: "Microsoft YaHei UI", "Microsoft YaHei", "微软雅黑", "Segoe UI", sans-serif;
background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
text-fill-color: transparent;
}
.brand-tagline {
font-size: 15px;
color: #8a94a6;
margin: 14px 0 0 0;
font-weight: 400;
letter-spacing: 5px;
}
/* ==================== 2. 消息气泡 ==================== */
.message-wrapper {
display: flex;
align-items: flex-start;
margin-bottom: 30px;
}
.message-wrapper.user { justify-content: flex-end; }
/* ==================== 3. 头像 ==================== */
.avatar {
width: 32px; height: 32px;
border-radius: 6px;
display: flex; justify-content: center; align-items: center;
font-weight: bold; font-size: 14px;
flex-shrink: 0;
margin-top: 20px;
}
.assistant .avatar { background-color: #4a90d9; color: white; margin-right: 15px; }
.user .avatar { background-color: #e3e3e3; color: #555; margin-left: 15px; order: 2; }
/* ==================== 4. 消息内容区 ==================== */
.message-content {
max-width: 85%;
display: flex;
flex-direction: column;
}
.assistant .message-content {
align-items: flex-start;
/* 🆕 助手气泡恒定宽度:输出中/完成后一律 85%,不随内容伸缩(流式特效不受影响) */
width: 85%;
}
/* 🆕 内层同步撑满:.reply-content 基类是 fit-content(贴内容缩),
可见气泡(md-segment 背景/思考卡/工具卡)都在它里面 → 必须 100% 才真正恒定 */
.assistant .reply-content { width: 100%; }
.user .message-content { align-items: flex-end; }
.sender-name {
font-size: 11px; color: #aaa;
margin-bottom: 6px; font-weight: 600;
text-transform: uppercase; letter-spacing: 0.5px;
}
.reply-content {
line-height: 1.6; font-size: 15px;
width: fit-content; max-width: 100%;
}
.user .reply-content {
background-color: #f4f4f4;
padding: 10px 18px;
border-radius: 18px; border-top-right-radius: 4px;
color: #333; text-align: left;
}
/* ==================== 5. 思考过程 ==================== */
.think-block {
margin-bottom: 12px; border-radius: 8px;
background-color: #ffffff; border: 1px solid #eaeaea;
overflow: hidden; transition: all 0.3s ease; width: 100%;
}
.think-block summary {
padding: 8px 14px; font-size: 13px; color: #888;
cursor: pointer; user-select: none;
background-color: #fafafa; list-style: none;
display: flex; align-items: center; font-weight: 500;
}
.think-block summary .chev { color: #b8b8b8; margin-right: 8px; }
.think-block[open] summary { border-bottom: 1px dashed #eaeaea; }
.think-content {
padding: 12px 14px; font-size: 13px; color: #777;
background-color: #fff; max-height: 400px; overflow-y: auto;
}
/* 🌟 流式输出中:flex 锁底,展示最新思考内容 */
/* 🌟 流式输出中:思考框保持可滚动,JS 控制锁底 */
.message-wrapper.streaming .think-content {
overflow-y: auto !important;
}
.think-inner {
flex-shrink: 0;
}
.think-content.markdown-body p { color: #777; margin-bottom: 8px; }
/* ============ 思考/压缩气泡内:代码朴素渲染 ============
压缩摘要充满代码:浅色语法色(#a626a4 紫)+ code-block-wrapper 边框/语言栏,
让定格后的气泡观感"紫色带边框"。此处统一朴素灰、无边框无头部;
正文气泡的代码块不受影响。 */
.think-content .code-block-wrapper {
border: none;
border-radius: 0;
background: transparent;
min-width: 0;
margin: 8px 0;
}
.think-content .code-header { display: none; }
.think-content .code-block-wrapper pre,
.think-content .code-block-wrapper pre code,
.think-content .code-block-wrapper .hljs,
.think-content .code-block-wrapper .hljs * {
color: #777 !important;
background: transparent !important;
}
.think-content code,
.think-content.markdown-body p code {
color: #777;
background-color: #f6f7f9;
}
/* ==================== 6. Markdown 基础 ==================== */
.markdown-body p { margin-top: 0; margin-bottom: 10px; }
.markdown-body code {
font-family: 'Consolas', 'Courier New', monospace; font-size: 13px;
}
.markdown-body p code {
background-color: #f0f0f0; color: #e83e8c;
padding: 2px 5px; border-radius: 4px;
}
/* ==================== 6b. 🌟 KaTeX 公式渲染 ==================== */
.katex-display {
margin: 10px 0; padding: 4px 2px;
overflow-x: auto; overflow-y: hidden;
}
.katex-display::-webkit-scrollbar { height: 6px; }
.katex-display::-webkit-scrollbar-thumb { background: #d0d0d0; border-radius: 3px; }
.markdown-body .katex { font-size: 1.06em; }
.markdown-body .katex-display { font-size: 1.08em; }
/* ==================== 7. 代码块 ==================== */
.code-block-wrapper {
position: relative;
border: 1px solid #e5e5e5; border-radius: 8px;
margin: 12px 0; overflow: hidden;
background-color: #ffffff;
min-width: 500px; /* 短代码也保持基本宽度,避免气泡过窄难看 */
}
.code-header {
display: flex; justify-content: space-between; align-items: center;
background-color: #fafafa; padding: 4px 10px;
border-bottom: 1px solid #eee;
}
.code-lang-label {
font-size: 11px; color: #aaa; font-weight: 500;
text-transform: lowercase; letter-spacing: 0.3px;
}
.code-header-actions { display: flex; align-items: center; gap: 4px; }
.code-header button {
background: transparent; border: 1px solid transparent;
color: #999; cursor: pointer;
display: flex; align-items: center; gap: 4px;
font-size: 12px; padding: 3px 8px; border-radius: 4px;
transition: all 0.15s ease; font-family: inherit; outline: none;
}
.code-header button:hover {
background-color: #f0f0f0; border-color: #e0e0e0; color: #555;
}
.copy-btn.copied {
color: #52c41a !important;
border-color: rgba(82, 196, 26, 0.3) !important;
background-color: rgba(82, 196, 26, 0.06) !important;
}
/* 代码主体 */
.code-body {
margin: 0; padding: 14px;
background-color: #fafafa !important;
overflow-x: auto;
max-height: 5000px;
}
/* 修改折叠态:允许滚动但隐藏滚动条 */
.code-body.collapsed {
max-height: 140px !important;
overflow-y: auto !important; /* ← 从 hidden 改为 auto */
scrollbar-width: none; /* Firefox */
}
.code-body.collapsed::-webkit-scrollbar {
display: none; /* Chrome/Safari */
}
/* 🌟 流式输出中:用 flex 布局天然展示底部代码,不依赖 JS scrollTop */
.message-wrapper.streaming .code-body.collapsed {
display: flex !important;
flex-direction: column !important;
justify-content: flex-end !important;
overflow: hidden !important;
}
/* 默认折叠遮罩:底部渐隐(展示顶部代码) */
.code-block-wrapper:has(.code-body.collapsed)::after {
content: '';
position: absolute;
bottom: 0; left: 0; right: 0;
height: 40px;
background: linear-gradient(transparent, #fafafa);
pointer-events: none;
border-radius: 0 0 8px 8px;
}
/* 🌟 流式输出中遮罩:顶部渐隐(展示底部最新代码) */
.message-wrapper.streaming .code-block-wrapper:has(.code-body.collapsed)::after {
top: 36px; bottom: auto;
border-radius: 0;
background: linear-gradient(#fafafa, transparent);
}
/* 强制覆盖 highlight.js 深色背景 */
.code-block-wrapper pre,
.code-block-wrapper pre code,
.code-block-wrapper .hljs {
background-color: #fafafa !important;
color: #383a42 !important;
}
/* 浅色语法高亮 */
.code-block-wrapper .hljs-keyword,
.code-block-wrapper .hljs-selector-tag,
.code-block-wrapper .hljs-title,
.code-block-wrapper .hljs-section,
.code-block-wrapper .hljs-doctag,
.code-block-wrapper .hljs-name,
.code-block-wrapper .hljs-strong { color: #a626a4; }
.code-block-wrapper .hljs-string,
.code-block-wrapper .hljs-attr { color: #50a14f; }
.code-block-wrapper .hljs-number,
.code-block-wrapper .hljs-literal,
.code-block-wrapper .hljs-variable,
.code-block-wrapper .hljs-template-variable,
.code-block-wrapper .hljs-tag .hljs-attr { color: #986801; }
.code-block-wrapper .hljs-comment,
.code-block-wrapper .hljs-quote { color: #a0a1a7; font-style: italic; }
.code-block-wrapper .hljs-built_in,
.code-block-wrapper .hljs-builtin-name { color: #c18401; }
.code-block-wrapper .hljs-function .hljs-title,
.code-block-wrapper .hljs-class .hljs-title { color: #4078f2; }
.code-block-wrapper .hljs-type,
.code-block-wrapper .hljs-params { color: #c18401; }
.code-block-wrapper .hljs-meta { color: #4078f2; }
.code-block-wrapper code {
font-family: 'Consolas', 'Courier New', monospace; font-size: 13px;
}
/* ==================== 7.1 SVG 代码块:绘制/源码 切换 ==================== */
/* 渲染容器:透明背景、居中,防超大 SVG 撑破布局 */
.svg-render-body {
display: flex;
align-items: center;
justify-content: center;
padding: 14px;
background: transparent;
overflow: auto;
max-height: 5000px;
}
.svg-render-body svg {
min-width: 360px; /* 小图适当放大,避免渲染出来太小看不清 */
max-width: 100%;
height: auto;
}
/* 渲染态:隐藏代码体与折叠渐隐遮罩 */
.code-block-wrapper.svg-mode .code-body { display: none !important; }
.code-block-wrapper.svg-mode:has(.code-body.collapsed)::after { display: none; }
/* 渲染态按钮高亮(此时按钮文案为「源码」) */
.code-header .svg-render-btn.active {
color: #10a37f;
border-color: rgba(16, 163, 127, 0.3);
background-color: rgba(16, 163, 127, 0.06);
}
/* 折叠按钮被锁定时(处于渲染态) */
.code-header .fold-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
}
/* 流式输出中隐藏「绘制」按钮(与复制按钮同等待遇,避免重渲冲掉切换状态) */
.message-wrapper.streaming .svg-render-btn {
display: none !important;
}
/* ==================== 8. 长文本附件卡片 ==================== */
.long-text-card {
display: flex; align-items: center; gap: 12px;
padding: 12px 16px; background-color: #f8f8f8;
border: 1px solid #e8e8e8; border-radius: 12px;
cursor: pointer; transition: all 0.2s ease; max-width: 320px;
}
.long-text-card:hover {
background-color: #f0f0f0; border-color: #d0d0d0;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}
.card-icon {
flex-shrink: 0; width: 40px; height: 40px;
display: flex; align-items: center; justify-content: center;
background-color: #fff; border-radius: 8px; border: 1px solid #eee;
}
.card-info { overflow: hidden; }
.card-title {
font-size: 13px; color: #333; font-weight: 500;
white-space: nowrap; overflow: hidden;
text-overflow: ellipsis; max-width: 220px;
}
.card-meta { font-size: 11px; color: #aaa; margin-top: 3px; }
/* ==================== 9. 模态框 ==================== */
.content-modal-overlay {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background-color: rgba(0,0,0,0);
display: flex; justify-content: center; align-items: center;
z-index: 9999; transition: background-color 0.2s ease;
}
.content-modal-overlay.visible { background-color: rgba(0,0,0,0.4); }
.content-modal {
background-color: #fff; border-radius: 12px;
width: 70%; max-width: 700px; max-height: 75vh;
display: flex; flex-direction: column;
box-shadow: 0 20px 60px rgba(0,0,0,0.15);
transform: scale(0.95); opacity: 0;
transition: all 0.2s ease; overflow: hidden;
}
.content-modal-overlay.visible .content-modal {
transform: scale(1); opacity: 1;
}
.modal-header {
display: flex; justify-content: space-between; align-items: center;
padding: 14px 20px; border-bottom: 1px solid #eee;
font-size: 14px; font-weight: 600; color: #333;
}
.modal-close-btn {
background: transparent; border: none; font-size: 18px;
color: #999; cursor: pointer; padding: 4px 8px;
border-radius: 4px; transition: all 0.15s ease; line-height: 1;
}
.modal-close-btn:hover { background-color: #f0f0f0; color: #333; }
.modal-body {
padding: 16px 20px; overflow-y: auto;
font-family: 'Consolas', 'Courier New', monospace;
font-size: 13px; line-height: 1.6; color: #444;
white-space: pre-wrap; word-break: break-all;
margin: 0; background-color: #fafafa;
}
/* ==================== 10. 错误与动画 ==================== */
.system-error {
background-color: #fef0f0; border: 1px solid #fde2e2;
color: #f56c6c; padding: 10px 15px; border-radius: 8px;
margin-top: 10px; font-size: 13px; font-weight: bold;
display: inline-block;
}
/* ==================== 11. 多附件容器 ==================== */
.attachments-container {
display: flex; flex-direction: column;
gap: 8px; margin-bottom: 8px;
}
.attachments-container .long-text-card { max-width: 300px; }
.user .attachments-container + .reply-content { margin-top: 4px; }
/* ==================== 12. 流式输出状态(纯 CSS 控制,不闪烁) ==================== */
.message-wrapper.streaming .copy-btn {
display: none !important;
}
.message-wrapper.streaming .fold-btn {
font-size: 0 !important;
pointer-events: none;
position: relative;
}
.message-wrapper.streaming .fold-btn::after {
content: '输出中...';
font-size: 12px;
color: #f5a623;
}
/* 过渡态:从 flex-end 切换到 scroll 模式,保持显示底部 */
.code-body.collapsed.scroll-locked {
display: block !important;
overflow-y: auto !important;
justify-content: initial !important;
flex-direction: initial !important;
scrollbar-width: none;
}
.code-body.collapsed.scroll-locked::-webkit-scrollbar {
display: none;
}
/* ==================== 13. 消息操作栏 ==================== */
.message-actions {
display: flex;
align-items: center;
gap: 2px;
margin-top: 8px;
}
.action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border: none;
background: transparent;
border-radius: 6px;
cursor: pointer;
color: #999;
transition: all 0.15s ease;
padding: 0;
}
.action-btn:hover {
background-color: #f0f0f0;
color: #555;
}
.action-btn svg {
pointer-events: none;
}
/* 流式输出中隐藏操作栏 */
.message-wrapper.streaming .message-actions {
display: none !important;
}
/* ================= 分支选择器 ================= */
.branch-selector {
display: inline-flex;
align-items: center;
margin-left: 10px;
background-color: #f0f0f0;
border-radius: 12px;
padding: 2px 6px;
font-size: 11px;
color: #666;
user-select: none;
}
/* 暗色模式的话可以加这句,如果是纯亮色就不用 */
@media (prefers-color-scheme: dark) {
.branch-selector { background-color: #333; color: #aaa; }
}
.branch-btn {
background: transparent;
border: none;
cursor: pointer;
padding: 0 4px;
color: #888;
font-size: 10px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
.branch-btn:hover:not(:disabled) {
color: #111;
}
.branch-btn:disabled {
color: #ccc;
cursor: not-allowed;
}
.branch-text {
margin: 0 4px;
font-family: monospace;
}
/* ================= 删除确认气泡 ================= */
.delete-confirm-popover {
position: absolute;
bottom: 120%; /* 浮在按钮上方 */
left: 50%;
transform: translateX(-50%);
background-color: #fee2e2; /* 淡红色警告背景 */
border: 1px solid #f87171;
border-radius: 6px;
padding: 6px 10px;
display: flex;
align-items: center;
gap: 4px;
box-shadow: 0 4px 10px rgba(239, 68, 68, 0.2);
z-index: 100;
cursor: pointer;
white-space: nowrap;
width: max-content; /* 🌟 核心修复:保证气泡背景宽度根据文字自适应,不被按钮挤压 */
}
.delete-confirm-popover:hover {
background-color: #fecaca;
}
.delete-confirm-text {
color: #ef4444;
font-size: 12px;
font-weight: bold;
}
.delete-confirm-popover svg {
stroke: #ef4444;
}
/* ================= 用户消息专属操作栏 ================= */
.user-actions {
display: flex;
justify-content: flex-end; /* 靠右对齐 */
margin-top: 4px;
opacity: 1; /* 默认隐藏,悬浮显示 */
transition: opacity 0.2s ease-in-out;
}
.message-wrapper.user:hover .user-actions {
opacity: 1; /* 鼠标悬浮气泡时,显示操作栏 */
}
/* 强制指定按钮的图标颜色、大小和边距,打破继承魔咒 */
.user-actions .action-btn {
background: transparent;
border: none;
color: #999999; /* 强制指定为高级灰,让 SVG 显形 */
padding: 6px;
margin-left: 4px;
border-radius: 6px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
}
.user-actions .action-btn:hover {
background-color: #eaeaea;
color: #333333; /* 悬浮时图标变深 */
}
.user-actions .user-delete-btn:hover {
background-color: #fee2e2;
color: #ef4444;
}
/* 🌟 用户侧靠右对齐 */
.user-actions .delete-confirm-popover {
left: auto;
right: 0;
transform: none;
}
/* ⚠️ 注意:删除原本文件最底部的 "确认删除弹窗基础样式" 这一大段重复代码,避免样式冲突 */
/* ================= 确认删除弹窗基础样式 ================= */
.delete-confirm-popover {
position: absolute;
bottom: 120%;
left: 50%;
transform: translateX(-50%);
background-color: #fee2e2;
border: 1px solid #f87171;
border-radius: 6px;
padding: 6px 10px;
display: flex;
align-items: center;
gap: 4px;
box-shadow: 0 4px 10px rgba(239, 68, 68, 0.2);
z-index: 100;
cursor: pointer;
white-space: nowrap;
}
.delete-confirm-popover:hover {
background-color: #fecaca;
}
.delete-confirm-text {
color: #ef4444;
font-size: 12px;
font-weight: bold;
}
.delete-confirm-popover svg {
stroke: #ef4444;
}
/* ==================== 工具执行气泡(pi tool_execution_* 事件,浅色·对齐思考气泡) ==================== */
.tool-chip {
margin: 10px 0 4px 0; border-radius: 8px;
background-color: #ffffff; border: 1px solid #eaeaea;
overflow: hidden; transition: all 0.3s ease; width: 100%;
}
.tool-chip.streaming {
border-color: #d6e4ff;
background-color: #fbfdff;
}
.tool-chip summary {
padding: 8px 14px; font-size: 12.5px; color: #888;
cursor: pointer; user-select: none;
background-color: #fafafa; list-style: none;
display: flex; align-items: center; gap: 8px; font-weight: 500;
}
.tool-chip summary::-webkit-details-marker { display: none; }
.tool-chip summary .chev { color: #b8b8b8; }
.tool-chip[open] summary { border-bottom: 1px dashed #eaeaea; }
.tool-chip-icon { font-size: 13px; color: #bbb; }
.tool-chip-name {
font-family: Consolas, Monaco, monospace;
font-weight: 600; color: #666; font-size: 12px;
}
.tool-chip-brief {
font-family: Consolas, Monaco, monospace;
color: #aaa; font-size: 12px;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
flex: 1;
}
.tool-chip-brief.done { color: #52c41a; }
.tool-chip-status {
margin-left: auto; flex-shrink: 0;
font-size: 11px; padding: 1px 8px; border-radius: 10px;
}
.tool-chip-status.running {
color: #4a7fd4; background: #eef4ff;
animation: toolPulse 1.2s ease-in-out infinite;
}
.tool-chip-status.ok { color: #52c41a; background: #f6ffed; }
.tool-chip-status.fail { color: #cf1322; background: #fff1f0; }
@keyframes toolPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.45; }
}
.tool-chip-body {
padding: 10px 14px 12px 14px;
max-height: 260px; overflow: auto; background-color: #fff;
}
.tool-chip-label {
font-size: 11px; color: #bbb; font-weight: 600;
margin: 6px 0 4px 0; letter-spacing: 0.5px;
}
.tool-chip-label:first-child { margin-top: 0; }
.tool-chip-pre {
margin: 0 0 6px 0; padding: 8px 10px;
background-color: #f6f7f9; color: #555;
border: 1px solid #eef0f3; border-radius: 6px;
font-family: Consolas, Monaco, monospace;
font-size: 11.5px; line-height: 1.55;
white-space: pre-wrap; word-break: break-all;
max-height: 150px; overflow: auto;
}
/* ==================== 系统提示(如:已自动压缩上下文) ==================== */
.system-note {
text-align: center;
font-size: 12px;
color: #6b7280;
background: #f3f4f6;
border-radius: 10px;
padding: 6px 14px;
margin: 10px auto;
max-width: 70%;
}
/* ==================== 增量流式渲染容器 ==================== */
/* 注意:不能用 display:contents —— 本机 Chromium 下父盒(.md-segment)高度坍缩为 0
导致流式正文整段不可见(切会话重渲走无包裹路径所以可见)。用 block 规避。 */
.md-stable, .md-tail {
display: block;
}
/* ==================== 浅色 SVG 箭头(替代文字字形) ==================== */
.chev {
flex: none; display: inline-block;
transition: transform 0.2s ease-in-out;
}
.think-block[open] .chev, .tool-chip[open] .chev { transform: rotate(90deg); }
.think-label { color: inherit; }
/* ==================== 深度思考:默认收起 + 进行中蓝色呼吸动画 ==================== */
.think-block.streaming-think > summary .think-label {
color: #3b82f6;
font-weight: 600;
animation: think-breathe 1.6s ease-in-out infinite;
}
.think-block.streaming-think > summary .think-label::after {
content: '';
display: inline-block;
width: 1.2em;
text-align: left;
animation: think-dots 1.4s infinite;
}
@keyframes think-breathe {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
@keyframes think-dots {
0% { content: ''; }
25% { content: '.'; }
50% { content: '..'; }
75% { content: '...'; }
}
/* ==================== 工具 chip:耗时 / 超时徽章 ==================== */
.tool-chip-time {
font-size: 11px;
color: #9ca3af;
font-variant-numeric: tabular-nums;
margin-left: 2px;
}
.tool-chip-timeout {
font-size: 11px;
color: #dc2626;
font-weight: 600;
margin-left: 2px;
}
.tool-chip-livetimer {
/* 🆕 bash 运行中每秒读秒(N/Ts):琥珀色 + 等宽数字,不跳动 */
font-size: 11px;
color: #d97706;
font-weight: 600;
font-variant-numeric: tabular-nums;
margin-left: 2px;
}
/* ==================== 工具结果:长输出展开按钮 ==================== */
.tool-expand-btn {
display: block;
width: 100%;
margin: 4px 0 0;
padding: 3px 8px;
font-size: 11px;
line-height: 1.5;
color: #3b82f6;
background: #eff6ff;
border: 1px solid #bfdbfe;
border-radius: 6px;
cursor: pointer;
text-align: center;
font-family: inherit;
}
.tool-expand-btn:hover { background: #dbeafe; }
.tool-chip-pre.tool-chip-full {
max-height: 360px;
overflow: auto;
}
/* ==================== 时间线:正文段 / 流式光标 ==================== */
.md-segment:empty { display: none; }
.md-segment > :first-child { margin-top: 0; }
.md-segment > :last-child { margin-bottom: 0; }
/* block 包裹下,额外压平内层首尾段落的边距,保持气泡观感 */
.md-segment > :last-child > :last-child { margin-bottom: 0; }
/* 光标 "Chasing a shining star":文字顶住不动,只有 ● 缩放闪烁(星光脉冲无限循环)。
生命周期=原版:消息第一个时间线事件出现即显示 → finishMessage 移除,
思考/工具/压缩/正文全程保持(无休眠态) */
.streaming-typing {
display: inline-block; margin-left: 4px; color: #3b82f6;
font-size: 13px;
font-family: "Cascadia Mono", "Cascadia Code", Consolas, "Lucida Console", monospace;
}
.streaming-typing .typing-dot {
display: inline-block;
animation: cursor-star 1.6s ease-in-out infinite;
}
@keyframes cursor-star {
0%, 100% {
opacity: .25;
transform: scale(.85);
text-shadow: none;
}
50% {
opacity: 1;
transform: scale(1.15);
text-shadow: 0 0 6px rgba(59, 130, 246, .8), 0 0 12px rgba(59, 130, 246, .45);
}
}
/* ========== 助手正文透明气泡(流式中/完成后一致) ==========
🐛 修复:原选择器 .message.assistant 是死代码(JS 只挂 message-wrapper 类),背景从未渲染 */
/* 🆕 用户决定:助手正文气泡【透明】——不要背景/边框/圆角/内边距(2026-07 像素取证调试后明确:
原死选择器 P0 修复带来的浅灰底不是想要的效果),正文直接裸排在 85% 定宽列内 */
.message-wrapper.assistant .reply-content .md-segment + .md-segment {
margin-top: 6px;
}