chore: bump version to 0.1.5 and improve accessibility in components

This commit is contained in:
Xi Zhang
2026-06-24 00:11:21 +01:00
parent 80ff57e866
commit 834046c1ff
5 changed files with 104 additions and 43 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@evoscientist/webui",
"version": "0.1.4",
"version": "0.1.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@evoscientist/webui",
"version": "0.1.4",
"version": "0.1.5",
"license": "Apache-2.0",
"dependencies": {
"@langchain/core": "1.1.19",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@evoscientist/webui",
"version": "0.1.4",
"version": "0.1.5",
"description": "Web UI for EvoScientist — a self-evolving AI scientist built on DeepAgents/LangGraph.",
"author": "Xi Zhang <zacharyzhang2022@gmail.com>",
"type": "module",
+7 -2
View File
@@ -62,7 +62,12 @@ export const MarkdownContent = React.memo<MarkdownContentProps>(
children?: React.ReactNode;
}) {
const match = /language-(\w+)/.exec(className || "");
const code = String(children).replace(/\n$/, "");
const raw = String(children);
// Fenced blocks always end with \n; inline code does not. Use
// this to distinguish unlanguaged fenced blocks from inline code
// so detectFileLink is only called for true inline code.
const isBlock = raw.endsWith("\n");
const code = raw.replace(/\n$/, "");
// react-markdown v9 dropped the `inline` prop. Block code carries
// a `language-*` className from the fenced ```lang form; inline
// code does not. Treat absence of a language match as inline.
@@ -85,7 +90,7 @@ export const MarkdownContent = React.memo<MarkdownContentProps>(
// Inline code that names a workspace or memory file becomes a
// click-to-open link. ChatInterface (or any ancestor) listens
// for `evosci:open-file` and routes to the right dialog.
const link = detectFileLink(code);
const link = !isBlock ? detectFileLink(code) : null;
if (link) {
return (
<button
+51 -10
View File
@@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useMemo, useState } from "react";
import { flushSync } from "react-dom";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
@@ -16,6 +16,30 @@ interface ToolApprovalInterruptProps {
onSubmitted?: () => void;
}
function argsToRecord(args: unknown): Record<string, unknown> {
return args && typeof args === "object"
? (args as Record<string, unknown>)
: {};
}
function cloneArgs(args: Record<string, unknown>): Record<string, unknown> {
try {
return JSON.parse(JSON.stringify(args)) as Record<string, unknown>;
} catch {
return { ...args };
}
}
function formatValue(value: unknown): string {
if (typeof value === "string") return value;
if (value === undefined) return "undefined";
try {
return JSON.stringify(value, null, 2) ?? String(value);
} catch {
return String(value);
}
}
export function ToolApprovalInterrupt({
actionRequest,
reviewConfig,
@@ -29,6 +53,10 @@ export function ToolApprovalInterrupt({
const [showRejectionInput, setShowRejectionInput] = useState(false);
const [submitted, setSubmitted] = useState(false);
const actionArgs = useMemo(
() => argsToRecord(actionRequest.args),
[actionRequest.args]
);
const allowedDecisions = reviewConfig?.allowedDecisions ??
reviewConfig?.allowed_decisions ?? ["approve", "reject", "edit"];
@@ -94,7 +122,7 @@ export function ToolApprovalInterrupt({
const startEditing = () => {
setIsEditing(true);
setEditedArgs(JSON.parse(JSON.stringify(actionRequest.args)));
setEditedArgs(cloneArgs(actionArgs));
setShowRejectionInput(false);
};
@@ -126,6 +154,7 @@ export function ToolApprovalInterrupt({
<AlertCircle
size={16}
className="text-yellow-600 dark:text-yellow-400"
aria-hidden="true"
/>
<span className="text-xs font-semibold uppercase tracking-wider">
Approval Required
@@ -156,7 +185,7 @@ export function ToolApprovalInterrupt({
Edit Arguments
</span>
<div className="mt-2 space-y-3">
{Object.entries(actionRequest.args).map(([key, value]) => (
{Object.entries(actionArgs).map(([key, value]) => (
<div key={key}>
<label className="mb-1 block text-xs font-medium text-foreground">
{key}
@@ -166,10 +195,10 @@ export function ToolApprovalInterrupt({
editedArgs[key] !== undefined
? typeof editedArgs[key] === "string"
? (editedArgs[key] as string)
: JSON.stringify(editedArgs[key], null, 2)
: formatValue(editedArgs[key])
: typeof value === "string"
? value
: JSON.stringify(value, null, 2)
: formatValue(value)
}
onChange={(e) => updateEditedArg(key, e.target.value)}
className="font-mono text-xs"
@@ -188,7 +217,7 @@ export function ToolApprovalInterrupt({
Arguments
</span>
<pre className="mt-2 overflow-x-auto whitespace-pre-wrap break-all rounded-sm border border-border bg-muted/40 p-2 font-mono text-xs text-foreground">
{JSON.stringify(actionRequest.args, null, 2)}
{formatValue(actionArgs)}
</pre>
</div>
)}
@@ -231,7 +260,10 @@ export function ToolApprovalInterrupt({
disabled={isLoading}
className="bg-green-600 text-white hover:bg-green-700 dark:bg-green-600 dark:hover:bg-green-700"
>
<Check size={14} />
<Check
size={14}
aria-hidden="true"
/>
{isLoading ? "Saving…" : "Save & Approve"}
</Button>
</>
@@ -270,7 +302,10 @@ export function ToolApprovalInterrupt({
disabled={isLoading}
className="text-destructive hover:bg-destructive/10"
>
<X size={14} />
<X
size={14}
aria-hidden="true"
/>
Reject
</Button>
)}
@@ -282,7 +317,10 @@ export function ToolApprovalInterrupt({
onClick={startEditing}
disabled={isLoading}
>
<Pencil size={14} />
<Pencil
size={14}
aria-hidden="true"
/>
Edit
</Button>
)}
@@ -297,7 +335,10 @@ export function ToolApprovalInterrupt({
"dark:bg-green-600 dark:hover:bg-green-700"
)}
>
<Check size={14} />
<Check
size={14}
aria-hidden="true"
/>
{isLoading ? "Approving…" : "Approve"}
</Button>
)}
+43 -28
View File
@@ -264,7 +264,10 @@ export const WorkspaceFileDialog = React.memo<{
<DialogTitle className="sr-only">{path}</DialogTitle>
<div className="mb-4 flex items-center justify-between gap-3 border-b border-border pb-4">
<div className="flex min-w-0 items-center gap-2">
<FileText className="h-5 w-5 shrink-0 text-[var(--color-text-tertiary)]" />
<FileText
className="h-5 w-5 shrink-0 text-[var(--color-text-tertiary)]"
aria-hidden="true"
/>
<span className="overflow-hidden text-ellipsis whitespace-nowrap text-base font-medium text-primary">
{path}
</span>
@@ -284,6 +287,7 @@ export const WorkspaceFileDialog = React.memo<{
<Eye
size={16}
className="mr-1"
aria-hidden="true"
/>
Cancel
</Button>
@@ -297,11 +301,13 @@ export const WorkspaceFileDialog = React.memo<{
<Loader2
size={16}
className="mr-1 animate-spin"
aria-hidden="true"
/>
) : (
<Save
size={16}
className="mr-1"
aria-hidden="true"
/>
)}
{saving ? "Saving…" : "Save"}
@@ -321,29 +327,29 @@ export const WorkspaceFileDialog = React.memo<{
<Pencil
size={16}
className="mr-1"
aria-hidden="true"
/>
Edit
</Button>
)}
<a
href={workspaceFileUrl(path, true)}
download={name}
<Button
variant="ghost"
size="sm"
className="h-8 px-2"
asChild
>
<Button
variant="ghost"
size="sm"
className="h-8 px-2"
asChild
<a
href={workspaceFileUrl(path, true)}
download={name}
>
<span>
<Download
size={16}
className="mr-1"
/>
Download
</span>
</Button>
</a>
<Download
size={16}
className="mr-1"
aria-hidden="true"
/>
Download
</a>
</Button>
<Button
variant="ghost"
size="sm"
@@ -357,9 +363,13 @@ export const WorkspaceFileDialog = React.memo<{
<Loader2
size={16}
className="animate-spin"
aria-hidden="true"
/>
) : (
<Trash2 size={16} />
<Trash2
size={16}
aria-hidden="true"
/>
)}
</Button>
</>
@@ -409,25 +419,30 @@ export const WorkspaceFileDialog = React.memo<{
? "This file is too large to preview inline."
: "This file type can't be previewed."}
</p>
<a
href={workspaceFileUrl(path, true)}
download={name}
<Button
variant="outline"
size="sm"
asChild
>
<Button
variant="outline"
size="sm"
<a
href={workspaceFileUrl(path, true)}
download={name}
>
<Download
size={16}
className="mr-1"
aria-hidden="true"
/>
Download file
</Button>
</a>
</a>
</Button>
</div>
) : loading ? (
<div className="flex h-full items-center justify-center">
<Loader2 className="size-5 animate-spin text-muted-foreground" />
<Loader2
className="size-5 animate-spin text-muted-foreground"
aria-hidden="true"
/>
</div>
) : error ? (
<div className="flex h-full items-center justify-center p-12">