fix: hide AI activity panel when monitor unreachable; show access message on form 4xx

This commit is contained in:
Bhanu Prakash Sai Potteri 2026-08-19 17:18:52 +05:30
parent 63ac64fffe
commit 9b1c3fbc32
2 changed files with 42 additions and 14 deletions

View File

@ -318,18 +318,22 @@ export default function DetailViewPanel({ viewId, instanceId, onTitle }: Props)
</Block>
)}
<Block label="AI Activity" icon={Bot}>
{instance.loading ? (
<Skeleton className="h-16 w-full rounded-lg" />
) : (
<AiActivity
decisions={instance.decisions}
escalations={instance.escalations}
traces={instance.traces}
unavailable={instance.aiUnavailable}
/>
)}
</Block>
{/* Hidden entirely when the monitoring service can't be read
app-user tokens have no scope on /monitor/*, so for them this
panel can never load. */}
{!instance.aiUnavailable && (
<Block label="AI Activity" icon={Bot}>
{instance.loading ? (
<Skeleton className="h-16 w-full rounded-lg" />
) : (
<AiActivity
decisions={instance.decisions}
escalations={instance.escalations}
traces={instance.traces}
/>
)}
</Block>
)}
<Block label="History" icon={History}>
{instance.loading ? (

View File

@ -24,6 +24,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import {
ApiError,
getFormScreen,
performActivity,
startWorkflow,
@ -68,11 +69,18 @@ export default function FormModal({ activityId, instanceId, title, onClose, onSu
const [succeeded, setSucceeded] = useState(false);
const [error, setError] = useState<string | null>(null);
const { data, isPending } = useQuery({
const { data, isPending, isError, error: loadError } = useQuery({
queryKey: ["form-screen", activityId, instanceId],
queryFn: () => getFormScreen(activityId, instanceId) as Promise<any>,
// A 4xx (no permission / wrong state) won't change on retry.
retry: (count, err) => (err instanceof ApiError && err.status < 500 ? false : count < 1),
});
const loadErrorMessage =
loadError instanceof ApiError && loadError.status === 403
? "You don't have access to this form."
: "This form couldn't be loaded. Please try again.";
const fields: FormField[] = data?.fields ?? data?.form_json?.fields ?? [];
const gridConfig: GridItem[] = data?.grid_config ?? [];
const formTitle = data?.activity_name || title || "Form";
@ -234,6 +242,14 @@ export default function FormModal({ activityId, instanceId, title, onClose, onSu
</div>
))}
</div>
) : isError ? (
<div
role="alert"
className="flex items-start gap-2 rounded-lg border border-destructive/30 bg-destructive/10 px-3 py-2.5 text-base text-destructive"
>
<AlertCircle className="mt-0.5 size-4 shrink-0" aria-hidden />
<span>{loadErrorMessage}</span>
</div>
) : (
<form id="activity-form" onSubmit={handleSubmit} className="space-y-4">
{rows.map((row, ri) => (
@ -300,9 +316,17 @@ export default function FormModal({ activityId, instanceId, title, onClose, onSu
)}
</div>
{isError && (
<DialogFooter className="mx-0 mb-0 border-t border-border px-5 py-3">
<Button type="button" variant="ghost" onClick={onClose}>
Close
</Button>
</DialogFooter>
)}
{/* mx-0/mb-0 cancel DialogFooter's default negative margins, which
assume the content keeps its own padding this dialog is p-0. */}
{!isPending && (
{!isPending && !isError && (
<DialogFooter className="mx-0 mb-0 items-center border-t border-border px-5 py-3 sm:justify-between">
<p className="text-xs text-muted-foreground">
<span className="text-destructive">*</span> Required