Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
92 changes: 92 additions & 0 deletions src/components/EvaluatorPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { EvaluatorSummary } from "@aws-sdk/client-bedrock-agentcore-control";
import { useNavigate } from "react-router";
import type { ScreenProps } from "../handlers/types";
import { coreOptsFromCtx } from "../handlers/utils";
import { formatTimestamp } from "./formatTimestamp";
import { PaginatedTablePicker } from "./PaginatedTablePicker";
import type { DataTableColumn } from "./ui/data-table";

// EvaluatorRow is the flat, display-ready shape the table renders. It also
// satisfies DataTable's `T extends Record<string, unknown>` constraint, which the
// SDK's EvaluatorSummary interface does not.
interface EvaluatorRow extends Record<string, unknown> {
evaluatorId: string;
evaluatorName: string;
evaluatorType: string;
level: string;
updatedAt: string;
}

export const evaluatorColumns = [
{ key: "evaluatorName", header: "name", flex: true },
{ key: "evaluatorType", header: "type", width: 12 },
{ key: "level", header: "level", width: 10 },
{
key: "updatedAt",
header: "updated UTC",
width: 16,
render: formatTimestamp,
},
] satisfies DataTableColumn<EvaluatorRow>[];

function toRow(evaluator: EvaluatorSummary): EvaluatorRow {
const id = evaluator.evaluatorId ?? "";
return {
evaluatorId: id,
evaluatorName: evaluator.evaluatorName ?? id,
evaluatorType: evaluator.evaluatorType ?? "-",
level: evaluator.level ?? "-",
updatedAt: evaluator.updatedAt?.toISOString() ?? "-",
};
}

export interface EvaluatorPickerProps extends ScreenProps {
breadcrumb: string[];
description?: string;
onSelect: (evaluatorId: string) => void;
onEscape?: () => void;
}

/**
* Fetches the caller's evaluators and renders them as a navigable table.
*
* The shared body of every "pick an evaluator" screen (list, and — in the write
* TUI — update/delete). Esc returns to the parent menu derived from the
* breadcrumb unless a host supplies its own onEscape.
*/
export function EvaluatorPicker({
ctx,
core,
breadcrumb,
description,
onSelect,
onEscape,
}: EvaluatorPickerProps) {
const opts = coreOptsFromCtx(ctx);
const navigate = useNavigate();
const goBack = onEscape ?? (() => navigate("/" + breadcrumb.slice(0, -1).join("/")));

return (
<PaginatedTablePicker
breadcrumb={breadcrumb}
description={description}
queryKey={["evaluators", opts.region]}
loadPage={async (token, pageSize) => {
const response = await core.eval.listEvaluators(token, pageSize, opts);
return {
items: response.evaluators ?? [],
nextToken: response.nextToken,
};
}}
toRow={toRow}
columns={evaluatorColumns}
getValue={(row) => row.evaluatorId}
onSelect={onSelect}
onBack={goBack}
loadingMessage="Loading evaluators…"
errorMessage={(error) => `Error: ${error.message}`}
emptyMessage="No evaluators found in this Region."
emptyPageMessage="No evaluators on this page."
/>
);
}
93 changes: 93 additions & 0 deletions src/components/OnlineEvalPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { OnlineEvaluationConfigSummary } from "@aws-sdk/client-bedrock-agentcore-control";
import { useNavigate } from "react-router";
import type { ScreenProps } from "../handlers/types";
import { coreOptsFromCtx } from "../handlers/utils";
import { formatTimestamp } from "./formatTimestamp";
import { PaginatedTablePicker } from "./PaginatedTablePicker";
import type { DataTableColumn } from "./ui/data-table";

// OnlineEvalRow is the flat, display-ready shape the table renders. It also
// satisfies DataTable's `T extends Record<string, unknown>` constraint, which the
// SDK's OnlineEvaluationConfigSummary interface does not. The list API returns
// only summary fields (name/status/executionStatus/timestamps); richer detail
// like sampling rate and evaluators comes from GetOnlineEvaluationConfig.
interface OnlineEvalRow extends Record<string, unknown> {
configId: string;
configName: string;
status: string;
executionStatus: string;
updatedAt: string;
}

export const onlineEvalColumns = [
{ key: "configName", header: "name", flex: true },
{ key: "status", header: "status", width: 12 },
{ key: "executionStatus", header: "execution", width: 11 },
{
key: "updatedAt",
header: "updated UTC",
width: 16,
render: formatTimestamp,
},
] satisfies DataTableColumn<OnlineEvalRow>[];

function toRow(config: OnlineEvaluationConfigSummary): OnlineEvalRow {
const id = config.onlineEvaluationConfigId ?? "";
return {
configId: id,
configName: config.onlineEvaluationConfigName ?? id,
status: config.status ?? "-",
executionStatus: config.executionStatus ?? "-",
updatedAt: config.updatedAt?.toISOString() ?? "-",
};
}

export interface OnlineEvalPickerProps extends ScreenProps {
breadcrumb: string[];
description?: string;
onSelect: (configId: string) => void;
onEscape?: () => void;
}

/**
* Fetches the caller's online evaluation configs and renders them as a navigable
* table. The shared body of every "pick a config" screen (list, and — in the
* write TUI — update/pause/resume/delete). Esc returns to the parent menu derived
* from the breadcrumb unless a host supplies its own onEscape.
*/
export function OnlineEvalPicker({
ctx,
core,
breadcrumb,
description,
onSelect,
onEscape,
}: OnlineEvalPickerProps) {
const opts = coreOptsFromCtx(ctx);
const navigate = useNavigate();
const goBack = onEscape ?? (() => navigate("/" + breadcrumb.slice(0, -1).join("/")));

return (
<PaginatedTablePicker
breadcrumb={breadcrumb}
description={description}
queryKey={["online-evals", opts.region]}
loadPage={async (token, pageSize) => {
const response = await core.eval.listOnlineEvaluationConfigs(token, pageSize, opts);
return {
items: response.onlineEvaluationConfigs ?? [],
nextToken: response.nextToken,
};
}}
toRow={toRow}
columns={onlineEvalColumns}
getValue={(row) => row.configId}
onSelect={onSelect}
onBack={goBack}
loadingMessage="Loading online evaluation configs…"
errorMessage={(error) => `Error: ${error.message}`}
emptyMessage="No online evaluation configs found in this Region."
emptyPageMessage="No online evaluation configs on this page."
/>
);
}
55 changes: 55 additions & 0 deletions src/components/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ import { MemoryScreen } from "../handlers/memory/screen.tsx";
import { MemoryGetJsonScreen, MemoryGetScreen } from "../handlers/memory/get/screen.tsx";
import { MemoryListScreen } from "../handlers/memory/list/screen.tsx";
import { RuntimeInvokeScreen } from "../handlers/runtime/invoke/screen.tsx";
import { EvalScreen } from "../handlers/eval/screen.tsx";
import { EvaluatorScreen } from "../handlers/eval/evaluator/screen.tsx";
import { EvaluatorListScreen } from "../handlers/eval/evaluator/list/screen.tsx";
import {
EvaluatorGetScreen,
EvaluatorGetJsonScreen,
} from "../handlers/eval/evaluator/get/screen.tsx";
import { OnlineEvalScreen } from "../handlers/eval/online-eval/screen.tsx";
import { OnlineEvalListScreen } from "../handlers/eval/online-eval/list/screen.tsx";
import {
OnlineEvalGetScreen,
OnlineEvalGetJsonScreen,
} from "../handlers/eval/online-eval/get/screen.tsx";
import { MemoryEventScreen } from "../handlers/memory/event/screen.tsx";
import { MemoryEventGetScreen } from "../handlers/memory/event/get/screen.tsx";
import { MemoryEventListScreen } from "../handlers/memory/event/list/screen.tsx";
Expand Down Expand Up @@ -291,6 +304,48 @@ export function Root({ path, ctx, core, queryClient }: RootProps) {
path="agentcore/runtime/invoke/:runtimeId/:qualifier"
element={<RuntimeInvokeScreen ctx={ctx} core={core} />}
/>
<Route path="agentcore/eval" element={<EvalScreen ctx={ctx} core={core} />} />
<Route
path="agentcore/eval/evaluator"
element={<EvaluatorScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/eval/evaluator/list"
element={<EvaluatorListScreen ctx={ctx} core={core} />}
/>
{/* Bare `get` (no id) has nothing to show — send the user to the list. */}
<Route
path="agentcore/eval/evaluator/get"
element={<Navigate to="/agentcore/eval/evaluator/list" replace />}
/>
<Route
path="agentcore/eval/evaluator/get/:evaluatorId"
element={<EvaluatorGetScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/eval/evaluator/get/:evaluatorId/json"
element={<EvaluatorGetJsonScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/eval/online-eval"
element={<OnlineEvalScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/eval/online-eval/list"
element={<OnlineEvalListScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/eval/online-eval/get"
element={<Navigate to="/agentcore/eval/online-eval/list" replace />}
/>
<Route
path="agentcore/eval/online-eval/get/:configId"
element={<OnlineEvalGetScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/eval/online-eval/get/:configId/json"
element={<OnlineEvalGetJsonScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/memory/event"
element={<MemoryEventScreen ctx={ctx} core={core} />}
Expand Down
14 changes: 11 additions & 3 deletions src/components/RouterScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,29 @@ export interface RouterScreenProps extends ScreenProps {
// segment is the app root; the last is the command whose subcommands are the
// menu options.
path: string[];
// omit hides subcommands from the menu by name. The command tree still carries
// them (they remain usable from the CLI), but they are not offered here — used
// when the TUI intentionally does not route a command yet, so it can't fall
// through to the HelpScreen catch-all (which exits the app).
omit?: string[];
}

// RouterScreen renders the interactive command menu for a Router node: a filter
// input at the top and the node's subcommands (read straight off the Commander
// Command) as navigable options below. Selecting an option routes to that
// subcommand's screen.
export function RouterScreen({ ctx, path }: RouterScreenProps) {
export function RouterScreen({ ctx, path, omit }: RouterScreenProps) {
const navigate = useNavigate();
const { isRawModeSupported } = useStdin();
const { exit } = useApp();

const command = resolveCommand(ctx.require(CommandKey), path);
const options: Option[] = useMemo(
() => command.commands.map((c) => ({ name: c.name(), description: c.description() })),
[command],
() =>
command.commands
.filter((c) => !omit?.includes(c.name()))
.map((c) => ({ name: c.name(), description: c.description() })),
[command, omit],
);

const [query, setQuery] = useState("");
Expand Down
Loading
Loading