feat: enhance dialog components with accessibility improvements and error handling

This commit is contained in:
Xi Zhang
2026-06-03 12:12:32 +01:00
parent 05915cc9c5
commit a42c44c8f0
5 changed files with 77 additions and 58 deletions
+4 -1
View File
@@ -138,7 +138,10 @@ export const FileViewDialog = React.memo<{
open={true}
onOpenChange={onClose}
>
<DialogContent className="flex h-[80vh] max-h-[80vh] min-w-[60vw] flex-col p-6">
<DialogContent
aria-describedby={undefined}
className="flex h-[80vh] max-h-[80vh] min-w-[60vw] flex-col p-6"
>
<DialogTitle className="sr-only">
{file?.path || "New File"}
</DialogTitle>
+4 -1
View File
@@ -72,7 +72,10 @@ export const SkillDetailDialog = React.memo<{
open={true}
onOpenChange={onClose}
>
<DialogContent className="flex h-[80vh] max-h-[80vh] min-w-[60vw] flex-col p-6">
<DialogContent
aria-describedby={undefined}
className="flex h-[80vh] max-h-[80vh] min-w-[60vw] flex-col p-6"
>
<DialogTitle className="sr-only">{title}</DialogTitle>
<div className="mb-3 flex items-start gap-3 border-b border-border pb-4">
<Puzzle
+4 -1
View File
@@ -139,7 +139,10 @@ export const WorkspaceFileDialog = React.memo<{
open={true}
onOpenChange={onClose}
>
<DialogContent className="flex h-[80vh] max-h-[80vh] min-w-[60vw] flex-col p-6">
<DialogContent
aria-describedby={undefined}
className="flex h-[80vh] max-h-[80vh] min-w-[60vw] flex-col p-6"
>
<DialogTitle className="sr-only">{path}</DialogTitle>
<div className="mb-4 flex items-center justify-between border-b border-border pb-4">
<div className="flex min-w-0 items-center gap-2">
+24 -10
View File
@@ -101,7 +101,10 @@ export function useThreads(props: {
try {
if (thread.values && typeof thread.values === "object") {
const values = thread.values as any;
const firstHumanMessage = values.messages.find(
const messages: any[] = Array.isArray(values.messages)
? values.messages
: [];
const firstHumanMessage = messages.find(
(m: any) => m.type === "human"
);
if (firstHumanMessage?.content) {
@@ -111,15 +114,26 @@ export function useThreads(props: {
: firstHumanMessage.content[0]?.text || "";
title = content.slice(0, 50) + (content.length > 50 ? "..." : "");
}
const firstAiMessage = values.messages.find(
(m: any) => m.type === "ai"
);
if (firstAiMessage?.content) {
const content =
typeof firstAiMessage.content === "string"
? firstAiMessage.content
: firstAiMessage.content[0]?.text || "";
description = content.slice(0, 100);
// Preview = the first AI message that actually has text. Agentic
// threads often open with tool-call-only AI messages (empty
// content), so picking the literal first AI message would leave the
// row blank (looking like an "empty" thread). Also join all text
// parts rather than just content[0].
const aiText = (content: any): string => {
if (typeof content === "string") return content;
if (Array.isArray(content))
return content
.map((p: any) => (typeof p?.text === "string" ? p.text : ""))
.join("");
return "";
};
for (const m of messages) {
if (m?.type !== "ai") continue;
const t = aiText(m.content).trim();
if (t) {
description = t.slice(0, 100);
break;
}
}
}
} catch {
+41 -45
View File
@@ -52,58 +52,54 @@ function HomePageInner({
config.assistantId
);
if (isUUID) {
// We should try to fetch the assistant directly with this UUID
try {
const data = await client.assistants.get(config.assistantId);
setAssistant(data);
} catch (error) {
console.error("Failed to fetch assistant:", error);
setAssistant({
assistant_id: config.assistantId,
graph_id: config.assistantId,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
config: {},
metadata: {},
version: 1,
name: "Assistant",
context: {},
});
const resolve = async (): Promise<Assistant> => {
// A UUID addresses one assistant directly; otherwise list the graph's
// assistants and prefer the system default (fall back to the first).
if (isUUID) {
return await client.assistants.get(config.assistantId);
}
} else {
const assistants = await client.assistants.search({
graphId: config.assistantId,
limit: 100,
});
const found =
assistants.find((a) => a.metadata?.["created_by"] === "system") ??
assistants[0];
if (!found) throw new Error("No assistant found for this graph.");
return found;
};
// The langgraph backend may not be ready the instant the page mounts — the
// request then fails with "Failed to fetch". Retry a few times so a transient
// startup race self-heals instead of surfacing a scary console error.
for (let attempt = 0; attempt < 3; attempt += 1) {
try {
// We should try to list out the assistants for this graph, and then use the default one.
// TODO: Paginate this search, but 100 should be enough for graph name
const assistants = await client.assistants.search({
graphId: config.assistantId,
limit: 100,
});
const defaultAssistant = assistants.find(
(assistant) => assistant.metadata?.["created_by"] === "system"
);
if (defaultAssistant === undefined) {
throw new Error("No default assistant found");
}
setAssistant(defaultAssistant);
setAssistant(await resolve());
return;
} catch (error) {
console.error(
"Failed to find default assistant from graph_id: try setting the assistant_id directly:",
if (attempt < 2) {
await new Promise((r) => setTimeout(r, 700));
continue;
}
console.warn(
"Couldn't resolve the EvoScientist assistant; addressing the graph by id instead. Is the backend running?",
error
);
setAssistant({
assistant_id: config.assistantId,
graph_id: config.assistantId,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
config: {},
metadata: {},
version: 1,
name: config.assistantId,
context: {},
});
}
}
// Fallback: address the graph directly by id (works on `langgraph dev`).
setAssistant({
assistant_id: config.assistantId,
graph_id: config.assistantId,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
config: {},
metadata: {},
version: 1,
name: config.assistantId,
context: {},
});
}, [client, config.assistantId]);
useEffect(() => {