Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
9ee6ec5
feat(errors): add structured transform error reporting with frontend …
BloggerBust Jun 12, 2025
f9be49f
feat(errors): add structured runtime error reporting and display impr…
BloggerBust Jun 13, 2025
ce9a160
chore(transformer): minor cleanup and import reordering
BloggerBust Jun 13, 2025
59dcc7a
feat(runtime): fallback component rendering with AST reconstruction a…
BloggerBust Jun 20, 2025
1decb80
refactor(runtime): unify fallback rendering and improve component err…
BloggerBust Jun 23, 2025
7c58d00
fix(alert): show runtime error state with tooltip and dimmed icon
BloggerBust Jun 23, 2025
43a2df9
fix(chat): improve error handling and rendering for ChatWidget
BloggerBust Jun 25, 2025
f077fde
feat(checkbox): add error rendering and tooltip to CheckboxWidget
BloggerBust Jun 25, 2025
fd6da0e
feat(generic): add error rendering to GenericWidget
BloggerBust Jun 25, 2025
42aa4b1
feat(image): add error rendering and consistent error styling to Imag…
BloggerBust Jun 25, 2025
1990509
fix(json_viewer): preserve title and render error state when rebuild …
BloggerBust Jun 25, 2025
b5ef3d2
feat(matplotlib): add error state rendering to MatplotlibWidget
BloggerBust Jun 25, 2025
ecf5f40
feat(playground): add error rendering and styling to PlaygroundWidget
BloggerBust Jun 25, 2025
1025034
feat(plotly): add consistent error rendering to DataVisualizationWidget
BloggerBust Jun 25, 2025
a5f2eeb
feat(progress-widget): add error state rendering with tooltip and imp…
BloggerBust Jun 25, 2025
8d2051c
feat(ui): add consistent error state and spacing for SelectboxWidget
BloggerBust Jun 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
import { Route, Routes } from 'react-router-dom';
import { BrowserRouter as Router } from 'react-router-dom';

import { TooltipProvider } from '@/components/ui/tooltip';
import Layout from './components/Layout';
import LoadingState from './components/LoadingState';
import Dashboard from './components/pages/Dashboard';
Expand Down Expand Up @@ -88,8 +89,9 @@ const App = () => {
const currentState = comm.getComponentState(component.id);
return {
...component,
value: currentState !== undefined ? currentState : component.value,
error: null,
value: component.error ? component.value : (
currentState !== undefined ? currentState : component.value
),
};
})
);
Expand Down Expand Up @@ -163,20 +165,22 @@ const App = () => {
};

return (
<Router>
<Layout>
{!isConnected || areComponentsLoading ? (
<LoadingState isConnected={isConnected} />
) : (
<Dashboard
components={components}
error={error}
transformErrors={transformErrors}
handleComponentUpdate={handleComponentUpdate}
/>
)}
</Layout>
</Router>
<TooltipProvider>
<Router>
<Layout>
{!isConnected || areComponentsLoading ? (
<LoadingState isConnected={isConnected} />
) : (
<Dashboard
components={components}
error={error}
transformErrors={transformErrors}
handleComponentUpdate={handleComponentUpdate}
/>
)}
</Layout>
</Router>
</TooltipProvider>
);
};

Expand Down
14 changes: 8 additions & 6 deletions frontend/src/components/DynamicComponents.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,18 +81,22 @@ const MemoizedComponent = memo(
);

case 'button':
// TODO: the backend defines size as a layout property. All components have
// this layout propty, but the semantic difference causes naming collisions
// preventing the ability to override a widgets real size property.
// For now, I am overriding the button's size property to 'default'
return (
<ButtonWidget
key={componentKey}
{...props}
onClick={() => {
if (component.onClick) {
handleUpdate(componentId, true);
}
}}
disabled={component.disabled}
isLoading={component.loading}
variant={component.variant}
id={componentId}
size='default'
>
{component.label}
</ButtonWidget>
Expand Down Expand Up @@ -148,10 +152,9 @@ const MemoizedComponent = memo(
return (
<JSONViewerWidget
key={componentKey}
{...props}
data={component.data || component.value} // fallback if `data` isn't set
title={component.title}
expanded={component.expanded !== false}
className={component.className}
id={componentId}
/>
);
Expand Down Expand Up @@ -258,8 +261,7 @@ const MemoizedComponent = memo(
<MarkdownRendererWidget
key={componentKey}
{...props}
markdown={component.markdown || component.content || component.value || ''}
error={component.error}
markdown={component.markdown || component.value || ''}
variant={component.variant || 'default'}
id={componentId}
/>
Expand Down
33 changes: 25 additions & 8 deletions frontend/src/components/widgets/AlertWidget.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { AlertTriangle, CheckCircle2, Info, XCircle } from 'lucide-react';

import React from 'react';

import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';

import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip';
import { cn } from '@/lib/utils';

const levelConfig = {
Expand All @@ -29,16 +28,34 @@ const levelConfig = {
},
};

const AlertWidget = ({ id, message, level = 'info', className }) => {
const AlertWidget = ({ id, message, level = 'info', className, error }) => {
const config = levelConfig[level] || levelConfig.info;
const Icon = config.icon;

return (
<Alert id={id} variant={config.variant} className={cn('alertwidget-container', className)}>
<Icon className="alertwidget-icon" />
<AlertTitle>{config.title}</AlertTitle>
<AlertDescription>{message}</AlertDescription>
</Alert>
<div id={id} className='relative'>
{error && (
<Tooltip>
<TooltipTrigger asChild>
<div className="absolute top-2 right-2 text-destructive z-10 pointer-events-auto cursor-pointer">
<AlertTriangle className="w-5 h-5" />
</div>
</TooltipTrigger>
<TooltipContent>
<span>{error.toString()}</span>
</TooltipContent>
</Tooltip>
)}

<Alert variant={config.variant}
className={cn('alertwidget-container',
className,
error && 'border-destructive border-2 bg-red-50 rounded-md')}>
<Icon className={cn("alertwidget-icon", error && 'text-gray-300')} />
<AlertTitle>{config.title}</AlertTitle>
<AlertDescription>{message}</AlertDescription>
</Alert>
</div>
);
};

Expand Down
50 changes: 39 additions & 11 deletions frontend/src/components/widgets/BigNumberWidget.jsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,62 @@
import { ArrowDown, ArrowUp } from 'lucide-react';

import { ArrowDown, ArrowUp, AlertTriangle } from 'lucide-react';
import React from 'react';

// Utility to format large numbers
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from '@/components/ui/tooltip';
import { cn } from '@/lib/utils';

const formatNumber = (num) => {
if (Math.abs(num) >= 1e9) return (num / 1e9).toFixed(1) + 'B';
if (Math.abs(num) >= 1e6) return (num / 1e6).toFixed(1) + 'M';
if (Math.abs(num) >= 1e3) return (num / 1e3).toFixed(1) + 'K';
return num;
};

const BigNumberCard = ({ id, label, value, delta, unit }) => {
const BigNumberCard = ({ id, label, value, delta, unit, error, className }) => {
const deltaNumber = parseFloat(delta);
const isPositive = deltaNumber >= 0;

const displayDelta =
typeof delta === 'string' ? delta : `${isPositive ? '+' : ''}${delta}${unit ?? ''}`;

return (
<div id={id} className="bg-white rounded-xl shadow p-4 w-full max-w-xs">
<div
id={id}
className={cn(
'bg-white rounded-xl shadow p-4 w-full max-w-xs relative',
error && 'border-destructive border-2 bg-red-50',
className
)}
>
{error && (
<Tooltip>
<TooltipTrigger asChild>
<div className="absolute top-2 right-2 text-destructive">
<AlertTriangle className="w-5 h-5" />
</div>
</TooltipTrigger>
<TooltipContent>
<span>{error.toString()}</span>
</TooltipContent>
</Tooltip>
)}

<div className="text-sm text-gray-500">{label}</div>
<div className="text-3xl font-bold text-gray-800">
{formatNumber(value)}
{unit ?? ''}
<div className="text-3xl font-bold text-gray-800 break-words leading-tight max-w-full">
{error
? <span className="text-gray-400 italic">unavailable</span>
: Number.isFinite(value)
? `${formatNumber(value)}${unit ?? ''}`
: String(value)}
</div>
{delta !== undefined && (
<div
className={`flex items-center mt-1 text-sm ${
className={cn(
'flex items-center mt-1 text-sm',
isPositive ? 'text-green-600' : 'text-red-600'
}`}
)}
>
{isPositive ? <ArrowUp size={16} /> : <ArrowDown size={16} />}
<span className="ml-1">{displayDelta}</span>
Expand Down
61 changes: 44 additions & 17 deletions frontend/src/components/widgets/ButtonWidget.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react';
import { AlertTriangle } from 'lucide-react';

import { Button } from '@/components/ui/button';

import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip';
import { cn } from '@/lib/utils';

const ButtonWidget = ({
Expand All @@ -13,28 +14,54 @@ const ButtonWidget = ({
variant = 'default',
size = 'default',
className,
error,
...props
}) => {
return (
<Button
id={id}
variant={variant}
size={size}
onClick={onClick}
disabled={disabled || isLoading}
className={cn('font-medium', isLoading && 'opacity-50 cursor-not-allowed', className)}
{...props}
<div
className={cn(
'relative inline-block',
error && 'border-destructive border-2 bg-red-50 p-1 rounded-md'
)}
>
{isLoading ? (
<div className="flex items-center gap-2">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
{children}
</div>
) : (
children
{error && (
<Tooltip>
<TooltipTrigger asChild>
<div className="absolute top-1 right-1 text-destructive">
<AlertTriangle className="w-4 h-4" />
</div>
</TooltipTrigger>
<TooltipContent>
<span>{error.toString()}</span>
</TooltipContent>
</Tooltip>
)}
</Button>

<Button
id={id}
variant={variant}
size={size}
onClick={onClick}
disabled={disabled || isLoading}
className={cn(
'font-medium',
isLoading && 'opacity-50 cursor-not-allowed',
className
)}
{...props}
>
{isLoading ? (
<div className="flex items-center gap-2">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
{children}
</div>
) : (
children
)}
</Button>
</div>
);
};

export default ButtonWidget;

Loading