@manta-eu/horns-client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/app/coverage-run.d.ts +5 -0
- package/dist/app/coverage-run.js +221 -0
- package/dist/app/home-page.d.ts +1 -0
- package/dist/app/home-page.js +11 -0
- package/dist/app/horn-page.d.ts +1 -0
- package/dist/app/horn-page.js +38 -0
- package/dist/app/horn-studio.d.ts +30 -0
- package/dist/app/horn-studio.js +428 -0
- package/dist/app/horn-view-page.d.ts +1 -0
- package/dist/app/horn-view-page.js +96 -0
- package/dist/app/horns-page.d.ts +1 -0
- package/dist/app/horns-page.js +43 -0
- package/dist/app/new-horn-page.d.ts +1 -0
- package/dist/app/new-horn-page.js +34 -0
- package/dist/app/paths.d.ts +13 -0
- package/dist/app/paths.js +32 -0
- package/dist/app/routes.d.ts +12 -0
- package/dist/app/routes.js +32 -0
- package/dist/app/rule-page.d.ts +1 -0
- package/dist/app/rule-page.js +84 -0
- package/dist/app/rules-page.d.ts +1 -0
- package/dist/app/rules-page.js +74 -0
- package/dist/app/saved-horn.d.ts +7 -0
- package/dist/app/saved-horn.js +26 -0
- package/dist/app/session-gate.d.ts +6 -0
- package/dist/app/session-gate.js +19 -0
- package/dist/app/shape-entity-page.d.ts +1 -0
- package/dist/app/shape-entity-page.js +68 -0
- package/dist/app/shape-view-studio.d.ts +14 -0
- package/dist/app/shape-view-studio.js +163 -0
- package/dist/app/shape-zoo-page.d.ts +1 -0
- package/dist/app/shape-zoo-page.js +58 -0
- package/dist/app/shapes-page.d.ts +1 -0
- package/dist/app/shapes-page.js +75 -0
- package/dist/app/showcase-page.d.ts +2 -0
- package/dist/app/showcase-page.js +99 -0
- package/dist/app/variable-bar.d.ts +6 -0
- package/dist/app/variable-bar.js +42 -0
- package/dist/client.d.ts +10 -0
- package/dist/client.js +22 -0
- package/dist/components/app-shell.d.ts +1 -0
- package/dist/components/app-shell.js +18 -0
- package/dist/components/catalog-card-body.d.ts +8 -0
- package/dist/components/catalog-card-body.js +29 -0
- package/dist/components/catalog-card-view.d.ts +2 -0
- package/dist/components/catalog-card-view.js +13 -0
- package/dist/components/chat-column.d.ts +22 -0
- package/dist/components/chat-column.js +20 -0
- package/dist/components/page-header.d.ts +15 -0
- package/dist/components/page-header.js +11 -0
- package/dist/components/plan-runs.d.ts +28 -0
- package/dist/components/plan-runs.js +90 -0
- package/dist/components/rasterize.d.ts +34 -0
- package/dist/components/rasterize.js +115 -0
- package/dist/components/read-the-code.d.ts +14 -0
- package/dist/components/read-the-code.js +43 -0
- package/dist/components/snapshot-view.d.ts +12 -0
- package/dist/components/snapshot-view.js +25 -0
- package/dist/components/states.d.ts +23 -0
- package/dist/components/states.js +22 -0
- package/dist/components/studio-chat.d.ts +47 -0
- package/dist/components/studio-chat.js +63 -0
- package/dist/components/written-horn-view.d.ts +15 -0
- package/dist/components/written-horn-view.js +25 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +16 -0
- package/dist/lib/horn-review.d.ts +27 -0
- package/dist/lib/horn-review.js +59 -0
- package/dist/lib/manta/__tests__/sdk-realm.d.ts +2 -0
- package/dist/lib/manta/__tests__/sdk-realm.js +17 -0
- package/dist/lib/manta/ldo.d.ts +6 -0
- package/dist/lib/manta/ldo.js +14 -0
- package/dist/lib/manta/providers.d.ts +10 -0
- package/dist/lib/manta/providers.js +48 -0
- package/dist/lib/manta/session.d.ts +47 -0
- package/dist/lib/manta/session.js +204 -0
- package/dist/lib/manta/sparql-select.d.ts +2 -0
- package/dist/lib/manta/sparql-select.js +19 -0
- package/dist/lib/plan-runs.d.ts +14 -0
- package/dist/lib/plan-runs.js +26 -0
- package/dist/lib/problem.d.ts +8 -0
- package/dist/lib/problem.js +16 -0
- package/dist/lib/shapes/catalog.d.ts +14 -0
- package/dist/lib/shapes/catalog.js +25 -0
- package/dist/lib/shapes/ldkit-source.d.ts +14 -0
- package/dist/lib/shapes/ldkit-source.js +26 -0
- package/dist/lib/studio-progress.d.ts +7 -0
- package/dist/lib/studio-progress.js +36 -0
- package/dist/lib/telemetry/sentry.d.ts +1 -0
- package/dist/lib/telemetry/sentry.js +17 -0
- package/dist/lib/theme.d.ts +2 -0
- package/dist/lib/theme.js +7 -0
- package/dist/lib/time-ago.d.ts +5 -0
- package/dist/lib/time-ago.js +35 -0
- package/dist/lib/written-surface.d.ts +9 -0
- package/dist/lib/written-surface.js +10 -0
- package/dist/render/horn-client.d.ts +1 -0
- package/dist/render/horn-client.js +10 -0
- package/dist/styles.css +2 -0
- package/package.json +77 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function hornsPath(datastoreId: string): string;
|
|
2
|
+
export declare function newHornPath(datastoreId: string): string;
|
|
3
|
+
export declare function hornPath(datastoreId: string, slug: string): string;
|
|
4
|
+
/** A saved Horn as the thing itself: what a shared link opens, with the variables it carries. */
|
|
5
|
+
export declare function hornViewPath(datastoreId: string, slug: string): string;
|
|
6
|
+
export declare function shapesPath(datastoreId: string): string;
|
|
7
|
+
export declare function shapeZooPath(datastoreId: string, schema: string): string;
|
|
8
|
+
export declare function shapeEntityPath(datastoreId: string, schema: string, entity: string): string;
|
|
9
|
+
/** A datastore's Horns as cards: what each one answers, who it is for, what it reads. */
|
|
10
|
+
export declare const showcasePath: (datastoreId: string) => string;
|
|
11
|
+
/** What Manta works out about a datastore: every rule, and whether it can be looked at. */
|
|
12
|
+
export declare function rulesPath(datastoreId: string): string;
|
|
13
|
+
export declare function rulePath(datastoreId: string, ruleId: string): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function hornsPath(datastoreId) {
|
|
2
|
+
return `/d/${encodeURIComponent(datastoreId)}/horns`;
|
|
3
|
+
}
|
|
4
|
+
export function newHornPath(datastoreId) {
|
|
5
|
+
return `${hornsPath(datastoreId)}/new`;
|
|
6
|
+
}
|
|
7
|
+
export function hornPath(datastoreId, slug) {
|
|
8
|
+
return `${hornsPath(datastoreId)}/${encodeURIComponent(slug)}`;
|
|
9
|
+
}
|
|
10
|
+
/** A saved Horn as the thing itself: what a shared link opens, with the variables it carries. */
|
|
11
|
+
export function hornViewPath(datastoreId, slug) {
|
|
12
|
+
return `${hornPath(datastoreId, slug)}/view`;
|
|
13
|
+
}
|
|
14
|
+
export function shapesPath(datastoreId) {
|
|
15
|
+
return `/d/${encodeURIComponent(datastoreId)}/shapes`;
|
|
16
|
+
}
|
|
17
|
+
export function shapeZooPath(datastoreId, schema) {
|
|
18
|
+
return `${shapesPath(datastoreId)}/${encodeURIComponent(schema)}`;
|
|
19
|
+
}
|
|
20
|
+
// An entity IRI carries slashes and colons, so the entity travels as a query parameter rather than a segment.
|
|
21
|
+
export function shapeEntityPath(datastoreId, schema, entity) {
|
|
22
|
+
return `${shapeZooPath(datastoreId, schema)}/entity?iri=${encodeURIComponent(entity)}`;
|
|
23
|
+
}
|
|
24
|
+
/** A datastore's Horns as cards: what each one answers, who it is for, what it reads. */
|
|
25
|
+
export const showcasePath = (datastoreId) => `/d/${encodeURIComponent(datastoreId)}/showcase`;
|
|
26
|
+
/** What Manta works out about a datastore: every rule, and whether it can be looked at. */
|
|
27
|
+
export function rulesPath(datastoreId) {
|
|
28
|
+
return `/d/${encodeURIComponent(datastoreId)}/rules`;
|
|
29
|
+
}
|
|
30
|
+
export function rulePath(datastoreId, ruleId) {
|
|
31
|
+
return `${rulesPath(datastoreId)}/${encodeURIComponent(ruleId)}`;
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Horns' routes as `<Route>` children, for a host that splices them into its own `<Routes>`. A nested
|
|
3
|
+
* `<Routes>` would rebase every path on its parent's match, so a host mounting Horns under a prefix would
|
|
4
|
+
* push `d/:datastoreId/...` a segment too deep; spliced in, the paths are the ones `paths.ts` writes.
|
|
5
|
+
*
|
|
6
|
+
* `catchAll` is for the host that gives Horns the whole URL space, as `mountHorns` does. A host with routes
|
|
7
|
+
* of its own leaves it off and keeps its own 404.
|
|
8
|
+
*/
|
|
9
|
+
export declare function hornsRoutes({ catchAll }?: {
|
|
10
|
+
catchAll?: boolean;
|
|
11
|
+
}): import("react").JSX.Element;
|
|
12
|
+
export declare function AppRoutes(): import("react").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Navigate, Outlet, Route, Routes } from "react-router";
|
|
3
|
+
import { AppShell } from "../components/app-shell";
|
|
4
|
+
import { HomePage } from "./home-page";
|
|
5
|
+
import { HornPage } from "./horn-page";
|
|
6
|
+
import { HornsPage } from "./horns-page";
|
|
7
|
+
import { HornViewPage } from "./horn-view-page";
|
|
8
|
+
import { NewHornPage } from "./new-horn-page";
|
|
9
|
+
import { SessionGate } from "./session-gate";
|
|
10
|
+
import { RulePage } from "./rule-page";
|
|
11
|
+
import { RulesPage } from "./rules-page";
|
|
12
|
+
import { ShapeEntityPage } from "./shape-entity-page";
|
|
13
|
+
import { ShapeZooPage } from "./shape-zoo-page";
|
|
14
|
+
import { ShapesPage } from "./shapes-page";
|
|
15
|
+
import { ShowcasePage } from "./showcase-page";
|
|
16
|
+
function GatedOutlet() {
|
|
17
|
+
return (_jsx(SessionGate, { children: _jsx(Outlet, {}) }));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Horns' routes as `<Route>` children, for a host that splices them into its own `<Routes>`. A nested
|
|
21
|
+
* `<Routes>` would rebase every path on its parent's match, so a host mounting Horns under a prefix would
|
|
22
|
+
* push `d/:datastoreId/...` a segment too deep; spliced in, the paths are the ones `paths.ts` writes.
|
|
23
|
+
*
|
|
24
|
+
* `catchAll` is for the host that gives Horns the whole URL space, as `mountHorns` does. A host with routes
|
|
25
|
+
* of its own leaves it off and keeps its own 404.
|
|
26
|
+
*/
|
|
27
|
+
export function hornsRoutes({ catchAll = false } = {}) {
|
|
28
|
+
return (_jsxs(Route, { element: _jsx(GatedOutlet, {}), children: [_jsx(Route, { path: "d/:datastoreId/horns/new", element: _jsx(NewHornPage, {}) }), _jsx(Route, { path: "d/:datastoreId/horns/:slug", element: _jsx(HornPage, {}) }), _jsxs(Route, { element: _jsx(AppShell, {}), children: [_jsx(Route, { path: "d/:datastoreId/horns", element: _jsx(HornsPage, {}) }), _jsx(Route, { path: "d/:datastoreId/horns/:slug/view", element: _jsx(HornViewPage, {}) }), _jsx(Route, { path: "d/:datastoreId/showcase", element: _jsx(ShowcasePage, {}) }), _jsx(Route, { path: "d/:datastoreId/rules", element: _jsx(RulesPage, {}) }), _jsx(Route, { path: "d/:datastoreId/rules/:rule", element: _jsx(RulePage, {}) }), _jsx(Route, { path: "d/:datastoreId/shapes", element: _jsx(ShapesPage, {}) }), _jsx(Route, { path: "d/:datastoreId/shapes/:schema", element: _jsx(ShapeZooPage, {}) }), _jsx(Route, { path: "d/:datastoreId/shapes/:schema/entity", element: _jsx(ShapeEntityPage, {}) }), catchAll ? _jsx(Route, { index: true, element: _jsx(HomePage, {}) }) : null] }), catchAll ? _jsx(Route, { path: "*", element: _jsx(Navigate, { to: "/", replace: true }) }) : null] }));
|
|
29
|
+
}
|
|
30
|
+
export function AppRoutes() {
|
|
31
|
+
return _jsx(Routes, { children: hornsRoutes({ catchAll: true }) });
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function RulePage(): import("react").JSX.Element;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useQuery } from "@tanstack/react-query";
|
|
3
|
+
import { isPresent } from "@manta-eu/lib/present";
|
|
4
|
+
import { useParams } from "react-router";
|
|
5
|
+
import { Badge, Skeleton } from "@manta-eu/react";
|
|
6
|
+
import { buildHornSnapshot, maxSnapshotRows } from "@manta-eu/horns/render";
|
|
7
|
+
import { standaloneHorn } from "@manta-eu/horns/shapes";
|
|
8
|
+
import { rulesQuery, readRules } from "@manta-eu/horns/shapes";
|
|
9
|
+
import { ruleCoverage, ruleHornView, ruleViewId } from "@manta-eu/horns/rules";
|
|
10
|
+
import { Card, CardTitle, PageHeader } from "../components/page-header";
|
|
11
|
+
import { DatastoreMissing, EmptyState, ErrorState } from "../components/states";
|
|
12
|
+
import { MantaSparqlProvider } from "../lib/manta/providers";
|
|
13
|
+
import { useMantaSession } from "../lib/manta/session";
|
|
14
|
+
import { browserSparqlSelect } from "../lib/manta/sparql-select";
|
|
15
|
+
import { shapesRealm } from "../lib/shapes/catalog";
|
|
16
|
+
import { SnapshotViewBody } from "../components/snapshot-view";
|
|
17
|
+
// One rule, drawn. The view is built from the rule's own tree, so what is on screen is what the rule does —
|
|
18
|
+
// and the Datalog sits under it, because a person judging the view has to be able to check it against the
|
|
19
|
+
// rule rather than trust the parser.
|
|
20
|
+
const ruleRowLimit = 60;
|
|
21
|
+
function Drawn({ entry, datastoreId }) {
|
|
22
|
+
const realm = shapesRealm();
|
|
23
|
+
const spec = entry.spec;
|
|
24
|
+
const snapshot = useQuery({
|
|
25
|
+
enabled: spec !== undefined,
|
|
26
|
+
queryKey: ["rule-view", datastoreId, entry.rule.iri],
|
|
27
|
+
queryFn: async ({ signal }) => {
|
|
28
|
+
if (!spec)
|
|
29
|
+
throw new Error("This rule describes no view.");
|
|
30
|
+
const view = ruleHornView(spec);
|
|
31
|
+
return buildHornSnapshot({
|
|
32
|
+
horn: standaloneHorn({
|
|
33
|
+
id: view.id,
|
|
34
|
+
title: view.title,
|
|
35
|
+
views: [view],
|
|
36
|
+
realm,
|
|
37
|
+
datastoreId,
|
|
38
|
+
}),
|
|
39
|
+
select: browserSparqlSelect(datastoreId),
|
|
40
|
+
plans: false,
|
|
41
|
+
rowLimit: Math.min(ruleRowLimit, maxSnapshotRows),
|
|
42
|
+
signal,
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
if (!spec) {
|
|
47
|
+
return (_jsx(EmptyState, { title: "This rule has no view.", hint: entry.issues?.[0] ?? "Its body does not translate to a query." }));
|
|
48
|
+
}
|
|
49
|
+
if (snapshot.isPending)
|
|
50
|
+
return _jsx(Skeleton, { className: "h-64 w-full" });
|
|
51
|
+
if (snapshot.isError)
|
|
52
|
+
return _jsx(ErrorState, { title: "The rule could not be drawn.", hint: snapshot.error.message });
|
|
53
|
+
const view = snapshot.data.views[0];
|
|
54
|
+
if (!view)
|
|
55
|
+
return _jsx(EmptyState, { title: "The rule drew nothing." });
|
|
56
|
+
return _jsx(SnapshotViewBody, { snapshot: snapshot.data, view: view });
|
|
57
|
+
}
|
|
58
|
+
function Rule({ datastoreId, ruleId }) {
|
|
59
|
+
const realm = shapesRealm();
|
|
60
|
+
const rules = useQuery({
|
|
61
|
+
queryKey: ["rules", datastoreId],
|
|
62
|
+
queryFn: async ({ signal }) => readRules(await browserSparqlSelect(datastoreId)(rulesQuery(realm, [], 200), signal)),
|
|
63
|
+
});
|
|
64
|
+
if (rules.isPending)
|
|
65
|
+
return _jsx(Skeleton, { className: "h-64 w-full" });
|
|
66
|
+
if (rules.isError)
|
|
67
|
+
return _jsx(ErrorState, { title: "The rules could not be read.", hint: rules.error.message });
|
|
68
|
+
const entry = ruleCoverage({ rules: rules.data, realm }).entries.find((candidate) => ruleViewId(candidate.rule.iri) === ruleId);
|
|
69
|
+
if (!entry)
|
|
70
|
+
return _jsx(EmptyState, { title: "This datastore runs no rule by that name." });
|
|
71
|
+
return (_jsxs("div", { className: "flex flex-col gap-6", children: [_jsx(PageHeader, { title: entry.spec?.title ?? ruleId, description: entry.spec === undefined
|
|
72
|
+
? "Read, but it describes no view."
|
|
73
|
+
: `What this rule works out, over the rows its own body selects.`, ...(entry.spec?.kind === "plan" ? { meta: _jsx(Badge, { variant: "info", children: "Drafts work" }) } : {}) }), _jsx(Drawn, { entry: entry, datastoreId: datastoreId }), _jsxs(Card, { children: [_jsx(CardTitle, { children: "The rule itself" }), _jsx("pre", { className: "text-muted-foreground overflow-auto px-4 pb-4 font-mono text-xs whitespace-pre-wrap", children: entry.rule.body })] })] }));
|
|
74
|
+
}
|
|
75
|
+
export function RulePage() {
|
|
76
|
+
const { datastoreId, rule } = useParams();
|
|
77
|
+
const { datastores } = useMantaSession();
|
|
78
|
+
if (!isPresent(datastoreId) || !datastores.some((one) => one.id === datastoreId)) {
|
|
79
|
+
return _jsx(DatastoreMissing, {});
|
|
80
|
+
}
|
|
81
|
+
if (!isPresent(rule))
|
|
82
|
+
return _jsx(EmptyState, { title: "No rule was named." });
|
|
83
|
+
return (_jsx(MantaSparqlProvider, { datastoreId: datastoreId, children: _jsx(Rule, { datastoreId: datastoreId, ruleId: rule }) }));
|
|
84
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function RulesPage(): import("react").JSX.Element;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useQuery } from "@tanstack/react-query";
|
|
3
|
+
import { isPresent } from "@manta-eu/lib/present";
|
|
4
|
+
import { Link, useParams } from "react-router";
|
|
5
|
+
import { ChevronRight } from "lucide-react";
|
|
6
|
+
import { Badge, Progress, Skeleton } from "@manta-eu/react";
|
|
7
|
+
import { rulesQuery, readRules } from "@manta-eu/horns/shapes";
|
|
8
|
+
import { ruleCoverage, ruleViewId } from "@manta-eu/horns/rules";
|
|
9
|
+
import { rulePath } from "./paths";
|
|
10
|
+
import { Card, CardTitle, PageHeader } from "../components/page-header";
|
|
11
|
+
import { DatastoreMissing, ErrorState } from "../components/states";
|
|
12
|
+
import { MantaSparqlProvider } from "../lib/manta/providers";
|
|
13
|
+
import { useMantaSession } from "../lib/manta/session";
|
|
14
|
+
import { browserSparqlSelect } from "../lib/manta/sparql-select";
|
|
15
|
+
import { shapesRealm } from "../lib/shapes/catalog";
|
|
16
|
+
// The other half of what a datastore knows. A shape says what a thing is and the shapes page covers those;
|
|
17
|
+
// a rule says what Manta worked out about it, and until this page nothing drew one.
|
|
18
|
+
function percentOf(part, whole) {
|
|
19
|
+
return whole === 0 ? 0 : Math.round((part / whole) * 1000) / 10;
|
|
20
|
+
}
|
|
21
|
+
function StatusBadge({ entry }) {
|
|
22
|
+
if (entry.status === "covered") {
|
|
23
|
+
return entry.spec?.kind === "plan" ? _jsx(Badge, { variant: "info", children: "Drafts work" }) : _jsx(Badge, { variant: "success", children: "View" });
|
|
24
|
+
}
|
|
25
|
+
// Two different gaps: one the parser could close, one Datalog itself will not.
|
|
26
|
+
if (entry.status === "unparsed")
|
|
27
|
+
return _jsx(Badge, { variant: "warning", children: "Not read yet" });
|
|
28
|
+
return _jsx(Badge, { variant: "secondary", children: "No query" });
|
|
29
|
+
}
|
|
30
|
+
function RuleRow({ entry, datastoreId }) {
|
|
31
|
+
const spec = entry.spec;
|
|
32
|
+
const derived = spec?.derives === undefined
|
|
33
|
+
? undefined
|
|
34
|
+
: spec.derives.kind === "column"
|
|
35
|
+
? `works out ${spec.derives.column.name}`
|
|
36
|
+
: `marks them ${spec.derives.value}`;
|
|
37
|
+
const said = spec
|
|
38
|
+
? [
|
|
39
|
+
spec.columns.map((column) => column.name).join(", "),
|
|
40
|
+
derived,
|
|
41
|
+
isPresent(spec.conditions) ? `when ${spec.conditions.join(" and ")}` : undefined,
|
|
42
|
+
]
|
|
43
|
+
.filter((part) => part !== undefined)
|
|
44
|
+
.join(" · ")
|
|
45
|
+
: (entry.issues?.[0] ?? "No view.");
|
|
46
|
+
return (_jsxs("li", { "data-rule": entry.rule.iri, "data-status": entry.status, className: "flex flex-wrap items-center justify-between gap-3 px-4 py-2.5", children: [_jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "text-default-foreground truncate text-sm font-medium", children: spec?.title ?? entry.rule.iri.slice(entry.rule.iri.lastIndexOf("#") + 1) }), _jsx("span", { className: "text-muted-foreground truncate text-xs", children: said })] }), _jsxs("span", { className: "flex items-center gap-3", children: [_jsx(StatusBadge, { entry: entry }), spec ? (_jsxs(Link, { className: "text-muted-foreground hover:text-default-foreground flex items-center gap-1 text-sm", to: rulePath(datastoreId, ruleViewId(entry.rule.iri)), children: ["Look at it", _jsx(ChevronRight, { size: 14, "aria-hidden": "true" })] })) : null] })] }));
|
|
47
|
+
}
|
|
48
|
+
function Rules({ datastoreId }) {
|
|
49
|
+
const realm = shapesRealm();
|
|
50
|
+
const rules = useQuery({
|
|
51
|
+
queryKey: ["rules", datastoreId],
|
|
52
|
+
queryFn: async ({ signal }) => {
|
|
53
|
+
const select = browserSparqlSelect(datastoreId);
|
|
54
|
+
// Every rule, not a search: this page is the coverage, so it cannot filter by a word.
|
|
55
|
+
return readRules(await select(rulesQuery(realm, [], allRules), signal));
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
if (rules.isPending)
|
|
59
|
+
return _jsx(Skeleton, { className: "h-64 w-full" });
|
|
60
|
+
if (rules.isError)
|
|
61
|
+
return _jsx(ErrorState, { title: "The rules could not be read.", hint: rules.error.message });
|
|
62
|
+
const coverage = ruleCoverage({ rules: rules.data, realm });
|
|
63
|
+
return (_jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs(Card, { children: [_jsx(CardTitle, { children: "Covered" }), _jsxs("div", { className: "flex items-center gap-3 px-4 pb-4", children: [_jsx(Progress, { value: percentOf(coverage.covered, coverage.total), className: "grow" }), _jsx("span", { className: "text-muted-foreground text-sm tabular-nums", children: `${coverage.covered} of ${coverage.total}` })] }), _jsx("p", { className: "text-muted-foreground px-4 pb-4 text-sm", children: `${coverage.plans} of them draft work rather than working a value out.` })] }), _jsxs(Card, { children: [_jsx(CardTitle, { children: "Every rule this datastore runs" }), _jsx("ul", { className: "divide-default-border border-default-border divide-y border-t", children: coverage.entries.map((entry) => (_jsx(RuleRow, { entry: entry, datastoreId: datastoreId }, `${entry.rule.iri}-${entry.rule.body.length}`))) })] })] }));
|
|
64
|
+
}
|
|
65
|
+
/** Enough for every rule a realm has; the query caps itself well below what a datastore holds. */
|
|
66
|
+
const allRules = 200;
|
|
67
|
+
export function RulesPage() {
|
|
68
|
+
const { datastoreId } = useParams();
|
|
69
|
+
const { datastores } = useMantaSession();
|
|
70
|
+
if (!isPresent(datastoreId) || !datastores.some((one) => one.id === datastoreId)) {
|
|
71
|
+
return _jsx(DatastoreMissing, {});
|
|
72
|
+
}
|
|
73
|
+
return (_jsxs("div", { className: "flex flex-col gap-6", children: [_jsx(PageHeader, { title: "Rules", description: "What Manta works out about this datastore, and whether it can be looked at." }), _jsx(MantaSparqlProvider, { datastoreId: datastoreId, children: _jsx(Rules, { datastoreId: datastoreId }) })] }));
|
|
74
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type UseQueryResult } from "@tanstack/react-query";
|
|
2
|
+
import { type Horn } from "@manta-eu/horns";
|
|
3
|
+
export type SavedHorn = {
|
|
4
|
+
horn: Horn;
|
|
5
|
+
revision: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function useSavedHorn(datastoreId: string, slug: string): UseQueryResult<SavedHorn>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
import { isPresent } from "@manta-eu/lib/present";
|
|
3
|
+
import { hornSchema } from "@manta-eu/horns";
|
|
4
|
+
import { isRecord } from "@manta-eu/horns";
|
|
5
|
+
export function useSavedHorn(datastoreId, slug) {
|
|
6
|
+
return useQuery({
|
|
7
|
+
queryKey: ["horn", datastoreId, slug],
|
|
8
|
+
enabled: isPresent(slug),
|
|
9
|
+
// The chat turns a loaded Horn into a draft; refetching underneath it would swap the draft for a Horn the
|
|
10
|
+
// user has moved past.
|
|
11
|
+
staleTime: Infinity,
|
|
12
|
+
queryFn: async ({ signal }) => {
|
|
13
|
+
const response = await fetch(`/api/d/${encodeURIComponent(datastoreId)}/horns/${encodeURIComponent(slug)}`, {
|
|
14
|
+
signal,
|
|
15
|
+
});
|
|
16
|
+
if (!response.ok)
|
|
17
|
+
throw new Error(`That Horn could not be read (${response.status}).`);
|
|
18
|
+
const body = await response.json();
|
|
19
|
+
const record = isRecord(body) ? body : {};
|
|
20
|
+
return {
|
|
21
|
+
horn: hornSchema.parse(record["horn"]),
|
|
22
|
+
revision: typeof record["revision"] === "string" ? record["revision"] : "",
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useLocation } from "react-router";
|
|
3
|
+
import { match } from "ts-pattern";
|
|
4
|
+
import { sessionGate, signInHref, useMantaSession } from "../lib/manta/session";
|
|
5
|
+
function Centered({ children }) {
|
|
6
|
+
return _jsx("main", { className: "grid min-h-screen place-items-center p-8 text-center", children: children });
|
|
7
|
+
}
|
|
8
|
+
export function SessionGate({ children }) {
|
|
9
|
+
const session = useMantaSession();
|
|
10
|
+
const location = useLocation();
|
|
11
|
+
const signIn = signInHref(`${location.pathname}${location.search}`);
|
|
12
|
+
return match(sessionGate(session))
|
|
13
|
+
.with("loading", () => (_jsx(Centered, { children: _jsx("p", { className: "text-sm opacity-70", children: "Loading\u2026" }) })))
|
|
14
|
+
.with("sign-in", () => (_jsx(Centered, { children: _jsxs("div", { className: "flex flex-col items-center gap-4", children: [_jsx("h1", { className: "text-xl font-semibold", children: "Sign in to Manta Horns" }), _jsx("a", { className: "rounded-md border px-4 py-2", href: signIn, children: "Sign in" })] }) })))
|
|
15
|
+
.with("looped", () => (_jsx(Centered, { children: _jsxs("div", { className: "flex flex-col items-center gap-4", children: [_jsx("p", { role: "alert", children: "Signing in did not complete. Try again, or ask an admin to check your access." }), _jsx("a", { className: "rounded-md border px-4 py-2", href: signIn, children: "Sign in" })] }) })))
|
|
16
|
+
.with("blocked", () => (_jsx(Centered, { children: _jsxs("div", { className: "flex flex-col items-center gap-4", children: [_jsx("p", { role: "alert", children: session.authError ?? session.error ?? "Manta Horns is unavailable." }), _jsx("a", { className: "rounded-md border px-4 py-2", href: signIn, children: "Try again" })] }) })))
|
|
17
|
+
.with("ready", () => children)
|
|
18
|
+
.exhaustive();
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ShapeEntityPage(): import("react").JSX.Element;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useQuery } from "@tanstack/react-query";
|
|
3
|
+
import { isPresent } from "@manta-eu/lib/present";
|
|
4
|
+
import { Link, useParams, useSearchParams } from "react-router";
|
|
5
|
+
import { shapeViewSpecs } from "@manta-eu/horns-kit";
|
|
6
|
+
import { buildShapeDetail, shapeLinks } from "@manta-eu/horns/shapes";
|
|
7
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@manta-eu/react";
|
|
8
|
+
import { shapeZooPath } from "./paths";
|
|
9
|
+
import { SnapshotViewBody } from "../components/snapshot-view";
|
|
10
|
+
import { PageHeader } from "../components/page-header";
|
|
11
|
+
import { DatastoreMissing, EmptyState, ErrorState, Loading } from "../components/states";
|
|
12
|
+
import { MantaSparqlProvider } from "../lib/manta/providers";
|
|
13
|
+
import { useMantaSession } from "../lib/manta/session";
|
|
14
|
+
import { browserSparqlSelect } from "../lib/manta/sparql-select";
|
|
15
|
+
import { ldkitShapeCatalog, shapesRealm } from "../lib/shapes/catalog";
|
|
16
|
+
const specOf = (schema) => shapeViewSpecs.find((spec) => spec.schema === schema);
|
|
17
|
+
function Section({ page }) {
|
|
18
|
+
const view = page.snapshot.views[0];
|
|
19
|
+
if (!view)
|
|
20
|
+
return null;
|
|
21
|
+
return _jsx(SnapshotViewBody, { snapshot: page.snapshot, view: { ...view, title: page.section.title } });
|
|
22
|
+
}
|
|
23
|
+
function Detail({ datastoreId, schema, entity }) {
|
|
24
|
+
const realm = shapesRealm();
|
|
25
|
+
const shape = ldkitShapeCatalog().shapes.find((candidate) => candidate.schema === schema);
|
|
26
|
+
const spec = specOf(schema);
|
|
27
|
+
const detail = useQuery({
|
|
28
|
+
queryKey: ["shape-entity", datastoreId, schema, entity],
|
|
29
|
+
enabled: isPresent(shape) && isPresent(spec),
|
|
30
|
+
queryFn: async ({ signal }) => {
|
|
31
|
+
if (!shape || !spec)
|
|
32
|
+
throw new Error("The shape has no view to open.");
|
|
33
|
+
const select = browserSparqlSelect(datastoreId);
|
|
34
|
+
const links = await shapeLinks({ shape, shapes: ldkitShapeCatalog().shapes, realm, select, signal });
|
|
35
|
+
return buildShapeDetail({ spec, links, specOf, realm, datastoreId, entity, select, signal });
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
if (!isPresent(spec)) {
|
|
39
|
+
return _jsx(EmptyState, { title: `${schema} has no HornView yet.`, hint: "There is no page to open for its instances." });
|
|
40
|
+
}
|
|
41
|
+
if (detail.isPending)
|
|
42
|
+
return _jsx(Loading, { label: "Reading this instance\u2026", rows: 3 });
|
|
43
|
+
if (detail.isError)
|
|
44
|
+
return _jsx(ErrorState, { title: "This instance could not be read.", hint: detail.error.message });
|
|
45
|
+
if (!isPresent(detail.data)) {
|
|
46
|
+
return _jsx(EmptyState, { title: "This instance is not in the datastore." });
|
|
47
|
+
}
|
|
48
|
+
const pages = detail.data;
|
|
49
|
+
const first = pages[0];
|
|
50
|
+
if (!first)
|
|
51
|
+
return null;
|
|
52
|
+
// A lens for an entity the datastore does not hold returns no rows, and an empty card is not an answer.
|
|
53
|
+
const card = first.snapshot.views[0]?.content;
|
|
54
|
+
if (card?.lane === "sandbox" && !isPresent(card.rows)) {
|
|
55
|
+
return _jsx(EmptyState, { title: "This instance is not in the datastore." });
|
|
56
|
+
}
|
|
57
|
+
return (_jsxs(Tabs, { defaultValue: first.section.id, children: [_jsx(TabsList, { className: "flex flex-wrap", children: pages.map((page) => (_jsx(TabsTrigger, { value: page.section.id, children: page.section.title }, page.section.id))) }), pages.map((page) => (_jsx(TabsContent, { value: page.section.id, "data-detail-section": page.section.id, children: _jsx(Section, { page: page }) }, page.section.id)))] }));
|
|
58
|
+
}
|
|
59
|
+
export function ShapeEntityPage() {
|
|
60
|
+
const { datastoreId, schema } = useParams();
|
|
61
|
+
const [params] = useSearchParams();
|
|
62
|
+
const entity = params.get("iri");
|
|
63
|
+
const { datastores } = useMantaSession();
|
|
64
|
+
const datastore = datastores.find((candidate) => candidate.id === datastoreId);
|
|
65
|
+
if (!isPresent(datastore) || !isPresent(schema) || !isPresent(entity))
|
|
66
|
+
return _jsx(DatastoreMissing, {});
|
|
67
|
+
return (_jsx(MantaSparqlProvider, { datastoreId: datastore.id, children: _jsxs("div", { className: "flex w-full flex-col gap-6", children: [_jsx(PageHeader, { title: schema.replace(/Schema$/, ""), description: entity, actions: _jsx(Link, { className: "text-sm text-muted-foreground hover:text-default-foreground", to: shapeZooPath(datastore.id, schema), children: "Back to zoo" }) }), _jsx(Detail, { datastoreId: datastore.id, schema: schema, entity: entity })] }) }));
|
|
68
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ShapeViewSpec } from "@manta-eu/horns/shapes";
|
|
2
|
+
type ShapeViewStudioProps = {
|
|
3
|
+
datastoreId: string;
|
|
4
|
+
stored: ShapeViewSpec;
|
|
5
|
+
/** The zoo, redrawn from the view as it stands and a number that changes whenever it does. */
|
|
6
|
+
children: (spec: ShapeViewSpec, version: number) => React.ReactNode;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Chat beside the zoo it redraws. The view lives here and nowhere else: a turn swaps in what the model
|
|
10
|
+
* returned, the zoo rebuilds from it, and saving is a separate act — which is what makes the conversation
|
|
11
|
+
* free to be wrong. Nothing here reaches a datastore; the save writes the kit's own file, in dev only.
|
|
12
|
+
*/
|
|
13
|
+
export declare function ShapeViewStudio(props: ShapeViewStudioProps): import("react").JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { isPresent } from "@manta-eu/lib/present";
|
|
4
|
+
import { ArrowUp } from "lucide-react";
|
|
5
|
+
import { Badge, Button } from "@manta-eu/react";
|
|
6
|
+
import { shapeViewSpecSchema } from "@manta-eu/horns/shapes";
|
|
7
|
+
import { ChatColumn } from "../components/chat-column";
|
|
8
|
+
import { ErrorState } from "../components/states";
|
|
9
|
+
import { isRecord } from "@manta-eu/horns";
|
|
10
|
+
import { readNdjson } from "@manta-eu/horns-server/shared";
|
|
11
|
+
import { problemFrom, problemOf } from "../lib/problem";
|
|
12
|
+
const starters = [
|
|
13
|
+
"Title the card with something a person would say out loud",
|
|
14
|
+
"Show fewer fields — only what tells two of these apart",
|
|
15
|
+
"The link should be the one that goes somewhere on the web",
|
|
16
|
+
];
|
|
17
|
+
/** What to say about a round while it is happening, which is the only feedback a minute-long turn has. */
|
|
18
|
+
function progressLine(progress) {
|
|
19
|
+
if (progress.kind === "round")
|
|
20
|
+
return progress.round === 1 ? "Designing the card…" : `Round ${progress.round}: trying again…`;
|
|
21
|
+
if (progress.kind === "accepted")
|
|
22
|
+
return "The zoo is happy with it.";
|
|
23
|
+
const [first] = progress.problems;
|
|
24
|
+
return `The zoo refused it: ${first ?? "no reason given"}`;
|
|
25
|
+
}
|
|
26
|
+
function readProgress(line) {
|
|
27
|
+
const round = line["round"];
|
|
28
|
+
if (typeof round !== "number")
|
|
29
|
+
return undefined;
|
|
30
|
+
if (line["kind"] === "round")
|
|
31
|
+
return { kind: "round", round };
|
|
32
|
+
if (line["kind"] === "accepted")
|
|
33
|
+
return { kind: "accepted", round };
|
|
34
|
+
if (line["kind"] !== "rejected")
|
|
35
|
+
return undefined;
|
|
36
|
+
const problems = Array.isArray(line["problems"])
|
|
37
|
+
? line["problems"].filter((one) => typeof one === "string")
|
|
38
|
+
: [];
|
|
39
|
+
return { kind: "rejected", round, problems };
|
|
40
|
+
}
|
|
41
|
+
function readReply(body) {
|
|
42
|
+
if (!isRecord(body))
|
|
43
|
+
return undefined;
|
|
44
|
+
const spec = shapeViewSpecSchema.safeParse(body["spec"]);
|
|
45
|
+
if (!spec.success)
|
|
46
|
+
return undefined;
|
|
47
|
+
const problems = Array.isArray(body["problems"])
|
|
48
|
+
? body["problems"].filter((one) => typeof one === "string")
|
|
49
|
+
: [];
|
|
50
|
+
return {
|
|
51
|
+
spec: spec.data,
|
|
52
|
+
reply: typeof body["reply"] === "string" ? body["reply"] : "",
|
|
53
|
+
status: body["status"] === "gave-up" ? "gave-up" : "accepted",
|
|
54
|
+
problems,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** What to show when the model proposed the change without saying anything about it. */
|
|
58
|
+
function said(reply) {
|
|
59
|
+
if (isPresent(reply.reply))
|
|
60
|
+
return reply.reply;
|
|
61
|
+
if (reply.status === "accepted")
|
|
62
|
+
return "Changed the card.";
|
|
63
|
+
return `I could not make that fit the data:\n${reply.problems.map((problem) => `- ${problem}`).join("\n")}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Chat beside the zoo it redraws. The view lives here and nowhere else: a turn swaps in what the model
|
|
67
|
+
* returned, the zoo rebuilds from it, and saving is a separate act — which is what makes the conversation
|
|
68
|
+
* free to be wrong. Nothing here reaches a datastore; the save writes the kit's own file, in dev only.
|
|
69
|
+
*/
|
|
70
|
+
export function ShapeViewStudio(props) {
|
|
71
|
+
const { datastoreId, stored, children } = props;
|
|
72
|
+
const [spec, setSpec] = useState(stored);
|
|
73
|
+
const [turns, setTurns] = useState([]);
|
|
74
|
+
const [version, setVersion] = useState(0);
|
|
75
|
+
const [saveState, setSaveState] = useState("idle");
|
|
76
|
+
const [failure, setFailure] = useState();
|
|
77
|
+
const [pending, setPending] = useState(false);
|
|
78
|
+
const [progress, setProgress] = useState([]);
|
|
79
|
+
const changed = version > 0;
|
|
80
|
+
async function send(asked) {
|
|
81
|
+
const conversation = [...turns, { role: "user", content: asked }];
|
|
82
|
+
setTurns(conversation);
|
|
83
|
+
setPending(true);
|
|
84
|
+
setFailure(undefined);
|
|
85
|
+
// The shape, its fill rates and its links are read before the first model call, and that is not nothing.
|
|
86
|
+
setProgress(["Reading the shape and the data behind it…"]);
|
|
87
|
+
try {
|
|
88
|
+
const response = await fetch(`/api/d/${encodeURIComponent(datastoreId)}/shape-views/${encodeURIComponent(spec.schema)}/studio`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: { "content-type": "application/json" },
|
|
91
|
+
body: JSON.stringify({ spec, messages: conversation }),
|
|
92
|
+
});
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
setFailure(await problemFrom(response, `The Studio could not answer (${response.status}).`));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const body = await readNdjson(response, (line) => {
|
|
98
|
+
if (line["type"] !== "progress")
|
|
99
|
+
return;
|
|
100
|
+
const progress = readProgress(line);
|
|
101
|
+
if (isPresent(progress))
|
|
102
|
+
setProgress((lines) => [...lines, progressLine(progress)]);
|
|
103
|
+
});
|
|
104
|
+
if (!isPresent(body)) {
|
|
105
|
+
setFailure("That turn ended without an answer.");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (body["type"] === "error") {
|
|
109
|
+
setFailure(problemOf(body, "The Studio failed."));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const reply = readReply(body);
|
|
113
|
+
if (!isPresent(reply)) {
|
|
114
|
+
setFailure("The Studio answered with something that is not a view.");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
setTurns([...conversation, { role: "assistant", content: said(reply) }]);
|
|
118
|
+
setSpec(reply.spec);
|
|
119
|
+
setVersion((current) => current + 1);
|
|
120
|
+
setSaveState("idle");
|
|
121
|
+
}
|
|
122
|
+
catch (caught) {
|
|
123
|
+
setFailure(caught instanceof Error ? caught.message : String(caught));
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
setPending(false);
|
|
127
|
+
setProgress([]);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function discard() {
|
|
131
|
+
setSpec(stored);
|
|
132
|
+
setTurns([]);
|
|
133
|
+
setVersion((current) => current + 1);
|
|
134
|
+
setSaveState("idle");
|
|
135
|
+
setFailure(undefined);
|
|
136
|
+
}
|
|
137
|
+
async function save() {
|
|
138
|
+
setSaveState("saving");
|
|
139
|
+
setFailure(undefined);
|
|
140
|
+
try {
|
|
141
|
+
const response = await fetch(`/api/kit/shape-views/${encodeURIComponent(spec.schema)}`, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: { "content-type": "application/json" },
|
|
144
|
+
body: JSON.stringify({ spec }),
|
|
145
|
+
});
|
|
146
|
+
const body = await response.json().catch(() => undefined);
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
setSaveState("failed");
|
|
149
|
+
setFailure(problemOf(body, `That save was refused (${response.status}).`));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
setSaveState("saved");
|
|
153
|
+
}
|
|
154
|
+
catch (caught) {
|
|
155
|
+
setSaveState("failed");
|
|
156
|
+
setFailure(caught instanceof Error ? caught.message : String(caught));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return (
|
|
160
|
+
// Plain utilities: this app's stylesheet is the design system's, precompiled, so a responsive variant of a
|
|
161
|
+
// utility it already defines does not necessarily win.
|
|
162
|
+
_jsxs("div", { className: "flex flex-row items-start gap-6", children: [_jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-6", children: [_jsxs("div", { className: "flex items-center justify-end gap-2", children: [changed && saveState !== "saved" ? _jsx(Badge, { variant: "warning", children: "Unsaved" }) : null, saveState === "saved" ? _jsx(Badge, { variant: "success", children: "Saved" }) : null, changed ? (_jsx(Button, { variant: "secondary", size: "sm", disabled: saveState === "saving", onClick: discard, children: "Discard" })) : null, _jsx(Button, { variant: "primary", size: "sm", disabled: !changed || saveState === "saving", onClick: () => void save(), children: saveState === "saving" ? "Saving…" : "Save to kit" })] }), isPresent(failure) ? _jsx(ErrorState, { title: "That did not work", hint: failure }) : null, children(spec, version)] }), _jsx(ChatColumn, { title: "Talk to this view", subtitle: "Tell me how the card should read", starters: starters.map((starter, index) => ({ id: `starter-${index}`, title: starter })), turns: turns, progress: progress, pending: pending, placeholder: "Change the card\u2026", submitIcon: ArrowUp, onSend: (value) => void send(value) })] }));
|
|
163
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ShapeZooPage(): import("react").JSX.Element;
|