feat(web): improve conversation side rails
This commit is contained in:
parent
38deea7d1d
commit
37aa83f8fb
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- 对话两侧的软件作业与轮次导航改为更紧凑的一致样式,并增加常显的名称和数量;既可悬停速览,也可点击固定展开,状态和当前位置更清楚。
|
||||||
|
|
||||||
- DeepSeek Flash 的思考过程恢复实时显示,不再等到正文开始后才集中出现。
|
- DeepSeek Flash 的思考过程恢复实时显示,不再等到正文开始后才集中出现。
|
||||||
|
|
||||||
- Windows 专业软件节点启用完整的新管理界面,节点状态、首次注册、软件环境、本机任务、ANSYS 验收和数据目录迁移均可直接管理;应用图标、导航、状态提示和窄窗口布局也更清晰,关闭窗口后仍会继续在托盘运行。
|
- Windows 专业软件节点启用完整的新管理界面,节点状态、首次注册、软件环境、本机任务、ANSYS 验收和数据目录迁移均可直接管理;应用图标、导航、状态提示和窄窗口布局也更清晰,关闭窗口后仍会继续在托盘运行。
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,10 @@ class StaticVendorTests(unittest.TestCase):
|
||||||
self.assertNotIn('<div class="role">科研助手</div>', frontend)
|
self.assertNotIn('<div class="role">科研助手</div>', frontend)
|
||||||
self.assertIn(".tool-call.activity > summary", css)
|
self.assertIn(".tool-call.activity > summary", css)
|
||||||
self.assertIn("#task-progress-dock .task-progress", css)
|
self.assertIn("#task-progress-dock .task-progress", css)
|
||||||
|
self.assertIn(".conversation-rail-caption", html)
|
||||||
|
self.assertIn("#msg-outline-rail.pinned .ol-label", html)
|
||||||
|
self.assertIn("点击固定展开或收起对话导航", frontend)
|
||||||
|
self.assertIn("点击固定展开或收起软件作业", frontend)
|
||||||
|
|
||||||
def test_frontend_surfaces_context_and_cache_stats(self) -> None:
|
def test_frontend_surfaces_context_and_cache_stats(self) -> None:
|
||||||
src = _frontend_source()
|
src = _frontend_source()
|
||||||
|
|
|
||||||
|
|
@ -843,56 +843,94 @@
|
||||||
0%, 100% { box-shadow: none; }
|
0%, 100% { box-shadow: none; }
|
||||||
18% { box-shadow: 0 0 0 2px var(--accent), 0 0 0 6px var(--accent-soft); }
|
18% { box-shadow: 0 0 0 2px var(--accent), 0 0 0 6px var(--accent-soft); }
|
||||||
}
|
}
|
||||||
/* 消息目录:悬浮在对话右缘的圆点轨道。容器 pointer-events:none 让圆点间隙漏给
|
/* 对话两侧索引轨共用紧凑视觉:收起时只占一条窄边轨,hover / 键盘聚焦后
|
||||||
滚动条/正文(不挡滚动),只有圆点本身可点;hover 圆点整列展开标题(ChatGPT 式)。 */
|
展开可读标签。轨道本身不拦截正文滚动,只有条目可交互。 */
|
||||||
#msg-outline-rail {
|
#msg-outline-rail {
|
||||||
position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
|
position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
|
||||||
max-height: 72%; display: flex; flex-direction: column; align-items: flex-end;
|
max-height: 72%; display: flex; flex-direction: column; align-items: flex-end;
|
||||||
gap: 7px; padding: 8px 4px; overflow-y: auto; overflow-x: hidden; z-index: 5;
|
gap: 3px; padding: 5px 3px; overflow-y: auto; overflow-x: hidden; z-index: 5;
|
||||||
pointer-events: none; scrollbar-width: none;
|
border: 1px solid rgba(225, 227, 229, .82); border-radius: 10px;
|
||||||
|
background: rgba(255, 255, 255, .84); box-shadow: 0 2px 10px rgba(24, 32, 41, .06);
|
||||||
|
backdrop-filter: blur(8px); pointer-events: none; scrollbar-width: none;
|
||||||
|
transition: border-color .15s ease, box-shadow .15s ease;
|
||||||
|
}
|
||||||
|
#msg-outline-rail:hover, #msg-outline-rail:focus-within {
|
||||||
|
border-color: var(--border); box-shadow: 0 6px 20px rgba(24, 32, 41, .10);
|
||||||
}
|
}
|
||||||
#msg-outline-rail::-webkit-scrollbar { display: none; }
|
#msg-outline-rail::-webkit-scrollbar { display: none; }
|
||||||
.ol-dot {
|
.conversation-rail-caption {
|
||||||
display: flex; align-items: center; justify-content: flex-end; gap: 8px;
|
display: inline-flex; align-items: center; gap: 4px; min-height: 20px;
|
||||||
background: transparent; border: none; padding: 0; cursor: pointer;
|
padding: 2px 6px; border: 0; border-radius: 6px; background: var(--panel-muted);
|
||||||
max-width: 22px; pointer-events: auto; transition: max-width .18s ease;
|
color: var(--muted); font-size: 10px; font-weight: 600; line-height: 1;
|
||||||
|
white-space: nowrap; pointer-events: auto;
|
||||||
}
|
}
|
||||||
#msg-outline-rail:hover .ol-dot { max-width: 240px; }
|
.conversation-rail-caption::after {
|
||||||
|
content: ""; width: 4px; height: 4px; border-right: 1.5px solid currentColor;
|
||||||
|
border-bottom: 1.5px solid currentColor; transform: rotate(45deg) translateY(-1px);
|
||||||
|
transition: transform .15s ease;
|
||||||
|
}
|
||||||
|
.conversation-rail-caption:hover, .conversation-rail-caption:focus-visible,
|
||||||
|
.pinned > .conversation-rail-caption {
|
||||||
|
color: var(--accent); background: var(--accent-soft);
|
||||||
|
}
|
||||||
|
.pinned > .conversation-rail-caption::after {
|
||||||
|
transform: rotate(225deg) translate(-1px, -1px);
|
||||||
|
}
|
||||||
|
.ol-dot {
|
||||||
|
display: flex; align-items: center; justify-content: flex-end; gap: 6px;
|
||||||
|
min-height: 16px; max-width: 18px; padding: 0; border: 0; border-radius: 7px;
|
||||||
|
background: transparent; cursor: pointer; pointer-events: auto;
|
||||||
|
transition: max-width .18s ease, background .15s ease;
|
||||||
|
}
|
||||||
|
#msg-outline-rail:hover .ol-dot, #msg-outline-rail:focus-within .ol-dot,
|
||||||
|
#msg-outline-rail.pinned .ol-dot { max-width: 246px; }
|
||||||
.ol-dot .ol-num { display: none; }
|
.ol-dot .ol-num { display: none; }
|
||||||
.ol-dot .ol-label {
|
.ol-dot .ol-label {
|
||||||
font-size: 12px; line-height: 1.3; color: var(--muted); white-space: nowrap;
|
font-size: 12px; line-height: 1.3; color: var(--muted); white-space: nowrap;
|
||||||
overflow: hidden; text-overflow: ellipsis; max-width: 0; opacity: 0;
|
overflow: hidden; text-overflow: ellipsis; max-width: 0; opacity: 0;
|
||||||
background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
|
padding: 1px 0; transition: max-width .18s ease, opacity .18s ease, padding .18s ease;
|
||||||
padding: 2px 8px; box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
}
|
||||||
transition: max-width .18s ease, opacity .18s ease;
|
#msg-outline-rail:hover .ol-label, #msg-outline-rail:focus-within .ol-label,
|
||||||
|
#msg-outline-rail.pinned .ol-label {
|
||||||
|
max-width: 210px; opacity: 1; padding-left: 7px;
|
||||||
}
|
}
|
||||||
#msg-outline-rail:hover .ol-label { max-width: 210px; opacity: 1; }
|
|
||||||
.ol-dot::after {
|
.ol-dot::after {
|
||||||
content: ""; flex: none; width: 16px; height: 4px; border-radius: 3px;
|
content: ""; flex: none; width: 12px; height: 3px; border-radius: 3px;
|
||||||
background: var(--border); transition: background .15s ease, width .15s ease;
|
background: var(--border); transition: background .15s ease, width .15s ease;
|
||||||
}
|
}
|
||||||
.ol-dot:hover::after { background: var(--muted); }
|
.ol-dot:hover, .ol-dot:focus-visible { background: var(--panel-muted); }
|
||||||
.ol-dot.active::after { background: var(--accent); width: 20px; }
|
.ol-dot:hover::after { background: #9aa0a6; }
|
||||||
|
.ol-dot.active .ol-label { color: var(--text-strong); font-weight: 600; }
|
||||||
|
.ol-dot.active::after { background: var(--accent); width: 16px; }
|
||||||
/* 专业软件结果轨:与右侧消息目录对称,按 Job 独立堆叠。折叠态只露状态点,
|
/* 专业软件结果轨:与右侧消息目录对称,按 Job 独立堆叠。折叠态只露状态点,
|
||||||
hover/focus 展开标题;结果与运行状态来自 software_jobs,不复制进聊天消息。 */
|
hover/focus 展开标题;结果与运行状态来自 software_jobs,不复制进聊天消息。 */
|
||||||
#software-result-rail {
|
#software-result-rail {
|
||||||
position: absolute; left: 4px; top: 50%; transform: translateY(-50%);
|
position: absolute; left: 6px; top: 50%; transform: translateY(-50%);
|
||||||
max-height: 72%; display: flex; flex-direction: column; align-items: flex-start;
|
max-height: 72%; display: flex; flex-direction: column; align-items: flex-start;
|
||||||
gap: 7px; padding: 8px 4px; overflow-y: auto; overflow-x: hidden; z-index: 6;
|
gap: 3px; padding: 5px 3px; overflow-y: auto; overflow-x: hidden; z-index: 6;
|
||||||
pointer-events: none; scrollbar-width: none;
|
border: 1px solid rgba(225, 227, 229, .82); border-radius: 10px;
|
||||||
|
background: rgba(255, 255, 255, .84); box-shadow: 0 2px 10px rgba(24, 32, 41, .06);
|
||||||
|
backdrop-filter: blur(8px); pointer-events: none; scrollbar-width: none;
|
||||||
|
transition: border-color .15s ease, box-shadow .15s ease;
|
||||||
|
}
|
||||||
|
#software-result-rail:hover, #software-result-rail:focus-within {
|
||||||
|
border-color: var(--border); box-shadow: 0 6px 20px rgba(24, 32, 41, .10);
|
||||||
}
|
}
|
||||||
#software-result-rail::-webkit-scrollbar { display: none; }
|
#software-result-rail::-webkit-scrollbar { display: none; }
|
||||||
#mobile-software-results { display: none; }
|
#mobile-software-results { display: none; }
|
||||||
.sr-item {
|
.sr-item {
|
||||||
display: flex; align-items: center; gap: 8px; max-width: 24px; min-height: 24px;
|
display: flex; align-items: center; gap: 6px; max-width: 18px; min-height: 16px;
|
||||||
padding: 0; border: 0; background: transparent; color: var(--text); cursor: pointer;
|
padding: 0; border: 0; border-radius: 7px; background: transparent;
|
||||||
pointer-events: auto; transition: max-width .18s ease;
|
color: var(--text); cursor: pointer; pointer-events: auto;
|
||||||
|
transition: max-width .18s ease, background .15s ease;
|
||||||
}
|
}
|
||||||
#software-result-rail:hover .sr-item,
|
#software-result-rail:hover .sr-item,
|
||||||
#software-result-rail:focus-within .sr-item { max-width: 260px; }
|
#software-result-rail:focus-within .sr-item,
|
||||||
|
#software-result-rail.pinned .sr-item { max-width: 278px; }
|
||||||
|
.sr-item:hover, .sr-item:focus-visible { background: var(--panel-muted); }
|
||||||
.sr-item::before {
|
.sr-item::before {
|
||||||
content: ""; flex: none; width: 10px; height: 10px; margin-left: 3px;
|
content: ""; flex: none; width: 8px; height: 8px; margin-left: 2px;
|
||||||
border: 2px solid var(--panel); border-radius: 50%; background: var(--border);
|
border: 1.5px solid var(--panel); border-radius: 50%; background: var(--border);
|
||||||
box-shadow: 0 0 0 1px var(--border);
|
box-shadow: 0 0 0 1px var(--border);
|
||||||
}
|
}
|
||||||
.sr-item.running::before, .sr-item.queued::before, .sr-item.dispatched::before,
|
.sr-item.running::before, .sr-item.queued::before, .sr-item.dispatched::before,
|
||||||
|
|
@ -905,18 +943,18 @@
|
||||||
.sr-item.cancelled::before { background: #8a929b; box-shadow: 0 0 0 1px #8a929b; }
|
.sr-item.cancelled::before { background: #8a929b; box-shadow: 0 0 0 1px #8a929b; }
|
||||||
@keyframes sr-pulse { 50% { opacity: .45; transform: scale(.82); } }
|
@keyframes sr-pulse { 50% { opacity: .45; transform: scale(.82); } }
|
||||||
.sr-label {
|
.sr-label {
|
||||||
min-width: 0; max-width: 0; opacity: 0; overflow: hidden; white-space: nowrap;
|
display: flex; align-items: baseline; gap: 6px; min-width: 0; max-width: 0;
|
||||||
border: 1px solid transparent; border-radius: 10px; padding: 4px 0;
|
opacity: 0; overflow: hidden; white-space: nowrap; padding: 1px 0;
|
||||||
background: var(--panel); box-shadow: 0 2px 8px rgba(0,0,0,.08);
|
|
||||||
text-align: left; transition: max-width .18s ease, opacity .18s ease, padding .18s ease;
|
text-align: left; transition: max-width .18s ease, opacity .18s ease, padding .18s ease;
|
||||||
}
|
}
|
||||||
#software-result-rail:hover .sr-label,
|
#software-result-rail:hover .sr-label,
|
||||||
#software-result-rail:focus-within .sr-label {
|
#software-result-rail:focus-within .sr-label,
|
||||||
max-width: 228px; opacity: 1; padding: 4px 8px; border-color: var(--border);
|
#software-result-rail.pinned .sr-label {
|
||||||
|
max-width: 250px; opacity: 1; padding-right: 7px;
|
||||||
}
|
}
|
||||||
.sr-label strong, .sr-label small { display: block; overflow: hidden; text-overflow: ellipsis; }
|
.sr-label strong, .sr-label small { overflow: hidden; text-overflow: ellipsis; }
|
||||||
.sr-label strong { font-size: 12px; font-weight: 600; }
|
.sr-label strong { max-width: 142px; font-size: 12px; font-weight: 600; }
|
||||||
.sr-label small { margin-top: 1px; color: var(--muted); font-size: 10px; }
|
.sr-label small { max-width: 100px; color: var(--muted); font-size: 10px; }
|
||||||
/* 阅读宽度:assistant/system/tool 限到 ~48rem(约 60-80 字/行,长文不至于满屏铺开难回扫);
|
/* 阅读宽度:assistant/system/tool 限到 ~48rem(约 60-80 字/行,长文不至于满屏铺开难回扫);
|
||||||
user 气泡更窄(36rem)。宽屏下提升可读性,窄屏 92% 仍生效(min 取小者) */
|
user 气泡更窄(36rem)。宽屏下提升可读性,窄屏 92% 仍生效(min 取小者) */
|
||||||
.msg { border: 1px solid var(--border); border-radius: var(--r-md); padding: 8px 12px; max-width: min(92%, 48rem); animation: msg-in .22s cubic-bezier(.2,.7,.2,1); scroll-margin-top: 16px; }
|
.msg { border: 1px solid var(--border); border-radius: var(--r-md); padding: 8px 12px; max-width: min(92%, 48rem); animation: msg-in .22s cubic-bezier(.2,.7,.2,1); scroll-margin-top: 16px; }
|
||||||
|
|
|
||||||
|
|
@ -1257,12 +1257,17 @@ function renderOutlineRail() {
|
||||||
if (items.length < 2) { // 0/1 轮没必要显示目录
|
if (items.length < 2) { // 0/1 轮没必要显示目录
|
||||||
rail.style.display = "none";
|
rail.style.display = "none";
|
||||||
rail.innerHTML = "";
|
rail.innerHTML = "";
|
||||||
|
rail.classList.remove("pinned");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
rail.style.display = "";
|
rail.style.display = "";
|
||||||
rail.innerHTML = items.map((it, i) => {
|
const pinned = rail.classList.contains("pinned");
|
||||||
|
const caption = `<button type="button" class="conversation-rail-caption"`
|
||||||
|
+ ` aria-expanded="${pinned}" title="点击固定展开或收起对话导航">导航 ${items.length}</button>`;
|
||||||
|
rail.innerHTML = caption + items.map((it, i) => {
|
||||||
const label = it.snippet || `第 ${i + 1} 轮`;
|
const label = it.snippet || `第 ${i + 1} 轮`;
|
||||||
return `<button type="button" class="ol-dot" data-idx="${it.idx}" title="${escapeHtml(label)}">`
|
return `<button type="button" class="ol-dot" data-idx="${it.idx}" title="${escapeHtml(label)}"`
|
||||||
|
+ ` aria-label="跳转到第 ${i + 1} 轮:${escapeHtml(label)}">`
|
||||||
+ `<span class="ol-num">${i + 1}</span>`
|
+ `<span class="ol-num">${i + 1}</span>`
|
||||||
+ `<span class="ol-label">${escapeHtml(label)}</span></button>`;
|
+ `<span class="ol-label">${escapeHtml(label)}</span></button>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
|
|
@ -1273,7 +1278,10 @@ function setActiveOutlineIdx(idx) {
|
||||||
const rail = $("msg-outline-rail");
|
const rail = $("msg-outline-rail");
|
||||||
if (!rail) return;
|
if (!rail) return;
|
||||||
rail.querySelectorAll(".ol-dot").forEach((d) => {
|
rail.querySelectorAll(".ol-dot").forEach((d) => {
|
||||||
d.classList.toggle("active", Number(d.dataset.idx) === Number(idx));
|
const active = Number(d.dataset.idx) === Number(idx);
|
||||||
|
d.classList.toggle("active", active);
|
||||||
|
if (active) d.setAttribute("aria-current", "step");
|
||||||
|
else d.removeAttribute("aria-current");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1335,6 +1343,12 @@ $("chat-stream").addEventListener("scroll", () => {
|
||||||
const _outlineRailEl = $("msg-outline-rail");
|
const _outlineRailEl = $("msg-outline-rail");
|
||||||
if (_outlineRailEl) {
|
if (_outlineRailEl) {
|
||||||
_outlineRailEl.addEventListener("click", (e) => {
|
_outlineRailEl.addEventListener("click", (e) => {
|
||||||
|
const caption = e.target.closest(".conversation-rail-caption");
|
||||||
|
if (caption) {
|
||||||
|
const pinned = _outlineRailEl.classList.toggle("pinned");
|
||||||
|
caption.setAttribute("aria-expanded", String(pinned));
|
||||||
|
return;
|
||||||
|
}
|
||||||
const dot = e.target.closest(".ol-dot");
|
const dot = e.target.closest(".ol-dot");
|
||||||
if (dot) jumpToMessage(Number(dot.dataset.idx));
|
if (dot) jumpToMessage(Number(dot.dataset.idx));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -153,13 +153,18 @@ function renderTaskResultRail() {
|
||||||
if (!state.taskId || taskJobsTaskId !== state.taskId || !taskJobs.length) {
|
if (!state.taskId || taskJobsTaskId !== state.taskId || !taskJobs.length) {
|
||||||
rail.innerHTML = "";
|
rail.innerHTML = "";
|
||||||
rail.style.display = "none";
|
rail.style.display = "none";
|
||||||
|
rail.classList.remove("pinned");
|
||||||
if (mobileResults) mobileResults.hidden = true;
|
if (mobileResults) mobileResults.hidden = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
rail.innerHTML = taskJobs.map((job) => `
|
const pinned = rail.classList.contains("pinned");
|
||||||
|
rail.innerHTML = `<button type="button" class="conversation-rail-caption"`
|
||||||
|
+ ` aria-expanded="${pinned}" title="点击固定展开或收起软件作业">作业 ${taskJobs.length}</button>`
|
||||||
|
+ taskJobs.map((job) => `
|
||||||
<button type="button" class="sr-item ${escapeHtml(job.status)}"
|
<button type="button" class="sr-item ${escapeHtml(job.status)}"
|
||||||
data-result-job-id="${escapeHtml(job.job_id)}"
|
data-result-job-id="${escapeHtml(job.job_id)}"
|
||||||
title="${escapeHtml(taskResultLabel(job))} · ${escapeHtml(taskResultMeta(job))}">
|
title="${escapeHtml(taskResultLabel(job))} · ${escapeHtml(taskResultMeta(job))}"
|
||||||
|
aria-label="打开软件作业:${escapeHtml(taskResultLabel(job))},${escapeHtml(taskResultMeta(job))}">
|
||||||
<span class="sr-label"><strong>${escapeHtml(taskResultLabel(job))}</strong>
|
<span class="sr-label"><strong>${escapeHtml(taskResultLabel(job))}</strong>
|
||||||
<small>${escapeHtml(taskResultMeta(job))}</small></span>
|
<small>${escapeHtml(taskResultMeta(job))}</small></span>
|
||||||
</button>`).join("");
|
</button>`).join("");
|
||||||
|
|
@ -168,6 +173,13 @@ function renderTaskResultRail() {
|
||||||
mobileResults.hidden = false;
|
mobileResults.hidden = false;
|
||||||
mobileResults.textContent = `软件结果 ${taskJobs.length}`;
|
mobileResults.textContent = `软件结果 ${taskJobs.length}`;
|
||||||
}
|
}
|
||||||
|
const caption = rail.querySelector(".conversation-rail-caption");
|
||||||
|
if (caption) {
|
||||||
|
caption.onclick = () => {
|
||||||
|
const next = rail.classList.toggle("pinned");
|
||||||
|
caption.setAttribute("aria-expanded", String(next));
|
||||||
|
};
|
||||||
|
}
|
||||||
rail.querySelectorAll("[data-result-job-id]").forEach((button) => {
|
rail.querySelectorAll("[data-result-job-id]").forEach((button) => {
|
||||||
button.onclick = () => void openTaskResult(button.dataset.resultJobId);
|
button.onclick = () => void openTaskResult(button.dataset.resultJobId);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue