@protobase/ui 0.1.6 → 0.2.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.
Files changed (57) hide show
  1. package/dist/action-card/action-card.d.ts +26 -0
  2. package/dist/action-card/action-card.js +93 -0
  3. package/dist/action-card/field-list.d.ts +9 -0
  4. package/dist/action-card/field-list.js +18 -0
  5. package/dist/action-card/index.d.ts +3 -0
  6. package/dist/app/app.d.ts +3 -1
  7. package/dist/app/app.js +20 -7
  8. package/dist/app/assistant-shell.js +39 -0
  9. package/dist/app-shell/global-search.d.ts +1 -1
  10. package/dist/assistant-dock/assistant-dock.d.ts +10 -10
  11. package/dist/assistant-dock/assistant-dock.js +39 -74
  12. package/dist/assistant-dock/index.d.ts +5 -0
  13. package/dist/assistant-dock/part-view.d.ts +8 -0
  14. package/dist/assistant-dock/part-view.js +47 -0
  15. package/dist/assistant-dock/use-assistant-client.d.ts +4 -0
  16. package/dist/assistant-dock/use-assistant-client.js +17 -0
  17. package/dist/assistant-dock/use-assistant.d.ts +11 -0
  18. package/dist/assistant-dock/use-assistant.js +27 -0
  19. package/dist/auth/use-api-token.d.ts +7 -0
  20. package/dist/auth/use-api-token.js +37 -0
  21. package/dist/chat/chat-message.d.ts +8 -0
  22. package/dist/chat/chat-message.js +11 -0
  23. package/dist/chat/chat-thread.d.ts +12 -0
  24. package/dist/chat/chat-thread.js +23 -0
  25. package/dist/chat/composer.d.ts +12 -0
  26. package/dist/{assistant-dock → chat}/composer.js +5 -4
  27. package/dist/chat/index.d.ts +4 -0
  28. package/dist/compact-table/compact-table.d.ts +13 -0
  29. package/dist/compact-table/compact-table.js +49 -0
  30. package/dist/compact-table/format-cell.js +9 -0
  31. package/dist/data/meta-data.d.ts +4 -0
  32. package/dist/data/use-meta.js +2 -1
  33. package/dist/diff-view/diff-view.d.ts +13 -0
  34. package/dist/diff-view/diff-view.js +62 -0
  35. package/dist/diff-view/diff.d.ts +13 -0
  36. package/dist/diff-view/diff.js +29 -0
  37. package/dist/index.d.ts +19 -2
  38. package/dist/index.js +16 -2
  39. package/dist/side-panel/index.d.ts +2 -0
  40. package/dist/side-panel/side-panel.d.ts +17 -0
  41. package/dist/side-panel/side-panel.js +40 -0
  42. package/dist/step-list/step-list.d.ts +12 -0
  43. package/dist/step-list/step-list.js +40 -0
  44. package/package.json +4 -4
  45. package/dist/assistant-dock/chat-message.js +0 -9
  46. package/dist/assistant-dock/dock-item.d.ts +0 -4
  47. package/dist/assistant-dock/dock-item.js +0 -37
  48. package/dist/assistant-dock/model.d.ts +0 -84
  49. package/dist/assistant-dock/model.js +0 -26
  50. package/dist/assistant-dock/plan-card.d.ts +0 -13
  51. package/dist/assistant-dock/plan-card.js +0 -133
  52. package/dist/assistant-dock/progress-card.js +0 -18
  53. package/dist/assistant-dock/query-card.js +0 -52
  54. package/dist/assistant-dock/queue-card.js +0 -31
  55. package/dist/assistant-dock/result-card.js +0 -77
  56. package/dist/assistant-panel/content.d.ts +0 -7
  57. package/dist/assistant-panel/step-list.js +0 -30
@@ -0,0 +1,23 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { jsx } from "react/jsx-runtime";
3
+ //#region src/chat/chat-thread.tsx
4
+ /** The scrolling list of a chat's messages, kept at the newest one as messages arrive. */
5
+ const ChatThread = ({ children, empty, length }) => {
6
+ const list = useRef(null);
7
+ useEffect(() => {
8
+ const element = list.current;
9
+ if (element) element.scrollTop = element.scrollHeight;
10
+ }, [length]);
11
+ return /* @__PURE__ */ jsx("div", {
12
+ ref: list,
13
+ role: "log",
14
+ "aria-live": "polite",
15
+ className: "flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4",
16
+ children: length === 0 && empty ? /* @__PURE__ */ jsx("div", {
17
+ className: "m-auto max-w-60 text-center text-[13px] text-muted-foreground",
18
+ children: empty
19
+ }) : children
20
+ });
21
+ };
22
+ //#endregion
23
+ export { ChatThread };
@@ -0,0 +1,12 @@
1
+ import { Ref } from "react";
2
+ //#region src/chat/composer.d.ts
3
+ export type ComposerProps = {
4
+ onSend?: (text: string) => void;
5
+ /** The other side is still answering: the text can be written but not sent. */
6
+ busy?: boolean;
7
+ placeholder?: string;
8
+ inputRef?: Ref<HTMLInputElement>;
9
+ };
10
+ /** The text box under a chat. Enter or the button sends the trimmed text and clears the box. */
11
+ export declare const Composer: ({ onSend, busy, placeholder, inputRef }: ComposerProps) => import("react").JSX.Element;
12
+ //#endregion
@@ -3,12 +3,13 @@ import { Button } from "../primitives/button.js";
3
3
  import { useState } from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { SendHorizontal } from "lucide-react";
6
- //#region src/assistant-dock/composer.tsx
7
- const Composer = ({ onSend, replying, placeholder, inputRef }) => {
6
+ //#region src/chat/composer.tsx
7
+ /** The text box under a chat. Enter or the button sends the trimmed text and clears the box. */
8
+ const Composer = ({ onSend, busy, placeholder, inputRef }) => {
8
9
  const [text, setText] = useState("");
9
10
  const message = text.trim();
10
11
  const send = () => {
11
- if (!message || replying) return;
12
+ if (!message || busy) return;
12
13
  onSend?.(message);
13
14
  setText("");
14
15
  };
@@ -30,7 +31,7 @@ const Composer = ({ onSend, replying, placeholder, inputRef }) => {
30
31
  variant: "primary",
31
32
  className: "w-8 px-0",
32
33
  "aria-label": "Send",
33
- disabled: !message || replying,
34
+ disabled: !message || busy,
34
35
  children: /* @__PURE__ */ jsx(SendHorizontal, { className: "size-4" })
35
36
  })]
36
37
  });
@@ -0,0 +1,4 @@
1
+ import { ChatMessage } from "./chat-message.js";
2
+ import { ChatThread, ChatThreadProps } from "./chat-thread.js";
3
+ import { Composer, ComposerProps } from "./composer.js";
4
+ export { ChatMessage, ChatThread, type ChatThreadProps, Composer, type ComposerProps };
@@ -0,0 +1,13 @@
1
+ //#region src/compact-table/compact-table.d.ts
2
+ export type CompactTableProps = {
3
+ columns: string[];
4
+ rows: unknown[][];
5
+ /** Above the table, in monospace: what the rows are, such as the query that read them. */
6
+ caption?: string;
7
+ /** The rows stop at a limit. */
8
+ truncated?: boolean;
9
+ className?: string;
10
+ };
11
+ /** A small read-only table of raw values with a row count, for narrow places such as a side panel. */
12
+ export declare const CompactTable: ({ columns, rows, caption, truncated, className }: CompactTableProps) => import("react").JSX.Element;
13
+ //#endregion
@@ -0,0 +1,49 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import { formatCell } from "./format-cell.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/compact-table/compact-table.tsx
5
+ /** A small read-only table of raw values with a row count, for narrow places such as a side panel. */
6
+ const CompactTable = ({ columns, rows, caption, truncated, className }) => /* @__PURE__ */ jsxs("section", {
7
+ "aria-label": caption ?? "Table",
8
+ className: cn("shrink-0 overflow-hidden rounded-lg border border-border-strong bg-background shadow-sm", className),
9
+ children: [
10
+ caption && /* @__PURE__ */ jsx("code", {
11
+ className: "block whitespace-pre-wrap break-words bg-surface px-3 py-2 font-mono text-[11px] text-foreground",
12
+ children: caption
13
+ }),
14
+ rows.length === 0 ? /* @__PURE__ */ jsx("p", {
15
+ className: "px-3 py-2 text-xs text-muted-foreground",
16
+ children: "No rows."
17
+ }) : /* @__PURE__ */ jsx("div", {
18
+ className: "max-h-64 overflow-auto",
19
+ children: /* @__PURE__ */ jsxs("table", {
20
+ className: "w-full border-collapse text-left text-[11px]",
21
+ children: [/* @__PURE__ */ jsx("thead", {
22
+ className: "sticky top-0 bg-background",
23
+ children: /* @__PURE__ */ jsx("tr", { children: columns.map((column) => /* @__PURE__ */ jsx("th", {
24
+ scope: "col",
25
+ className: "border-b border-border px-2 py-1 font-semibold whitespace-nowrap",
26
+ children: column
27
+ }, column)) })
28
+ }), /* @__PURE__ */ jsx("tbody", { children: rows.map((row, index) => /* @__PURE__ */ jsx("tr", {
29
+ className: "border-b border-border last:border-b-0",
30
+ children: row.map((value, column) => /* @__PURE__ */ jsx("td", {
31
+ className: cn("px-2 py-1 whitespace-nowrap", value === null && "text-faint-foreground"),
32
+ children: formatCell(value)
33
+ }, column))
34
+ }, index)) })]
35
+ })
36
+ }),
37
+ /* @__PURE__ */ jsxs("footer", {
38
+ className: "border-t border-border px-3 py-1.5 text-[11px] text-muted-foreground",
39
+ children: [
40
+ rows.length,
41
+ " ",
42
+ rows.length === 1 ? "row" : "rows",
43
+ truncated && ", cut off at the limit"
44
+ ]
45
+ })
46
+ ]
47
+ });
48
+ //#endregion
49
+ export { CompactTable };
@@ -0,0 +1,9 @@
1
+ //#region src/compact-table/format-cell.ts
2
+ /** A cell as text: `null` stays visible, objects and arrays as JSON. */
3
+ const formatCell = (value) => {
4
+ if (value === null || value === void 0) return "null";
5
+ if (typeof value === "object") return JSON.stringify(value);
6
+ return String(value);
7
+ };
8
+ //#endregion
9
+ export { formatCell };
@@ -10,5 +10,9 @@ export type MetaData = {
10
10
  pages: Record<string, PageModel>;
11
11
  permissions: Record<string, ResourcePermissions>;
12
12
  userMenu?: UserMenuModel;
13
+ /** The assistant backend, when the app has one and the user has the admin or ai role. */
14
+ assistant?: {
15
+ url: string;
16
+ };
13
17
  };
14
18
  //#endregion
@@ -26,7 +26,8 @@ const useMeta = () => {
26
26
  views: Object.fromEntries(result.meta.views.map((model) => [model.resource, model])),
27
27
  pages: Object.fromEntries((result.meta.pages ?? []).map((model) => [model.name, model])),
28
28
  permissions: result.meta.permissions ?? {},
29
- ...result.meta.userMenu && { userMenu: result.meta.userMenu }
29
+ ...result.meta.userMenu && { userMenu: result.meta.userMenu },
30
+ ...result.meta.assistant && { assistant: result.meta.assistant }
30
31
  };
31
32
  }
32
33
  });
@@ -0,0 +1,13 @@
1
+ //#region src/diff-view/diff-view.d.ts
2
+ export type DiffViewProps = {
3
+ /** Lines starting with `+` were added, with `-` removed; anything else is context. */
4
+ source: string;
5
+ /** The number of the first line. */
6
+ start?: number;
7
+ /** Shown above the lines with the counts of added and removed ones. */
8
+ path?: string;
9
+ className?: string;
10
+ };
11
+ /** Changed lines of one file, numbered, with added and removed lines marked. */
12
+ export declare const DiffView: ({ source, start, path, className }: DiffViewProps) => import("react").JSX.Element;
13
+ //#endregion
@@ -0,0 +1,62 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import { diffStats, parseDiff } from "./diff.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { FileCode2 } from "lucide-react";
5
+ //#region src/diff-view/diff-view.tsx
6
+ const row = {
7
+ add: "bg-success-soft text-success-text",
8
+ del: "bg-danger-soft text-danger-text",
9
+ ctx: "text-muted-foreground"
10
+ };
11
+ const marker = {
12
+ add: "+",
13
+ del: "−",
14
+ ctx: ""
15
+ };
16
+ /** Changed lines of one file, numbered, with added and removed lines marked. */
17
+ const DiffView = ({ source, start, path, className }) => {
18
+ const lines = parseDiff(source, start);
19
+ const stats = diffStats(lines);
20
+ return /* @__PURE__ */ jsxs("div", {
21
+ className: cn("overflow-hidden", className),
22
+ children: [path && /* @__PURE__ */ jsxs("div", {
23
+ className: "flex items-center gap-2 border-b border-border bg-surface px-3 py-1.5",
24
+ children: [
25
+ /* @__PURE__ */ jsx(FileCode2, { className: "size-3.5 text-muted-foreground" }),
26
+ /* @__PURE__ */ jsx("span", {
27
+ className: "min-w-0 flex-1 truncate font-mono text-[11px] text-foreground",
28
+ children: path
29
+ }),
30
+ /* @__PURE__ */ jsxs("span", {
31
+ className: "text-[11px] font-medium text-success-text",
32
+ children: ["+", stats.added]
33
+ }),
34
+ stats.removed > 0 && /* @__PURE__ */ jsxs("span", {
35
+ className: "text-[11px] font-medium text-danger-text",
36
+ children: ["−", stats.removed]
37
+ })
38
+ ]
39
+ }), /* @__PURE__ */ jsx("pre", {
40
+ className: "overflow-x-auto bg-background py-1 font-mono text-[11px] leading-[18px]",
41
+ children: lines.map((line, index) => /* @__PURE__ */ jsxs("div", {
42
+ className: cn("flex", row[line.kind]),
43
+ children: [
44
+ /* @__PURE__ */ jsx("span", {
45
+ className: "w-8 shrink-0 select-none pr-2 text-right opacity-60",
46
+ children: line.newNo ?? line.oldNo
47
+ }),
48
+ /* @__PURE__ */ jsx("span", {
49
+ className: "w-3 shrink-0 select-none",
50
+ children: marker[line.kind]
51
+ }),
52
+ /* @__PURE__ */ jsx("span", {
53
+ className: "min-w-0 whitespace-pre-wrap break-words pr-3",
54
+ children: line.text
55
+ })
56
+ ]
57
+ }, index))
58
+ })]
59
+ });
60
+ };
61
+ //#endregion
62
+ export { DiffView };
@@ -0,0 +1,13 @@
1
+ //#region src/diff-view/diff.d.ts
2
+ export type DiffLine = {
3
+ kind: 'add' | 'del' | 'ctx';
4
+ text: string;
5
+ oldNo?: number;
6
+ newNo?: number;
7
+ };
8
+ export declare const parseDiff: (source: string, start?: number) => DiffLine[];
9
+ export declare const diffStats: (lines: DiffLine[]) => {
10
+ added: number;
11
+ removed: number;
12
+ };
13
+ //#endregion
@@ -0,0 +1,29 @@
1
+ //#region src/diff-view/diff.ts
2
+ const parseDiff = (source, start = 1) => {
3
+ let oldNo = start;
4
+ let newNo = start;
5
+ return source.split("\n").map((raw) => {
6
+ if (raw.startsWith("+")) return {
7
+ kind: "add",
8
+ text: raw.slice(1),
9
+ newNo: newNo++
10
+ };
11
+ if (raw.startsWith("-")) return {
12
+ kind: "del",
13
+ text: raw.slice(1),
14
+ oldNo: oldNo++
15
+ };
16
+ return {
17
+ kind: "ctx",
18
+ text: raw.startsWith(" ") ? raw.slice(1) : raw,
19
+ oldNo: oldNo++,
20
+ newNo: newNo++
21
+ };
22
+ });
23
+ };
24
+ const diffStats = (lines) => ({
25
+ added: lines.filter((line) => line.kind === "add").length,
26
+ removed: lines.filter((line) => line.kind === "del").length
27
+ });
28
+ //#endregion
29
+ export { diffStats, parseDiff };
package/dist/index.d.ts CHANGED
@@ -9,6 +9,23 @@ import { useRecord } from "./data/use-record.js";
9
9
  import { useFacets, useHistogram, useSeries } from "./data/use-aggregates.js";
10
10
  import { isConflict, useUpdateRecord } from "./data/use-update-record.js";
11
11
  import { LayoutRecord, useLayoutRecord } from "./app/pages/record-scope.js";
12
- import { DockItem, JobResult, Plan, PlanSize, QueryResult } from "./assistant-dock/model.js";
12
+ import { useApiToken } from "./auth/use-api-token.js";
13
13
  import { AssistantDock, AssistantDockProps } from "./assistant-dock/assistant-dock.js";
14
- export { type ActionContext, type ActionHandler, ApiProvider, type ApiProviderProps, App, type AppProps, AssistantDock, type AssistantDockProps, type DockItem, type JobResult, type LayoutRecord, type MetaData, type Plan, type PlanSize, type ProjectUi, type QueryResult, defineUi, isConflict, useClient, useFacets, useHistogram, useLayoutRecord, useList, useMeta, useRecord, useSeries, useUpdateRecord };
14
+ import { PartView } from "./assistant-dock/part-view.js";
15
+ import { useAssistant } from "./assistant-dock/use-assistant.js";
16
+ import { useAssistantClient } from "./assistant-dock/use-assistant-client.js";
17
+ import "./assistant-dock/index.js";
18
+ import { ChatMessage } from "./chat/chat-message.js";
19
+ import { ChatThread, ChatThreadProps } from "./chat/chat-thread.js";
20
+ import { Composer, ComposerProps } from "./chat/composer.js";
21
+ import "./chat/index.js";
22
+ import { SidePanel, SidePanelProps } from "./side-panel/side-panel.js";
23
+ import "./side-panel/index.js";
24
+ import { ActionCard, ActionCardProps, CardAction, CardTone } from "./action-card/action-card.js";
25
+ import { FieldList } from "./action-card/field-list.js";
26
+ import "./action-card/index.js";
27
+ import { Step, StepList, StepState } from "./step-list/step-list.js";
28
+ import { DiffView, DiffViewProps } from "./diff-view/diff-view.js";
29
+ import { DiffLine, diffStats, parseDiff } from "./diff-view/diff.js";
30
+ import { CompactTable, CompactTableProps } from "./compact-table/compact-table.js";
31
+ export { ActionCard, type ActionCardProps, type ActionContext, type ActionHandler, ApiProvider, type ApiProviderProps, App, type AppProps, AssistantDock, type AssistantDockProps, type CardAction, type CardTone, ChatMessage, ChatThread, type ChatThreadProps, CompactTable, type CompactTableProps, Composer, type ComposerProps, type DiffLine, DiffView, type DiffViewProps, FieldList, type LayoutRecord, type MetaData, PartView, type ProjectUi, SidePanel, type SidePanelProps, type Step, StepList, type StepState, defineUi, diffStats, isConflict, parseDiff, useApiToken, useAssistant, useAssistantClient, useClient, useFacets, useHistogram, useLayoutRecord, useList, useMeta, useRecord, useSeries, useUpdateRecord };
package/dist/index.js CHANGED
@@ -1,5 +1,20 @@
1
1
  import { ApiProvider, useClient } from "./data/api-provider.js";
2
+ import { useApiToken } from "./auth/use-api-token.js";
2
3
  import { useRecord } from "./data/use-record.js";
4
+ import { ChatMessage } from "./chat/chat-message.js";
5
+ import { ChatThread } from "./chat/chat-thread.js";
6
+ import { Composer } from "./chat/composer.js";
7
+ import { SidePanel } from "./side-panel/side-panel.js";
8
+ import { ActionCard } from "./action-card/action-card.js";
9
+ import { FieldList } from "./action-card/field-list.js";
10
+ import { CompactTable } from "./compact-table/compact-table.js";
11
+ import { diffStats, parseDiff } from "./diff-view/diff.js";
12
+ import { DiffView } from "./diff-view/diff-view.js";
13
+ import { StepList } from "./step-list/step-list.js";
14
+ import { PartView } from "./assistant-dock/part-view.js";
15
+ import { AssistantDock } from "./assistant-dock/assistant-dock.js";
16
+ import { useAssistant } from "./assistant-dock/use-assistant.js";
17
+ import { useAssistantClient } from "./assistant-dock/use-assistant-client.js";
3
18
  import { useMeta } from "./data/use-meta.js";
4
19
  import { useFacets, useHistogram, useSeries } from "./data/use-aggregates.js";
5
20
  import { useLayoutRecord } from "./app/pages/record-scope.js";
@@ -7,5 +22,4 @@ import { defineUi } from "./app/pages/project-ui.js";
7
22
  import { useList } from "./data/use-list.js";
8
23
  import { isConflict, useUpdateRecord } from "./data/use-update-record.js";
9
24
  import { App } from "./app/app.js";
10
- import { AssistantDock } from "./assistant-dock/assistant-dock.js";
11
- export { ApiProvider, App, AssistantDock, defineUi, isConflict, useClient, useFacets, useHistogram, useLayoutRecord, useList, useMeta, useRecord, useSeries, useUpdateRecord };
25
+ export { ActionCard, ApiProvider, App, AssistantDock, ChatMessage, ChatThread, CompactTable, Composer, DiffView, FieldList, PartView, SidePanel, StepList, defineUi, diffStats, isConflict, parseDiff, useApiToken, useAssistant, useAssistantClient, useClient, useFacets, useHistogram, useLayoutRecord, useList, useMeta, useRecord, useSeries, useUpdateRecord };
@@ -0,0 +1,2 @@
1
+ import { SidePanel, SidePanelProps } from "./side-panel.js";
2
+ export { SidePanel, type SidePanelProps };
@@ -0,0 +1,17 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/side-panel/side-panel.d.ts
3
+ export type SidePanelProps = {
4
+ /** The heading, and the panel's accessible name. */
5
+ title: string;
6
+ icon?: ReactNode;
7
+ /** Short text at the end of the header. */
8
+ status?: ReactNode;
9
+ onClose?: () => void;
10
+ /** Pinned under the content, such as a composer. */
11
+ footer?: ReactNode;
12
+ /** The content; it scrolls on its own, so give it `min-h-0 flex-1`. */
13
+ children: ReactNode;
14
+ };
15
+ /** A full-height panel beside the page, for the shell's `rightPanel`: a header with a close button, content and a footer. */
16
+ export declare const SidePanel: ({ title, icon, status, onClose, footer, children }: SidePanelProps) => import("react").JSX.Element;
17
+ //#endregion
@@ -0,0 +1,40 @@
1
+ import { Button } from "../primitives/button.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { X } from "lucide-react";
4
+ //#region src/side-panel/side-panel.tsx
5
+ /** A full-height panel beside the page, for the shell's `rightPanel`: a header with a close button, content and a footer. */
6
+ const SidePanel = ({ title, icon, status, onClose, footer, children }) => /* @__PURE__ */ jsxs("aside", {
7
+ "aria-label": title,
8
+ className: "flex h-full w-[380px] max-w-full shrink-0 flex-col border-l border-border bg-surface",
9
+ children: [
10
+ /* @__PURE__ */ jsxs("header", {
11
+ className: "flex h-12 shrink-0 items-center gap-2 border-b border-border bg-background px-4",
12
+ children: [
13
+ icon,
14
+ /* @__PURE__ */ jsx("h2", {
15
+ className: "flex-1 text-[13px] font-semibold",
16
+ children: title
17
+ }),
18
+ status && /* @__PURE__ */ jsx("span", {
19
+ className: "truncate text-xs font-medium text-muted-foreground",
20
+ children: status
21
+ }),
22
+ onClose && /* @__PURE__ */ jsx(Button, {
23
+ variant: "ghost",
24
+ size: "sm",
25
+ className: "w-7 px-0",
26
+ "aria-label": "Close",
27
+ onClick: onClose,
28
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
29
+ })
30
+ ]
31
+ }),
32
+ children,
33
+ footer && /* @__PURE__ */ jsx("footer", {
34
+ className: "shrink-0 space-y-2 border-t border-border bg-background p-3",
35
+ children: footer
36
+ })
37
+ ]
38
+ });
39
+ //#endregion
40
+ export { SidePanel };
@@ -0,0 +1,12 @@
1
+ //#region src/step-list/step-list.d.ts
2
+ export type StepState = 'pending' | 'running' | 'done' | 'failed';
3
+ export type Step = {
4
+ label: string;
5
+ state: StepState;
6
+ };
7
+ /** Steps of a longer piece of work, each pending, running, done or failed. */
8
+ export declare const StepList: ({ steps, className }: {
9
+ steps: Step[];
10
+ className?: string;
11
+ }) => import("react").JSX.Element;
12
+ //#endregion
@@ -0,0 +1,40 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import { Spinner } from "../primitives/spinner.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { Check, Circle, X } from "lucide-react";
5
+ //#region src/step-list/step-list.tsx
6
+ const icons = {
7
+ pending: /* @__PURE__ */ jsx(Circle, { className: "size-3 text-faint-foreground" }),
8
+ running: /* @__PURE__ */ jsx(Spinner, { className: "size-3.5 text-primary" }),
9
+ done: /* @__PURE__ */ jsx(Check, {
10
+ className: "size-3.5 text-success",
11
+ strokeWidth: 2.5
12
+ }),
13
+ failed: /* @__PURE__ */ jsx(X, {
14
+ className: "size-3.5 text-danger",
15
+ strokeWidth: 2.5
16
+ })
17
+ };
18
+ const tones = {
19
+ pending: "text-muted-foreground",
20
+ running: "text-foreground",
21
+ done: "text-muted-foreground",
22
+ failed: "text-danger-text"
23
+ };
24
+ /** Steps of a longer piece of work, each pending, running, done or failed. */
25
+ const StepList = ({ steps, className }) => /* @__PURE__ */ jsx("ul", {
26
+ className: cn("shrink-0 space-y-2", className),
27
+ children: steps.map((step, index) => /* @__PURE__ */ jsxs("li", {
28
+ className: "flex items-start gap-2.5 text-[13px]",
29
+ "data-state": step.state,
30
+ children: [/* @__PURE__ */ jsx("span", {
31
+ className: "mt-0.5 flex size-4 shrink-0 items-center justify-center",
32
+ children: icons[step.state]
33
+ }), /* @__PURE__ */ jsx("span", {
34
+ className: tones[step.state],
35
+ children: step.label
36
+ })]
37
+ }, `${index}-${step.label}`))
38
+ });
39
+ //#endregion
40
+ export { StepList };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/ui",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "description": "The Protobase admin app and the React hooks and components for custom UI.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -38,9 +38,9 @@
38
38
  "react-dom": "^19.3.0",
39
39
  "recharts": "^3.10.1",
40
40
  "tailwindcss": "^4.3.3",
41
- "@protobase/client": "0.1.6",
42
- "@protobase/schema": "0.1.6",
43
- "@protobase/layout": "0.1.6"
41
+ "@protobase/client": "0.2.0",
42
+ "@protobase/layout": "0.2.0",
43
+ "@protobase/schema": "0.2.0"
44
44
  },
45
45
  "scripts": {
46
46
  "build": "tsdown"
@@ -1,9 +0,0 @@
1
- import { cn } from "../lib/cn.js";
2
- import { jsx } from "react/jsx-runtime";
3
- //#region src/assistant-dock/chat-message.tsx
4
- const ChatMessage = ({ from, text }) => /* @__PURE__ */ jsx("div", {
5
- className: cn("whitespace-pre-wrap break-words text-[13px]", from === "user" ? "ml-8 self-end rounded-lg rounded-br-sm bg-primary px-3 py-2 text-primary-foreground shadow-sm" : "mr-4 text-foreground"),
6
- children: text
7
- });
8
- //#endregion
9
- export { ChatMessage };
@@ -1,4 +0,0 @@
1
- import { PlanCardProps } from "./plan-card.js";
2
- //#region src/assistant-dock/dock-item.d.ts
3
- export type DockItemHandlers = Pick<PlanCardProps, 'onApprove' | 'onRequestChanges' | 'onCancel' | 'onUpdatePlan'>;
4
- //#endregion
@@ -1,37 +0,0 @@
1
- import { ChatMessage } from "./chat-message.js";
2
- import { PlanCard } from "./plan-card.js";
3
- import { ProgressCard } from "./progress-card.js";
4
- import { QueryCard } from "./query-card.js";
5
- import { QueueCard } from "./queue-card.js";
6
- import { ResultCard } from "./result-card.js";
7
- import { jsx } from "react/jsx-runtime";
8
- //#region src/assistant-dock/dock-item.tsx
9
- const DockItemView = ({ item, balance, handlers }) => {
10
- switch (item.kind) {
11
- case "user":
12
- case "assistant": return /* @__PURE__ */ jsx(ChatMessage, {
13
- from: item.kind,
14
- text: item.text
15
- });
16
- case "queue": return /* @__PURE__ */ jsx(QueueCard, {
17
- taskId: item.taskId,
18
- request: item.request,
19
- ahead: item.ahead,
20
- waitingOnPlan: item.waitingOnPlan,
21
- onCancel: handlers.onCancel
22
- });
23
- case "progress": return /* @__PURE__ */ jsx(ProgressCard, {
24
- phase: item.phase,
25
- steps: item.steps
26
- });
27
- case "plan": return /* @__PURE__ */ jsx(PlanCard, {
28
- plan: item.plan,
29
- balance,
30
- ...handlers
31
- });
32
- case "result": return /* @__PURE__ */ jsx(ResultCard, { result: item.result });
33
- case "query": return /* @__PURE__ */ jsx(QueryCard, { query: item.query });
34
- }
35
- };
36
- //#endregion
37
- export { DockItemView };
@@ -1,84 +0,0 @@
1
- import { Step } from "../assistant-panel/content.js";
2
- //#region src/assistant-dock/model.d.ts
3
- export type PlanSize = 'S' | 'M' | 'L';
4
- /** A plan as the planner returned it and central priced it. */
5
- export type Plan = {
6
- taskId: string;
7
- /** 1 for the first plan; "Ask for changes" and "Update plan" make the next one. */
8
- revision: number;
9
- summary: string;
10
- steps: Array<{
11
- title: string;
12
- files?: string[];
13
- }>;
14
- migrations: Array<{
15
- description: string;
16
- destructive: boolean;
17
- }>;
18
- risks: string[];
19
- size: PlanSize;
20
- /** The quote: what Approve holds. */
21
- credits: number;
22
- /** The branch moved on GitHub since the plan was made. */
23
- outdated: boolean;
24
- /** Tasks behind this one in the app's queue, waiting until it is answered. */
25
- waiting: number;
26
- /** `dropped`: the platform dropped it and refunded the deposit (repository replaced, app archived). */
27
- status: 'awaiting' | 'approved' | 'canceled' | 'superseded' | 'dropped';
28
- };
29
- export type JobResult = {
30
- taskId: string;
31
- outcome: 'live' | 'failed' | 'capped';
32
- summary: string;
33
- commit?: {
34
- sha: string;
35
- url?: string;
36
- };
37
- /** The migrations the job applied; they stay live when it fails. */
38
- dbChanges: string[];
39
- /** A finished change GitHub refused, kept on `protobase/task-<n>`. */
40
- keptOnBranch?: string;
41
- /** What was charged: the quote when live, 0 otherwise. */
42
- credits: number;
43
- };
44
- export type QueryResult = {
45
- sql: string;
46
- columns: string[];
47
- rows: unknown[][];
48
- truncated: boolean;
49
- };
50
- export type DockItem = {
51
- kind: 'user';
52
- id: string;
53
- text: string;
54
- } | {
55
- kind: 'assistant';
56
- id: string;
57
- text: string;
58
- } | {
59
- kind: 'queue';
60
- id: string;
61
- taskId: string;
62
- request: string;
63
- ahead: number;
64
- waitingOnPlan: boolean;
65
- } | {
66
- kind: 'progress';
67
- id: string;
68
- taskId: string;
69
- phase: 'planning' | 'running';
70
- steps: Step[];
71
- } | {
72
- kind: 'plan';
73
- id: string;
74
- plan: Plan;
75
- } | {
76
- kind: 'result';
77
- id: string;
78
- result: JobResult;
79
- } | {
80
- kind: 'query';
81
- id: string;
82
- query: QueryResult;
83
- };
84
- //#endregion
@@ -1,26 +0,0 @@
1
- //#region src/assistant-dock/model.ts
2
- const formatCredits = (credits) => `${credits} ${credits === 1 ? "credit" : "credits"}`;
3
- /** Approve is offered only on a plan awaiting an answer, and only when the balance covers the quote. */
4
- const approval = (plan, balance) => {
5
- if (plan.status !== "awaiting") return { enabled: false };
6
- if (balance < plan.credits) return {
7
- enabled: false,
8
- reason: `Needs ${formatCredits(plan.credits)}; the balance is ${formatCredits(balance)}.`
9
- };
10
- return { enabled: true };
11
- };
12
- const approveLabel = (plan) => `${plan.outdated ? "Approve anyway" : "Approve"} · ${formatCredits(plan.credits)}`;
13
- const queueText = (ahead, waitingOnPlan) => {
14
- if (waitingOnPlan) return "Waiting for the plan above to be approved or canceled.";
15
- if (ahead === 0) return "Next in line.";
16
- return `Waiting, ${ahead} ${ahead === 1 ? "task" : "tasks"} ahead.`;
17
- };
18
- const waitingText = (waiting) => waiting === 0 ? void 0 : `${waiting} ${waiting === 1 ? "task waits" : "tasks wait"} on this plan.`;
19
- /** A query cell as text: `null` stays visible, objects and arrays as JSON. */
20
- const formatCell = (value) => {
21
- if (value === null || value === void 0) return "null";
22
- if (typeof value === "object") return JSON.stringify(value);
23
- return String(value);
24
- };
25
- //#endregion
26
- export { approval, approveLabel, formatCell, formatCredits, queueText, waitingText };