Compare commits

..

No commits in common. "a50a793306d1c96679cfd70f100c0c88f0dd6bf5" and "f2676caa80aeeb578b4c736c1880b0ce868cc094" have entirely different histories.

4 changed files with 70 additions and 198 deletions

View File

@ -171,7 +171,6 @@
- 提交前至少做与改动相关的最小验证(命令输出或手工步骤要可复现)。
- 视觉验证默认由用户完成:除非用户特别说明,所有需要视觉确认的修改(尤其是动画/过渡效果),构建/lint 通过后交由用户亲自查看确认,不要用 Playwright 截图或浏览器自动化代替用户验收。
- 运行根目录前端构建时,默认使用 `npm run build --silent 2>&1 | tail -n 5`
- 构建/安装/Lint 类命令(如 `npm run build`、`npm install`、`npm run lint`)因沙箱权限失败时(如报 `EPERM` / `Operation not permitted` / 沙箱拒绝写入),直接向用户说明失败原因并申请权限或调整路径授权,禁止尝试任何绕过沙箱的做法(如换执行方式规避限制、向其他路径写入等)。
### CLI 当前交互约束2026-05-15

View File

@ -23,7 +23,7 @@
</template>
<script setup lang="ts">
import { computed, inject, nextTick, onBeforeUnmount, onMounted, onUpdated, ref, watch } from 'vue';
import { computed, inject, nextTick, onMounted, onUpdated, ref, watch } from 'vue';
import CodeBlock from './CodeBlock.vue';
import {
parseMarkdownSegments,
@ -32,7 +32,6 @@ import {
type MarkdownSegment
} from '@/composables/useMarkdownRenderer';
import { chunkRenderedHtml, type HtmlChunk } from '@/utils/htmlChunks';
import { classifyMarkdownTables, observeMarkdownTables } from '@/utils/tableLayout';
import { enhanceCitationChips, type CitationAnnotation } from './citationChips';
defineOptions({ name: 'MarkdownRenderer' });
@ -106,27 +105,6 @@ function enhanceCitations() {
onMounted(enhanceCitations);
onUpdated(enhanceCitations);
watch(() => [props.citations, props.citationsFinal], enhanceCitations);
// /
let stopTableObserver: (() => void) | null = null;
function classifyTables() {
nextTick(() => {
const el = containerRef.value;
if (!el) return;
classifyMarkdownTables(el);
stopTableObserver?.();
stopTableObserver = observeMarkdownTables(el);
});
}
onMounted(classifyTables);
onUpdated(classifyTables);
watch(() => props.content, classifyTables);
onBeforeUnmount(() => {
stopTableObserver?.();
stopTableObserver = null;
});
</script>
<style scoped>

View File

@ -872,22 +872,17 @@
background: transparent;
}
/* 与正文表格同一横线风格无外框/无竖线/无底色块
布局模式换行/横向滚动 utils/tableLayout.ts 判定 */
.user-message.user-message--multi-agent .bubble-text .md-table-scroll,
.user-message.user-message--multi-agent .bubble-text [data-md-table-scroll='1'] {
display: block;
width: auto;
max-width: none;
margin: 10px 0;
overflow: hidden;
}
.user-message.user-message--multi-agent .bubble-text .md-table-scroll[data-md-table-mode='scroll'],
.user-message.user-message--multi-agent .bubble-text [data-md-table-scroll='1'][data-md-table-mode='scroll'] {
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
border: 1px solid var(--border-default);
border-radius: 8px;
background: color-mix(in srgb, var(--text-primary) 4%, var(--chip-bg));
}
.user-message.user-message--multi-agent .bubble-text .md-table-scroll > table,
@ -895,38 +890,18 @@
margin: 0;
}
.user-message.user-message--multi-agent .bubble-text [data-md-table-mode='scroll'] > table {
width: max-content;
min-width: 100%;
}
.user-message.user-message--multi-agent .bubble-text [data-md-table-mode='scroll'] > table td {
white-space: nowrap;
}
.user-message.user-message--multi-agent .bubble-text th,
.user-message.user-message--multi-agent .bubble-text td {
border: none;
padding: 8px 14px 8px 0;
border: 1px solid var(--border-default);
padding: 6px 10px;
text-align: left;
vertical-align: top;
vertical-align: middle;
font-size: 14px;
}
.user-message.user-message--multi-agent .bubble-text th:last-child,
.user-message.user-message--multi-agent .bubble-text td:last-child {
padding-right: 0;
}
.user-message.user-message--multi-agent .bubble-text thead th {
.user-message.user-message--multi-agent .bubble-text th {
font-weight: 600;
white-space: nowrap;
border-bottom: 1.5px solid var(--border-strong);
}
.user-message.user-message--multi-agent .bubble-text tbody td {
border-bottom: 1px solid var(--border-default);
overflow-wrap: break-word;
background: color-mix(in srgb, var(--text-primary) 7%, var(--chip-bg));
}
.message-block--last
@ -2212,14 +2187,12 @@ show-html[data-rendered='1'] {
animation: none !important; /* 防止外部动画样式影响代码块,导致视觉抖动 */
}
/* 表格清爽横线风格无外框/无竖线/无底色块
布局模式由 utils/tableLayout.ts 按内容自然宽度判定
默认换行模式表格占满内容区单元格折行
自然宽度超过阈值时容器打 data-md-table-mode='scroll'退化为整表横向滚动 */
.text-output .text-content table {
width: 100%;
width: max-content;
min-width: 100%;
border-collapse: collapse;
margin: 0;
background: var(--theme-surface-soft);
}
.text-output .text-content .md-table-scroll,
@ -2227,16 +2200,14 @@ show-html[data-rendered='1'] {
display: block;
width: auto;
max-width: none;
margin: 12px calc(var(--chat-content-x) * -1);
padding: 0 var(--chat-content-x);
overflow: hidden;
}
.text-output .text-content .md-table-scroll[data-md-table-mode='scroll'],
.text-output .text-content [data-md-table-scroll='1'][data-md-table-mode='scroll'] {
margin: 16px calc(var(--chat-content-x) * -1);
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
border: 1px solid var(--border-default);
border-radius: 12px;
background: var(--theme-surface-soft);
box-shadow: none;
}
.text-output .text-content .md-table-scroll > table,
@ -2244,62 +2215,47 @@ show-html[data-rendered='1'] {
margin: 0;
}
.text-output .text-content [data-md-table-mode='scroll'] > table {
width: max-content;
min-width: 100%;
.text-output .text-content .md-table-scroll::-webkit-scrollbar,
.text-output .text-content [data-md-table-scroll='1']::-webkit-scrollbar {
height: 10px;
}
.text-output .text-content [data-md-table-mode='scroll'] > table td {
white-space: nowrap;
.text-output .text-content .md-table-scroll::-webkit-scrollbar-track,
.text-output .text-content [data-md-table-scroll='1']::-webkit-scrollbar-track {
background: color-mix(in srgb, var(--theme-surface-muted) 70%, transparent);
border-radius: 999px;
margin: 0 8px 6px;
}
/* 细滚动条仅在滚动模式出现 */
.text-output .text-content .md-table-scroll[data-md-table-mode='scroll']::-webkit-scrollbar,
.text-output .text-content [data-md-table-scroll='1'][data-md-table-mode='scroll']::-webkit-scrollbar {
height: 8px;
}
.text-output .text-content .md-table-scroll[data-md-table-mode='scroll']::-webkit-scrollbar-track,
.text-output .text-content [data-md-table-scroll='1'][data-md-table-mode='scroll']::-webkit-scrollbar-track {
background: transparent;
}
.text-output .text-content .md-table-scroll[data-md-table-mode='scroll']::-webkit-scrollbar-thumb,
.text-output .text-content [data-md-table-scroll='1'][data-md-table-mode='scroll']::-webkit-scrollbar-thumb {
background: color-mix(in srgb, var(--text-secondary) 45%, transparent);
.text-output .text-content .md-table-scroll::-webkit-scrollbar-thumb,
.text-output .text-content [data-md-table-scroll='1']::-webkit-scrollbar-thumb {
background: color-mix(in srgb, var(--text-secondary) 55%, transparent);
border-radius: 999px;
}
.text-output .text-content .md-table-scroll[data-md-table-mode='scroll'],
.text-output .text-content [data-md-table-scroll='1'][data-md-table-mode='scroll'] {
scrollbar-color: color-mix(in srgb, var(--text-secondary) 45%, transparent) transparent;
.text-output .text-content .md-table-scroll,
.text-output .text-content [data-md-table-scroll='1'] {
scrollbar-color: color-mix(in srgb, var(--text-secondary) 55%, transparent)
color-mix(in srgb, var(--theme-surface-muted) 70%, transparent);
scrollbar-width: thin;
}
.text-output .text-content thead {
background: var(--theme-chip-bg);
}
.text-output .text-content th,
.text-output .text-content td {
border: none;
padding: 11px 18px 11px 0;
border: 1px solid var(--border-default);
padding: 10px 14px;
text-align: left;
vertical-align: top;
vertical-align: middle;
font-size: 14px;
}
.text-output .text-content th:last-child,
.text-output .text-content td:last-child {
padding-right: 0;
}
.text-output .text-content thead th {
.text-output .text-content th {
font-weight: 600;
color: var(--text-primary);
white-space: nowrap;
border-bottom: 1.5px solid var(--border-strong);
}
.text-output .text-content tbody td {
border-bottom: 1px solid var(--border-default);
overflow-wrap: break-word;
}
.text-output .text-content hr {
@ -2734,7 +2690,14 @@ body[data-theme='dark'] {
color: var(--text-primary);
}
/* 表格为无底色横线风格,三主题通用,无需覆盖 */
/* 表格 */
.text-output .text-content table {
background: var(--badge-bg);
}
.text-output .text-content thead {
background: var(--surface-panel);
}
/* 滚动锁定按钮 */
.scroll-lock-btn {
@ -2785,10 +2748,9 @@ body[data-theme='dark'] {
/* ========== 明亮主题变体(对话区组件) ========== */
/* 背景浅色主题表面 token 阶梯近乎纯平base/card/raised 全为 #ffffff
基础样式中代码块白底落白底不可见
基础样式中代码块白底落白底表格 #f9f9f9 落白底均不可见
参照上方深色覆盖块的做法按用户确认的方向做组件级补偿
代码块仅头部着色代码区与对话底保持同色
表格为无底色横线风格三主题通用无需补偿 */
代码块仅头部着色代码区与对话底保持同色表格白体+浅灰表头+可见边框 */
body[data-theme='light'] {
/* 代码块 - 头部浅灰、代码区与对话底同为白色,外框加强到可见 */
.code-block-wrapper {
@ -2799,4 +2761,24 @@ body[data-theme='light'] {
background: var(--surface-muted);
border-bottom-color: var(--border-strong);
}
/* 表格 - 白体 + 浅灰表头 + 可见边框 */
.text-output .text-content table {
background: var(--surface-raised);
}
.text-output .text-content thead {
background: var(--surface-muted);
}
.text-output .text-content th,
.text-output .text-content td {
border-color: var(--border-strong);
}
.text-output .text-content .md-table-scroll,
.text-output .text-content [data-md-table-scroll='1'] {
border-color: var(--border-strong);
background: var(--surface-raised);
}
}

View File

@ -1,87 +0,0 @@
/**
* Markdown /
*
*
* - ×
* - > × data-md-table-mode="scroll"退
*
* styles/components/chat/_chat-area.scss
*/
/** 自然宽度超出可用宽度的该倍数以内时,仍用换行模式吸收(避免略宽一点的表被迫滚动) */
const SCROLL_RATIO_THRESHOLD = 1.35;
const MODE_ATTR = 'data-md-table-mode';
const TABLE_SCROLLER_SELECTOR = '.md-table-scroll, [data-md-table-scroll="1"]';
function measureNaturalWidth(table: HTMLTableElement): number {
// 同一帧内临时切到「自然宽度 + 全部不折行」布局测量,读完立即恢复——
// 浏览器不会在两次样式变更之间绘制,无闪烁
const prevWidth = table.style.width;
table.style.width = 'max-content';
const cells = table.querySelectorAll('th, td');
const savedWhiteSpace: string[] = [];
cells.forEach((cell, i) => {
const el = cell as HTMLElement;
savedWhiteSpace[i] = el.style.whiteSpace;
el.style.whiteSpace = 'nowrap';
});
const width = table.offsetWidth;
table.style.width = prevWidth;
cells.forEach((cell, i) => {
(cell as HTMLElement).style.whiteSpace = savedWhiteSpace[i];
});
return width;
}
function availableWidth(scroller: HTMLElement): number {
const style = getComputedStyle(scroller);
return (
scroller.clientWidth -
parseFloat(style.paddingLeft || '0') -
parseFloat(style.paddingRight || '0')
);
}
function classifyOne(scroller: HTMLElement): void {
const table = scroller.querySelector('table');
if (!table) return;
const avail = availableWidth(scroller);
// 容器不可见(折叠块/未激活标签页内)时宽度为 0跳过等可见后由 ResizeObserver 补判
if (avail <= 0) return;
const natural = measureNaturalWidth(table);
const mode = natural <= avail * SCROLL_RATIO_THRESHOLD ? 'wrap' : 'scroll';
if (scroller.getAttribute(MODE_ATTR) !== mode) {
scroller.setAttribute(MODE_ATTR, mode);
}
}
/** 扫描 root 下所有 markdown 表格滚动容器,按当前可用宽度重新判定布局模式(幂等) */
export function classifyMarkdownTables(root: ParentNode): void {
root.querySelectorAll<HTMLElement>(TABLE_SCROLLER_SELECTOR).forEach(classifyOne);
}
/**
* root /
*
*/
export function observeMarkdownTables(root: ParentNode): () => void {
if (typeof ResizeObserver === 'undefined') return () => {};
const scrollers = Array.from(root.querySelectorAll<HTMLElement>(TABLE_SCROLLER_SELECTOR));
if (!scrollers.length) return () => {};
const lastWidths = new WeakMap<HTMLElement, number>();
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const el = entry.target as HTMLElement;
const width = entry.contentRect.width;
const last = lastWidths.get(el);
if (last === undefined || Math.abs(width - last) > 1) {
lastWidths.set(el, width);
classifyOne(el);
}
}
});
scrollers.forEach((el) => observer.observe(el));
return () => observer.disconnect();
}