@protobase/ui 0.0.0-stage → 0.1.1
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 +202 -0
- package/dist/activity-chart/activity-chart.js +125 -0
- package/dist/activity-chart/activity-math.js +11 -0
- package/dist/activity-chart/activity-tooltip.js +24 -0
- package/dist/app/action-icon.js +10 -0
- package/dist/app/app.d.ts +24 -0
- package/dist/app/app.js +123 -0
- package/dist/app/batch-failure.js +21 -0
- package/dist/app/breadcrumb-for.js +27 -0
- package/dist/app/create/create-page.js +118 -0
- package/dist/app/create/create-record-form.js +68 -0
- package/dist/app/create/create-values.js +52 -0
- package/dist/app/delete/dialogs.js +109 -0
- package/dist/app/delete/record-diff.js +17 -0
- package/dist/app/delete/use-delete-flow.js +193 -0
- package/dist/app/delete/use-restore.js +9 -0
- package/dist/app/draft-storage.js +18 -0
- package/dist/app/error-banner.js +28 -0
- package/dist/app/field-editor.js +88 -0
- package/dist/app/icons.js +27 -0
- package/dist/app/index.d.ts +2 -0
- package/dist/app/index.html +12 -0
- package/dist/app/list-chart.js +36 -0
- package/dist/app/list-order.js +15 -0
- package/dist/app/list-page.js +213 -0
- package/dist/app/list-search.js +30 -0
- package/dist/app/main.d.ts +1 -0
- package/dist/app/main.js +14 -0
- package/dist/app/menu-icons.js +21 -0
- package/dist/app/meta-gate.js +46 -0
- package/dist/app/nav-from-meta.js +65 -0
- package/dist/app/nav-recent.js +33 -0
- package/dist/app/pages/action-href.js +7 -0
- package/dist/app/pages/blocks/action.js +38 -0
- package/dist/app/pages/blocks/block-props.js +5 -0
- package/dist/app/pages/blocks/card-frame.js +34 -0
- package/dist/app/pages/blocks/card-row.js +55 -0
- package/dist/app/pages/blocks/card.js +19 -0
- package/dist/app/pages/blocks/field.js +45 -0
- package/dist/app/pages/blocks/grid.js +34 -0
- package/dist/app/pages/blocks/link.js +27 -0
- package/dist/app/pages/blocks/modal-form.js +149 -0
- package/dist/app/pages/blocks/page-block.js +20 -0
- package/dist/app/pages/blocks/progress.js +54 -0
- package/dist/app/pages/blocks/record-card.js +56 -0
- package/dist/app/pages/blocks/show.js +43 -0
- package/dist/app/pages/blocks/stat.js +78 -0
- package/dist/app/pages/blocks/table.js +65 -0
- package/dist/app/pages/composed-page.js +19 -0
- package/dist/app/pages/confirm-action.js +20 -0
- package/dist/app/pages/field-value.js +58 -0
- package/dist/app/pages/project-ui.d.ts +30 -0
- package/dist/app/pages/project-ui.js +13 -0
- package/dist/app/pages/record-actions.js +40 -0
- package/dist/app/pages/record-scope.d.ts +16 -0
- package/dist/app/pages/record-scope.js +12 -0
- package/dist/app/pages/record-table.js +110 -0
- package/dist/app/pages/render-node.js +84 -0
- package/dist/app/pages/use-layout-data.js +73 -0
- package/dist/app/pages/use-path-value.js +65 -0
- package/dist/app/pages/use-relation-labels.js +30 -0
- package/dist/app/pages/use-run-action.js +88 -0
- package/dist/app/permissions.js +15 -0
- package/dist/app/record-lines.js +39 -0
- package/dist/app/record-page.js +318 -0
- package/dist/app/related-section.js +126 -0
- package/dist/app/relation-picker.js +69 -0
- package/dist/app/router.js +155 -0
- package/dist/app/search/search-model.js +70 -0
- package/dist/app/search/use-global-search.js +68 -0
- package/dist/app/sensitive-field.js +65 -0
- package/dist/app/use-lines-draft.js +45 -0
- package/dist/app/use-lines.js +19 -0
- package/dist/app/use-live-save.js +54 -0
- package/dist/app/use-nav-recent.js +41 -0
- package/dist/app/use-record-editor.js +154 -0
- package/dist/app/use-related-records.js +31 -0
- package/dist/app/user-menu-from-meta.js +36 -0
- package/dist/app-shell/app-shell.d.ts +4 -0
- package/dist/app-shell/app-shell.js +57 -0
- package/dist/app-shell/breadcrumb.js +37 -0
- package/dist/app-shell/drawer.js +30 -0
- package/dist/app-shell/global-search.d.ts +1 -0
- package/dist/app-shell/global-search.js +145 -0
- package/dist/app-shell/index.d.ts +9 -0
- package/dist/app-shell/logo.js +46 -0
- package/dist/app-shell/nav-open-storage.js +10 -0
- package/dist/app-shell/nav.d.ts +2 -0
- package/dist/app-shell/nav.js +85 -0
- package/dist/app-shell/page-header.d.ts +1 -0
- package/dist/app-shell/page-header.js +17 -0
- package/dist/app-shell/profile-menu.d.ts +2 -0
- package/dist/app-shell/profile-menu.js +115 -0
- package/dist/app-shell/sidebar-group.js +94 -0
- package/dist/app-shell/sidebar-item.js +46 -0
- package/dist/app-shell/sidebar-mode.d.ts +4 -0
- package/dist/app-shell/sidebar-mode.js +13 -0
- package/dist/app-shell/sidebar.d.ts +2 -0
- package/dist/app-shell/sidebar.js +64 -0
- package/dist/app-shell/status-dot.d.ts +1 -0
- package/dist/app-shell/status-dot.js +20 -0
- package/dist/app-shell/top-bar.js +82 -0
- package/dist/assistant-dock/assistant-dock.d.ts +15 -0
- package/dist/assistant-dock/assistant-dock.js +81 -0
- package/dist/assistant-dock/chat-message.js +9 -0
- package/dist/assistant-dock/composer.js +39 -0
- package/dist/assistant-dock/dock-item.d.ts +4 -0
- package/dist/assistant-dock/dock-item.js +37 -0
- package/dist/assistant-dock/model.d.ts +84 -0
- package/dist/assistant-dock/model.js +26 -0
- package/dist/assistant-dock/plan-card.d.ts +13 -0
- package/dist/assistant-dock/plan-card.js +133 -0
- package/dist/assistant-dock/progress-card.js +18 -0
- package/dist/assistant-dock/query-card.js +52 -0
- package/dist/assistant-dock/queue-card.js +31 -0
- package/dist/assistant-dock/result-card.js +77 -0
- package/dist/assistant-panel/content.d.ts +7 -0
- package/dist/assistant-panel/step-list.js +30 -0
- package/dist/auth/auth-gate.js +70 -0
- package/dist/auth/auth-layout.js +29 -0
- package/dist/auth/auth-provider.js +71 -0
- package/dist/auth/first-run-page.js +51 -0
- package/dist/auth/forgot-password-form.js +75 -0
- package/dist/auth/forgot-password-page.js +35 -0
- package/dist/auth/reset-link.js +29 -0
- package/dist/auth/reset-password-form.js +88 -0
- package/dist/auth/reset-password-page.js +43 -0
- package/dist/auth/sign-in-form.js +90 -0
- package/dist/auth/sign-in-page.js +39 -0
- package/dist/auth/user-to-shell.js +18 -0
- package/dist/data/api-provider.d.ts +54 -0
- package/dist/data/api-provider.js +53 -0
- package/dist/data/meta-data.d.ts +14 -0
- package/dist/data/meta-data.js +9 -0
- package/dist/data/query-keys.js +95 -0
- package/dist/data/use-aggregates.d.ts +8 -0
- package/dist/data/use-aggregates.js +43 -0
- package/dist/data/use-list.d.ts +231 -0
- package/dist/data/use-list.js +31 -0
- package/dist/data/use-meta.d.ts +8 -0
- package/dist/data/use-meta.js +35 -0
- package/dist/data/use-record.d.ts +6 -0
- package/dist/data/use-record.js +16 -0
- package/dist/data/use-update-record.d.ts +15 -0
- package/dist/data/use-update-record.js +26 -0
- package/dist/data-grid/cells.js +85 -0
- package/dist/data-grid/column-spec.js +4 -0
- package/dist/data-grid/columns.js +16 -0
- package/dist/data-grid/data-grid.js +238 -0
- package/dist/data-grid/geometry.js +32 -0
- package/dist/data-grid/grid-footer.js +33 -0
- package/dist/data-grid/grid-header.js +69 -0
- package/dist/data-grid/grid-row.js +83 -0
- package/dist/data-grid/grid-template.js +12 -0
- package/dist/data-grid/header-menu.js +52 -0
- package/dist/data-grid/page-cache.js +27 -0
- package/dist/data-grid/pinned-storage.js +13 -0
- package/dist/data-grid/row-menu.js +88 -0
- package/dist/data-grid/use-grid-scroll.js +80 -0
- package/dist/data-grid/use-horizontal-scroll.js +38 -0
- package/dist/data-grid/use-paged-rows.js +50 -0
- package/dist/data-grid/use-pinned-columns.js +16 -0
- package/dist/filter-panel/active-filter-chips.js +34 -0
- package/dist/filter-panel/date-presets.js +20 -0
- package/dist/filter-panel/facet-group.js +48 -0
- package/dist/filter-panel/filter-config.js +18 -0
- package/dist/filter-panel/filter-menu.js +41 -0
- package/dist/filter-panel/filter-panel.js +112 -0
- package/dist/filter-panel/filter-section.js +11 -0
- package/dist/filter-panel/filter-state.js +63 -0
- package/dist/filter-panel/range-filter.js +54 -0
- package/dist/filter-panel/range-math.js +11 -0
- package/dist/filter-panel/range-slider.css +32 -0
- package/dist/filter-panel/toggle-row.js +13 -0
- package/dist/filter-panel/use-filter-state.js +15 -0
- package/dist/format/date.js +37 -0
- package/dist/format/money.js +15 -0
- package/dist/format/number.js +12 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +11 -0
- package/dist/inline-child-table/columns.js +4 -0
- package/dist/inline-child-table/inline-child-table.js +120 -0
- package/dist/inline-child-table/line-menu.js +54 -0
- package/dist/inline-child-table/line-row.js +91 -0
- package/dist/inline-child-table/number-cell.js +25 -0
- package/dist/inline-child-table/reorder.js +10 -0
- package/dist/inline-child-table/totals-footer.js +42 -0
- package/dist/lib/cn.js +4 -0
- package/dist/lib/guarded.js +12 -0
- package/dist/lib/use-debounced.js +13 -0
- package/dist/lib/use-focus-trap.js +46 -0
- package/dist/lib/use-media-query.js +12 -0
- package/dist/live/columns-from-model.js +93 -0
- package/dist/live/display-value.js +24 -0
- package/dist/live/field-values.js +32 -0
- package/dist/live/filter-string.js +135 -0
- package/dist/live/grid-loader.js +63 -0
- package/dist/live/model-helpers.js +30 -0
- package/dist/live/naming.js +9 -0
- package/dist/live/relation-labels.js +53 -0
- package/dist/live/use-filter-config.js +88 -0
- package/dist/live/use-live-rows.js +45 -0
- package/dist/live/view-fields.js +35 -0
- package/dist/mocks/activity.js +24 -0
- package/dist/mocks/invoices.js +13 -0
- package/dist/mocks/products.js +167 -0
- package/dist/mocks/rng.js +20 -0
- package/dist/mocks/users.js +63 -0
- package/dist/primitives/avatar.js +22 -0
- package/dist/primitives/badge.d.ts +1 -0
- package/dist/primitives/badge.js +25 -0
- package/dist/primitives/button.js +23 -0
- package/dist/primitives/checkbox.js +21 -0
- package/dist/primitives/dialog.js +41 -0
- package/dist/primitives/input.js +16 -0
- package/dist/primitives/kbd.js +8 -0
- package/dist/primitives/segmented.js +14 -0
- package/dist/primitives/sheet.js +44 -0
- package/dist/primitives/skeleton.js +6 -0
- package/dist/primitives/spinner.js +10 -0
- package/dist/primitives/switch.js +14 -0
- package/dist/primitives/tooltip.js +13 -0
- package/dist/record-view/action-rail.js +28 -0
- package/dist/record-view/field.js +62 -0
- package/dist/record-view/record-header.js +50 -0
- package/dist/record-view/record-sidebar.js +58 -0
- package/dist/record-view/section.js +21 -0
- package/dist/styles.css +132 -0
- package/dist/toasts/toast-provider.js +69 -0
- package/dist/toasts/toast.js +45 -0
- package/package.json +45 -3
- package/README.md +0 -3
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { navOpenStorageKey, readNavOpen, writeNavOpen } from "./nav-open-storage.js";
|
|
3
|
+
import { SidebarItem } from "./sidebar-item.js";
|
|
4
|
+
import { StatusDot } from "./status-dot.js";
|
|
5
|
+
import { useId, useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { ChevronRight } from "lucide-react";
|
|
8
|
+
//#region src/app-shell/sidebar-group.tsx
|
|
9
|
+
const row = {
|
|
10
|
+
"text-large": "h-8 gap-2.5 px-2.5 text-[13px]",
|
|
11
|
+
"text-small": "h-7 gap-2 px-2 text-xs"
|
|
12
|
+
};
|
|
13
|
+
/** Lines the records up under the entry's icon. */
|
|
14
|
+
const indent = {
|
|
15
|
+
"text-large": "ml-[20px]",
|
|
16
|
+
"text-small": "ml-[17px]"
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* A sidebar entry with a few of its records under it. The label opens the list, the chevron shows or hides the
|
|
20
|
+
* records (remembered in this browser), and "View all" at the end opens the list too.
|
|
21
|
+
*/
|
|
22
|
+
const SidebarGroup = ({ item, recent, mode, active, onNavigate }) => {
|
|
23
|
+
const storageKey = navOpenStorageKey(item.id);
|
|
24
|
+
const [open, setOpen] = useState(() => readNavOpen(localStorage, storageKey));
|
|
25
|
+
const listId = useId();
|
|
26
|
+
const href = item.href ?? `#${item.id}`;
|
|
27
|
+
const toggle = () => {
|
|
28
|
+
writeNavOpen(localStorage, storageKey, !open);
|
|
29
|
+
setOpen(!open);
|
|
30
|
+
};
|
|
31
|
+
const link = (to) => ({
|
|
32
|
+
href: to,
|
|
33
|
+
onClick: onNavigate && ((event) => {
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
onNavigate(to);
|
|
36
|
+
})
|
|
37
|
+
});
|
|
38
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
39
|
+
className: "flex flex-col gap-0.5",
|
|
40
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
41
|
+
className: "relative",
|
|
42
|
+
children: [/* @__PURE__ */ jsx(SidebarItem, {
|
|
43
|
+
item,
|
|
44
|
+
mode,
|
|
45
|
+
active,
|
|
46
|
+
onNavigate,
|
|
47
|
+
className: "pr-8"
|
|
48
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
49
|
+
type: "button",
|
|
50
|
+
"aria-expanded": open,
|
|
51
|
+
"aria-controls": listId,
|
|
52
|
+
"aria-label": `Recent ${item.label.toLowerCase()}`,
|
|
53
|
+
onClick: toggle,
|
|
54
|
+
className: "absolute right-1 top-1/2 inline-flex size-6 -translate-y-1/2 items-center justify-center rounded text-faint-foreground hover:bg-muted hover:text-foreground",
|
|
55
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: cn("size-3.5 transition-transform", open && "rotate-90") })
|
|
56
|
+
})]
|
|
57
|
+
}), open && /* @__PURE__ */ jsxs("ul", {
|
|
58
|
+
id: listId,
|
|
59
|
+
"aria-label": `Recent ${item.label.toLowerCase()}`,
|
|
60
|
+
className: cn("flex flex-col gap-0.5 border-l pl-1.5", indent[mode]),
|
|
61
|
+
children: [
|
|
62
|
+
recent.state === "loading" && /* @__PURE__ */ jsx("li", {
|
|
63
|
+
className: cn("flex items-center text-faint-foreground", row[mode]),
|
|
64
|
+
children: "Loading..."
|
|
65
|
+
}),
|
|
66
|
+
recent.state === "error" && /* @__PURE__ */ jsx("li", {
|
|
67
|
+
className: cn("flex items-center text-faint-foreground", row[mode]),
|
|
68
|
+
children: "Could not load"
|
|
69
|
+
}),
|
|
70
|
+
recent.state === "ready" && recent.records.map((record) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", {
|
|
71
|
+
...link(record.href),
|
|
72
|
+
"aria-label": `${record.label}, ${record.status}`,
|
|
73
|
+
title: `${record.label} (${record.status})`,
|
|
74
|
+
"aria-current": record.active ? "page" : void 0,
|
|
75
|
+
className: cn("flex min-w-0 items-center rounded-md font-medium transition-colors", row[mode], record.active ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"),
|
|
76
|
+
children: [/* @__PURE__ */ jsx(StatusDot, {
|
|
77
|
+
tone: record.tone,
|
|
78
|
+
pulse: record.pulse
|
|
79
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
80
|
+
className: "truncate",
|
|
81
|
+
children: record.label
|
|
82
|
+
})]
|
|
83
|
+
}) }, record.id)),
|
|
84
|
+
/* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", {
|
|
85
|
+
...link(href),
|
|
86
|
+
className: cn("flex items-center rounded-md text-faint-foreground transition-colors hover:bg-muted hover:text-foreground", row[mode]),
|
|
87
|
+
children: "View all"
|
|
88
|
+
}) })
|
|
89
|
+
]
|
|
90
|
+
})]
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
94
|
+
export { SidebarGroup };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { Tooltip } from "../primitives/tooltip.js";
|
|
3
|
+
import { isTextMode, showsLabelInline } from "./sidebar-mode.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/app-shell/sidebar-item.tsx
|
|
6
|
+
const layout = {
|
|
7
|
+
"text-large": "h-9 gap-3 px-3 text-sm",
|
|
8
|
+
"text-small": "h-7 gap-2.5 px-2.5 text-xs",
|
|
9
|
+
icon: "size-9 justify-center",
|
|
10
|
+
"icon-label": "w-full flex-col gap-0.5 py-1.5 text-[10px]",
|
|
11
|
+
"icon-tooltip": "size-9 justify-center",
|
|
12
|
+
"icon-expand": "h-8 gap-3 px-3 text-[13px]"
|
|
13
|
+
};
|
|
14
|
+
const SidebarItem = ({ item, mode, active, onNavigate, className }) => {
|
|
15
|
+
const Icon = item.icon;
|
|
16
|
+
const button = /* @__PURE__ */ jsxs("a", {
|
|
17
|
+
onClick: onNavigate && ((event) => {
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
onNavigate(item.href ?? `#${item.id}`);
|
|
20
|
+
}),
|
|
21
|
+
href: item.href ?? `#${item.id}`,
|
|
22
|
+
"aria-label": item.label,
|
|
23
|
+
"aria-current": active ? "page" : void 0,
|
|
24
|
+
className: cn("flex shrink-0 items-center rounded-md font-medium transition-colors", layout[mode], active ? "bg-primary-soft text-primary-text" : "text-muted-foreground hover:bg-muted hover:text-foreground", className),
|
|
25
|
+
children: [
|
|
26
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
27
|
+
className: cn("shrink-0", mode === "text-small" ? "size-4" : "size-[18px]"),
|
|
28
|
+
strokeWidth: active ? 2.2 : 1.9
|
|
29
|
+
}),
|
|
30
|
+
(showsLabelInline(mode) || mode === "icon-label") && /* @__PURE__ */ jsx("span", {
|
|
31
|
+
className: "whitespace-nowrap",
|
|
32
|
+
children: item.label
|
|
33
|
+
}),
|
|
34
|
+
isTextMode(mode) && item.count && /* @__PURE__ */ jsx("span", {
|
|
35
|
+
className: "ml-auto rounded bg-muted px-1.5 text-[11px] tabular-nums text-muted-foreground",
|
|
36
|
+
children: item.count
|
|
37
|
+
})
|
|
38
|
+
]
|
|
39
|
+
});
|
|
40
|
+
return mode === "icon-tooltip" ? /* @__PURE__ */ jsx(Tooltip, {
|
|
41
|
+
label: item.label,
|
|
42
|
+
children: button
|
|
43
|
+
}) : button;
|
|
44
|
+
};
|
|
45
|
+
//#endregion
|
|
46
|
+
export { SidebarItem };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/app-shell/sidebar-mode.ts
|
|
2
|
+
const showsLabelInline = (mode) => mode === "text-large" || mode === "text-small" || mode === "icon-expand";
|
|
3
|
+
const isTextMode = (mode) => mode === "text-large" || mode === "text-small";
|
|
4
|
+
const sidebarWidth = {
|
|
5
|
+
"text-large": "w-60",
|
|
6
|
+
"text-small": "w-52",
|
|
7
|
+
icon: "w-14",
|
|
8
|
+
"icon-label": "w-[76px]",
|
|
9
|
+
"icon-tooltip": "w-14",
|
|
10
|
+
"icon-expand": "w-14"
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { isTextMode, showsLabelInline, sidebarWidth };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { Logo } from "./logo.js";
|
|
3
|
+
import { navGroups } from "./nav.js";
|
|
4
|
+
import { ProfileMenu } from "./profile-menu.js";
|
|
5
|
+
import { isTextMode, sidebarWidth } from "./sidebar-mode.js";
|
|
6
|
+
import { SidebarItem } from "./sidebar-item.js";
|
|
7
|
+
import { SidebarGroup } from "./sidebar-group.js";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
//#region src/app-shell/sidebar.tsx
|
|
10
|
+
const Sidebar = ({ mode, activeItem, user, workspace, forceExpanded, profileMenuOpen, groups = navGroups, userMenu, onNavigate, onSignOut }) => {
|
|
11
|
+
const expanding = mode === "icon-expand";
|
|
12
|
+
const expanded = expanding && forceExpanded;
|
|
13
|
+
const inline = isTextMode(mode) || expanding;
|
|
14
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
15
|
+
"aria-label": "Resources",
|
|
16
|
+
className: cn("relative z-30 shrink-0", sidebarWidth[mode]),
|
|
17
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
18
|
+
className: cn("group/side absolute inset-y-0 left-0 flex flex-col overflow-hidden border-r bg-sidebar", expanding ? "w-14 transition-[width,box-shadow] duration-150 hover:w-60 hover:shadow-pop focus-within:w-60 focus-within:shadow-pop" : "w-full", expanded && "w-60 shadow-pop"),
|
|
19
|
+
children: [
|
|
20
|
+
/* @__PURE__ */ jsx("div", {
|
|
21
|
+
className: cn("flex h-12 shrink-0 items-center px-3.5", mode === "icon-label" && "justify-center px-0"),
|
|
22
|
+
children: /* @__PURE__ */ jsx(Logo, {
|
|
23
|
+
showName: isTextMode(mode) || expanding,
|
|
24
|
+
workspace
|
|
25
|
+
})
|
|
26
|
+
}),
|
|
27
|
+
/* @__PURE__ */ jsx("div", {
|
|
28
|
+
className: cn("flex-1 overflow-y-auto overflow-x-hidden pb-2", mode === "icon-label" ? "px-2" : "px-2"),
|
|
29
|
+
children: groups.map((group, index) => /* @__PURE__ */ jsxs("div", {
|
|
30
|
+
className: cn("flex flex-col gap-0.5", index > 0 && "mt-3"),
|
|
31
|
+
children: [inline ? /* @__PURE__ */ jsx("div", {
|
|
32
|
+
className: cn("px-3 pb-1 pt-1 text-[11px] font-medium uppercase tracking-wide text-faint-foreground", expanding && "h-4 whitespace-nowrap py-0 opacity-0 transition-opacity group-hover/side:opacity-100 group-focus-within/side:opacity-100", expanded && "opacity-100"),
|
|
33
|
+
children: group.label
|
|
34
|
+
}) : index > 0 && /* @__PURE__ */ jsx("div", { className: "mx-auto mb-1 h-px w-6 bg-border" }), group.items.map((item) => item.recent && isTextMode(mode) ? /* @__PURE__ */ jsx(SidebarGroup, {
|
|
35
|
+
item,
|
|
36
|
+
recent: item.recent,
|
|
37
|
+
mode,
|
|
38
|
+
active: item.id === activeItem,
|
|
39
|
+
onNavigate
|
|
40
|
+
}, item.id) : /* @__PURE__ */ jsx(SidebarItem, {
|
|
41
|
+
item,
|
|
42
|
+
mode,
|
|
43
|
+
active: item.id === activeItem,
|
|
44
|
+
onNavigate
|
|
45
|
+
}, item.id))]
|
|
46
|
+
}, group.label))
|
|
47
|
+
}),
|
|
48
|
+
/* @__PURE__ */ jsx("div", {
|
|
49
|
+
className: cn("shrink-0 border-t p-2", mode !== "text-large" && mode !== "text-small" && !expanding && "flex justify-center"),
|
|
50
|
+
children: /* @__PURE__ */ jsx(ProfileMenu, {
|
|
51
|
+
user,
|
|
52
|
+
compact: !inline,
|
|
53
|
+
open: profileMenuOpen,
|
|
54
|
+
items: userMenu,
|
|
55
|
+
onNavigate,
|
|
56
|
+
onSignOut
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
]
|
|
60
|
+
})
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
//#endregion
|
|
64
|
+
export { Sidebar };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "@protobase/schema";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region src/app-shell/status-dot.tsx
|
|
4
|
+
const colour = {
|
|
5
|
+
neutral: "bg-faint-foreground",
|
|
6
|
+
info: "bg-primary",
|
|
7
|
+
success: "bg-success",
|
|
8
|
+
warning: "bg-warning",
|
|
9
|
+
danger: "bg-danger"
|
|
10
|
+
};
|
|
11
|
+
/** A record's status as a coloured dot; `pulse` animates a ring around it for states in progress (still under reduced motion). */
|
|
12
|
+
const StatusDot = ({ tone, pulse = false, className }) => /* @__PURE__ */ jsxs("span", {
|
|
13
|
+
"aria-hidden": true,
|
|
14
|
+
className: cn("relative flex size-2 shrink-0", className),
|
|
15
|
+
"data-tone": tone,
|
|
16
|
+
"data-pulse": pulse || void 0,
|
|
17
|
+
children: [pulse && /* @__PURE__ */ jsx("span", { className: cn("absolute inset-0 animate-ping rounded-full opacity-60 motion-reduce:animate-none", colour[tone]) }), /* @__PURE__ */ jsx("span", { className: cn("relative size-2 rounded-full", colour[tone]) })]
|
|
18
|
+
});
|
|
19
|
+
//#endregion
|
|
20
|
+
export { StatusDot };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { Breadcrumb } from "./breadcrumb.js";
|
|
3
|
+
import { GlobalSearch } from "./global-search.js";
|
|
4
|
+
import { useEffect, useRef, useState } from "react";
|
|
5
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { Menu, Search, X } from "lucide-react";
|
|
7
|
+
//#region src/app-shell/top-bar.tsx
|
|
8
|
+
const iconButton = "inline-flex size-11 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted md:hidden";
|
|
9
|
+
const TopBar = ({ breadcrumb, onNavigate, actions, onMenu, menuOpen, menuControls, search }) => {
|
|
10
|
+
const [searchOpen, setSearchOpen] = useState(false);
|
|
11
|
+
const desktopSearch = useRef(null);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (!search) return;
|
|
14
|
+
const onKey = (event) => {
|
|
15
|
+
if (event.key.toLowerCase() !== "k" || !(event.metaKey || event.ctrlKey)) return;
|
|
16
|
+
event.preventDefault();
|
|
17
|
+
const input = desktopSearch.current;
|
|
18
|
+
if (input && input.offsetParent !== null) input.focus();
|
|
19
|
+
else setSearchOpen(true);
|
|
20
|
+
};
|
|
21
|
+
window.addEventListener("keydown", onKey);
|
|
22
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
23
|
+
}, [search]);
|
|
24
|
+
return /* @__PURE__ */ jsxs("header", {
|
|
25
|
+
className: "relative flex h-12 shrink-0 items-center gap-1 border-b bg-background px-2 md:gap-4 md:px-5",
|
|
26
|
+
children: [
|
|
27
|
+
onMenu && /* @__PURE__ */ jsx("button", {
|
|
28
|
+
type: "button",
|
|
29
|
+
"aria-label": "Open navigation",
|
|
30
|
+
"aria-expanded": Boolean(menuOpen),
|
|
31
|
+
"aria-controls": menuControls,
|
|
32
|
+
onClick: onMenu,
|
|
33
|
+
className: cn(iconButton, "-ml-0.5"),
|
|
34
|
+
children: /* @__PURE__ */ jsx(Menu, { className: "size-5" })
|
|
35
|
+
}),
|
|
36
|
+
/* @__PURE__ */ jsx(Breadcrumb, {
|
|
37
|
+
parts: breadcrumb,
|
|
38
|
+
onNavigate
|
|
39
|
+
}),
|
|
40
|
+
/* @__PURE__ */ jsxs("div", {
|
|
41
|
+
className: "ml-auto flex items-center gap-1 md:gap-3",
|
|
42
|
+
children: [search && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
43
|
+
className: "hidden md:block",
|
|
44
|
+
children: /* @__PURE__ */ jsx(GlobalSearch, {
|
|
45
|
+
ref: desktopSearch,
|
|
46
|
+
search
|
|
47
|
+
})
|
|
48
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
49
|
+
type: "button",
|
|
50
|
+
"aria-label": "Search",
|
|
51
|
+
"aria-expanded": searchOpen,
|
|
52
|
+
onClick: () => setSearchOpen(true),
|
|
53
|
+
className: iconButton,
|
|
54
|
+
children: /* @__PURE__ */ jsx(Search, { className: "size-5" })
|
|
55
|
+
})] }), actions]
|
|
56
|
+
}),
|
|
57
|
+
search && searchOpen && /* @__PURE__ */ jsxs("div", {
|
|
58
|
+
className: "absolute inset-0 z-20 flex items-center gap-1 bg-background px-3 md:hidden",
|
|
59
|
+
children: [/* @__PURE__ */ jsx(GlobalSearch, {
|
|
60
|
+
className: "min-w-0 flex-1",
|
|
61
|
+
autoFocus: true,
|
|
62
|
+
showShortcut: false,
|
|
63
|
+
search: {
|
|
64
|
+
...search,
|
|
65
|
+
onSelect: (href) => {
|
|
66
|
+
search.onSelect(href);
|
|
67
|
+
setSearchOpen(false);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
71
|
+
type: "button",
|
|
72
|
+
"aria-label": "Close search",
|
|
73
|
+
onClick: () => setSearchOpen(false),
|
|
74
|
+
className: iconButton,
|
|
75
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-5" })
|
|
76
|
+
})]
|
|
77
|
+
})
|
|
78
|
+
]
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
//#endregion
|
|
82
|
+
export { TopBar };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DockItem } from "./model.js";
|
|
2
|
+
import { DockItemHandlers } from "./dock-item.js";
|
|
3
|
+
//#region src/assistant-dock/assistant-dock.d.ts
|
|
4
|
+
export type AssistantDockProps = DockItemHandlers & {
|
|
5
|
+
/** The chat in order: messages and the cards of its tasks and queries. */
|
|
6
|
+
items: DockItem[];
|
|
7
|
+
/** The organization's credits; Approve needs the plan's quote. */
|
|
8
|
+
balance: number;
|
|
9
|
+
replying?: boolean;
|
|
10
|
+
onSend?: (text: string) => void;
|
|
11
|
+
onClose?: () => void;
|
|
12
|
+
};
|
|
13
|
+
/** The assistant beside the app: chat, plans to approve, the queue, progress, results and query tables. */
|
|
14
|
+
export declare const AssistantDock: ({ items, balance, replying, onSend, onClose, onRequestChanges, ...handlers }: AssistantDockProps) => import("react").JSX.Element;
|
|
15
|
+
//#endregion
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Button } from "../primitives/button.js";
|
|
2
|
+
import { Composer } from "./composer.js";
|
|
3
|
+
import { formatCredits } from "./model.js";
|
|
4
|
+
import { DockItemView } from "./dock-item.js";
|
|
5
|
+
import { useEffect, useRef, useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { Sparkles, X } from "lucide-react";
|
|
8
|
+
//#region src/assistant-dock/assistant-dock.tsx
|
|
9
|
+
/** The assistant beside the app: chat, plans to approve, the queue, progress, results and query tables. */
|
|
10
|
+
const AssistantDock = ({ items, balance, replying, onSend, onClose, onRequestChanges, ...handlers }) => {
|
|
11
|
+
const list = useRef(null);
|
|
12
|
+
const input = useRef(null);
|
|
13
|
+
const [revising, setRevising] = useState(false);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const element = list.current;
|
|
16
|
+
if (element) element.scrollTop = element.scrollHeight;
|
|
17
|
+
}, [items.length]);
|
|
18
|
+
const requestChanges = (taskId) => {
|
|
19
|
+
setRevising(true);
|
|
20
|
+
input.current?.focus();
|
|
21
|
+
onRequestChanges?.(taskId);
|
|
22
|
+
};
|
|
23
|
+
const send = (text) => {
|
|
24
|
+
setRevising(false);
|
|
25
|
+
onSend?.(text);
|
|
26
|
+
};
|
|
27
|
+
return /* @__PURE__ */ jsxs("aside", {
|
|
28
|
+
"aria-label": "Assistant",
|
|
29
|
+
className: "flex h-full w-[380px] max-w-full shrink-0 flex-col border-l border-border bg-surface",
|
|
30
|
+
children: [
|
|
31
|
+
/* @__PURE__ */ jsxs("header", {
|
|
32
|
+
className: "flex h-12 shrink-0 items-center gap-2 border-b border-border bg-background px-4",
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ jsx(Sparkles, { className: "size-4 text-primary" }),
|
|
35
|
+
/* @__PURE__ */ jsx("h2", {
|
|
36
|
+
className: "flex-1 text-[13px] font-semibold",
|
|
37
|
+
children: "Assistant"
|
|
38
|
+
}),
|
|
39
|
+
/* @__PURE__ */ jsx("span", {
|
|
40
|
+
className: "text-xs font-medium text-muted-foreground",
|
|
41
|
+
children: formatCredits(balance)
|
|
42
|
+
}),
|
|
43
|
+
onClose && /* @__PURE__ */ jsx(Button, {
|
|
44
|
+
variant: "ghost",
|
|
45
|
+
size: "sm",
|
|
46
|
+
className: "w-7 px-0",
|
|
47
|
+
"aria-label": "Close",
|
|
48
|
+
onClick: onClose,
|
|
49
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-4" })
|
|
50
|
+
})
|
|
51
|
+
]
|
|
52
|
+
}),
|
|
53
|
+
/* @__PURE__ */ jsxs("div", {
|
|
54
|
+
ref: list,
|
|
55
|
+
className: "flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4",
|
|
56
|
+
children: [items.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
57
|
+
className: "m-auto max-w-60 text-center text-[13px] text-muted-foreground",
|
|
58
|
+
children: "Ask for a change to this app, or a question about its data."
|
|
59
|
+
}), items.map((item) => /* @__PURE__ */ jsx(DockItemView, {
|
|
60
|
+
item,
|
|
61
|
+
balance,
|
|
62
|
+
handlers: {
|
|
63
|
+
...handlers,
|
|
64
|
+
onRequestChanges: requestChanges
|
|
65
|
+
}
|
|
66
|
+
}, item.id))]
|
|
67
|
+
}),
|
|
68
|
+
/* @__PURE__ */ jsx("footer", {
|
|
69
|
+
className: "shrink-0 border-t border-border bg-background p-3",
|
|
70
|
+
children: /* @__PURE__ */ jsx(Composer, {
|
|
71
|
+
inputRef: input,
|
|
72
|
+
onSend: send,
|
|
73
|
+
replying,
|
|
74
|
+
placeholder: revising ? "What should change in the plan?" : "Ask for a change or about your data..."
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
]
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
//#endregion
|
|
81
|
+
export { AssistantDock };
|
|
@@ -0,0 +1,9 @@
|
|
|
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 };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Input } from "../primitives/input.js";
|
|
2
|
+
import { Button } from "../primitives/button.js";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { SendHorizontal } from "lucide-react";
|
|
6
|
+
//#region src/assistant-dock/composer.tsx
|
|
7
|
+
const Composer = ({ onSend, replying, placeholder, inputRef }) => {
|
|
8
|
+
const [text, setText] = useState("");
|
|
9
|
+
const message = text.trim();
|
|
10
|
+
const send = () => {
|
|
11
|
+
if (!message || replying) return;
|
|
12
|
+
onSend?.(message);
|
|
13
|
+
setText("");
|
|
14
|
+
};
|
|
15
|
+
return /* @__PURE__ */ jsxs("form", {
|
|
16
|
+
className: "flex gap-2",
|
|
17
|
+
onSubmit: (event) => {
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
send();
|
|
20
|
+
},
|
|
21
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
22
|
+
ref: inputRef,
|
|
23
|
+
"aria-label": "Message",
|
|
24
|
+
placeholder,
|
|
25
|
+
value: text,
|
|
26
|
+
onChange: (event) => setText(event.target.value),
|
|
27
|
+
wrapperClassName: "flex-1"
|
|
28
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
29
|
+
type: "submit",
|
|
30
|
+
variant: "primary",
|
|
31
|
+
className: "w-8 px-0",
|
|
32
|
+
"aria-label": "Send",
|
|
33
|
+
disabled: !message || replying,
|
|
34
|
+
children: /* @__PURE__ */ jsx(SendHorizontal, { className: "size-4" })
|
|
35
|
+
})]
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
export { Composer };
|
|
@@ -0,0 +1,37 @@
|
|
|
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 };
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
|
@@ -0,0 +1,26 @@
|
|
|
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 };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Plan } from "./model.js";
|
|
2
|
+
//#region src/assistant-dock/plan-card.d.ts
|
|
3
|
+
export type PlanCardProps = {
|
|
4
|
+
plan: Plan;
|
|
5
|
+
balance: number;
|
|
6
|
+
onApprove?: (taskId: string) => void;
|
|
7
|
+
/** Asks for the next revision; the dock moves to the chat box for what should change. */
|
|
8
|
+
onRequestChanges?: (taskId: string) => void;
|
|
9
|
+
onCancel?: (taskId: string) => void;
|
|
10
|
+
/** A free re-plan from the moved branch. */
|
|
11
|
+
onUpdatePlan?: (taskId: string) => void;
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|