diff --git a/apps/web/src/app.css b/apps/web/src/app.css index fd8d5180c..3b8e45f45 100644 --- a/apps/web/src/app.css +++ b/apps/web/src/app.css @@ -4052,6 +4052,88 @@ tr.insights-row-clickable:hover { overflow-y: auto; padding: 20px; } +.drawer-sec-head { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} +.drawer-sec-head h3 { + margin: 0; + font-size: 12.5px; + font-weight: 800; + color: var(--ink-2); +} +.drawer-sec-head a { + font-size: 12.5px; + color: var(--ink-3); + text-decoration: none; +} +.drawer-sec-head a:hover { + color: var(--ink); +} +.drawer-list { + display: grid; + gap: 2px; +} +.drawer-li { + display: flex; + align-items: center; + gap: 10px; + min-height: 40px; + padding: 6px 8px; + margin: 0 -8px; + border-radius: var(--r-md); + color: inherit; + text-decoration: none; + transition: background-color 120ms ease; +} +a.drawer-li:hover, +.drawer-li:hover { + background: var(--hover); +} +.drawer-li > svg { + color: var(--ink-3); + flex: 0 0 auto; +} +.drawer-li-t { + flex: 1; + min-width: 0; + color: inherit; + text-decoration: none; +} +.drawer-li-t b, +.drawer-li-t span { + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.drawer-li-t b { + font-size: 13.5px; + font-weight: 600; +} +.drawer-li-t span { + font-size: 12px; + color: var(--ink-3); +} +.drawer-li-m { + font-size: 12px; + color: var(--ink-3); + font-variant-numeric: tabular-nums; + white-space: nowrap; +} +.drawer-file-ic { + width: 32px; + height: 32px; + flex: 0 0 32px; + display: grid; + place-items: center; + border-radius: var(--r-md); + background: var(--surface-sunk); + box-shadow: inset 0 0 0 1px var(--line); + color: var(--ink-3); +} @media (max-width: 860px) { .wb[data-drawer="open"] { grid-template-columns: minmax(0, 1fr) 0px; diff --git a/apps/web/src/bench/artifacts-tab.tsx b/apps/web/src/bench/artifacts-tab.tsx new file mode 100644 index 000000000..e16e29b07 --- /dev/null +++ b/apps/web/src/bench/artifacts-tab.tsx @@ -0,0 +1,47 @@ +import { Skeleton, formatRelativeTime } from "@corbits/react-ui"; + +import { ArtifactListPageSchema, useAPIQuery } from "@/api"; +import { libraryArtifactPath } from "@/library"; +import { FileText } from "@/lib/icons"; +import { Link } from "@/navigation"; +import { ARTIFACTS_PATH_PREFIX } from "@/path-ids"; +import { benchLink } from "../shell/page-crumbs"; + +/** The files saved in this bench, newest first. */ +export function ArtifactsTab({ workbenchTenantId }: { readonly workbenchTenantId: string }) { + const page = useAPIQuery(`/api/tenants/${workbenchTenantId}/artifacts`, ArtifactListPageSchema); + const rows = + page.kind === "ready" + ? [...page.data.artifacts].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)) + : []; + + return ( +
+
+

Files saved here

+ See all +
+ {page.kind === "loading" ? : null} + {page.kind === "error" ?

{page.message}

: null} + {page.kind === "ready" && rows.length === 0 ? ( +

Nothing saved here yet.

+ ) : null} + {rows.length > 0 ? ( +
+ {rows.map((artifact) => ( + + + + + {artifact.title} + {artifact.kind} + + {formatRelativeTime(artifact.updatedAt)} + + ))} +
+ ) : null} +
+ ); +} diff --git a/apps/web/src/bench/workflows-tab.tsx b/apps/web/src/bench/workflows-tab.tsx new file mode 100644 index 000000000..3de717ead --- /dev/null +++ b/apps/web/src/bench/workflows-tab.tsx @@ -0,0 +1,68 @@ +import { RunNowButton, Skeleton } from "@corbits/react-ui"; +import { useQuery } from "@tanstack/react-query"; + +import { routineDetailPath, useRoutineActions } from "@/global-routines"; +import { scheduleSentence } from "@/pages/routines-page"; +import { describeApiError } from "@/lib/api-query"; +import { FlowArrow } from "@/lib/icons"; +import { Link } from "@/navigation"; +import { WORKFLOWS_PATH_PREFIX } from "@/path-ids"; +import { tenantKeys } from "@/query-client"; +import { listScheduledWorkflows } from "@/routines-api"; +import { benchLink } from "../shell/page-crumbs"; + +/** The workflows deployed in this bench with their schedule and state. */ +export function WorkflowsTab({ workbenchTenantId }: { readonly workbenchTenantId: string }) { + const actions = useRoutineActions(); + const flows = useQuery({ + queryKey: tenantKeys.routines(workbenchTenantId), + queryFn: () => listScheduledWorkflows(workbenchTenantId), + }); + const rows = flows.data ?? []; + + return ( +
+
+

Scheduled here

+ See all +
+ {flows.isLoading ? : null} + {flows.isError ? ( +

+ {describeApiError(flows.error, "loading workflows")} +

+ ) : null} + {flows.isSuccess && rows.length === 0 ? ( +

No workflows deployed here yet.

+ ) : null} + {rows.length > 0 ? ( +
+ {rows.map((definition) => ( +
+
+ ))} +
+ ) : null} +
+ ); +} diff --git a/apps/web/src/pages/workbench-page.tsx b/apps/web/src/pages/workbench-page.tsx index dfefdf878..bbc03fabb 100644 --- a/apps/web/src/pages/workbench-page.tsx +++ b/apps/web/src/pages/workbench-page.tsx @@ -26,12 +26,14 @@ import { import { VoiceOverlay } from "../voice/voice-overlay"; import { BenchDrawer } from "../bench/bench-drawer"; import { BenchPill } from "../bench/bench-pill"; +import { ArtifactsTab } from "../bench/artifacts-tab"; import { useWorkerStatus } from "../worker-status"; import { GrantsTab } from "../bench/grants-tab"; import { InformationTab } from "../bench/information-tab"; import { InsightsTab } from "../bench/insights-tab"; import { MembersTab } from "../bench/members-tab"; import { ToolsTab } from "../bench/tools-tab"; +import { WorkflowsTab } from "../bench/workflows-tab"; import { useBench } from "../bench-context"; import { createFetchStockHub } from "../needs-converge"; import { workbenchKeys } from "../chat-path"; @@ -333,6 +335,7 @@ function Workbench({ workbenchTenantId }: { readonly workbenchTenantId: string } participants={participants.data ?? []} /> ), + Artifacts: , Tools: , Grants: , Insights: , @@ -342,6 +345,7 @@ function Workbench({ workbenchTenantId }: { readonly workbenchTenantId: string } participants={participants.data ?? []} /> ), + Workflows: , }} />