@@ -33,131 +33,169 @@ interface GraphSectionProps {
3333}
3434
3535export function GraphSection ( { result } : GraphSectionProps ) {
36- // Check if result is a streamable value (has a value property or internal structure)
37- // We use a heuristic or just try-catch if needed, but useStreamableValue must be called at the top level.
38- // Actually, we can check if it looks like a streamable value.
39- const isStreamable = result && typeof result === 'object' && ( 'value' in result || 'done' in result || ( result as any ) . _isStreamable ) ;
36+ if ( ! result ) return null ;
4037
41- const [ streamData , error , pending ] = useStreamableValue ( isStreamable ? ( result as any ) : undefined )
38+ // Check if result is a static DataAnalysisResult object
39+ // A StreamableValue is an opaque object and shouldn't have these properties
40+ const isStatic = typeof result === 'object' && result !== null &&
41+ ( 'chartType' in ( result as any ) || 'title' in ( result as any ) || 'data' in ( result as any ) ) ;
42+ const isString = typeof result === 'string' ;
4243
43- const data = isStreamable ? streamData : result ;
44+ if ( isStatic || isString ) {
45+ return < GraphCard data = { result as any } /> ;
46+ }
47+
48+ // Handle case where it might be a streamable value or something else
49+ // We use a safe wrapper to avoid crashing if useStreamableValue throws
50+ return < StreamedGraphSection result = { result as any } /> ;
51+ }
4452
45- const chartData : DataAnalysisResult | undefined = typeof data === 'string'
46- ? JSON . parse ( data )
47- : data as DataAnalysisResult
53+ function StreamedGraphSection ( { result } : { result : StreamableValue < any > } ) {
54+ const [ data , error , pending ] = useStreamableValue ( result ) ;
4855
49- if ( pending && ! chartData ) {
56+ if ( pending && ! data ) {
5057 return (
5158 < Section className = "py-2" >
5259 < div className = "animate-pulse flex space-y-4 flex-col" >
5360 < div className = "h-4 bg-muted rounded w-3/4" > </ div >
5461 < div className = "h-64 bg-muted rounded" > </ div >
5562 </ div >
5663 </ Section >
57- )
64+ ) ;
5865 }
5966
60- if ( ! chartData ) return null
67+ return < GraphCard data = { data } /> ;
68+ }
69+
70+ function GraphCard ( { data, pending } : { data : any , pending ?: boolean } ) {
71+ const chartData : DataAnalysisResult | undefined = React . useMemo ( ( ) => {
72+ if ( ! data ) return undefined ;
73+ if ( typeof data === 'string' ) {
74+ try {
75+ return JSON . parse ( data ) ;
76+ } catch ( e ) {
77+ console . error ( 'Error parsing graph data:' , e ) ;
78+ return undefined ;
79+ }
80+ }
81+ return data as DataAnalysisResult ;
82+ } , [ data ] ) ;
83+
84+ if ( ! chartData ) return null ;
6185
62- const { title, description, chartType, data : plotData , config } = chartData
86+ const { title, description, chartType, data : plotData , config } = chartData ;
6387
6488 const renderChart = ( ) => {
89+ if ( ! plotData || ! config ) return < div className = "flex items-center justify-center h-full text-muted-foreground italic" > Missing chart data or configuration</ div > ;
90+
6591 switch ( chartType ) {
6692 case 'bar' :
6793 return (
68- < BarChart data = { plotData } >
69- < CartesianGrid strokeDasharray = "3 3" />
70- < XAxis dataKey = { config . xAxisKey } />
71- < YAxis />
72- < Tooltip />
73- < Legend />
74- { config . series . map ( ( s , i ) => (
75- < Bar key = { s . key } dataKey = { s . key } name = { s . name } fill = { s . color || COLORS [ i % COLORS . length ] } />
76- ) ) }
77- </ BarChart >
78- )
94+ < ResponsiveContainer width = "100%" height = "100%" >
95+ < BarChart data = { plotData } >
96+ < CartesianGrid strokeDasharray = "3 3" />
97+ < XAxis dataKey = { config . xAxisKey } />
98+ < YAxis />
99+ < Tooltip />
100+ < Legend />
101+ { config . series ?. map ( ( s , i ) => (
102+ < Bar key = { s . key } dataKey = { s . key } name = { s . name } fill = { s . color || COLORS [ i % COLORS . length ] } />
103+ ) ) }
104+ </ BarChart >
105+ </ ResponsiveContainer >
106+ ) ;
79107 case 'line' :
80108 return (
81- < LineChart data = { plotData } >
82- < CartesianGrid strokeDasharray = "3 3" />
83- < XAxis dataKey = { config . xAxisKey } />
84- < YAxis />
85- < Tooltip />
86- < Legend />
87- { config . series . map ( ( s , i ) => (
88- < Line key = { s . key } type = "monotone" dataKey = { s . key } name = { s . name } stroke = { s . color || COLORS [ i % COLORS . length ] } />
89- ) ) }
90- </ LineChart >
91- )
109+ < ResponsiveContainer width = "100%" height = "100%" >
110+ < LineChart data = { plotData } >
111+ < CartesianGrid strokeDasharray = "3 3" />
112+ < XAxis dataKey = { config . xAxisKey } />
113+ < YAxis />
114+ < Tooltip />
115+ < Legend />
116+ { config . series ?. map ( ( s , i ) => (
117+ < Line key = { s . key } type = "monotone" dataKey = { s . key } name = { s . name } stroke = { s . color || COLORS [ i % COLORS . length ] } />
118+ ) ) }
119+ </ LineChart >
120+ </ ResponsiveContainer >
121+ ) ;
92122 case 'area' :
93123 return (
94- < AreaChart data = { plotData } >
95- < CartesianGrid strokeDasharray = "3 3" />
96- < XAxis dataKey = { config . xAxisKey } />
97- < YAxis />
98- < Tooltip />
99- < Legend />
100- { config . series . map ( ( s , i ) => (
101- < Area key = { s . key } type = "monotone" dataKey = { s . key } name = { s . name } stroke = { s . color || COLORS [ i % COLORS . length ] } fill = { s . color || COLORS [ i % COLORS . length ] } />
102- ) ) }
103- </ AreaChart >
104- )
124+ < ResponsiveContainer width = "100%" height = "100%" >
125+ < AreaChart data = { plotData } >
126+ < CartesianGrid strokeDasharray = "3 3" />
127+ < XAxis dataKey = { config . xAxisKey } />
128+ < YAxis />
129+ < Tooltip />
130+ < Legend />
131+ { config . series ?. map ( ( s , i ) => (
132+ < Area key = { s . key } type = "monotone" dataKey = { s . key } name = { s . name } stroke = { s . color || COLORS [ i % COLORS . length ] } fill = { s . color || COLORS [ i % COLORS . length ] } />
133+ ) ) }
134+ </ AreaChart >
135+ </ ResponsiveContainer >
136+ ) ;
105137 case 'pie' :
106138 return (
107- < PieChart >
108- < Pie
109- data = { plotData }
110- dataKey = { config . series [ 0 ] . key }
111- nameKey = { config . xAxisKey }
112- cx = "50%"
113- cy = "50%"
114- outerRadius = { 80 }
115- label
116- >
117- { plotData . map ( ( entry , index ) => (
118- < Cell key = { `cell-${ index } ` } fill = { COLORS [ index % COLORS . length ] } />
119- ) ) }
120- </ Pie >
121- < Tooltip />
122- < Legend />
123- </ PieChart >
124- )
139+ < ResponsiveContainer width = "100%" height = "100%" >
140+ < PieChart >
141+ < Pie
142+ data = { plotData }
143+ dataKey = { config . series ?. [ 0 ] ?. key }
144+ nameKey = { config . xAxisKey }
145+ cx = "50%"
146+ cy = "50%"
147+ outerRadius = { 80 }
148+ label
149+ >
150+ { plotData . map ( ( entry , index ) => (
151+ < Cell key = { `cell-${ index } ` } fill = { COLORS [ index % COLORS . length ] } />
152+ ) ) }
153+ </ Pie >
154+ < Tooltip />
155+ < Legend />
156+ </ PieChart >
157+ </ ResponsiveContainer >
158+ ) ;
125159 case 'scatter' :
126160 return (
127- < ScatterChart >
128- < CartesianGrid strokeDasharray = "3 3" />
129- < XAxis type = "number" dataKey = { config . xAxisKey } name = { config . xAxisKey } />
130- < YAxis type = "number" dataKey = { config . yAxisKey } name = { config . yAxisKey } />
131- < Tooltip cursor = { { strokeDasharray : '3 3' } } />
132- < Legend />
133- { config . series . map ( ( s , i ) => (
134- < Scatter key = { s . key } name = { s . name } data = { plotData } fill = { s . color || COLORS [ i % COLORS . length ] } />
135- ) ) }
136- </ ScatterChart >
137- )
161+ < ResponsiveContainer width = "100%" height = "100%" >
162+ < ScatterChart >
163+ < CartesianGrid strokeDasharray = "3 3" />
164+ < XAxis type = "number" dataKey = { config . xAxisKey } name = { config . xAxisKey } />
165+ < YAxis type = "number" dataKey = { config . yAxisKey } name = { config . yAxisKey } />
166+ < Tooltip cursor = { { strokeDasharray : '3 3' } } />
167+ < Legend />
168+ { config . series ?. map ( ( s , i ) => (
169+ < Scatter key = { s . key } name = { s . name } data = { plotData } fill = { s . color || COLORS [ i % COLORS . length ] } />
170+ ) ) }
171+ </ ScatterChart >
172+ </ ResponsiveContainer >
173+ ) ;
138174 default :
139- return < div > Unsupported chart type: { chartType } </ div >
175+ return (
176+ < div className = "flex items-center justify-center h-full text-muted-foreground" >
177+ Unsupported chart type: { chartType || 'None' }
178+ </ div >
179+ ) ;
140180 }
141- }
181+ } ;
142182
143183 return (
144184 < Section className = "py-2" >
145185 < div className = "mb-2" >
146- < ToolBadge tool = "dataAnalysis" > Graph: { title } </ ToolBadge >
186+ < ToolBadge tool = "dataAnalysis" > Graph: { title || 'Untitled' } </ ToolBadge >
147187 </ div >
148188 < Card >
149189 < CardHeader className = "pb-2" >
150- < CardTitle className = "text-lg font-medium" > { title } </ CardTitle >
190+ < CardTitle className = "text-lg font-medium" > { title || 'Data Analysis' } </ CardTitle >
151191 { description && < CardDescription > { description } </ CardDescription > }
152192 </ CardHeader >
153193 < CardContent >
154194 < div className = "h-[300px] w-full" >
155- < ResponsiveContainer width = "100%" height = "100%" >
156- { renderChart ( ) }
157- </ ResponsiveContainer >
195+ { renderChart ( ) }
158196 </ div >
159197 </ CardContent >
160198 </ Card >
161199 </ Section >
162- )
200+ ) ;
163201}
0 commit comments