feat(chat): 收拢式AI思考/执行过程可视化 + 修复span(name)重复传参导致技能/MCP执行崩溃

This commit is contained in:
qihu-dev committed 2026-07-22 00:17:54 +08:00
1 parent 46528eddbb
commit c32eed39bd
4 files changed
+2298 -4

No files matched your search

File diff suppressed because it is too large. Load diff
+107
View File
@@ -1557,3 +1557,110 @@ del {
border-color: var(--color-accent, #5B5FED); border-color: var(--color-accent, #5B5FED);
color: var(--color-accent, #5B5FED); color: var(--color-accent, #5B5FED);
} }
/* ===== AI 思考 / 执行过程面板(收拢式) ===== */
.ai-process {
margin-top: 10px;
border: 1px solid var(--color-border-light, #e6e9f0);
border-radius: var(--radius-md, 12px);
background: color-mix(in srgb, var(--color-bg-secondary, #fff) 75%, var(--color-accent, #5B5FED) 4%);
backdrop-filter: blur(8px) saturate(160%);
overflow: hidden;
transition: border-color var(--transition-fast, .2s), box-shadow var(--transition-fast, .2s);
}
.ai-process[open] {
box-shadow: 0 6px 22px rgba(91, 95, 237, 0.10);
border-color: color-mix(in srgb, var(--color-accent, #5B5FED) 35%, transparent);
}
.ai-process-summary {
list-style: none;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
padding: 9px 14px;
font-size: var(--text-sm, 13px);
font-weight: 600;
color: var(--color-text-secondary, #475069);
user-select: none;
transition: background var(--transition-fast, .2s);
}
.ai-process-summary::-webkit-details-marker { display: none; }
.ai-process-summary::marker { content: ""; }
.ai-process-summary:hover {
background: color-mix(in srgb, var(--color-accent, #5B5FED) 7%, transparent);
}
.ai-process-badge {
font-size: 15px;
line-height: 1;
filter: drop-shadow(0 1px 2px rgba(91, 95, 237, 0.25));
}
.ai-process-list {
padding: 4px 14px 12px;
display: flex;
flex-direction: column;
gap: 8px;
animation: ai-process-in .28s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes ai-process-in {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
.ai-process-item {
display: flex;
gap: 10px;
padding: 9px 12px;
border-radius: 10px;
background: var(--color-bg-secondary, #fff);
border: 1px solid var(--color-border-light, #e6e9f0);
border-left: 3px solid var(--color-accent, #5B5FED);
}
.ai-process-thinking { border-left-color: #8b8fe6; }
.ai-process-skill { border-left-color: #5B5FED; }
.ai-process-mcp { border-left-color: #12a594; }
.ai-process-ico {
font-size: 15px;
line-height: 1.4;
flex-shrink: 0;
}
.ai-process-body {
min-width: 0;
flex: 1;
}
.ai-process-title {
display: block;
font-size: var(--text-sm, 13px);
font-weight: 600;
color: var(--color-text-primary, #1f2430);
margin-bottom: 2px;
}
.ai-process-text {
margin: 0;
font-size: var(--text-xs, 12px);
line-height: 1.55;
color: var(--color-text-tertiary, #8a90a6);
max-height: 160px;
overflow-y: auto;
word-break: break-word;
white-space: pre-wrap;
}
/* 暗色主题微调 */
[data-theme="dark"] .ai-process,
html.dark .ai-process {
background: color-mix(in srgb, var(--color-bg-secondary, #1b1d29) 70%, var(--color-accent, #5B5FED) 8%);
border-color: color-mix(in srgb, var(--color-accent, #5B5FED) 25%, transparent);
}
+30
View File
@@ -895,6 +895,7 @@ const ChatPage: React.FC = () => {
createdAt: data.assistant_message.created_at || new Date().toISOString(), createdAt: data.assistant_message.created_at || new Date().toISOString(),
used_skills: data.assistant_message.used_skills || [], used_skills: data.assistant_message.used_skills || [],
used_mcp: data.assistant_message.used_mcp || [], used_mcp: data.assistant_message.used_mcp || [],
process_steps: data.assistant_message.process_steps || [],
}); });
} }
return [...filtered, ...newMsgs]; return [...filtered, ...newMsgs];
@@ -1168,6 +1169,7 @@ const ChatPage: React.FC = () => {
// Real used_skills from API response // Real used_skills from API response
const usedSkills: string[] = (msg.role === 'assistant' && msg.used_skills) ? msg.used_skills : []; const usedSkills: string[] = (msg.role === 'assistant' && msg.used_skills) ? msg.used_skills : [];
const usedMcp: string[] = (msg.role === 'assistant' && msg.used_mcp) ? msg.used_mcp : []; const usedMcp: string[] = (msg.role === 'assistant' && msg.used_mcp) ? msg.used_mcp : [];
const steps: any[] = (msg as any).process_steps || [];
// 模糊创作意图确认卡片 // 模糊创作意图确认卡片
const clarify = msg.role === 'assistant' ? parseClarify(msg.content) : null; const clarify = msg.role === 'assistant' ? parseClarify(msg.content) : null;
return ( return (
@@ -1235,6 +1237,34 @@ const ChatPage: React.FC = () => {
)} )}
</div> </div>
)} )}
{msg.role === 'assistant' && steps.length > 0 && (
<details className="ai-process" open={false}>
<summary className="ai-process-summary">
<span className="ai-process-badge">🧠</span>
思考 / 执行过程({steps.length} 步,点击展开)
</summary>
<div className="ai-process-list">
{steps.map((s: any, i: number) => (
<div key={i} className={`ai-process-item ai-process-${s.type}`}>
<span className="ai-process-ico">
{s.type === 'thinking' ? '💡' : s.type === 'skill' ? '🛠' : '🔌'}
</span>
<div className="ai-process-body">
{s.type === 'thinking' && (
<><b className="ai-process-title">思考</b><p className="ai-process-text">{s.text}</p></>
)}
{s.type === 'skill' && (
<><b className="ai-process-title">调用技能:{s.name}</b><p className="ai-process-text">{(s.result || '').slice(0, 400)}</p></>
)}
{s.type === 'mcp' && (
<><b className="ai-process-title">MCP · {s.server}:{s.name}</b><p className="ai-process-text">{(s.result || '').slice(0, 400)}</p></>
)}
</div>
</div>
))}
</div>
</details>
)}
<div className={`message-meta ${msg.role}`}> <div className={`message-meta ${msg.role}`}>
<span className="message-time">{formatTime(msg.createdAt)}</span> <span className="message-time">{formatTime(msg.createdAt)}</span>
<span className="message-tokens">{msg.tokenUsed} tokens</span> <span className="message-tokens">{msg.tokenUsed} tokens</span>
+7
View File
@@ -62,6 +62,13 @@ export interface Message {
createdAt: string; createdAt: string;
used_skills?: string[]; used_skills?: string[];
used_mcp?: string[]; used_mcp?: string[];
process_steps?: Array<{
type: 'thinking' | 'skill' | 'mcp';
text?: string;
name?: string;
server?: string;
result?: string;
}>;
} }