@@ -564,10 +611,60 @@ export const CodeReviewToolCard: React.FC = React.memo(({
{review_mode === 'deep' ? (
- {renderReportGroupList(
- reportSections.remediationGroups,
- (id) => getRemediationGroupTitle(id, t),
- )}
+ {reportSections.remediationGroups.map((group) => {
+ const groupTitle = getRemediationGroupTitle(group.id, t);
+
+ // Render needs_decision group with structured decision context
+ if (group.id === 'needs_decision') {
+ const rawEntries = reviewData?.report_sections?.remediation_groups?.needs_decision;
+ return (
+
+ );
+ }
+
+ // Default rendering for other groups
+ return (
+
+ );
+ })}
) : (
diff --git a/src/web-ui/src/flow_chat/tool-cards/ContextCompressionDisplay.tsx b/src/web-ui/src/flow_chat/tool-cards/ContextCompressionDisplay.tsx
index 3e4ad3fbc..f8f0a09e0 100644
--- a/src/web-ui/src/flow_chat/tool-cards/ContextCompressionDisplay.tsx
+++ b/src/web-ui/src/flow_chat/tool-cards/ContextCompressionDisplay.tsx
@@ -66,7 +66,7 @@ export const ContextCompressionDisplay: React.FC
case 'tool_batch':
return t('toolCards.contextCompression.toolBatchComplete');
case 'ai_response':
- return 'After AI response';
+ return t('toolCards.contextCompression.afterAiResponse');
case 'manual':
return t('toolCards.contextCompression.manualTrigger');
default:
@@ -111,16 +111,22 @@ export const ContextCompressionDisplay: React.FC
{data.tokensBefore !== undefined && data.tokensAfter !== undefined ? (
<>
- {data.tokensBefore.toLocaleString()} → {data.tokensAfter.toLocaleString()} tokens
+ {t('toolCards.contextCompression.tokenChange', {
+ before: data.tokensBefore.toLocaleString(),
+ after: data.tokensAfter.toLocaleString(),
+ })}
{savedTokens !== undefined && data.compressionRatio !== undefined && (
- Saved {savedTokens.toLocaleString()} · Ratio {(data.compressionRatio * 100).toFixed(0)}%
+ {t('toolCards.contextCompression.savingsTag', {
+ saved: savedTokens.toLocaleString(),
+ ratio: (data.compressionRatio * 100).toFixed(0),
+ })}
)}
>
) : (
- Compressing context...
+ {t('toolCards.contextCompression.compressingContext')}
)}
}
@@ -128,7 +134,7 @@ export const ContextCompressionDisplay: React.FC
<>
{data.status === 'completed' && data.compressionCount && (
- {getTriggerText(data.trigger)} · Compression #{data.compressionCount}
+ {getTriggerText(data.trigger)} · {t('toolCards.contextCompression.compressionCount', { count: data.compressionCount })}
)}
>
diff --git a/src/web-ui/src/flow_chat/tool-cards/TaskToolDisplay.scss b/src/web-ui/src/flow_chat/tool-cards/TaskToolDisplay.scss
index 0a57c2d9d..732b29633 100644
--- a/src/web-ui/src/flow_chat/tool-cards/TaskToolDisplay.scss
+++ b/src/web-ui/src/flow_chat/tool-cards/TaskToolDisplay.scss
@@ -227,7 +227,7 @@
that can leave only padding visible and push the prompt text out of view. */
.task-expanded-content .task-prompt-content {
padding: 0 12px;
- max-height: calc(var(--flowchat-font-size-base) * 1.65 * 3 + 10px);
+ max-height: calc(var(--flowchat-font-size-base) * 1.5 * 2 + 6px);
overflow-y: auto;
}
@@ -271,7 +271,7 @@
/* Extra breathing room below header / divider before prompt + actions. */
&.base-tool-card-wrapper > .base-tool-card-expanded {
- padding-top: 14px;
+ padding-top: 8px;
}
&.base-tool-card-wrapper > .base-tool-card-expanded:has(.task-prompt-content) {
@@ -319,7 +319,7 @@
.task-expanded-content {
display: flex;
flex-direction: column;
- gap: 12px;
+ gap: 8px;
}
diff --git a/src/web-ui/src/flow_chat/utils/codeReviewRemediation.ts b/src/web-ui/src/flow_chat/utils/codeReviewRemediation.ts
index bff899cbc..01ae8ddee 100644
--- a/src/web-ui/src/flow_chat/utils/codeReviewRemediation.ts
+++ b/src/web-ui/src/flow_chat/utils/codeReviewRemediation.ts
@@ -1,8 +1,10 @@
import type {
CodeReviewReportSectionsData,
+ DecisionContext,
RemediationGroupId,
ReviewMode,
} from './codeReviewReport';
+import { normalizeDecisionEntry } from './codeReviewReport';
export interface CodeReviewRemediationSummary {
overall_assessment?: string;
@@ -34,10 +36,14 @@ export interface CodeReviewRemediationData {
export interface ReviewRemediationItem {
id: string;
index: number;
+ groupIndex: number;
plan: string;
issue?: CodeReviewRemediationIssue;
+ /** Index of the best-matching issue in the report's `issues` array, or -1. */
+ issueIndex: number;
groupId?: RemediationGroupId;
requiresDecision?: boolean;
+ decisionContext?: DecisionContext;
defaultSelected: boolean;
}
@@ -49,20 +55,54 @@ export const REMEDIATION_GROUP_ORDER: RemediationGroupId[] = [
'verification',
];
-function nonEmpty(values?: Array): string[] {
- const seen = new Set();
- const result: string[] = [];
+/**
+ * Find the best-matching issue index for a remediation plan text.
+ * Matches by extracting file paths from the plan and checking issue.file.
+ * Falls back to category matching, then to overall position order.
+ */
+function findMatchingIssueIndex(
+ plan: string,
+ issues: CodeReviewRemediationIssue[] | undefined,
+ positionHint: number,
+): number {
+ if (!issues || issues.length === 0) return -1;
+
+ const planLower = plan.toLowerCase();
+
+ // Strategy 1: match by file path mentioned in plan
+ for (let i = 0; i < issues.length; i++) {
+ const issue = issues[i];
+ if (issue.file && planLower.includes(issue.file.toLowerCase())) {
+ return i;
+ }
+ }
- for (const value of values ?? []) {
- const trimmed = value?.trim();
- if (!trimmed || seen.has(trimmed)) {
- continue;
+ // Strategy 2: match by category keyword in plan
+ for (let i = 0; i < issues.length; i++) {
+ const issue = issues[i];
+ if (issue.category && planLower.includes(issue.category.toLowerCase())) {
+ return i;
+ }
+ }
+
+ // Strategy 3: match by issue title keywords (significant words)
+ for (let i = 0; i < issues.length; i++) {
+ const issue = issues[i];
+ if (issue.title) {
+ const titleWords = issue.title.toLowerCase().split(/\s+/).filter(w => w.length > 3);
+ const matchCount = titleWords.filter(w => planLower.includes(w)).length;
+ if (matchCount >= Math.ceil(titleWords.length * 0.5) && matchCount >= 2) {
+ return i;
+ }
}
- seen.add(trimmed);
- result.push(trimmed);
}
- return result;
+ // Strategy 4: positional hint (for legacy data where plans and issues are 1:1 ordered)
+ if (positionHint < issues.length) {
+ return positionHint;
+ }
+
+ return -1;
}
function hasConcreteFixSignal(issue?: CodeReviewRemediationIssue): boolean {
@@ -95,20 +135,41 @@ function buildStructuredRemediationItems(
return [];
}
+ const issues = reviewData.issues;
const items: ReviewRemediationItem[] = [];
+ let globalIssueOffset = 0;
for (const groupId of REMEDIATION_GROUP_ORDER) {
- for (const plan of nonEmpty(remediationGroups[groupId])) {
+ const rawEntries = remediationGroups[groupId];
+ if (!rawEntries || !Array.isArray(rawEntries) || rawEntries.length === 0) {
+ continue;
+ }
+
+ let groupIndex = 0;
+ for (const raw of rawEntries) {
+ // Normalize: needs_decision entries may be structured objects or plain strings
+ const isDecision = groupId === 'needs_decision';
+ const normalized = isDecision ? normalizeDecisionEntry(raw as string | DecisionContext) : null;
+ const plan = isDecision && normalized ? normalized.plan : String(raw).trim();
+ if (!plan) {
+ continue;
+ }
+
const index = items.length;
- const requiresDecision = groupId === 'needs_decision';
+ const issueIndex = findMatchingIssueIndex(plan, issues, globalIssueOffset);
items.push({
id: `remediation-${groupId}-${index}`,
index,
+ groupIndex,
plan,
+ issueIndex,
groupId,
- requiresDecision,
+ requiresDecision: isDecision,
+ decisionContext: isDecision ? normalized ?? undefined : undefined,
defaultSelected: groupId === 'must_fix',
});
+ groupIndex++;
+ globalIssueOffset++;
}
}
@@ -132,10 +193,13 @@ export function buildReviewRemediationItems(
}
const issue = reviewData.issues?.[index];
+ const issueIndex = issue ? index : findMatchingIssueIndex(trimmedPlan, reviewData.issues, index);
items.push({
id: `remediation-${index}`,
index,
+ groupIndex: index,
plan: trimmedPlan,
+ issueIndex,
...(issue ? { issue } : {}),
defaultSelected: shouldSelectByDefault(reviewData, issue),
});
@@ -158,11 +222,29 @@ function formatIssueLocation(issue: CodeReviewRemediationIssue): string {
return issue.line ? `${issue.file}:${issue.line}` : issue.file;
}
-function formatIssueForPrompt(item: ReviewRemediationItem): string {
+function formatIssueForPrompt(item: ReviewRemediationItem, decisionSelection?: number): string {
const issue = item.issue;
+ const decisionCtx = item.decisionContext;
+
+ // Build decision context line if available
+ const decisionLines: string[] = [];
+ if (decisionCtx) {
+ decisionLines.push(` Decision: ${decisionCtx.question}`);
+ if (decisionCtx.options && decisionCtx.options.length > 0) {
+ if (decisionSelection != null) {
+ decisionLines.push(` User chose option ${decisionSelection + 1}: ${decisionCtx.options[decisionSelection]}`);
+ } else if (decisionCtx.recommendation != null) {
+ decisionLines.push(` Recommended option ${decisionCtx.recommendation + 1}: ${decisionCtx.options[decisionCtx.recommendation]}`);
+ }
+ }
+ }
+
if (!issue) {
const groupLabel = item.groupId ? ` [${item.groupId}]` : '';
- return `${item.index + 1}.${groupLabel} No directly-linked issue. Plan: ${item.plan}`;
+ return [
+ `${item.index + 1}.${groupLabel} No directly-linked issue. Plan: ${item.plan}`,
+ ...decisionLines,
+ ].filter(Boolean).join('\n');
}
return [
@@ -170,6 +252,7 @@ function formatIssueForPrompt(item: ReviewRemediationItem): string {
` Description: ${issue.description ?? 'N/A'}`,
` Suggestion: ${issue.suggestion ?? item.plan}`,
issue.validation_note ? ` Validation: ${issue.validation_note}` : undefined,
+ ...decisionLines,
].filter(Boolean).join('\n');
}
@@ -177,6 +260,7 @@ export function buildSelectedRemediationPrompt(params: {
reviewData: CodeReviewRemediationData;
selectedIds: Set;
rerunReview: boolean;
+ decisionSelections?: Record;
}): string {
return buildSelectedReviewRemediationPrompt({
...params,
@@ -190,6 +274,7 @@ export function buildSelectedReviewRemediationPrompt(params: {
rerunReview: boolean;
reviewMode: ReviewMode;
completedItems?: string[];
+ decisionSelections?: Record;
}): string {
if (params.selectedIds.size === 0) {
return '';
@@ -206,7 +291,7 @@ export function buildSelectedReviewRemediationPrompt(params: {
.map((item, index) => `${index + 1}. ${item.plan}`)
.join('\n');
const issuesBlock = selectedItems
- .map(formatIssueForPrompt)
+ .map((item) => formatIssueForPrompt(item, params.decisionSelections?.[item.id]))
.join('\n\n');
const isDeepReview = params.reviewMode === 'deep';
const reviewLabel = isDeepReview ? 'Deep Review' : 'Code Review';
diff --git a/src/web-ui/src/flow_chat/utils/codeReviewReport.ts b/src/web-ui/src/flow_chat/utils/codeReviewReport.ts
index ce95b8501..f34c57889 100644
--- a/src/web-ui/src/flow_chat/utils/codeReviewReport.ts
+++ b/src/web-ui/src/flow_chat/utils/codeReviewReport.ts
@@ -44,11 +44,31 @@ export interface CodeReviewReviewer {
export interface CodeReviewReportSectionsData {
executive_summary?: string[];
- remediation_groups?: Partial>;
+ remediation_groups?: Partial>;
strength_groups?: Partial>;
coverage_notes?: string[];
}
+/**
+ * Structured decision context for `needs_decision` remediation items.
+ * Falls back to a plain string when the AI returns a legacy format.
+ */
+export interface DecisionContext {
+ question: string;
+ plan: string;
+ options?: string[];
+ tradeoffs?: string;
+ recommendation?: number;
+}
+
+/** Normalize a raw `needs_decision` entry to a DecisionContext object. */
+export function normalizeDecisionEntry(entry: string | DecisionContext): DecisionContext {
+ if (typeof entry === 'string') {
+ return { question: entry, plan: entry };
+ }
+ return entry;
+}
+
export interface CodeReviewReportData {
schema_version?: number;
schemaVersion?: number;
@@ -253,7 +273,21 @@ function buildReviewerStats(reviewers: CodeReviewReviewer[] = []): ReviewReviewe
export function buildCodeReviewReportSections(report: CodeReviewReportData): ReviewReportSections {
const structuredSections = report.report_sections;
- const remediationGroups = buildGroups(REMEDIATION_GROUP_ORDER, structuredSections?.remediation_groups);
+
+ // Normalize remediation groups: DecisionContext entries become their plan text for display
+ const rawRemediationGroups = structuredSections?.remediation_groups;
+ const normalizedRemediationGroups: Partial> = {};
+ if (rawRemediationGroups) {
+ for (const [key, entries] of Object.entries(rawRemediationGroups) as [RemediationGroupId, (string | DecisionContext)[] | undefined][]) {
+ if (!entries) continue;
+ normalizedRemediationGroups[key] = entries.map((entry) => {
+ if (typeof entry === 'string') return entry;
+ return entry.plan;
+ });
+ }
+ }
+
+ const remediationGroups = buildGroups(REMEDIATION_GROUP_ORDER, normalizedRemediationGroups);
const strengthGroups = buildGroups(STRENGTH_GROUP_ORDER, structuredSections?.strength_groups);
const executiveSummary = nonEmpty(structuredSections?.executive_summary);
const coverageNotes = nonEmpty(structuredSections?.coverage_notes);
diff --git a/src/web-ui/src/locales/en-US/components.json b/src/web-ui/src/locales/en-US/components.json
index 756269e41..217b6995c 100644
--- a/src/web-ui/src/locales/en-US/components.json
+++ b/src/web-ui/src/locales/en-US/components.json
@@ -46,7 +46,8 @@
"openInExplorer": "Open in Explorer",
"openInBrowser": "Open in browser",
"openInBuiltInBrowser": "Open in built-in browser",
- "copyLink": "Copy link"
+ "copyLink": "Copy link",
+ "copyFilePath": "Copy file path"
},
"mermaidBlock": {
"renderFailed": "Diagram render failed",
diff --git a/src/web-ui/src/locales/en-US/flow-chat.json b/src/web-ui/src/locales/en-US/flow-chat.json
index 3b961ef19..757fd3c34 100644
--- a/src/web-ui/src/locales/en-US/flow-chat.json
+++ b/src/web-ui/src/locales/en-US/flow-chat.json
@@ -386,7 +386,7 @@
"close": "Close",
"minimize": "Minimize",
"restore": "Open {{label}}",
- "fixAndReviewRunning": "Fixing and preparing re-review...",
+ "fixAndReviewRunning": "Fixing \u0026 preparing re-review...",
"minimizedDeep": "Deep Review",
"minimizedStandard": "Code Review",
"minimizedFix": "Fixing",
@@ -400,6 +400,7 @@
"customInstructionsPlaceholder": "Describe additional requirements or context for the fix...",
"fillBackInput": "Fill to input",
"fillBackInputHint": "Copy selected fix plan to the input box for manual editing",
+ "fixCompletedMessage": "All fixes applied successfully.",
"replaceInputConfirmTitle": "Replace current input?",
"replaceInputConfirmMessage": "The chat input already has text. Filling this plan will replace the current draft.",
"replaceInputConfirmAction": "Replace input",
@@ -1000,11 +1001,16 @@
"toolBatchComplete": "Tool batch complete",
"manualTrigger": "Manual trigger",
"autoTrigger": "Auto trigger",
+ "afterAiResponse": "After AI response",
"contextCompressionFailed": "Context compression failed",
"contextCompression": "Context compression:",
- "localFallbackHeader": "Model request failed; local structured trimming",
+ "localFallbackHeader": "Model request failed; local structured compression",
"localFallbackNotice": "Model-based summary was unavailable, so this compression used local structured truncation.",
- "noSummaryNotice": "No additional summary was generated for this compaction pass."
+ "noSummaryNotice": "No additional summary was generated for this compaction pass.",
+ "tokenChange": "{{before}} → {{after}} tokens",
+ "savingsTag": "Saved {{saved}} · Ratio {{ratio}}%",
+ "compressingContext": "Compressing context...",
+ "compressionCount": "Compression #{{count}}"
},
"globSearch": {
"parsingPattern": "Parsing search pattern...",
@@ -1232,11 +1238,14 @@
"noSelectionHint": "Select at least one remediation item to start fixing.",
"ungrouped": "Other",
"startFix": "Start fixing",
- "fixAndReview": "Fix and re-review",
+ "fixAndReview": "Fix \\u0026 re-review",
"cancel": "Cancel",
"fixUnavailable": "Unable to start remediation because the review session is unavailable.",
"fixRequestDisplay": "Start fixing Deep Review findings",
- "fixAndReviewRequestDisplay": "Fix Deep Review findings and re-review"
+ "fixAndReviewRequestDisplay": "Fix Deep Review findings and re-review",
+ "recommended": "recommended",
+ "expandOptions": "Show options",
+ "collapseOptions": "Hide options"
},
"riskLevels": {
"low": "Low Risk",
@@ -1432,5 +1441,9 @@
"prompt": "Help me create a budget plan.\n\nContext:\n- Budget period (monthly / quarterly / project):\n- Total budget:\n- Fixed costs:\n- Variable costs:\n- Goal (save money / control spending / save for something):\n\nOutput:\n1) Suggested categories + ratios\n2) A category budget table (copy-paste friendly)\n3) Overrun rules + adjustment plan\n4) Weekly/monthly review checklist"
}
}
+ },
+ "subagent": {
+ "showingLines": "Showing {{shown}} of {{total}} lines",
+ "showAll": "Show all"
}
}
diff --git a/src/web-ui/src/locales/en-US/scenes/skills.json b/src/web-ui/src/locales/en-US/scenes/skills.json
index 1c300ee54..3687bd097 100644
--- a/src/web-ui/src/locales/en-US/scenes/skills.json
+++ b/src/web-ui/src/locales/en-US/scenes/skills.json
@@ -36,7 +36,8 @@
},
"toolbar": {
"searchPlaceholder": "Search skills...",
- "addTooltip": "Add Skill"
+ "addTooltip": "Add Skill",
+ "hideDuplicates": "Duplicates"
},
"filters": {
"all": "All",
diff --git a/src/web-ui/src/locales/zh-CN/components.json b/src/web-ui/src/locales/zh-CN/components.json
index 48dcb07f2..e60533c9c 100644
--- a/src/web-ui/src/locales/zh-CN/components.json
+++ b/src/web-ui/src/locales/zh-CN/components.json
@@ -46,7 +46,8 @@
"openInExplorer": "在资源管理器中打开",
"openInBrowser": "通过浏览器打开",
"openInBuiltInBrowser": "通过内置浏览器打开",
- "copyLink": "复制链接"
+ "copyLink": "复制链接",
+ "copyFilePath": "复制文件路径"
},
"mermaidBlock": {
"renderFailed": "图表渲染失败",
diff --git a/src/web-ui/src/locales/zh-CN/flow-chat.json b/src/web-ui/src/locales/zh-CN/flow-chat.json
index 28f02a6af..6c09614d8 100644
--- a/src/web-ui/src/locales/zh-CN/flow-chat.json
+++ b/src/web-ui/src/locales/zh-CN/flow-chat.json
@@ -386,7 +386,7 @@
"close": "关闭",
"minimize": "收起",
"restore": "打开 {{label}}",
- "fixAndReviewRunning": "正在修复并准备重新审核...",
+ "fixAndReviewRunning": "正在修复\u0026准备重审...",
"minimizedDeep": "深度审核",
"minimizedStandard": "代码审核",
"minimizedFix": "正在修复",
@@ -400,6 +400,7 @@
"customInstructionsPlaceholder": "描述修复的额外要求或上下文信息...",
"fillBackInput": "填入输入框",
"fillBackInputHint": "将选中的修复计划填入输入框,供手动编辑",
+ "fixCompletedMessage": "所有修复已成功应用。",
"replaceInputConfirmTitle": "替换当前输入?",
"replaceInputConfirmMessage": "输入框中已有文本,填入计划将替换当前草稿内容。",
"replaceInputConfirmAction": "替换输入",
@@ -1004,11 +1005,16 @@
"toolBatchComplete": "工具批次完成",
"manualTrigger": "手动触发",
"autoTrigger": "自动触发",
+ "afterAiResponse": "AI 回复后",
"contextCompressionFailed": "上下文压缩失败",
"contextCompression": "上下文压缩:",
- "localFallbackHeader": "模型请求异常,已改用本地结构化裁剪",
+ "localFallbackHeader": "模型请求异常,改用本地压缩",
"localFallbackNotice": "本次压缩无法使用模型生成总结,因此改用了本地结构化裁剪。",
- "noSummaryNotice": "本次压缩没有生成额外的总结内容。"
+ "noSummaryNotice": "本次压缩没有生成额外的总结内容。",
+ "tokenChange": "{{before}} → {{after}} tokens",
+ "savingsTag": "节省 {{saved}} · 压缩率 {{ratio}}%",
+ "compressingContext": "正在压缩上下文...",
+ "compressionCount": "第 {{count}} 次压缩"
},
"globSearch": {
"parsingPattern": "解析搜索模式中...",
@@ -1236,11 +1242,14 @@
"noSelectionHint": "至少选择一项修复计划后才能开始修复。",
"ungrouped": "其他",
"startFix": "开始修复",
- "fixAndReview": "修复后重新审核",
+ "fixAndReview": "修复\\u0026重审",
"cancel": "取消",
"fixUnavailable": "无法开始修复:审核会话不可用。",
"fixRequestDisplay": "开始修复深度审核问题",
- "fixAndReviewRequestDisplay": "修复深度审核问题并重新审核"
+ "fixAndReviewRequestDisplay": "修复深度审核问题并重新审核",
+ "recommended": "推荐",
+ "expandOptions": "查看选项",
+ "collapseOptions": "收起选项"
},
"riskLevels": {
"low": "低风险",
@@ -1437,5 +1446,9 @@
"prompt": "请帮我做一个预算规划。\n\n背景:\n- 预算周期(按月/按季度/按项目):\n- 总预算:\n- 固定支出:\n- 可变支出:\n- 目标(省钱/更可控/为某目标攒钱):\n\n请输出:\n1) 预算分类与比例建议\n2) 具体到类别的预算表(可复制到表格)\n3) 超支预案与调整规则\n4) 每周/每月复盘清单"
}
}
+ },
+ "subagent": {
+ "showingLines": "当前仅显示 {{shown}} / {{total}} 行",
+ "showAll": "显示全部"
}
}
diff --git a/src/web-ui/src/locales/zh-CN/scenes/skills.json b/src/web-ui/src/locales/zh-CN/scenes/skills.json
index 52c908918..106ad1f55 100644
--- a/src/web-ui/src/locales/zh-CN/scenes/skills.json
+++ b/src/web-ui/src/locales/zh-CN/scenes/skills.json
@@ -36,7 +36,8 @@
},
"toolbar": {
"searchPlaceholder": "搜索技能...",
- "addTooltip": "添加技能"
+ "addTooltip": "添加技能",
+ "hideDuplicates": "重复项"
},
"filters": {
"all": "全部",
diff --git a/src/web-ui/src/locales/zh-TW/components.json b/src/web-ui/src/locales/zh-TW/components.json
index e6b6a9284..e96556deb 100644
--- a/src/web-ui/src/locales/zh-TW/components.json
+++ b/src/web-ui/src/locales/zh-TW/components.json
@@ -46,7 +46,8 @@
"openInExplorer": "在檔案總管中開啟",
"openInBrowser": "透過瀏覽器開啟",
"openInBuiltInBrowser": "透過內建瀏覽器開啟",
- "copyLink": "複製連結"
+ "copyLink": "複製連結",
+ "copyFilePath": "複製檔案路徑"
},
"mermaidBlock": {
"renderFailed": "圖表渲染失敗",
diff --git a/src/web-ui/src/locales/zh-TW/flow-chat.json b/src/web-ui/src/locales/zh-TW/flow-chat.json
index 85344b74e..2d1aac44c 100644
--- a/src/web-ui/src/locales/zh-TW/flow-chat.json
+++ b/src/web-ui/src/locales/zh-TW/flow-chat.json
@@ -377,7 +377,7 @@
"close": "關閉",
"minimize": "收起",
"restore": "打開 {{label}}",
- "fixAndReviewRunning": "正在修復並準備重新審核...",
+ "fixAndReviewRunning": "正在修復\u0026準備重審...",
"minimizedDeep": "深度審核",
"minimizedStandard": "程式碼審核",
"minimizedFix": "正在修復",
@@ -391,6 +391,7 @@
"customInstructionsPlaceholder": "描述修復的額外要求或上下文信息...",
"fillBackInput": "填入輸入框",
"fillBackInputHint": "將選中的修復計劃填入輸入框,供手動編輯",
+ "fixCompletedMessage": "所有修復已成功應用。",
"replaceInputConfirmTitle": "替換當前輸入?",
"replaceInputConfirmMessage": "輸入框中已有文本,填入計劃將替換當前草稿內容。",
"replaceInputConfirmAction": "替換輸入",
@@ -959,11 +960,16 @@
"toolBatchComplete": "工具批次完成",
"manualTrigger": "手動觸發",
"autoTrigger": "自動觸發",
+ "afterAiResponse": "AI 回覆後",
"contextCompressionFailed": "上下文壓縮失敗",
"contextCompression": "上下文壓縮:",
- "localFallbackHeader": "模型請求異常,已改用本地結構化裁剪",
+ "localFallbackHeader": "模型請求異常,改用本地壓縮",
"localFallbackNotice": "本次壓縮無法使用模型生成總結,因此改用了本地結構化裁剪。",
- "noSummaryNotice": "本次壓縮沒有生成額外的總結內容。"
+ "noSummaryNotice": "本次壓縮沒有生成額外的總結內容。",
+ "tokenChange": "{{before}} → {{after}} tokens",
+ "savingsTag": "節省 {{saved}} · 壓縮率 {{ratio}}%",
+ "compressingContext": "正在壓縮上下文...",
+ "compressionCount": "第 {{count}} 次壓縮"
},
"globSearch": {
"parsingPattern": "解析搜索模式中...",
@@ -1191,11 +1197,14 @@
"noSelectionHint": "至少選擇一項修復計劃後才能開始修復。",
"ungrouped": "其他",
"startFix": "開始修復",
- "fixAndReview": "修復後重新審核",
+ "fixAndReview": "修復\\u0026重審",
"cancel": "取消",
"fixUnavailable": "無法開始修復:審核會話不可用。",
"fixRequestDisplay": "開始修復深度審核問題",
- "fixAndReviewRequestDisplay": "修復深度審核問題並重新審核"
+ "fixAndReviewRequestDisplay": "修復深度審核問題並重新審核",
+ "recommended": "推薦",
+ "expandOptions": "查看選項",
+ "collapseOptions": "收起選項"
},
"riskLevels": {
"low": "低風險",
@@ -1392,5 +1401,9 @@
"prompt": "請幫我做一個預算規劃。\n\n背景:\n- 預算週期(按月/按季度/按項目):\n- 總預算:\n- 固定支出:\n- 可變支出:\n- 目標(省錢/更可控/為某目標攢錢):\n\n請輸出:\n1) 預算分類與比例建議\n2) 具體到類別的預算表(可複製到表格)\n3) 超支預案與調整規則\n4) 每週/每月覆盤清單"
}
}
+ },
+ "subagent": {
+ "showingLines": "當前僅顯示 {{shown}} / {{total}} 行",
+ "showAll": "顯示全部"
}
}
diff --git a/src/web-ui/src/locales/zh-TW/scenes/skills.json b/src/web-ui/src/locales/zh-TW/scenes/skills.json
index fa5b876f8..33e7718ec 100644
--- a/src/web-ui/src/locales/zh-TW/scenes/skills.json
+++ b/src/web-ui/src/locales/zh-TW/scenes/skills.json
@@ -36,7 +36,8 @@
},
"toolbar": {
"searchPlaceholder": "搜索技能...",
- "addTooltip": "添加技能"
+ "addTooltip": "添加技能",
+ "hideDuplicates": "重複項"
},
"filters": {
"all": "全部",