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
4 changes: 2 additions & 2 deletions src/components/OnlineEvalPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ 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.
// 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;
Expand Down
93 changes: 93 additions & 0 deletions src/components/OnlineInsightPicker.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";

// OnlineInsightRow 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. ListOnlineInsights
// returns the same summary type as the online-eval list; richer detail like
// sampling rate and clustering comes from GetOnlineInsight.
interface OnlineInsightRow extends Record<string, unknown> {
configId: string;
configName: string;
status: string;
executionStatus: string;
updatedAt: string;
}

const onlineInsightColumns = [
{ 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<OnlineInsightRow>[];

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

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

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

return (
<PaginatedTablePicker
breadcrumb={breadcrumb}
description={description}
queryKey={["online-insights", opts.region]}
loadPage={async (token, pageSize) => {
const response = await core.eval.listOnlineInsights(token, pageSize, opts);
return {
items: response.onlineEvaluationConfigs ?? [],
nextToken: response.nextToken,
};
}}
toRow={toRow}
columns={onlineInsightColumns}
getValue={(row) => row.configId}
onSelect={onSelect}
onBack={goBack}
loadingMessage="Loading online insight configs…"
errorMessage={(error) => `Error: ${error.message}`}
emptyMessage="No online insight configs found in this Region."
emptyPageMessage="No online insight configs on this page."
/>
);
}
26 changes: 26 additions & 0 deletions src/components/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ import {
OnlineEvalGetScreen,
OnlineEvalGetJsonScreen,
} from "../handlers/eval/online-eval/get/screen.tsx";
import { OnlineInsightScreen } from "../handlers/eval/online-insight/screen.tsx";
import { OnlineInsightListScreen } from "../handlers/eval/online-insight/list/screen.tsx";
import {
OnlineInsightGetScreen,
OnlineInsightGetJsonScreen,
} from "../handlers/eval/online-insight/get/screen.tsx";
import { BatchEvaluationScreen } from "../handlers/eval/batch-evaluation/screen.tsx";
import { BatchEvaluationListScreen } from "../handlers/eval/batch-evaluation/list/screen.tsx";
import { BatchEvaluationGetJsonScreen } from "../handlers/eval/batch-evaluation/get/screen.tsx";
Expand Down Expand Up @@ -488,6 +494,26 @@ export function Root({ path, ctx, core, queryClient }: RootProps) {
path="agentcore/eval/online-eval/get/:configId/json"
element={<OnlineEvalGetJsonScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/eval/online-insight"
element={<OnlineInsightScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/eval/online-insight/list"
element={<OnlineInsightListScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/eval/online-insight/get"
element={<Navigate to="/agentcore/eval/online-insight/list" replace />}
/>
<Route
path="agentcore/eval/online-insight/get/:configId"
element={<OnlineInsightGetScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/eval/online-insight/get/:configId/json"
element={<OnlineInsightGetJsonScreen ctx={ctx} core={core} />}
/>
<Route path="agentcore/eval/dataset" element={<DatasetScreen ctx={ctx} core={core} />} />
<Route
path="agentcore/eval/dataset/list"
Expand Down
77 changes: 77 additions & 0 deletions src/handlers/eval/online-insight/get/screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useQuery } from "@tanstack/react-query";
import { useNavigate, useParams } from "react-router";
import { JsonDetail } from "../../../../components/JsonDetail";
import { ResourceDetailScreen } from "../../../../components/ResourceDetailScreen";
import type { ScreenProps } from "../../../types";
import { coreOptsFromCtx } from "../../../utils";

function useOnlineInsightDetail({ ctx, core }: ScreenProps, configId: string | undefined) {
const opts = coreOptsFromCtx(ctx);
return useQuery({
queryKey: ["online-insight", opts.region, configId],
queryFn: () => core.eval.getOnlineInsight(configId!, opts),
enabled: configId !== undefined,
});
}

export function OnlineInsightGetScreen(props: ScreenProps) {
const navigate = useNavigate();
const { configId } = useParams();
const detail = useOnlineInsightDetail(props, configId);
const config = detail.data;
const samplingPercentage = config?.rule?.samplingConfig?.samplingPercentage;
const insightCount = config?.insights?.length ?? 0;
const frequencies = config?.clusteringConfig?.frequencies ?? [];

return (
<ResourceDetailScreen
breadcrumb={["agentcore", "eval", "online-insight", "get", configId ?? ""]}
isPending={detail.isPending}
error={detail.isError ? (detail.error as Error) : null}
items={{
id: config?.onlineEvaluationConfigId ?? "",
name: config?.onlineEvaluationConfigName ?? "",
status: config?.status ?? "-",
execution: config?.executionStatus ?? "-",
sampling: samplingPercentage !== undefined ? `${samplingPercentage}%` : "-",
insights: insightCount > 0 ? insightCount.toString() : "-",
clustering: frequencies.length > 0 ? frequencies.join(", ") : "-",
...(config?.failureReason ? { failureReason: config.failureReason } : {}),
role: config?.evaluationExecutionRoleArn ?? "-",
}}
actions={
configId && config
? [
{
name: "detail",
description: "show the full JSON (insights, clustering, filters, data source)",
onSelect: () =>
navigate(
`/agentcore/eval/online-insight/get/${encodeURIComponent(configId)}/json`,
),
},
]
: []
}
loadingLabel="Loading online insight config…"
onRetry={() => void detail.refetch()}
selectLabel="open detail"
/>
);
}

export function OnlineInsightGetJsonScreen(props: ScreenProps) {
const { configId } = useParams();
const detail = useOnlineInsightDetail(props, configId);

return (
<JsonDetail
breadcrumb={["agentcore", "eval", "online-insight", "get", configId ?? "", "json"]}
isPending={detail.isPending}
error={detail.isError ? (detail.error as Error) : null}
data={detail.data}
loadingLabel="Loading online insight config…"
onRetry={() => void detail.refetch()}
/>
);
}
8 changes: 7 additions & 1 deletion src/handlers/eval/online-insight/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Router } from "../../../router";
import { renderTui } from "../../../tui";
import { withTuiOnEmptyFlagsAndArgs } from "../../../middleware";
import type { AppIO } from "../../../io";
import type { Core } from "../../types";
import { createCreateOnlineInsightHandler } from "./create";
Expand All @@ -11,7 +13,9 @@ import { createDeleteOnlineInsightHandler } from "./delete";

export function createOnlineInsightHandler(core: Core, io: AppIO): Router {
return new Router("online-insight", "manage AgentCore online insight configs")
.supportedTuiCommands()
.use(withTuiOnEmptyFlagsAndArgs(core, io))
.default(renderTui(core, io))
.supportedTuiCommands("get", "list")
.handler(createCreateOnlineInsightHandler(core, io))
.handler(createGetOnlineInsightHandler(core))
.handler(createListOnlineInsightHandler(core))
Expand All @@ -20,3 +24,5 @@ export function createOnlineInsightHandler(core: Core, io: AppIO): Router {
.handler(createResumeOnlineInsightHandler(core))
.handler(createDeleteOnlineInsightHandler(core));
}

export { OnlineInsightScreen } from "./screen.tsx";
17 changes: 17 additions & 0 deletions src/handlers/eval/online-insight/list/screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useNavigate } from "react-router";
import { OnlineInsightPicker } from "../../../../components/OnlineInsightPicker";
import type { ScreenProps } from "../../../types";

export function OnlineInsightListScreen(props: ScreenProps) {
const navigate = useNavigate();

return (
<OnlineInsightPicker
{...props}
breadcrumb={["agentcore", "eval", "online-insight", "list"]}
onSelect={(configId) =>
navigate(`/agentcore/eval/online-insight/get/${encodeURIComponent(configId)}`)
}
/>
);
}
Loading
Loading