@@ -11,7 +11,7 @@ import type { HTMLProps } from 'react';
11
11
import { MessageContent , MessageContentType } from '../../types/chat' ;
12
12
import { MessageHelper } from '../../services/message-helper' ;
13
13
import FileAttachmentDisplay from './FileAttachmentDisplay' ;
14
- import { Loader2 } from 'lucide-react' ;
14
+ import { Loader2 , ServerCog , AlertCircle } from 'lucide-react' ;
15
15
import { useTranslation } from '../../hooks/useTranslation' ;
16
16
17
17
interface MarkdownContentProps {
@@ -33,6 +33,7 @@ export const MarkdownContent: React.FC<MarkdownContentProps> = ({ content, isUse
33
33
const [ imageContents , setImageContents ] = useState < MessageContent [ ] > ( [ ] ) ;
34
34
const [ isProcessingImage , setIsProcessingImage ] = useState ( false ) ;
35
35
const [ imageGenerationError , setImageGenerationError ] = useState < string | null > ( null ) ;
36
+ const [ customToolCalls , setCustomToolCalls ] = useState < Array < { name : string , status : string } > > ( [ ] ) ;
36
37
37
38
// Process content and check for thinking blocks, files, and image generation
38
39
useEffect ( ( ) => {
@@ -67,10 +68,48 @@ export const MarkdownContent: React.FC<MarkdownContentProps> = ({ content, isUse
67
68
68
69
let processed = MessageHelper . MessageContentToText ( textContents ) ;
69
70
71
+ // Track custom tool calls
72
+ const toolCalls : Array < { name : string , status : string } > = [ ] ;
73
+
74
+ // Detect tool calls in progress
75
+ const customToolCallRegex = / \b E x e c u t i n g t o o l c a l l : \s + ( [ a - z A - Z 0 - 9 _ - ] + ) / g;
76
+ let match : RegExpExecArray | null ;
77
+ while ( ( match = customToolCallRegex . exec ( processed ) ) !== null ) {
78
+ const toolName = match [ 1 ] ;
79
+ if ( toolName !== 'generate_image' ) {
80
+ toolCalls . push ( { name : toolName , status : 'in_progress' } ) ;
81
+ }
82
+ }
83
+
84
+ // Detect tool results
85
+ const toolResultRegex = / \b T o o l r e s u l t : \s + ( { .+ } ) / g;
86
+ while ( ( match = toolResultRegex . exec ( processed ) ) !== null ) {
87
+ // Mark the last tool as completed if it exists
88
+ if ( toolCalls . length > 0 ) {
89
+ const lastTool = toolCalls [ toolCalls . length - 1 ] ;
90
+ lastTool . status = 'completed' ;
91
+ }
92
+ }
93
+
94
+ // Detect tool errors
95
+ const toolErrorRegex = / \b E r r o r i n t o o l c a l l \s + ( [ a - z A - Z 0 - 9 _ - ] + ) : \s + ( .+ ) / g;
96
+ while ( ( match = toolErrorRegex . exec ( processed ) ) !== null ) {
97
+ const toolName = match [ 1 ] ;
98
+ // Check if we already have this tool
99
+ const existingTool = toolCalls . find ( tool => tool . name === toolName ) ;
100
+ if ( existingTool ) {
101
+ existingTool . status = 'error' ;
102
+ } else {
103
+ toolCalls . push ( { name : toolName , status : 'error' } ) ;
104
+ }
105
+ }
106
+
107
+ setCustomToolCalls ( toolCalls ) ;
108
+
70
109
// Detect image generation in progress
71
110
const imageGenInProgressMatch = processed . match (
72
111
/ (?: g e n e r a t i n g | c r e a t i n g | p r o c e s s i n g ) \s + (?: a n \s + ) ? i m a g e (?: s ) ? \s + (?: w i t h | u s i n g | f o r | f r o m ) ? (?: \s + p r o m p t ) ? (?: : | ; ) ? \s * [ " ' ] ? ( [ ^ " ' ] + ) [ " ' ] ? / i
73
- ) || processed . match ( / \b i m a g e \s + g e n e r a t i o n \s + i n \s + p r o g r e s s \b / i) ;
112
+ ) || processed . match ( / \b i m a g e \s + g e n e r a t i o n \s + i n \s + p r o g r e s s \b / i) || processed . match ( / \b G e n e r a t i n g i m a g e \b / i ) ;
74
113
75
114
if ( ( imageGenInProgressMatch && images . length === 0 ) ||
76
115
( processed . includes ( 'generate_image' ) && processed . includes ( 'tool call' ) && images . length === 0 ) ) {
@@ -83,7 +122,7 @@ export const MarkdownContent: React.FC<MarkdownContentProps> = ({ content, isUse
83
122
// Detect image generation errors
84
123
const imageGenErrorMatch = processed . match (
85
124
/ (?: e r r o r | f a i l e d | c o u l d n ' t | u n a b l e ) \s + (?: i n \s + ) ? (?: g e n e r a t i n g | c r e a t i n g | p r o c e s s i n g ) \s + (?: a n \s + ) ? i m a g e (?: s ) ? (?: : | ; ) ? \s * [ " ' ] ? ( [ ^ " ' ] + ) [ " ' ] ? / i
86
- ) || processed . match ( / \b i m a g e \s + g e n e r a t i o n \s + (?: e r r o r | f a i l e d ) \b : ? \s * [ " ' ] ? ( [ ^ " ' ] + ) [ " ' ] ? / i) ;
125
+ ) || processed . match ( / \b i m a g e \s + g e n e r a t i o n \s + (?: e r r o r | f a i l e d ) \b : ? \s * [ " ' ] ? ( [ ^ " ' ] + ) [ " ' ] ? / i) || processed . match ( / \b E r r o r g e n e r a t i n g i m a g e \b : ? \s * ( [ ^ " \n ] + ) / i ) ;
87
126
88
127
if ( imageGenErrorMatch || ( processed . includes ( 'error' ) && processed . includes ( 'generate_image' ) ) ) {
89
128
const errorMessage = imageGenErrorMatch ? ( imageGenErrorMatch [ 1 ] || "Unknown error occurred" ) : "Failed to generate image" ;
@@ -93,6 +132,33 @@ export const MarkdownContent: React.FC<MarkdownContentProps> = ({ content, isUse
93
132
setImageGenerationError ( null ) ;
94
133
}
95
134
135
+ // Process the content - remove the tool call info to make the response cleaner
136
+ if ( processed . includes ( 'Executing tool call:' ) || processed . includes ( 'Tool result:' ) ||
137
+ processed . includes ( 'Generating image' ) || processed . includes ( 'Error generating image' ) ||
138
+ processed . includes ( 'Error in tool call' ) ) {
139
+
140
+ // Split by the first occurrence of a tool-related message
141
+ const toolMarkers = [
142
+ 'Executing tool call:' ,
143
+ 'Tool result:' ,
144
+ 'Generating image' ,
145
+ 'Error generating image' ,
146
+ 'Error in tool call'
147
+ ] ;
148
+
149
+ let firstToolIndex = processed . length ;
150
+ for ( const marker of toolMarkers ) {
151
+ const index = processed . indexOf ( marker ) ;
152
+ if ( index > - 1 && index < firstToolIndex ) {
153
+ firstToolIndex = index ;
154
+ }
155
+ }
156
+
157
+ if ( firstToolIndex < processed . length ) {
158
+ processed = processed . substring ( 0 , firstToolIndex ) ;
159
+ }
160
+ }
161
+
96
162
// Check if content contains thinking block
97
163
const thinkMatch = processed . match ( / < t h i n k > ( [ \s \S ] * ?) < \/ t h i n k > ( [ \s \S ] * ) / ) ;
98
164
@@ -172,9 +238,47 @@ export const MarkdownContent: React.FC<MarkdownContentProps> = ({ content, isUse
172
238
</ div >
173
239
) }
174
240
241
+ { /* Custom Tool Calls */ }
242
+ { customToolCalls . length > 0 && (
243
+ < div className = "mb-3 space-y-2" >
244
+ { customToolCalls . map ( ( toolCall , index ) => (
245
+ < div
246
+ key = { index }
247
+ className = { `p-3 border rounded-md flex items-center gap-2 ${
248
+ toolCall . status === 'error'
249
+ ? 'border-red-200 bg-red-50'
250
+ : toolCall . status === 'completed'
251
+ ? 'border-green-200 bg-green-50'
252
+ : 'border-blue-200 bg-blue-50'
253
+ } `}
254
+ >
255
+ { toolCall . status === 'in_progress' && (
256
+ < Loader2 size = { 18 } className = "text-blue-500 animate-spin" />
257
+ ) }
258
+ { toolCall . status === 'completed' && (
259
+ < ServerCog size = { 18 } className = "text-green-600" />
260
+ ) }
261
+ { toolCall . status === 'error' && (
262
+ < AlertCircle size = { 18 } className = "text-red-600" />
263
+ ) }
264
+ < div >
265
+ < div className = "font-medium" >
266
+ { toolCall . status === 'in_progress' && t ( 'tools.executing' ) }
267
+ { toolCall . status === 'completed' && t ( 'tools.executedSuccessfully' ) }
268
+ { toolCall . status === 'error' && t ( 'tools.executionFailed' ) }
269
+ </ div >
270
+ < div className = "text-sm" >
271
+ { t ( 'tools.toolName' ) } : { toolCall . name }
272
+ </ div >
273
+ </ div >
274
+ </ div >
275
+ ) ) }
276
+ </ div >
277
+ ) }
278
+
175
279
{ /* Image Generation In Progress */ }
176
280
{ isProcessingImage && (
177
- < div className = "p-4 mb-3 border border-gray -200 rounded-md" >
281
+ < div className = "p-4 mb-3 border border-blue -200 rounded-md bg-blue-50 " >
178
282
< div className = "flex items-center gap-2 mb-2" >
179
283
< Loader2 size = { 20 } className = "text-blue-500 animate-spin" />
180
284
< span className = "font-medium" > { t ( 'imageGeneration.generating' ) } </ span >
0 commit comments