feat(review): 对话回顾新增纯净对话模式(默认),修复 /new 页面无法生成
- build_review_lines 新增 content_mode:dialogue(默认推荐,仅保留有实际内容的 user/assistant)/ full(原完整行为,含工具调用与结果) - conversation_review 工具与 review_preview/review 两个手动 API 均支持 content_mode,落盘 md 文件首行加模式标注 - 回顾弹窗新增「完整记录」切换按钮,预览实时跟随模式刷新 - 修复 /new 页面无法生成回顾:移除活跃对话硬拦截,发送给模型时改走正常发送入口自动创建对话 Co-authored-by: Astrion powered by Kimi-K3 <astrion-agent@users.noreply.github.com>
This commit is contained in:
parent
c7ea8a395c
commit
2198e40c49
@ -202,7 +202,7 @@ class ToolsDefinitionContextToolsMixin:
|
|||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": "conversation_review",
|
"name": "conversation_review",
|
||||||
"description": "按 id 回顾当前工作区内的历史对话。mode=read 时直接返回回顾内容;若内容超过 50000 字符,将自动保存到 .astrion/review/ 并提示分段或查找阅读。mode=save 时保存 Markdown 文件到 .astrion/review/ 并返回路径。若 id 不属于当前工作区,将返回不存在或不属于当前工作区。",
|
"description": "按 id 回顾当前工作区内的历史对话。mode=read 时直接返回回顾内容;若内容超过 50000 字符,将自动保存到 .astrion/review/ 并提示分段或查找阅读。mode=save 时保存 Markdown 文件到 .astrion/review/ 并返回路径。若 id 不属于当前工作区,将返回不存在或不属于当前工作区。默认只保留有实际对话内容的 user 与 assistant 消息(不含工具调用/结果与空消息),需要含工具细节时用 content_mode=full。",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": self._inject_intent({
|
"properties": self._inject_intent({
|
||||||
@ -211,6 +211,11 @@ class ToolsDefinitionContextToolsMixin:
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["read", "save"],
|
"enum": ["read", "save"],
|
||||||
"description": "必要参数。read=直接返回回顾内容;save=保存为 .astrion/review/ 下的 Markdown 文件并返回路径。"
|
"description": "必要参数。read=直接返回回顾内容;save=保存为 .astrion/review/ 下的 Markdown 文件并返回路径。"
|
||||||
|
},
|
||||||
|
"content_mode": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["dialogue", "full"],
|
||||||
|
"description": "可选参数,默认 dialogue(推荐)。dialogue=纯净对话:只保留有实际内容的 user 与 assistant 消息,不含工具调用、工具结果、system 与空消息;full=完整记录:含工具调用与结果等全部内容。"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
"required": ["conversation_id", "mode"]
|
"required": ["conversation_id", "mode"]
|
||||||
|
|||||||
@ -2094,10 +2094,13 @@ class MainTerminalToolsExecutionMixin:
|
|||||||
elif tool_name == "conversation_review":
|
elif tool_name == "conversation_review":
|
||||||
conversation_id = str(arguments.get("conversation_id") or "").strip()
|
conversation_id = str(arguments.get("conversation_id") or "").strip()
|
||||||
review_mode = str(arguments.get("mode") or "").strip().lower()
|
review_mode = str(arguments.get("mode") or "").strip().lower()
|
||||||
|
content_mode = str(arguments.get("content_mode") or "dialogue").strip().lower()
|
||||||
if not conversation_id:
|
if not conversation_id:
|
||||||
result = {"success": False, "error": tr("tools_exec.conversation_id_empty")}
|
result = {"success": False, "error": tr("tools_exec.conversation_id_empty")}
|
||||||
elif review_mode not in {"read", "save"}:
|
elif review_mode not in {"read", "save"}:
|
||||||
result = {"success": False, "error": tr("tools_exec.review_mode_invalid"), "conversation_id": conversation_id}
|
result = {"success": False, "error": tr("tools_exec.review_mode_invalid"), "conversation_id": conversation_id}
|
||||||
|
elif content_mode not in {"dialogue", "full"}:
|
||||||
|
result = {"success": False, "error": tr("tools_exec.review_content_mode_invalid"), "conversation_id": conversation_id}
|
||||||
else:
|
else:
|
||||||
manager = getattr(self.context_manager, "conversation_manager", None)
|
manager = getattr(self.context_manager, "conversation_manager", None)
|
||||||
conversation_data = manager.load_conversation(conversation_id) if manager else None
|
conversation_data = manager.load_conversation(conversation_id) if manager else None
|
||||||
@ -2111,7 +2114,7 @@ class MainTerminalToolsExecutionMixin:
|
|||||||
from server.utils_common import build_review_lines, _sanitize_filename_component
|
from server.utils_common import build_review_lines, _sanitize_filename_component
|
||||||
|
|
||||||
messages = conversation_data.get("messages", [])
|
messages = conversation_data.get("messages", [])
|
||||||
content = "\n".join(build_review_lines(messages)) + "\n"
|
content = "\n".join(build_review_lines(messages, content_mode=content_mode)) + "\n"
|
||||||
title = conversation_data.get("title") or "untitled"
|
title = conversation_data.get("title") or "untitled"
|
||||||
char_count = len(content)
|
char_count = len(content)
|
||||||
|
|
||||||
@ -2122,13 +2125,19 @@ class MainTerminalToolsExecutionMixin:
|
|||||||
review_dir.mkdir(parents=True, exist_ok=True)
|
review_dir.mkdir(parents=True, exist_ok=True)
|
||||||
filename = f"review_{safe_title}_{timestamp}.md"
|
filename = f"review_{safe_title}_{timestamp}.md"
|
||||||
target = review_dir / filename
|
target = review_dir / filename
|
||||||
target.write_text(content, encoding="utf-8")
|
annotation = tr(
|
||||||
|
"conversation.review_annotation_dialogue"
|
||||||
|
if content_mode == "dialogue"
|
||||||
|
else "conversation.review_annotation_full"
|
||||||
|
)
|
||||||
|
target.write_text(f"> {annotation}\n\n" + content, encoding="utf-8")
|
||||||
return f"{WORKSPACE_REVIEW_DIRNAME}/{filename}"
|
return f"{WORKSPACE_REVIEW_DIRNAME}/{filename}"
|
||||||
|
|
||||||
if review_mode == "read" and char_count <= 50000:
|
if review_mode == "read" and char_count <= 50000:
|
||||||
result = {
|
result = {
|
||||||
"success": True,
|
"success": True,
|
||||||
"mode": "read",
|
"mode": "read",
|
||||||
|
"content_mode": content_mode,
|
||||||
"conversation_id": conversation_id,
|
"conversation_id": conversation_id,
|
||||||
"title": title,
|
"title": title,
|
||||||
"content": content,
|
"content": content,
|
||||||
@ -2141,6 +2150,7 @@ class MainTerminalToolsExecutionMixin:
|
|||||||
result = {
|
result = {
|
||||||
"success": True,
|
"success": True,
|
||||||
"mode": review_mode,
|
"mode": review_mode,
|
||||||
|
"content_mode": content_mode,
|
||||||
"conversation_id": conversation_id,
|
"conversation_id": conversation_id,
|
||||||
"title": title,
|
"title": title,
|
||||||
"path": rel_path,
|
"path": rel_path,
|
||||||
|
|||||||
@ -273,6 +273,14 @@ MESSAGES = {
|
|||||||
"zh-CN": "生成对话回顾时发生异常",
|
"zh-CN": "生成对话回顾时发生异常",
|
||||||
"en-US": "An error occurred while generating the conversation review",
|
"en-US": "An error occurred while generating the conversation review",
|
||||||
},
|
},
|
||||||
|
"conversation.review_annotation_dialogue": {
|
||||||
|
"zh-CN": "回顾模式:纯净对话(仅含用户与助手的实际对话内容)",
|
||||||
|
"en-US": "Review mode: clean dialogue (only actual user/assistant messages)",
|
||||||
|
},
|
||||||
|
"conversation.review_annotation_full": {
|
||||||
|
"zh-CN": "回顾模式:完整记录(含工具调用与结果)",
|
||||||
|
"en-US": "Review mode: full record (including tool calls and results)",
|
||||||
|
},
|
||||||
|
|
||||||
# ── 统计 / 当前对话 ──
|
# ── 统计 / 当前对话 ──
|
||||||
"conversation.get_statistics_exception": {
|
"conversation.get_statistics_exception": {
|
||||||
|
|||||||
@ -453,6 +453,10 @@ MESSAGES = {
|
|||||||
"zh-CN": "mode 必须为 read 或 save",
|
"zh-CN": "mode 必须为 read 或 save",
|
||||||
"en-US": "mode must be read or save",
|
"en-US": "mode must be read or save",
|
||||||
},
|
},
|
||||||
|
"tools_exec.review_content_mode_invalid": {
|
||||||
|
"zh-CN": "content_mode 必须为 dialogue 或 full",
|
||||||
|
"en-US": "content_mode must be dialogue or full",
|
||||||
|
},
|
||||||
"tools_exec.review_conversation_missing": {
|
"tools_exec.review_conversation_missing": {
|
||||||
"zh-CN": "对话不存在或不属于当前工作区",
|
"zh-CN": "对话不存在或不属于当前工作区",
|
||||||
"en-US": "Conversation does not exist or does not belong to the current workspace",
|
"en-US": "Conversation does not exist or does not belong to the current workspace",
|
||||||
|
|||||||
@ -75,6 +75,7 @@ from .gateway_auth import api_login_or_host_token_required
|
|||||||
from .context import with_terminal, get_gui_manager, get_upload_guard, build_upload_error_response, ensure_conversation_loaded, reset_system_state, get_user_resources, get_or_create_usage_tracker
|
from .context import with_terminal, get_gui_manager, get_upload_guard, build_upload_error_response, ensure_conversation_loaded, reset_system_state, get_user_resources, get_or_create_usage_tracker
|
||||||
from .utils_common import (
|
from .utils_common import (
|
||||||
build_review_lines,
|
build_review_lines,
|
||||||
|
REVIEW_CONTENT_MODES,
|
||||||
debug_log,
|
debug_log,
|
||||||
_sanitize_filename_component,
|
_sanitize_filename_component,
|
||||||
log_backend_chunk,
|
log_backend_chunk,
|
||||||
@ -2235,7 +2236,10 @@ def review_conversation_preview(conversation_id, terminal: WebTerminal, workspac
|
|||||||
}), 404
|
}), 404
|
||||||
|
|
||||||
limit = request.args.get('limit', default=20, type=int) or 20
|
limit = request.args.get('limit', default=20, type=int) or 20
|
||||||
lines = build_review_lines(conversation_data.get("messages", []), limit=limit)
|
content_mode = (request.args.get('content_mode') or 'dialogue').strip().lower()
|
||||||
|
if content_mode not in REVIEW_CONTENT_MODES:
|
||||||
|
content_mode = 'dialogue'
|
||||||
|
lines = build_review_lines(conversation_data.get("messages", []), limit=limit, content_mode=content_mode)
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
@ -2278,7 +2282,11 @@ def review_conversation(conversation_id, terminal: WebTerminal, workspace: UserW
|
|||||||
}), 404
|
}), 404
|
||||||
|
|
||||||
messages = conversation_data.get("messages", [])
|
messages = conversation_data.get("messages", [])
|
||||||
lines = build_review_lines(messages)
|
payload = request.get_json(silent=True) or {}
|
||||||
|
content_mode = str(payload.get("content_mode") or "dialogue").strip().lower()
|
||||||
|
if content_mode not in REVIEW_CONTENT_MODES:
|
||||||
|
content_mode = "dialogue"
|
||||||
|
lines = build_review_lines(messages, content_mode=content_mode)
|
||||||
content = "\n".join(lines) + "\n"
|
content = "\n".join(lines) + "\n"
|
||||||
char_count = len(content)
|
char_count = len(content)
|
||||||
|
|
||||||
@ -2291,13 +2299,19 @@ def review_conversation(conversation_id, terminal: WebTerminal, workspace: UserW
|
|||||||
filename = f"review_{safe_title}_{timestamp}.md"
|
filename = f"review_{safe_title}_{timestamp}.md"
|
||||||
target = review_dir / filename
|
target = review_dir / filename
|
||||||
|
|
||||||
target.write_text(content, encoding='utf-8')
|
annotation = tr(
|
||||||
|
"conversation.review_annotation_dialogue"
|
||||||
|
if content_mode == "dialogue"
|
||||||
|
else "conversation.review_annotation_full"
|
||||||
|
)
|
||||||
|
target.write_text(f"> {annotation}\n\n" + content, encoding='utf-8')
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
"data": {
|
"data": {
|
||||||
"path": f".astrion/review/{filename}",
|
"path": f".astrion/review/{filename}",
|
||||||
"char_count": char_count
|
"char_count": char_count,
|
||||||
|
"content_mode": content_mode
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@ -33,10 +33,16 @@ def sanitize_filename_preserve_unicode(filename: str) -> str:
|
|||||||
return cleaned[:255]
|
return cleaned[:255]
|
||||||
|
|
||||||
|
|
||||||
def build_review_lines(messages, limit=None):
|
# 对话回顾内容模式:dialogue=纯净对话(默认,仅含实际内容的 user/assistant);full=完整记录(含工具调用/结果与 system)
|
||||||
|
REVIEW_CONTENT_MODES = ("dialogue", "full")
|
||||||
|
|
||||||
|
|
||||||
|
def build_review_lines(messages, limit=None, content_mode="dialogue"):
|
||||||
"""
|
"""
|
||||||
将对话消息序列拍平成简化文本。
|
将对话消息序列拍平成简化文本。
|
||||||
保留 user / assistant / system 以及 assistant 内的 tool 调用与 tool 消息。
|
content_mode="dialogue"(默认):只保留提取文本后非空的 user / assistant 消息,
|
||||||
|
不含 tool / tool_call / system 行,空内容消息整条跳过。
|
||||||
|
content_mode="full":保留 user / assistant / system 以及 assistant 内的 tool 调用与 tool 消息。
|
||||||
limit 为正整数时,最多返回该数量的行(用于预览)。
|
limit 为正整数时,最多返回该数量的行(用于预览)。
|
||||||
"""
|
"""
|
||||||
lines: List[str] = []
|
lines: List[str] = []
|
||||||
@ -71,6 +77,14 @@ def build_review_lines(messages, limit=None):
|
|||||||
base_content_raw = msg.get("content") if isinstance(msg.get("content"), (str, list, dict)) else msg.get("text") or ""
|
base_content_raw = msg.get("content") if isinstance(msg.get("content"), (str, list, dict)) else msg.get("text") or ""
|
||||||
base_content = extract_text(base_content_raw)
|
base_content = extract_text(base_content_raw)
|
||||||
|
|
||||||
|
if content_mode == "dialogue":
|
||||||
|
# 纯净模式:仅保留有实际内容的 user / assistant 消息
|
||||||
|
if role in ("user", "assistant") and base_content.strip():
|
||||||
|
append_line(f"{role}:{base_content}")
|
||||||
|
if isinstance(limit, int) and limit > 0 and len(lines) >= limit:
|
||||||
|
return lines[:limit]
|
||||||
|
continue
|
||||||
|
|
||||||
if role in ("user", "assistant", "system"):
|
if role in ("user", "assistant", "system"):
|
||||||
append_line(f"{role}:{base_content}")
|
append_line(f"{role}:{base_content}")
|
||||||
|
|
||||||
|
|||||||
@ -522,12 +522,14 @@
|
|||||||
:preview-error="reviewPreviewError"
|
:preview-error="reviewPreviewError"
|
||||||
:preview-limit="reviewPreviewLimit"
|
:preview-limit="reviewPreviewLimit"
|
||||||
:send-to-model="reviewSendToModel"
|
:send-to-model="reviewSendToModel"
|
||||||
|
:content-mode="reviewContentMode"
|
||||||
:generated-path="reviewGeneratedPath"
|
:generated-path="reviewGeneratedPath"
|
||||||
:icon-style="iconStyle"
|
:icon-style="iconStyle"
|
||||||
@close="reviewDialogOpen = false"
|
@close="reviewDialogOpen = false"
|
||||||
@select="handleReviewSelect"
|
@select="handleReviewSelect"
|
||||||
@load-more="loadMoreReviewConversations"
|
@load-more="loadMoreReviewConversations"
|
||||||
@toggle-send="reviewSendToModel = $event"
|
@toggle-send="reviewSendToModel = $event"
|
||||||
|
@toggle-content-mode="toggleReviewContentMode"
|
||||||
@confirm="handleConfirmReview"
|
@confirm="handleConfirmReview"
|
||||||
/>
|
/>
|
||||||
</transition>
|
</transition>
|
||||||
|
|||||||
@ -178,6 +178,8 @@ export const dialogMethods = {
|
|||||||
this.reviewPreviewLines = [];
|
this.reviewPreviewLines = [];
|
||||||
this.reviewPreviewError = null;
|
this.reviewPreviewError = null;
|
||||||
this.reviewGeneratedPath = null;
|
this.reviewGeneratedPath = null;
|
||||||
|
// 每次打开默认回到纯净对话模式(推荐项)
|
||||||
|
this.reviewContentMode = 'dialogue';
|
||||||
this.closeQuickMenu();
|
this.closeQuickMenu();
|
||||||
// 弹窗使用独立列表(仅含有内容的对话),加载完成后自动选中首个可用项
|
// 弹窗使用独立列表(仅含有内容的对话),加载完成后自动选中首个可用项
|
||||||
this.loadReviewConversations();
|
this.loadReviewConversations();
|
||||||
|
|||||||
@ -87,6 +87,14 @@ export const reviewMethods = {
|
|||||||
this.reviewListLoadingMore = false;
|
this.reviewListLoadingMore = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
toggleReviewContentMode() {
|
||||||
|
if (this.reviewSubmitting) return;
|
||||||
|
this.reviewContentMode = this.reviewContentMode === 'full' ? 'dialogue' : 'full';
|
||||||
|
// 预览跟随模式实时刷新
|
||||||
|
if (this.reviewSelectedConversationId) {
|
||||||
|
this.loadReviewPreview(this.reviewSelectedConversationId);
|
||||||
|
}
|
||||||
|
},
|
||||||
async handleConfirmReview() {
|
async handleConfirmReview() {
|
||||||
if (this.reviewSubmitting) return;
|
if (this.reviewSubmitting) return;
|
||||||
if (!this.reviewSelectedConversationId) {
|
if (!this.reviewSelectedConversationId) {
|
||||||
@ -105,19 +113,12 @@ export const reviewMethods = {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.currentConversationId) {
|
|
||||||
this.uiPushToast({
|
|
||||||
title: t('appUi.cannotSend'),
|
|
||||||
message: t('appUi.noActiveConversationMessage'),
|
|
||||||
type: 'warning'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.reviewSubmitting = true;
|
this.reviewSubmitting = true;
|
||||||
try {
|
try {
|
||||||
const { path, char_count } = await this.generateConversationReview(
|
const { path, char_count } = await this.generateConversationReview(
|
||||||
this.reviewSelectedConversationId
|
this.reviewSelectedConversationId,
|
||||||
|
this.reviewContentMode
|
||||||
);
|
);
|
||||||
if (!path) {
|
if (!path) {
|
||||||
throw new Error(t('appUi.reviewPathMissing'));
|
throw new Error(t('appUi.reviewPathMissing'));
|
||||||
@ -132,7 +133,11 @@ export const reviewMethods = {
|
|||||||
count: count || t('appUi.unknown'),
|
count: count || t('appUi.unknown'),
|
||||||
suggestion
|
suggestion
|
||||||
});
|
});
|
||||||
const sent = await this.sendAutoUserMessage(message);
|
// /new 页面(无活跃对话)时走正常发送入口:自动创建新对话并跳转,
|
||||||
|
// 与用户手动发送首条消息的体验一致;有活跃对话时保持原自动消息路径。
|
||||||
|
const sent = this.currentConversationId
|
||||||
|
? await this.sendAutoUserMessage(message)
|
||||||
|
: await this.sendMessage({ presetText: message });
|
||||||
if (sent) {
|
if (sent) {
|
||||||
this.reviewDialogOpen = false;
|
this.reviewDialogOpen = false;
|
||||||
}
|
}
|
||||||
@ -160,7 +165,7 @@ export const reviewMethods = {
|
|||||||
this.reviewPreviewLines = [];
|
this.reviewPreviewLines = [];
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(
|
const resp = await fetch(
|
||||||
`/api/conversations/${conversationId}/review_preview?limit=${this.reviewPreviewLimit}`
|
`/api/conversations/${conversationId}/review_preview?limit=${this.reviewPreviewLimit}&content_mode=${this.reviewContentMode}`
|
||||||
);
|
);
|
||||||
const payload = await resp.json().catch(() => ({}));
|
const payload = await resp.json().catch(() => ({}));
|
||||||
if (!resp.ok || !payload?.success) {
|
if (!resp.ok || !payload?.success) {
|
||||||
@ -175,9 +180,11 @@ export const reviewMethods = {
|
|||||||
this.reviewPreviewLoading = false;
|
this.reviewPreviewLoading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async generateConversationReview(conversationId) {
|
async generateConversationReview(conversationId, contentMode = 'dialogue') {
|
||||||
const response = await fetch(`/api/conversations/${conversationId}/review`, {
|
const response = await fetch(`/api/conversations/${conversationId}/review`, {
|
||||||
method: 'POST'
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ content_mode: contentMode })
|
||||||
});
|
});
|
||||||
const payload = await response.json().catch(() => ({}));
|
const payload = await response.json().catch(() => ({}));
|
||||||
if (!response.ok || !payload?.success) {
|
if (!response.ok || !payload?.success) {
|
||||||
|
|||||||
@ -344,6 +344,8 @@ export function dataState() {
|
|||||||
reviewPreviewLimit: 20,
|
reviewPreviewLimit: 20,
|
||||||
reviewSendToModel: true,
|
reviewSendToModel: true,
|
||||||
reviewGeneratedPath: null,
|
reviewGeneratedPath: null,
|
||||||
|
// 回顾内容模式:dialogue=纯净对话(默认推荐);full=完整记录(含工具调用与结果)
|
||||||
|
reviewContentMode: 'dialogue',
|
||||||
// 回顾弹窗独立的对话列表(只含有内容的对话,不污染侧边栏)
|
// 回顾弹窗独立的对话列表(只含有内容的对话,不污染侧边栏)
|
||||||
reviewConversations: [],
|
reviewConversations: [],
|
||||||
reviewListLoading: false,
|
reviewListLoading: false,
|
||||||
|
|||||||
@ -1159,6 +1159,13 @@ function renderConversationReview(result: any, args: any): string {
|
|||||||
html += `<div><strong>${escapeHtml(t('toolResults.labels.conversationId'))}</strong>${escapeHtml(result?.conversation_id || args?.conversation_id || '')}</div>`;
|
html += `<div><strong>${escapeHtml(t('toolResults.labels.conversationId'))}</strong>${escapeHtml(result?.conversation_id || args?.conversation_id || '')}</div>`;
|
||||||
html += `<div><strong>${escapeHtml(t('toolResults.labels.status'))}</strong>${status}</div>`;
|
html += `<div><strong>${escapeHtml(t('toolResults.labels.status'))}</strong>${status}</div>`;
|
||||||
html += `<div><strong>${escapeHtml(t('toolResults.labels.mode'))}</strong>${escapeHtml(result?.mode || args?.mode || '')}</div>`;
|
html += `<div><strong>${escapeHtml(t('toolResults.labels.mode'))}</strong>${escapeHtml(result?.mode || args?.mode || '')}</div>`;
|
||||||
|
const reviewContentMode = result?.content_mode || args?.content_mode || '';
|
||||||
|
if (reviewContentMode) {
|
||||||
|
const reviewContentModeLabel = reviewContentMode === 'full'
|
||||||
|
? t('toolResults.values.reviewContentModeFull')
|
||||||
|
: t('toolResults.values.reviewContentModeDialogue');
|
||||||
|
html += `<div><strong>${escapeHtml(t('toolResults.labels.contentMode'))}</strong>${escapeHtml(reviewContentModeLabel)}</div>`;
|
||||||
|
}
|
||||||
if (result?.title) {
|
if (result?.title) {
|
||||||
html += `<div><strong>${escapeHtml(t('toolResults.labels.title'))}</strong>${escapeHtml(result.title)}</div>`;
|
html += `<div><strong>${escapeHtml(t('toolResults.labels.title'))}</strong>${escapeHtml(result.title)}</div>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,6 +109,18 @@
|
|||||||
<span class="switch"></span>
|
<span class="switch"></span>
|
||||||
<span class="label">{{ $t('overlay.sendToModel') }}</span>
|
<span class="label">{{ $t('overlay.sendToModel') }}</span>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="footer-actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="secondary-btn"
|
||||||
|
:class="{ active: contentMode === 'full' }"
|
||||||
|
:aria-pressed="contentMode === 'full'"
|
||||||
|
:title="contentMode === 'full' ? $t('overlay.cleanDialogueHint') : $t('overlay.fullRecordHint')"
|
||||||
|
@click="$emit('toggle-content-mode')"
|
||||||
|
:disabled="submitting"
|
||||||
|
>
|
||||||
|
{{ $t('overlay.fullRecord') }}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="primary-btn"
|
class="primary-btn"
|
||||||
@ -120,6 +132,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -147,6 +160,7 @@ const props = defineProps<{
|
|||||||
previewError?: string | null;
|
previewError?: string | null;
|
||||||
previewLimit?: number;
|
previewLimit?: number;
|
||||||
sendToModel: boolean;
|
sendToModel: boolean;
|
||||||
|
contentMode: 'dialogue' | 'full';
|
||||||
generatedPath?: string | null;
|
generatedPath?: string | null;
|
||||||
iconStyle?: (key: string) => Record<string, string>;
|
iconStyle?: (key: string) => Record<string, string>;
|
||||||
}>();
|
}>();
|
||||||
@ -158,6 +172,7 @@ defineEmits<{
|
|||||||
(event: 'select', id: string): void;
|
(event: 'select', id: string): void;
|
||||||
(event: 'load-more'): void;
|
(event: 'load-more'): void;
|
||||||
(event: 'confirm'): void;
|
(event: 'confirm'): void;
|
||||||
|
(event: 'toggle-content-mode'): void;
|
||||||
(event: 'toggle-send', value: boolean): void;
|
(event: 'toggle-send', value: boolean): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@ -569,6 +584,46 @@ const formatUpdatedAt = (value: string | number) => {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== 底部按钮组 ===== */
|
||||||
|
.footer-actions {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 次操作按钮(完整记录):中性色、固定高度,与主按钮对齐 */
|
||||||
|
.secondary-btn {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
height: 38px;
|
||||||
|
border: 1px solid var(--border-default);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(--surface-soft);
|
||||||
|
color: var(--text-primary);
|
||||||
|
transition:
|
||||||
|
background 140ms ease,
|
||||||
|
color 140ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-btn:hover:not(:disabled) {
|
||||||
|
background: var(--hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 切换按下态(完整记录模式):中性灰阶区分,不用彩色 */
|
||||||
|
.secondary-btn.active {
|
||||||
|
background: var(--surface-muted);
|
||||||
|
border-color: var(--border-strong);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== 主操作按钮:固定高度,无彩色光晕 ===== */
|
/* ===== 主操作按钮:固定高度,无彩色光晕 ===== */
|
||||||
.primary-btn {
|
.primary-btn {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|||||||
@ -33,6 +33,9 @@ export default {
|
|||||||
previewCount: '{n} lines',
|
previewCount: '{n} lines',
|
||||||
sendToModel: 'Send to model',
|
sendToModel: 'Send to model',
|
||||||
generating: 'Generating...',
|
generating: 'Generating...',
|
||||||
|
fullRecord: 'Full record',
|
||||||
|
fullRecordHint: 'Switch to full record: preview and output will include tool calls and results',
|
||||||
|
cleanDialogueHint: 'Switch back to clean dialogue: only the actual conversation',
|
||||||
|
|
||||||
// ── GoalProgressDialog ──
|
// ── GoalProgressDialog ──
|
||||||
goalDoneTitle: 'Goal completed',
|
goalDoneTitle: 'Goal completed',
|
||||||
|
|||||||
@ -82,6 +82,7 @@ export default {
|
|||||||
title: 'Title:',
|
title: 'Title:',
|
||||||
reviewFile: 'Review file:',
|
reviewFile: 'Review file:',
|
||||||
charCount: 'Characters:',
|
charCount: 'Characters:',
|
||||||
|
contentMode: 'Content mode:',
|
||||||
skill: 'Skill:',
|
skill: 'Skill:',
|
||||||
workflow: 'Workflow:',
|
workflow: 'Workflow:',
|
||||||
workflowCount: 'Workflows:',
|
workflowCount: 'Workflows:',
|
||||||
@ -252,6 +253,8 @@ export default {
|
|||||||
colon: ':',
|
colon: ':',
|
||||||
optionLabel: 'Option {n}',
|
optionLabel: 'Option {n}',
|
||||||
line: 'Line {line}',
|
line: 'Line {line}',
|
||||||
|
reviewContentModeDialogue: 'Clean dialogue',
|
||||||
|
reviewContentModeFull: 'Full record',
|
||||||
},
|
},
|
||||||
|
|
||||||
// —— Search topic / time range / domain limits ——
|
// —— Search topic / time range / domain limits ——
|
||||||
|
|||||||
@ -34,6 +34,9 @@ export default {
|
|||||||
previewCount: '{n} 条',
|
previewCount: '{n} 条',
|
||||||
sendToModel: '是否发送给模型',
|
sendToModel: '是否发送给模型',
|
||||||
generating: '生成中...',
|
generating: '生成中...',
|
||||||
|
fullRecord: '完整记录',
|
||||||
|
fullRecordHint: '切换到完整记录:预览与生成将包含工具调用与结果',
|
||||||
|
cleanDialogueHint: '点击切回纯净对话:仅含双方的实际对话内容',
|
||||||
|
|
||||||
// ── GoalProgressDialog:目标进度 ──
|
// ── GoalProgressDialog:目标进度 ──
|
||||||
goalDoneTitle: '目标已完成',
|
goalDoneTitle: '目标已完成',
|
||||||
|
|||||||
@ -89,6 +89,7 @@ export default {
|
|||||||
title: '标题:',
|
title: '标题:',
|
||||||
reviewFile: '回顾文件:',
|
reviewFile: '回顾文件:',
|
||||||
charCount: '字符数:',
|
charCount: '字符数:',
|
||||||
|
contentMode: '内容模式:',
|
||||||
skill: 'Skill:',
|
skill: 'Skill:',
|
||||||
workflow: '工作流:',
|
workflow: '工作流:',
|
||||||
workflowCount: '工作流数量:',
|
workflowCount: '工作流数量:',
|
||||||
@ -259,6 +260,8 @@ export default {
|
|||||||
colon: ':',
|
colon: ':',
|
||||||
optionLabel: '选项 {n}',
|
optionLabel: '选项 {n}',
|
||||||
line: '行 {line}',
|
line: '行 {line}',
|
||||||
|
reviewContentModeDialogue: '纯净对话',
|
||||||
|
reviewContentModeFull: '完整记录',
|
||||||
},
|
},
|
||||||
|
|
||||||
// —— 搜索主题 / 时间范围 / 网站限定 ——
|
// —— 搜索主题 / 时间范围 / 网站限定 ——
|
||||||
|
|||||||
@ -50,8 +50,14 @@ def _format_conversation_search(result_data: Dict[str, Any]) -> str:
|
|||||||
def _format_conversation_review(result_data: Dict[str, Any]) -> str:
|
def _format_conversation_review(result_data: Dict[str, Any]) -> str:
|
||||||
if not result_data.get("success"):
|
if not result_data.get("success"):
|
||||||
return _format_failure("conversation_review", result_data)
|
return _format_failure("conversation_review", result_data)
|
||||||
|
content_mode = result_data.get("content_mode")
|
||||||
|
mode_note = ""
|
||||||
|
if content_mode == "full":
|
||||||
|
mode_note = "(完整记录,含工具调用与结果)"
|
||||||
|
elif content_mode == "dialogue":
|
||||||
|
mode_note = "(纯净对话,仅含实际对话内容)"
|
||||||
if result_data.get("mode") == "read" and result_data.get("content"):
|
if result_data.get("mode") == "read" and result_data.get("content"):
|
||||||
lines = ["对话回顾内容:"]
|
lines = [f"对话回顾内容{mode_note}:"]
|
||||||
if result_data.get("title"):
|
if result_data.get("title"):
|
||||||
lines.append(f"标题:{result_data.get('title')}")
|
lines.append(f"标题:{result_data.get('title')}")
|
||||||
if result_data.get("char_count") is not None:
|
if result_data.get("char_count") is not None:
|
||||||
@ -70,7 +76,7 @@ def _format_conversation_review(result_data: Dict[str, Any]) -> str:
|
|||||||
lines.insert(1, f"标题:{result_data.get('title')}")
|
lines.insert(1, f"标题:{result_data.get('title')}")
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
lines = [
|
lines = [
|
||||||
"已生成对话回顾文件:",
|
f"已生成对话回顾文件{mode_note}:",
|
||||||
str(path),
|
str(path),
|
||||||
]
|
]
|
||||||
if result_data.get("title"):
|
if result_data.get("title"):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user