diff --git a/.env.example b/.env.example index a9216eebb..4f2b30080 100644 --- a/.env.example +++ b/.env.example @@ -79,6 +79,23 @@ STORE_SESSION_RESPONSE_BODY=true # 是否在 Redis 中存储会话响应 # - false:不存储响应体(注意:不影响本次请求处理;仅影响后续查看 response body) # 说明:该开关不影响内部统计读取响应体(tokens/费用统计、SSE 假 200 检测仍会进行) +# Dashboard CodeDisplay(长内容美化性能) +# 说明: +# - 方案1:长内容 Pretty 视图使用纯文本(textarea)展示,避免语法高亮 DOM 爆炸导致卡顿(默认开启)。 +# - 方案3:仅对可视窗口做语法高亮(虚拟化高亮)。默认关闭,需要显式开启;开启后可在 UI 切换“纯文本/虚拟高亮”。 +CCH_CODEDISPLAY_LARGE_PLAIN=true +CCH_CODEDISPLAY_VIRTUAL_HIGHLIGHT=false + +# 可选调参(一般无需修改) +CCH_CODEDISPLAY_WORKER_ENABLE=true +CCH_CODEDISPLAY_PERF_DEBUG=false +CCH_CODEDISPLAY_HIGHLIGHT_MAX_CHARS=30000 +CCH_CODEDISPLAY_VIRTUAL_OVERSCAN_LINES=50 +CCH_CODEDISPLAY_VIRTUAL_CONTEXT_LINES=50 +CCH_CODEDISPLAY_VIRTUAL_LINE_HEIGHT_PX=18 +CCH_CODEDISPLAY_MAX_PRETTY_OUTPUT_BYTES=20000000 +CCH_CODEDISPLAY_MAX_LINE_INDEX_LINES=200000 + # 熔断器配置 # 功能说明:控制网络错误是否计入熔断器失败计数 # - false (默认):网络错误(DNS 解析失败、连接超时、代理连接失败等)不计入熔断器,仅供应商错误(4xx/5xx HTTP 响应)计入 diff --git a/messages/en/dashboard.json b/messages/en/dashboard.json index 15195b0c5..414c93802 100644 --- a/messages/en/dashboard.json +++ b/messages/en/dashboard.json @@ -568,7 +568,27 @@ "codeDisplay": { "raw": "Raw", "pretty": "Pretty", + "prettyWorking": "Formatting... {percent}%", + "viewPlain": "Plain", + "viewVirtual": "Virtual highlight", + "virtualFallbackToPlain": "Virtual highlight unavailable, switched to plain", "searchPlaceholder": "Search", + "searchWorking": "Searching... {percent}%", + "search": { + "indexFailed": "Failed to build search index", + "indexCanceled": "Search index build canceled", + "indexTooManyLines": "Too many lines ({lineCount}, limit {maxLines})", + "indexTooManyLinesUnknown": "Too many lines (limit {maxLines})", + "failed": "Search failed", + "canceled": "Search canceled" + }, + "cancel": "Cancel", + "retry": "Retry", + "prettyCanceled": "Formatting canceled", + "prettyFailed": "Formatting failed", + "prettyInvalidJson": "Invalid JSON", + "prettyOutputTooLarge": "Formatted output too large", + "prettyWorkerUnavailable": "Worker unavailable, cannot format very large JSON", "expand": "Expand", "collapse": "Collapse", "themeAuto": "Auto", @@ -582,6 +602,13 @@ "pageInfo": "Page {page} / {total}", "sseEvent": "Event", "sseData": "Data", + "virtual": { + "indexWorking": "Indexing... {percent}%", + "indexFailed": "Failed to build index, switched to plain", + "indexCanceled": "Indexing canceled, switched to plain", + "indexTooManyLines": "Too many lines ({lineCount}, max {maxLines}), switched to plain", + "indexTooManyLinesUnknown": "Too many lines (max {maxLines}), switched to plain" + }, "hardLimit": { "title": "Content too large", "size": "Size: {sizeMB} MB ({sizeBytes} bytes)", diff --git a/messages/ja/dashboard.json b/messages/ja/dashboard.json index 1555e52af..fd2d8a3ef 100644 --- a/messages/ja/dashboard.json +++ b/messages/ja/dashboard.json @@ -568,7 +568,27 @@ "codeDisplay": { "raw": "Raw", "pretty": "Pretty", + "prettyWorking": "整形中... {percent}%", + "viewPlain": "テキスト", + "viewVirtual": "仮想ハイライト", + "virtualFallbackToPlain": "仮想ハイライトを利用できないため、テキスト表示に切り替えました", "searchPlaceholder": "検索", + "searchWorking": "検索中... {percent}%", + "search": { + "indexFailed": "検索インデックスの作成に失敗しました", + "indexCanceled": "検索インデックスの作成をキャンセルしました", + "indexTooManyLines": "行数が多すぎます({lineCount} 行、上限 {maxLines} 行)", + "indexTooManyLinesUnknown": "行数が多すぎます(上限 {maxLines} 行)", + "failed": "検索に失敗しました", + "canceled": "検索をキャンセルしました" + }, + "cancel": "キャンセル", + "retry": "再試行", + "prettyCanceled": "整形をキャンセルしました", + "prettyFailed": "整形に失敗しました", + "prettyInvalidJson": "無効な JSON", + "prettyOutputTooLarge": "整形結果が大きすぎます", + "prettyWorkerUnavailable": "Worker を利用できないため、大きな JSON を整形できません", "expand": "展開", "collapse": "折りたたむ", "themeAuto": "自動", @@ -582,6 +602,13 @@ "pageInfo": "{page} / {total} ページ", "sseEvent": "イベント", "sseData": "データ", + "virtual": { + "indexWorking": "索引作成中... {percent}%", + "indexFailed": "索引の作成に失敗したため、テキスト表示に切り替えました", + "indexCanceled": "索引作成をキャンセルしたため、テキスト表示に切り替えました", + "indexTooManyLines": "行数が多すぎます ({lineCount} 行、上限 {maxLines} 行)。テキスト表示に切り替えました", + "indexTooManyLinesUnknown": "行数が多すぎます (上限 {maxLines} 行)。テキスト表示に切り替えました" + }, "hardLimit": { "title": "コンテンツが大きすぎます", "size": "サイズ: {sizeMB} MB ({sizeBytes} bytes)", diff --git a/messages/ru/dashboard.json b/messages/ru/dashboard.json index 64a2c8211..25ca15dfb 100644 --- a/messages/ru/dashboard.json +++ b/messages/ru/dashboard.json @@ -568,7 +568,27 @@ "codeDisplay": { "raw": "Сырой", "pretty": "Форматированный", + "prettyWorking": "Форматирование... {percent}%", + "viewPlain": "Текст", + "viewVirtual": "Виртуальная подсветка", + "virtualFallbackToPlain": "Виртуальная подсветка недоступна — переключено на простой текст", "searchPlaceholder": "Поиск", + "searchWorking": "Поиск... {percent}%", + "search": { + "indexFailed": "Не удалось построить индекс поиска", + "indexCanceled": "Построение индекса поиска отменено", + "indexTooManyLines": "Слишком много строк ({lineCount}, лимит {maxLines})", + "indexTooManyLinesUnknown": "Слишком много строк (лимит {maxLines})", + "failed": "Поиск не удался", + "canceled": "Поиск отменён" + }, + "cancel": "Отмена", + "retry": "Повторить", + "prettyCanceled": "Форматирование отменено", + "prettyFailed": "Ошибка форматирования", + "prettyInvalidJson": "Некорректный JSON", + "prettyOutputTooLarge": "Результат форматирования слишком большой", + "prettyWorkerUnavailable": "Worker недоступен — невозможно отформатировать большой JSON", "expand": "Развернуть", "collapse": "Свернуть", "themeAuto": "Авто", @@ -582,6 +602,13 @@ "pageInfo": "Страница {page} / {total}", "sseEvent": "Событие", "sseData": "Данные", + "virtual": { + "indexWorking": "Индексация... {percent}%", + "indexFailed": "Не удалось построить индекс — переключено на простой текст", + "indexCanceled": "Индексация отменена — переключено на простой текст", + "indexTooManyLines": "Слишком много строк ({lineCount}, максимум {maxLines}) — переключено на простой текст", + "indexTooManyLinesUnknown": "Слишком много строк (максимум {maxLines}) — переключено на простой текст" + }, "hardLimit": { "title": "Содержимое слишком большое", "size": "Размер: {sizeMB} MB ({sizeBytes} bytes)", diff --git a/messages/zh-CN/dashboard.json b/messages/zh-CN/dashboard.json index 6743b6857..482b29f21 100644 --- a/messages/zh-CN/dashboard.json +++ b/messages/zh-CN/dashboard.json @@ -568,7 +568,27 @@ "codeDisplay": { "raw": "原始", "pretty": "美化", + "prettyWorking": "美化中... {percent}%", + "viewPlain": "纯文本", + "viewVirtual": "虚拟高亮", + "virtualFallbackToPlain": "虚拟高亮不可用,已回退到纯文本", "searchPlaceholder": "搜索", + "searchWorking": "搜索中... {percent}%", + "search": { + "indexFailed": "建立搜索索引失败", + "indexCanceled": "已取消建立搜索索引", + "indexTooManyLines": "内容行数过多({lineCount} 行,上限 {maxLines} 行)", + "indexTooManyLinesUnknown": "内容行数过多(上限 {maxLines} 行)", + "failed": "搜索失败", + "canceled": "已取消搜索" + }, + "cancel": "取消", + "retry": "重试", + "prettyCanceled": "已取消美化", + "prettyFailed": "美化失败", + "prettyInvalidJson": "无效的 JSON", + "prettyOutputTooLarge": "美化结果过大", + "prettyWorkerUnavailable": "当前环境无法使用 Worker,无法美化超大 JSON", "expand": "展开", "collapse": "收起", "themeAuto": "跟随系统", @@ -582,6 +602,13 @@ "pageInfo": "第 {page} / {total} 页", "sseEvent": "事件", "sseData": "数据", + "virtual": { + "indexWorking": "建立索引中... {percent}%", + "indexFailed": "建立索引失败,已回退到纯文本", + "indexCanceled": "已取消建立索引,已回退到纯文本", + "indexTooManyLines": "内容行数过多({lineCount} 行,上限 {maxLines} 行),已回退到纯文本", + "indexTooManyLinesUnknown": "内容行数过多(上限 {maxLines} 行),已回退到纯文本" + }, "hardLimit": { "title": "内容过大", "size": "大小:{sizeMB} MB({sizeBytes} 字节)", diff --git a/messages/zh-TW/dashboard.json b/messages/zh-TW/dashboard.json index e28e8eb07..5d4e7b7a3 100644 --- a/messages/zh-TW/dashboard.json +++ b/messages/zh-TW/dashboard.json @@ -568,7 +568,27 @@ "codeDisplay": { "raw": "原始內容", "pretty": "格式化", + "prettyWorking": "格式化中... {percent}%", + "viewPlain": "純文字", + "viewVirtual": "虛擬高亮", + "virtualFallbackToPlain": "虛擬高亮不可用,已回退到純文字", "searchPlaceholder": "搜尋", + "searchWorking": "搜尋中... {percent}%", + "search": { + "indexFailed": "建立搜尋索引失敗", + "indexCanceled": "已取消建立搜尋索引", + "indexTooManyLines": "內容行數過多({lineCount} 行,上限 {maxLines} 行)", + "indexTooManyLinesUnknown": "內容行數過多(上限 {maxLines} 行)", + "failed": "搜尋失敗", + "canceled": "已取消搜尋" + }, + "cancel": "取消", + "retry": "重試", + "prettyCanceled": "已取消美化", + "prettyFailed": "美化失敗", + "prettyInvalidJson": "無效的 JSON", + "prettyOutputTooLarge": "美化結果過大", + "prettyWorkerUnavailable": "目前環境無法使用 Worker,無法美化超大 JSON", "expand": "展開", "collapse": "摺疊", "themeAuto": "跟隨系統", @@ -582,6 +602,13 @@ "pageInfo": "第 {page} / {total} 頁", "sseEvent": "事件類型", "sseData": "資料", + "virtual": { + "indexWorking": "建立索引中... {percent}%", + "indexFailed": "建立索引失敗,已回退到純文字", + "indexCanceled": "已取消建立索引,已回退到純文字", + "indexTooManyLines": "內容行數過多({lineCount} 行,上限 {maxLines} 行),已回退到純文字", + "indexTooManyLinesUnknown": "內容行數過多(上限 {maxLines} 行),已回退到純文字" + }, "hardLimit": { "title": "內容過大", "size": "大小:{sizeMB} MB({sizeBytes} 位元組)", diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index a7c6c18d5..b521e0e97 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -4,6 +4,7 @@ import { notFound } from "next/navigation"; import { NextIntlClientProvider } from "next-intl"; import { getMessages } from "next-intl/server"; import { Footer } from "@/components/customs/footer"; +import { parseCodeDisplayConfigFromEnv } from "@/components/ui/code-display-config"; import { Toaster } from "@/components/ui/sonner"; import { type Locale, locales } from "@/i18n/config"; import { logger } from "@/lib/logger"; @@ -72,6 +73,7 @@ export default async function RootLayout({ // Load translation messages const messages = await getMessages(); const timeZone = await resolveSystemTimezone(); + const codeDisplayConfig = parseCodeDisplayConfigFromEnv(process.env); // Create a stable `now` timestamp to avoid SSR/CSR hydration mismatch for relative time const now = new Date(); @@ -79,7 +81,7 @@ export default async function RootLayout({ - +
{children}