Skip to content

Commit 2185313

Browse files
committed
feat(agent): add rich content support and refactor tool execution types
- Rename ToolExecutionResult to AgentToolExecutionResult for clarity - Add richContent field to tool execution results for enhanced output rendering - Move ToolExecutionContext to shared types for better code organization - Update toolRegistry reference to toolManager for consistency - Enhance RichContentRenderer with fullscreen image modal using portal - Add image expansion functionality with smooth animations and backdrop blur - Remove unused language parameter from getPromptTemplatePreview function - Update all imports and type references across agent core modules - Improve tool result handling to preserve and propagate rich content metadata
1 parent 2276c27 commit 2185313

File tree

9 files changed

+74
-33
lines changed

9 files changed

+74
-33
lines changed

src/renderer/agent/core/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ export type {
1515
LLMCallResult,
1616
LoopCheckResult,
1717
CompressionStats,
18-
ToolExecutionResult,
18+
AgentToolExecutionResult,
1919
} from './types'

src/renderer/agent/core/tools.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
import { api } from '@/renderer/services/electronAPI'
1313
import { logger } from '@utils/Logger'
1414
import { useAgentStore } from '../store/AgentStore'
15-
import { toolRegistry } from '../tools/registry'
15+
import { toolManager } from '../tools/providers'
1616
import { getToolApprovalType, isFileEditTool } from '@/shared/config/tools'
1717
import { pathStartsWith, joinPath } from '@shared/utils/pathUtils'
1818
import { useStore } from '@store'
1919
import { EventBus } from './EventBus'
2020
import { truncateToolResult } from '@/renderer/utils/partialJson'
2121
import { getAgentConfig } from '../utils/AgentConfig'
2222
import type { ToolCall } from '@/shared/types'
23-
import type { ToolExecutionContext, ToolExecutionResult } from './types'
23+
import type { ToolExecutionContext, AgentToolExecutionResult } from './types'
2424

2525
// ===== 审批服务 =====
2626

@@ -144,7 +144,7 @@ function analyzeToolDependencies(toolCalls: ToolCall[]): Map<string, Set<string>
144144
async function executeSingle(
145145
toolCall: ToolCall,
146146
context: ToolExecutionContext
147-
): Promise<ToolExecutionResult> {
147+
): Promise<AgentToolExecutionResult> {
148148
const store = useAgentStore.getState()
149149
const mainStore = useStore.getState()
150150
const { currentAssistantId, workspacePath } = context
@@ -164,7 +164,7 @@ async function executeSingle(
164164
})
165165

166166
try {
167-
const result = await toolRegistry.execute(
167+
const result = await toolManager.execute(
168168
toolCall.name,
169169
toolCall.arguments,
170170
{ workspacePath: workspacePath ?? null, currentAssistantId: currentAssistantId ?? null }
@@ -194,6 +194,7 @@ async function executeSingle(
194194
})
195195

196196
const meta = result.meta || {}
197+
const richContent = result.richContent
197198

198199
// 更新状态,并将 meta 数据合并到 arguments._meta
199200
if (currentAssistantId) {
@@ -205,16 +206,17 @@ async function executeSingle(
205206
status: result.success ? 'success' : 'error',
206207
result: content,
207208
arguments: updatedArguments,
209+
richContent,
208210
})
209211
store.addToolResult(toolCall.id, toolCall.name, content, result.success ? 'success' : 'tool_error')
210212
}
211213
EventBus.emit({
212214
type: result.success ? 'tool:completed' : 'tool:error',
213215
id: toolCall.id,
214-
...(result.success ? { result: content, meta } : { error: content }),
216+
...(result.success ? { result: content, meta, richContent } : { error: content }),
215217
} as any)
216218

217-
return { toolCall, result: { content, meta } }
219+
return { toolCall, result: { content, meta, richContent } }
218220
} catch (error) {
219221
const duration = Date.now() - startTime
220222
const errorMsg = error instanceof Error ? error.message : String(error)
@@ -251,9 +253,9 @@ export async function executeTools(
251253
toolCalls: ToolCall[],
252254
context: ToolExecutionContext,
253255
abortSignal?: AbortSignal
254-
): Promise<{ results: ToolExecutionResult[]; userRejected: boolean }> {
256+
): Promise<{ results: AgentToolExecutionResult[]; userRejected: boolean }> {
255257
const store = useAgentStore.getState()
256-
const results: ToolExecutionResult[] = []
258+
const results: AgentToolExecutionResult[] = []
257259
let userRejected = false
258260

259261
if (toolCalls.length === 0) {

src/renderer/agent/core/types.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,9 @@ export interface ExecutionContext {
2121
abortSignal?: AbortSignal
2222
}
2323

24-
// ===== 工具执行上下文 =====
24+
// ===== 工具执行上下文(重新导出 shared 定义) =====
2525

26-
export interface ToolExecutionContext {
27-
workspacePath: string | null
28-
currentAssistantId: string | null
29-
}
26+
export type { ToolExecutionContext } from '@/shared/types'
3027

3128
// ===== LLM 调用结果 =====
3229

@@ -49,13 +46,14 @@ export interface LoopCheckResult {
4946

5047
export type { CompressionStats, CompressionLevel } from '../context/CompressionManager'
5148

52-
// ===== 工具执行结果 =====
49+
// ===== 工具执行结果(Agent 内部使用,包含 toolCall 信息) =====
5350

54-
export interface ToolExecutionResult {
51+
export interface AgentToolExecutionResult {
5552
toolCall: ToolCall
5653
result: {
5754
content: string
5855
meta?: Record<string, unknown>
56+
richContent?: import('@/shared/types').ToolRichContent[]
5957
}
6058
}
6159

src/renderer/agent/prompts/promptTemplates.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ import { buildSystemPrompt, type PromptContext } from './PromptBuilder'
568568
* @param templateId 模板 ID
569569
* @param language 语言,'zh' 为中文,其他为英文
570570
*/
571-
export function getPromptTemplatePreview(templateId: string, language?: string): string {
571+
export function getPromptTemplatePreview(templateId: string): string {
572572
const template = getPromptTemplateById(templateId)
573573
if (!template) return 'Template not found'
574574

src/renderer/components/agent/RichContentRenderer.tsx

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import { useState, useMemo, memo } from 'react'
7+
import { createPortal } from 'react-dom'
78
import {
89
Image as ImageIcon, Code, FileText, Link as LinkIcon,
910
Table, Copy, Check, ExternalLink, Maximize2, X
@@ -95,6 +96,40 @@ function ImageContent({ item }: { item: ToolRichContent }) {
9596

9697
if (!imageSrc) return null
9798

99+
const modal = isExpanded ? createPortal(
100+
<AnimatePresence>
101+
<motion.div
102+
initial={{ opacity: 0 }}
103+
animate={{ opacity: 1 }}
104+
exit={{ opacity: 0 }}
105+
className="fixed inset-0 z-[99999] flex items-center justify-center bg-black/95 backdrop-blur-lg p-8"
106+
onClick={() => setIsExpanded(false)}
107+
style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0 }}
108+
>
109+
<motion.div
110+
initial={{ scale: 0.9, opacity: 0 }}
111+
animate={{ scale: 1, opacity: 1 }}
112+
exit={{ scale: 0.9, opacity: 0 }}
113+
className="relative max-w-[90vw] max-h-[90vh] flex items-center justify-center"
114+
onClick={(e) => e.stopPropagation()}
115+
>
116+
<img
117+
src={imageSrc}
118+
alt={item.title || 'Image'}
119+
className="max-w-full max-h-[90vh] object-contain rounded-xl shadow-2xl"
120+
/>
121+
</motion.div>
122+
<button
123+
onClick={() => setIsExpanded(false)}
124+
className="absolute top-6 right-6 p-3 rounded-full bg-white/10 text-white hover:bg-white/20 transition-all z-[100000]"
125+
>
126+
<X className="w-5 h-5" />
127+
</button>
128+
</motion.div>
129+
</AnimatePresence>,
130+
document.body
131+
) : null
132+
98133
return (
99134
<>
100135
<ContentCard
@@ -103,28 +138,35 @@ function ImageContent({ item }: { item: ToolRichContent }) {
103138
noPadding
104139
actions={
105140
<>
106-
<button onClick={() => { navigator.clipboard.writeText(imageSrc); setCopied(true); setTimeout(() => setCopied(false), 2000); }} className="p-1.5 rounded-lg hover:bg-white/10 text-text-muted transition-colors">
141+
<button
142+
onClick={() => {
143+
navigator.clipboard.writeText(imageSrc);
144+
setCopied(true);
145+
setTimeout(() => setCopied(false), 2000);
146+
}}
147+
className="p-1.5 rounded-lg hover:bg-white/10 text-text-muted transition-colors"
148+
>
107149
{copied ? <Check className="w-3.5 h-3.5 text-emerald-400" /> : <Copy className="w-3.5 h-3.5" />}
108150
</button>
109-
<button onClick={() => setIsExpanded(true)} className="p-1.5 rounded-lg hover:bg-white/10 text-text-muted transition-colors">
151+
<button
152+
onClick={() => setIsExpanded(true)}
153+
className="p-1.5 rounded-lg hover:bg-white/10 text-text-muted transition-colors"
154+
>
110155
<Maximize2 className="w-3.5 h-3.5" />
111156
</button>
112157
</>
113158
}
114159
>
115160
<div className="flex justify-center bg-black/40 group relative">
116-
<img src={imageSrc} className="max-w-full max-h-80 object-contain cursor-zoom-in" onClick={() => setIsExpanded(true)} />
161+
<img
162+
src={imageSrc}
163+
alt={item.title || 'Image'}
164+
className="max-w-full max-h-80 object-contain cursor-zoom-in"
165+
onClick={() => setIsExpanded(true)}
166+
/>
117167
</div>
118168
</ContentCard>
119-
120-
<AnimatePresence>
121-
{isExpanded && (
122-
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} className="fixed inset-0 z-[100] flex items-center justify-center bg-black/90 backdrop-blur-md p-8" onClick={() => setIsExpanded(false)}>
123-
<motion.img initial={{ scale: 0.9 }} animate={{ scale: 1 }} src={imageSrc} className="max-w-full max-h-full rounded-xl shadow-2xl" />
124-
<button className="absolute top-6 right-6 p-3 rounded-full bg-white/10 text-white hover:bg-white/20 transition-all"><X /></button>
125-
</motion.div>
126-
)}
127-
</AnimatePresence>
169+
{modal}
128170
</>
129171
)
130172
}

src/renderer/components/settings/tabs/PromptPreviewModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { PromptPreviewModalProps } from '../types'
1111

1212
export function PromptPreviewModal({ templateId, language, onClose }: PromptPreviewModalProps) {
1313
const template = getPromptTemplateById(templateId)
14-
const previewContent = template ? getPromptTemplatePreview(templateId, language) : ''
14+
const previewContent = template ? getPromptTemplatePreview(templateId) : ''
1515
const [searchQuery, setSearchQuery] = useState('')
1616
const [activeSection, setActiveSection] = useState<string | null>(null)
1717
const [copied, setCopied] = useState(false)

src/renderer/types/electron.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,8 @@ export interface ElectronAPI {
260260
killTerminal: (id?: string) => void
261261
getAvailableShells: () => Promise<{ label: string; path: string }[]>
262262
onTerminalData: (callback: (event: { id: string; data: string }) => void) => () => void
263+
onTerminalExit: (callback: (event: { id: string; exitCode: number; signal?: number }) => void) => () => void
264+
onTerminalError: (callback: (event: { id: string; error: string }) => void) => () => void
263265

264266
// Shell
265267
executeBackground: (params: { command: string; cwd?: string; timeout?: number; shell?: string }) => Promise<{

src/shared/types/llm.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,6 @@ export interface ToolRichContent {
229229
}
230230
/** 链接 URL(type 为 link 时使用) */
231231
url?: string
232-
/** 附加属性 */
233-
attributes?: Record<string, unknown>
234232
}
235233

236234
export interface ToolExecutionContext {

src/shared/utils/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export {
2525
validatePath,
2626
hasPathTraversal,
2727
isSensitivePath,
28-
type PathValidationResult,
2928
} from './pathUtils'
3029

3130
// JSON 工具函数

0 commit comments

Comments
 (0)