diff --git a/core/main_terminal_parts/tools_definition/context_tools.py b/core/main_terminal_parts/tools_definition/context_tools.py index db6327d..19b188c 100644 --- a/core/main_terminal_parts/tools_definition/context_tools.py +++ b/core/main_terminal_parts/tools_definition/context_tools.py @@ -252,7 +252,7 @@ class ToolsDefinitionContextToolsMixin: "type": "function", "function": { "name": "manage_personalization", - "description": "管理用户个性化设置。支持读取所有配置,或更新单个字段。可修改字段:self_identify(AI自称,最多20字)、user_name(AI如何称呼用户,最多20字)、profession(用户职业,最多20字)、tone(交流语气,最多20字)、considerations(注意事项列表,字符串数组,最多10项每项最多50字)、theme(主题配色:classic-经典/light-明亮/dark-暗黑)、communication_style(交流风格:default-标准AI风格/human_like-拟人聊天风格/auto-自动选择交流风格)、conversation_continuity(对话连续性:high-高/medium-中/low-低)。更新时会自动验证格式,验证通过后立即保存并生效,新主题会立即应用到界面。", + "description": "管理用户个性化设置。支持读取所有配置,或更新单个字段。可修改字段:self_identify(AI自称,最多20字)、user_name(AI如何称呼用户,最多20字)、profession(用户职业,最多20字)、tone(交流语气,最多20字)、considerations(回答时必须考虑的信息,字符串,最多2000字)、theme(主题配色:classic-经典/light-明亮/dark-暗黑)、communication_style(交流风格:default-标准AI风格/human_like-拟人聊天风格/auto-自动选择交流风格)、conversation_continuity(对话连续性:high-高/medium-中/low-低)。更新时会自动验证格式,验证通过后立即保存并生效,新主题会立即应用到界面。", "parameters": { "type": "object", "properties": self._inject_intent({ @@ -267,7 +267,7 @@ class ToolsDefinitionContextToolsMixin: "description": "要更新的字段名(仅action=update时需要)" }, "value": { - "description": "新值(仅action=update时需要)。注意事项需提供字符串数组,其他字段提供字符串" + "description": "新值(仅action=update时需要)。注意事项提供字符串,其他字段提供字符串" } }), "required": ["action"] diff --git a/core/main_terminal_parts/tools_execution.py b/core/main_terminal_parts/tools_execution.py index 5826ac7..f80e16e 100644 --- a/core/main_terminal_parts/tools_execution.py +++ b/core/main_terminal_parts/tools_execution.py @@ -72,8 +72,7 @@ from modules.personalization_manager import ( save_personalization_config, build_personalization_prompt, MAX_SHORT_FIELD_LENGTH, - MAX_CONSIDERATION_LENGTH, - MAX_CONSIDERATION_ITEMS, + MAX_CONSIDERATION_TEXT_LENGTH, ALLOWED_THEMES, ALLOWED_COMMUNICATION_STYLES, ALLOWED_CONVERSATION_CONTINUITY, @@ -1830,7 +1829,7 @@ class MainTerminalToolsExecutionMixin: "user_name": f"AI如何称呼用户: {result.get('user_name') or '(未设置)'}", "profession": f"用户职业: {result.get('profession') or '(未设置)'}", "tone": f"交流语气: {result.get('tone') or '(未设置)'}", - "considerations": f"注意事项: {len(result.get('considerations') or [])} 条", + "considerations": f"注意事项: {'已设置' if (result.get('considerations') or '').strip() else '未设置'}", "theme": f"主题配色: {result.get('theme', 'classic')}", "communication_style": f"交流风格: {result.get('communication_style', 'default')}", "conversation_continuity": f"对话连续性: {result.get('conversation_continuity', 'medium')}" @@ -1874,17 +1873,11 @@ class MainTerminalToolsExecutionMixin: validation_errors.append(f"{field} 不能超过 {MAX_SHORT_FIELD_LENGTH} 个字符") elif field == "considerations": - # 注意事项数组验证 - if not isinstance(value, list): - validation_errors.append("considerations 必须是字符串数组") - else: - if len(value) > MAX_CONSIDERATION_ITEMS: - validation_errors.append(f"considerations 不能超过 {MAX_CONSIDERATION_ITEMS} 项") - for i, item in enumerate(value): - if not isinstance(item, str): - validation_errors.append(f"considerations[{i}] 必须是字符串") - elif len(item) > MAX_CONSIDERATION_LENGTH: - validation_errors.append(f"considerations[{i}] 不能超过 {MAX_CONSIDERATION_LENGTH} 个字符") + # 注意事项文本验证 + if not isinstance(value, str): + validation_errors.append("considerations 必须是字符串") + elif len(value) > MAX_CONSIDERATION_TEXT_LENGTH: + validation_errors.append(f"considerations 不能超过 {MAX_CONSIDERATION_TEXT_LENGTH} 个字符") elif field == "theme": # 主题验证 diff --git a/modules/personalization_manager.py b/modules/personalization_manager.py index 520db11..0603180 100644 --- a/modules/personalization_manager.py +++ b/modules/personalization_manager.py @@ -5,7 +5,7 @@ from __future__ import annotations import json from copy import deepcopy from pathlib import Path -from typing import Any, Dict, Iterable, Optional, Union +from typing import Any, Dict, Optional, Union try: from config.limits import THINKING_FAST_INTERVAL @@ -30,7 +30,7 @@ GOAL_MAX_TOKENS_MAX = 100_000_000 PERSONALIZATION_FILENAME = "personalization.json" MAX_SHORT_FIELD_LENGTH = 20 -MAX_CONSIDERATION_LENGTH = 50 +MAX_CONSIDERATION_TEXT_LENGTH = 2000 MAX_CONSIDERATION_ITEMS = 10 TONE_PRESETS = ["健谈", "幽默", "直言不讳", "鼓励性", "诗意", "企业商务", "打破常规", "同理心"] THINKING_INTERVAL_MIN = 1 @@ -705,12 +705,10 @@ def build_personalization_prompt( if config.get("tone"): lines.append(f"用户希望你使用 {config['tone']} 的语气与TA交流") - considerations: Iterable[str] = config.get("considerations") or [] - considerations = [item for item in considerations if item] + considerations: str = config.get("considerations") or "" if considerations: lines.append("用户希望你在回答问题时必须考虑的信息是:") - for idx, item in enumerate(considerations, 1): - lines.append(f"{idx}. {item}") + lines.append(considerations) conversation_continuity = str(config.get("conversation_continuity") or "medium").strip().lower() if conversation_continuity == "high": @@ -761,20 +759,25 @@ def _sanitize_short_field(value: Optional[str]) -> str: return text[:MAX_SHORT_FIELD_LENGTH] -def _sanitize_considerations(value: Any) -> list: - if not isinstance(value, list): - return [] - cleaned = [] - for item in value: - if not isinstance(item, str): - continue - text = item.strip() - if not text: - continue - cleaned.append(text[:MAX_CONSIDERATION_LENGTH]) - if len(cleaned) >= MAX_CONSIDERATION_ITEMS: - break - return cleaned +def _sanitize_considerations(value: Any) -> str: + """Sanitize considerations text. Legacy array data is joined with newlines.""" + if value is None: + return "" + if isinstance(value, str): + return value[:MAX_CONSIDERATION_TEXT_LENGTH] + if isinstance(value, list): + cleaned = [] + for item in value: + if not isinstance(item, str): + continue + text = item.strip() + if not text: + continue + cleaned.append(text) + if len(cleaned) >= MAX_CONSIDERATION_ITEMS: + break + return "\n".join(cleaned)[:MAX_CONSIDERATION_TEXT_LENGTH] + return "" def _sanitize_thinking_interval(value: Any) -> Optional[int]: diff --git a/static/src/components/chat/actions/ToolAction.vue b/static/src/components/chat/actions/ToolAction.vue index d5003ea..e95ff89 100644 --- a/static/src/components/chat/actions/ToolAction.vue +++ b/static/src/components/chat/actions/ToolAction.vue @@ -375,8 +375,14 @@ function renderEditFile(result: any, args: any): string { if (details.length > 0) { html += '
'; - const renderDiffLine = (lineNo: number | null, marker: string, text: string, className = '') => { - const lineNoText = typeof lineNo === 'number' && Number.isFinite(lineNo) ? String(lineNo) : ''; + const renderDiffLine = ( + lineNo: number | null, + marker: string, + text: string, + className = '' + ) => { + const lineNoText = + typeof lineNo === 'number' && Number.isFinite(lineNo) ? String(lineNo) : ''; html += `
${escapeHtml(lineNoText)}${escapeHtml(marker)}${escapeHtml(text)}
`; }; const hunks: any[] = []; @@ -391,14 +397,24 @@ function renderEditFile(result: any, args: any): string { if (oldText) { const oldLines = oldText.split('\n'); oldLines.forEach((line: string, lineIdx: number) => { - rows.push({ lineNo: startLine === null ? null : startLine + lineIdx, marker: '-', text: line, className: 'diff-remove' }); + rows.push({ + lineNo: startLine === null ? null : startLine + lineIdx, + marker: '-', + text: line, + className: 'diff-remove' + }); }); } if (newText) { const newLines = newText.split('\n'); newLines.forEach((line: string, lineIdx: number) => { - rows.push({ lineNo: startLine === null ? null : startLine + lineIdx, marker: '+', text: line, className: 'diff-add' }); + rows.push({ + lineNo: startLine === null ? null : startLine + lineIdx, + marker: '+', + text: line, + className: 'diff-add' + }); }); } return rows; @@ -410,30 +426,37 @@ function renderEditFile(result: any, args: any): string { const rawBeforeLines = Array.isArray(context.before_lines) ? context.before_lines : []; const rawAfterLines = Array.isArray(context.after_lines) ? context.after_lines : []; const beforeLines = - rawBeforeLines.length >= 2 && rawBeforeLines.every((item: any) => String(item?.text ?? '') === '') + rawBeforeLines.length >= 2 && + rawBeforeLines.every((item: any) => String(item?.text ?? '') === '') ? [] - : - rawBeforeLines.length >= 2 && String(rawBeforeLines[0]?.text ?? '') === '' - ? rawBeforeLines.slice(1) - : rawBeforeLines; + : rawBeforeLines.length >= 2 && String(rawBeforeLines[0]?.text ?? '') === '' + ? rawBeforeLines.slice(1) + : rawBeforeLines; const afterLines = - rawAfterLines.length >= 2 && rawAfterLines.every((item: any) => String(item?.text ?? '') === '') + rawAfterLines.length >= 2 && + rawAfterLines.every((item: any) => String(item?.text ?? '') === '') ? [] - : - rawAfterLines.length >= 2 && String(rawAfterLines[rawAfterLines.length - 1]?.text ?? '') === '' - ? rawAfterLines.slice(0, -1) - : rawAfterLines; + : rawAfterLines.length >= 2 && + String(rawAfterLines[rawAfterLines.length - 1]?.text ?? '') === '' + ? rawAfterLines.slice(0, -1) + : rawAfterLines; const contextOldStartLine = Number(context?.old_start_line ?? detail.matched_lines?.[0]); const contextOldEndLine = Number(context?.old_end_line ?? contextOldStartLine); - const oldLineCount = Number.isFinite(contextOldStartLine) && Number.isFinite(contextOldEndLine) - ? Math.max(1, contextOldEndLine - contextOldStartLine + 1) - : Math.max(1, (oldText ? oldText.split('\n').length : 1)); + const oldLineCount = + Number.isFinite(contextOldStartLine) && Number.isFinite(contextOldEndLine) + ? Math.max(1, contextOldEndLine - contextOldStartLine + 1) + : Math.max(1, oldText ? oldText.split('\n').length : 1); const newLineCount = newText === '' ? 0 : newText.split('\n').length; const afterContextOffset = newLineCount - oldLineCount; beforeLines.forEach((item: any) => { - rows.push({ lineNo: Number(item.line), marker: ' ', text: String(item.text ?? ''), className: '' }); + rows.push({ + lineNo: Number(item.line), + marker: ' ', + text: String(item.text ?? ''), + className: '' + }); }); rows.push(...buildPairRows(context)); afterLines.forEach((item: any) => { @@ -441,28 +464,41 @@ function renderEditFile(result: any, args: any): string { const lineNo = Number.isFinite(rawLineNo) ? rawLineNo + afterContextOffset : rawLineNo; rows.push({ lineNo, marker: ' ', text: String(item.text ?? ''), className: '' }); }); - const numberedRows = rows.filter((row) => typeof row.lineNo === 'number' && Number.isFinite(row.lineNo)); + const numberedRows = rows.filter( + (row) => typeof row.lineNo === 'number' && Number.isFinite(row.lineNo) + ); hunks.push({ rows, - minLine: numberedRows.length ? Math.min(...numberedRows.map((row) => row.lineNo)) : null, - maxLine: numberedRows.length ? Math.max(...numberedRows.map((row) => row.lineNo)) : null, + minLine: numberedRows.length + ? Math.min(...numberedRows.map((row) => row.lineNo)) + : null, + maxLine: numberedRows.length ? Math.max(...numberedRows.map((row) => row.lineNo)) : null }); }); } else { const rows = buildPairRows(); - const numberedRows = rows.filter((row) => typeof row.lineNo === 'number' && Number.isFinite(row.lineNo)); + const numberedRows = rows.filter( + (row) => typeof row.lineNo === 'number' && Number.isFinite(row.lineNo) + ); hunks.push({ rows, minLine: numberedRows.length ? Math.min(...numberedRows.map((row) => row.lineNo)) : null, - maxLine: numberedRows.length ? Math.max(...numberedRows.map((row) => row.lineNo)) : null, + maxLine: numberedRows.length ? Math.max(...numberedRows.map((row) => row.lineNo)) : null }); } }); - const sortedHunks = hunks.sort((a, b) => (a.minLine ?? Number.MAX_SAFE_INTEGER) - (b.minLine ?? Number.MAX_SAFE_INTEGER)); + const sortedHunks = hunks.sort( + (a, b) => (a.minLine ?? Number.MAX_SAFE_INTEGER) - (b.minLine ?? Number.MAX_SAFE_INTEGER) + ); const mergedHunks: any[] = []; sortedHunks.forEach((hunk) => { const last = mergedHunks[mergedHunks.length - 1]; - if (last && hunk.minLine !== null && last.maxLine !== null && hunk.minLine <= last.maxLine + 1) { + if ( + last && + hunk.minLine !== null && + last.maxLine !== null && + hunk.minLine <= last.maxLine + 1 + ) { last.rows.push(...hunk.rows); last.maxLine = Math.max(last.maxLine, hunk.maxLine ?? last.maxLine); } else { @@ -481,7 +517,8 @@ function renderEditFile(result: any, args: any): string { return true; }) .sort((a: any, b: any) => { - const lineDelta = (a.lineNo ?? Number.MAX_SAFE_INTEGER) - (b.lineNo ?? Number.MAX_SAFE_INTEGER); + const lineDelta = + (a.lineNo ?? Number.MAX_SAFE_INTEGER) - (b.lineNo ?? Number.MAX_SAFE_INTEGER); return lineDelta || (markerOrder[a.marker] ?? 9) - (markerOrder[b.marker] ?? 9); }) .forEach((row: any) => renderDiffLine(row.lineNo, row.marker, row.text, row.className)); @@ -562,7 +599,10 @@ function renderReadFile(result: any, args: any): string { matches.forEach((match: any, idx: number) => { if (idx > 0) html += '
'; html += '
'; - const lineLabel = match.line_start === match.line_end ? `${match.line_start}` : `${match.line_start}-${match.line_end}`; + const lineLabel = + match.line_start === match.line_end + ? `${match.line_start}` + : `${match.line_start}-${match.line_end}`; html += `
行 ${lineLabel}
`; html += `
${escapeHtml(match.snippet || match.content || '')}
`; html += '
'; @@ -813,10 +853,12 @@ function renderConversationSearch(result: any, args: any): string { const results = Array.isArray(result?.results) ? result.results : []; const keywords = Array.isArray(result?.keywords) ? result.keywords - : (Array.isArray(args?.keywords) ? args.keywords : []); + : Array.isArray(args?.keywords) + ? args.keywords + : []; const keywordText = keywords.length ? keywords.join(' / ') - : (result?.query || args?.query || '(未指定)'); + : result?.query || args?.query || '(未指定)'; let html = '
'; html += `
关键词:${escapeHtml(keywordText)}
`; html += `
日期范围:${escapeHtml(result?.start_date || args?.start_date || '不限')} ~ ${escapeHtml(result?.end_date || args?.end_date || '不限')}
`; @@ -941,21 +983,21 @@ function formatPersonalizationFieldValue(field: string, value: any): string { return independenceMap[String(value)] || String(value); } if (field === 'considerations') { - if (!Array.isArray(value) || value.length === 0) { + if (typeof value !== 'string' || value.trim() === '') { return '未设置'; } - return value.map((item) => String(item)).join('、'); + return value.trim(); } return String(value); } - function renderAskUser(result: any, args: any): string { const question = args.question || result.question || ''; const context = args.context || result.context || ''; const status = formatToolStatusLabel(result, '✓ 已回答'); - const answerText = result.status === 'answered' ? String(result.answer_text || result.message || '').trim() : ''; + const answerText = + result.status === 'answered' ? String(result.answer_text || result.message || '').trim() : ''; let html = '
'; html += `
状态:${escapeHtml(status)}
`; @@ -983,16 +1025,20 @@ function renderAskUser(result: any, args: any): string { if (answerText) { html += '
'; html += '
'; - answerText.split('\n').map((line) => line.trim()).filter(Boolean).forEach((line) => { - const separator = line.indexOf(':'); - if (separator > 0) { - const label = line.slice(0, separator + 1); - const value = line.slice(separator + 1); - html += `
${escapeHtml(label)}${escapeHtml(value)}
`; - } else { - html += `
用户回答:${escapeHtml(line)}
`; - } - }); + answerText + .split('\n') + .map((line) => line.trim()) + .filter(Boolean) + .forEach((line) => { + const separator = line.indexOf(':'); + if (separator > 0) { + const label = line.slice(0, separator + 1); + const value = line.slice(separator + 1); + html += `
${escapeHtml(label)}${escapeHtml(value)}
`; + } else { + html += `
用户回答:${escapeHtml(line)}
`; + } + }); html += '
'; } @@ -1001,10 +1047,7 @@ function renderAskUser(result: any, args: any): string { function renderManagePersonalization(result: any, args: any): string { const action = String(args.action || result.action || 'read'); - const status = formatToolStatusLabel( - result, - action === 'update' ? '✓ 已更新' : '✓ 已读取' - ); + const status = formatToolStatusLabel(result, action === 'update' ? '✓ 已更新' : '✓ 已读取'); let html = '
'; html += `
操作:${escapeHtml(action === 'update' ? '更新配置' : '读取配置')}
`; @@ -1118,7 +1161,8 @@ function renderCreateSubAgent(result: any, args: any): string { const message = result.message ?? result.summary ?? ''; const stats = result.stats || {}; - const runtimeSeconds = result.runtime_seconds ?? result.elapsed_seconds ?? stats.runtime_seconds ?? 0; + const runtimeSeconds = + result.runtime_seconds ?? result.elapsed_seconds ?? stats.runtime_seconds ?? 0; const apiCalls = stats.api_calls ?? stats.turn_count ?? 0; const toolCount = (stats.files_read || 0) + @@ -1215,7 +1259,7 @@ function renderGetSubAgentStatus(result: any, args: any): string { const found = item.found !== false; const status = String(item.status || ''); const taskId = item.task_id ?? ''; - const summary = item.summary ?? (item.final_result?.message) ?? (item.final_result?.summary) ?? ''; + const summary = item.summary ?? item.final_result?.message ?? item.final_result?.summary ?? ''; html += '
'; html += `
子智能体 #${escapeHtml(String(agentId))}
`; @@ -1291,8 +1335,7 @@ function renderGetSubAgentStatus(result: any, args: any): string { .code-block pre, .output-block pre { scrollbar-width: thin; /* Firefox */ - scrollbar-color: color-mix(in srgb, var(--claude-text-secondary) 55%, transparent) - transparent; + scrollbar-color: color-mix(in srgb, var(--claude-text-secondary) 55%, transparent) transparent; } .tool-result-content.scrollable::-webkit-scrollbar, @@ -1571,7 +1614,11 @@ function renderGetSubAgentStatus(result: any, args: any): string { /* 彩蛋 */ .easter-egg-content { padding: 12px; - background: linear-gradient(135deg, color-mix(in srgb, var(--decorative-gold) 10%, transparent), color-mix(in srgb, var(--decorative-pink) 10%, transparent)); + background: linear-gradient( + 135deg, + color-mix(in srgb, var(--decorative-gold) 10%, transparent), + color-mix(in srgb, var(--decorative-pink) 10%, transparent) + ); border: 1px solid color-mix(in srgb, var(--decorative-gold) 30%, transparent); border-radius: 8px; font-size: 14px; diff --git a/static/src/components/chat/actions/toolRenderers.ts b/static/src/components/chat/actions/toolRenderers.ts index a127f6b..91d1d92 100644 --- a/static/src/components/chat/actions/toolRenderers.ts +++ b/static/src/components/chat/actions/toolRenderers.ts @@ -317,8 +317,14 @@ function renderEditFile(result: any, args: any): string { if (details.length > 0) { html += '
'; - const renderDiffLine = (lineNo: number | null, marker: string, text: string, className = '') => { - const lineNoText = typeof lineNo === 'number' && Number.isFinite(lineNo) ? String(lineNo) : ''; + const renderDiffLine = ( + lineNo: number | null, + marker: string, + text: string, + className = '' + ) => { + const lineNoText = + typeof lineNo === 'number' && Number.isFinite(lineNo) ? String(lineNo) : ''; html += `
${escapeHtml(lineNoText)}${escapeHtml(marker)}${escapeHtml(text)}
`; }; const hunks: any[] = []; @@ -334,13 +340,23 @@ function renderEditFile(result: any, args: any): string { const startLine = Number.isFinite(startLineCandidate) ? startLineCandidate : null; if (oldText) { oldLines.forEach((line: string, lineIdx: number) => { - rows.push({ lineNo: startLine === null ? null : startLine + lineIdx, marker: '-', text: line, className: 'diff-remove' }); + rows.push({ + lineNo: startLine === null ? null : startLine + lineIdx, + marker: '-', + text: line, + className: 'diff-remove' + }); }); } if (newText) { newLines.forEach((line: string, lineIdx: number) => { - rows.push({ lineNo: startLine === null ? null : startLine + lineIdx, marker: '+', text: line, className: 'diff-add' }); + rows.push({ + lineNo: startLine === null ? null : startLine + lineIdx, + marker: '+', + text: line, + className: 'diff-add' + }); }); } return rows; @@ -352,30 +368,37 @@ function renderEditFile(result: any, args: any): string { const rawBeforeLines = Array.isArray(context.before_lines) ? context.before_lines : []; const rawAfterLines = Array.isArray(context.after_lines) ? context.after_lines : []; const beforeLines = - rawBeforeLines.length >= 2 && rawBeforeLines.every((item: any) => String(item?.text ?? '') === '') + rawBeforeLines.length >= 2 && + rawBeforeLines.every((item: any) => String(item?.text ?? '') === '') ? [] - : - rawBeforeLines.length >= 2 && String(rawBeforeLines[0]?.text ?? '') === '' - ? rawBeforeLines.slice(1) - : rawBeforeLines; + : rawBeforeLines.length >= 2 && String(rawBeforeLines[0]?.text ?? '') === '' + ? rawBeforeLines.slice(1) + : rawBeforeLines; const afterLines = - rawAfterLines.length >= 2 && rawAfterLines.every((item: any) => String(item?.text ?? '') === '') + rawAfterLines.length >= 2 && + rawAfterLines.every((item: any) => String(item?.text ?? '') === '') ? [] - : - rawAfterLines.length >= 2 && String(rawAfterLines[rawAfterLines.length - 1]?.text ?? '') === '' - ? rawAfterLines.slice(0, -1) - : rawAfterLines; + : rawAfterLines.length >= 2 && + String(rawAfterLines[rawAfterLines.length - 1]?.text ?? '') === '' + ? rawAfterLines.slice(0, -1) + : rawAfterLines; const contextOldStartLine = Number(context?.old_start_line ?? detail.matched_lines?.[0]); const contextOldEndLine = Number(context?.old_end_line ?? contextOldStartLine); - const oldLineCount = Number.isFinite(contextOldStartLine) && Number.isFinite(contextOldEndLine) - ? Math.max(1, contextOldEndLine - contextOldStartLine + 1) - : Math.max(1, (oldText ? oldText.split('\n').length : 1)); + const oldLineCount = + Number.isFinite(contextOldStartLine) && Number.isFinite(contextOldEndLine) + ? Math.max(1, contextOldEndLine - contextOldStartLine + 1) + : Math.max(1, oldText ? oldText.split('\n').length : 1); const newLineCount = newText === '' ? 0 : newText.split('\n').length; const afterContextOffset = newLineCount - oldLineCount; beforeLines.forEach((item: any) => { - rows.push({ lineNo: Number(item.line), marker: ' ', text: String(item.text ?? ''), className: '' }); + rows.push({ + lineNo: Number(item.line), + marker: ' ', + text: String(item.text ?? ''), + className: '' + }); }); rows.push(...buildPairRows(context)); afterLines.forEach((item: any) => { @@ -383,28 +406,41 @@ function renderEditFile(result: any, args: any): string { const lineNo = Number.isFinite(rawLineNo) ? rawLineNo + afterContextOffset : rawLineNo; rows.push({ lineNo, marker: ' ', text: String(item.text ?? ''), className: '' }); }); - const numberedRows = rows.filter((row) => typeof row.lineNo === 'number' && Number.isFinite(row.lineNo)); + const numberedRows = rows.filter( + (row) => typeof row.lineNo === 'number' && Number.isFinite(row.lineNo) + ); hunks.push({ rows, - minLine: numberedRows.length ? Math.min(...numberedRows.map((row) => row.lineNo)) : null, - maxLine: numberedRows.length ? Math.max(...numberedRows.map((row) => row.lineNo)) : null, + minLine: numberedRows.length + ? Math.min(...numberedRows.map((row) => row.lineNo)) + : null, + maxLine: numberedRows.length ? Math.max(...numberedRows.map((row) => row.lineNo)) : null }); }); } else { const rows = buildPairRows(); - const numberedRows = rows.filter((row) => typeof row.lineNo === 'number' && Number.isFinite(row.lineNo)); + const numberedRows = rows.filter( + (row) => typeof row.lineNo === 'number' && Number.isFinite(row.lineNo) + ); hunks.push({ rows, minLine: numberedRows.length ? Math.min(...numberedRows.map((row) => row.lineNo)) : null, - maxLine: numberedRows.length ? Math.max(...numberedRows.map((row) => row.lineNo)) : null, + maxLine: numberedRows.length ? Math.max(...numberedRows.map((row) => row.lineNo)) : null }); } }); - const sortedHunks = hunks.sort((a, b) => (a.minLine ?? Number.MAX_SAFE_INTEGER) - (b.minLine ?? Number.MAX_SAFE_INTEGER)); + const sortedHunks = hunks.sort( + (a, b) => (a.minLine ?? Number.MAX_SAFE_INTEGER) - (b.minLine ?? Number.MAX_SAFE_INTEGER) + ); const mergedHunks: any[] = []; sortedHunks.forEach((hunk) => { const last = mergedHunks[mergedHunks.length - 1]; - if (last && hunk.minLine !== null && last.maxLine !== null && hunk.minLine <= last.maxLine + 1) { + if ( + last && + hunk.minLine !== null && + last.maxLine !== null && + hunk.minLine <= last.maxLine + 1 + ) { last.rows.push(...hunk.rows); last.maxLine = Math.max(last.maxLine, hunk.maxLine ?? last.maxLine); } else { @@ -428,13 +464,19 @@ function renderEditFile(result: any, args: any): string { // 仅变更行之间优先按 marker 分组(- 在前 + 在后),再按行号 if (aIsChange && bIsChange) { const markerDelta = (markerOrder[a.marker] ?? 9) - (markerOrder[b.marker] ?? 9); - return markerDelta || (a.lineNo ?? Number.MAX_SAFE_INTEGER) - (b.lineNo ?? Number.MAX_SAFE_INTEGER); + return ( + markerDelta || + (a.lineNo ?? Number.MAX_SAFE_INTEGER) - (b.lineNo ?? Number.MAX_SAFE_INTEGER) + ); } // 上下文行或跨类型:按行号优先 - const lineDelta = (a.lineNo ?? Number.MAX_SAFE_INTEGER) - (b.lineNo ?? Number.MAX_SAFE_INTEGER); + const lineDelta = + (a.lineNo ?? Number.MAX_SAFE_INTEGER) - (b.lineNo ?? Number.MAX_SAFE_INTEGER); return lineDelta || (markerOrder[a.marker] ?? 9) - (markerOrder[b.marker] ?? 9); }); - filteredRows.forEach((row: any) => renderDiffLine(row.lineNo, row.marker, row.text, row.className)); + filteredRows.forEach((row: any) => + renderDiffLine(row.lineNo, row.marker, row.text, row.className) + ); }); html += '
'; } @@ -512,7 +554,10 @@ function renderReadFile(result: any, args: any): string { matches.forEach((match: any, idx: number) => { if (idx > 0) html += '
'; html += '
'; - const lineLabel = match.line_start === match.line_end ? `${match.line_start}` : `${match.line_start}-${match.line_end}`; + const lineLabel = + match.line_start === match.line_end + ? `${match.line_start}` + : `${match.line_start}-${match.line_end}`; html += `
行 ${lineLabel}
`; html += `
${escapeHtml(match.snippet || match.content || '')}
`; html += '
'; @@ -794,10 +839,12 @@ function renderConversationSearch(result: any, args: any): string { const results = Array.isArray(result?.results) ? result.results : []; const keywords = Array.isArray(result?.keywords) ? result.keywords - : (Array.isArray(args?.keywords) ? args.keywords : []); + : Array.isArray(args?.keywords) + ? args.keywords + : []; const keywordText = keywords.length ? keywords.join(' / ') - : (result?.query || args?.query || '(未指定)'); + : result?.query || args?.query || '(未指定)'; let html = '
'; html += `
关键词:${escapeHtml(keywordText)}
`; html += `
日期范围:${escapeHtml(result?.start_date || args?.start_date || '不限')} ~ ${escapeHtml(result?.end_date || args?.end_date || '不限')}
`; @@ -897,10 +944,10 @@ function formatPersonalizationFieldValue(field: string, value: any): string { return value ? '开启' : '关闭'; } if (field === 'considerations') { - if (!Array.isArray(value) || value.length === 0) { + if (typeof value !== 'string' || value.trim() === '') { return '未设置'; } - return value.map((item) => String(item)).join('、'); + return value.trim(); } if (field === 'communication_style') { const styleMap: Record = { @@ -921,12 +968,12 @@ function formatPersonalizationFieldValue(field: string, value: any): string { return String(value); } - function renderAskUser(result: any, args: any): string { const question = args.question || result.question || ''; const context = args.context || result.context || ''; const status = formatToolStatusLabel(result, '✓ 已回答'); - const answerText = result.status === 'answered' ? String(result.answer_text || result.message || '').trim() : ''; + const answerText = + result.status === 'answered' ? String(result.answer_text || result.message || '').trim() : ''; let html = '
'; html += `
状态:${escapeHtml(status)}
`; @@ -954,16 +1001,20 @@ function renderAskUser(result: any, args: any): string { if (answerText) { html += '
'; html += '
'; - answerText.split('\n').map((line) => line.trim()).filter(Boolean).forEach((line) => { - const separator = line.indexOf(':'); - if (separator > 0) { - const label = line.slice(0, separator + 1); - const value = line.slice(separator + 1); - html += `
${escapeHtml(label)}${escapeHtml(value)}
`; - } else { - html += `
用户回答:${escapeHtml(line)}
`; - } - }); + answerText + .split('\n') + .map((line) => line.trim()) + .filter(Boolean) + .forEach((line) => { + const separator = line.indexOf(':'); + if (separator > 0) { + const label = line.slice(0, separator + 1); + const value = line.slice(separator + 1); + html += `
${escapeHtml(label)}${escapeHtml(value)}
`; + } else { + html += `
用户回答:${escapeHtml(line)}
`; + } + }); html += '
'; } @@ -1086,7 +1137,8 @@ function renderCreateSubAgent(result: any, args: any): string { const message = result.message ?? result.summary ?? ''; const stats = result.stats || {}; - const runtimeSeconds = result.runtime_seconds ?? result.elapsed_seconds ?? stats.runtime_seconds ?? 0; + const runtimeSeconds = + result.runtime_seconds ?? result.elapsed_seconds ?? stats.runtime_seconds ?? 0; const apiCalls = stats.api_calls ?? stats.turn_count ?? 0; const toolCount = (stats.files_read || 0) + @@ -1183,7 +1235,7 @@ function renderGetSubAgentStatus(result: any, args: any): string { const found = item.found !== false; const status = String(item.status || ''); const taskId = item.task_id ?? ''; - const summary = item.summary ?? (item.final_result?.message) ?? (item.final_result?.summary) ?? ''; + const summary = item.summary ?? item.final_result?.message ?? item.final_result?.summary ?? ''; html += '
'; html += `
子智能体 #${escapeHtml(String(agentId))}
`; diff --git a/static/src/components/personalization/PersonalizationDrawer.vue b/static/src/components/personalization/PersonalizationDrawer.vue index 4358b6f..67afeab 100644 --- a/static/src/components/personalization/PersonalizationDrawer.vue +++ b/static/src/components/personalization/PersonalizationDrawer.vue @@ -25,9 +25,7 @@
-
+
-
- +
+
回答时必须考虑的信息 - 最多 {{ maxConsiderations }} 条,可拖动排序 - -
-
- - -
-
    -
  • - - {{ item }} - -
  • -
+ 每次回答前都会参考这些背景信息
+
@@ -1379,9 +1337,7 @@
强约束系统仅对个人空间中已启用的 skill 生效 + >仅对个人空间中已启用的 skill 生效
@@ -1487,9 +1444,7 @@
默认禁用工具类别选择后,这些类别在新任务中保持关闭 + >选择后,这些类别在新任务中保持关闭