feat: enhance dialog components with accessibility improvements and error handling
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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(() => {
|
||||
|
||||
Reference in New Issue
Block a user