@growthub/cli 0.10.0 → 0.12.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/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +307 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/query/route.js +372 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/receipts/route.js +47 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +664 -82
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +1371 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +1383 -24
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +7 -21
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/ownership/ownership-panel.jsx +222 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/ownership/page.jsx +19 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/settings-shell.jsx +2 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +116 -24
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +497 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/growthub.config.json +20 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-intelligence.js +19 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +23 -5
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper-apply.js +473 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +583 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package-lock.json +34 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +3 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/export-training-traces.mjs +144 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/grade-raw-pairs.mjs +279 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/harvest-cursor-traces.mjs +288 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/helpers/upload-graded-traces.mjs +128 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +19 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/templates/seeded-configs/alignment-loop.config.json +264 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/workers/custom-workspace-operator/CLAUDE.md +38 -0
- package/dist/index.js +1416 -2627
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import { readAdapterConfig } from "@/lib/adapters/env";
|
|
|
3
3
|
import { describeIntegrationAdapter, listGovernedWorkspaceIntegrations } from "@/lib/adapters/integrations";
|
|
4
4
|
import { groupIntegrationsByLane } from "@/lib/domain/integrations";
|
|
5
5
|
import { readWorkspaceConfig } from "@/lib/workspace-config";
|
|
6
|
+
import { WorkspaceRail } from "../../workspace-rail.jsx";
|
|
6
7
|
|
|
7
8
|
function countConnected(rows) {
|
|
8
9
|
return rows.filter((item) => item.isConnected || item.status === "connected").length;
|
|
@@ -57,28 +58,13 @@ async function IntegrationsSettingsPage() {
|
|
|
57
58
|
const allRows = [...grouped.dataSources, ...grouped.workspaceIntegrations];
|
|
58
59
|
|
|
59
60
|
return <main className="workspace-builder workspace-settings-page">
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
color: branding.logoUrl ? undefined : textColorForAccent(branding.accent)
|
|
65
|
-
}}>
|
|
66
|
-
{branding.logoUrl ? <img src={branding.logoUrl} alt="" /> : workspaceName.slice(0, 1).toUpperCase()}
|
|
67
|
-
</span>
|
|
68
|
-
<span>{workspaceName}</span>
|
|
69
|
-
</div>
|
|
70
|
-
<nav className="workspace-nav">
|
|
71
|
-
<Link href="/">Dashboards</Link>
|
|
72
|
-
<Link href="/data-model">Data Model</Link>
|
|
61
|
+
<WorkspaceRail
|
|
62
|
+
workspaceConfig={workspaceConfig}
|
|
63
|
+
authority={adapter.authority}
|
|
64
|
+
managementSlot={(
|
|
73
65
|
<Link className="active" href="/settings/integrations">Integrations</Link>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
</nav>
|
|
77
|
-
<div className="workspace-rail-status">
|
|
78
|
-
<span className="status-dot" />
|
|
79
|
-
{adapter.authority}
|
|
80
|
-
</div>
|
|
81
|
-
</aside>
|
|
66
|
+
)}
|
|
67
|
+
/>
|
|
82
68
|
|
|
83
69
|
<section className="workspace-surface">
|
|
84
70
|
<header className="workspace-toolbar">
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ownership settings panel — the inspect-only "Management" surface that
|
|
5
|
+
* previously lived behind a modal triggered from the nav rail. Moved here
|
|
6
|
+
* as the 4th Workspace Settings tab so the rail can collapse its
|
|
7
|
+
* Management item into the renamed "Management" link (formerly Data
|
|
8
|
+
* Model) without losing the readiness/diagnostics surface.
|
|
9
|
+
*
|
|
10
|
+
* Pure presentational + one fetch effect for the live resolver list.
|
|
11
|
+
* Workflow execution stays in `growthub workflow` / `growthub bridge`;
|
|
12
|
+
* this panel never executes, never calls hosted endpoints, and never
|
|
13
|
+
* exposes tokens.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
17
|
+
|
|
18
|
+
const DEFAULT_PERSISTENCE = {
|
|
19
|
+
mode: "unknown",
|
|
20
|
+
canSave: false,
|
|
21
|
+
reason: "Persistence mode could not be resolved.",
|
|
22
|
+
guidance: "",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function ResolverManagementSection({ canSave, config }) {
|
|
26
|
+
const [resolverData, setResolverData] = useState(null);
|
|
27
|
+
const [uploading, setUploading] = useState(false);
|
|
28
|
+
const [uploadResult, setUploadResult] = useState(null);
|
|
29
|
+
const fileInputRef = useRef(null);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
fetch("/api/workspace/resolvers")
|
|
33
|
+
.then((r) => (r.ok ? r.json() : { files: [], registeredIds: [], resolvers: [], canUpload: false }))
|
|
34
|
+
.then(setResolverData)
|
|
35
|
+
.catch(() => setResolverData({ files: [], registeredIds: [], resolvers: [], canUpload: false }));
|
|
36
|
+
}, [uploadResult]);
|
|
37
|
+
|
|
38
|
+
const dataModelObjects = Array.isArray(config?.dataModel?.objects) ? config.dataModel.objects : [];
|
|
39
|
+
|
|
40
|
+
const linkedObjectsByResolver = useMemo(() => {
|
|
41
|
+
const map = {};
|
|
42
|
+
dataModelObjects.forEach((obj) => {
|
|
43
|
+
const intId = obj.binding?.integrationId;
|
|
44
|
+
if (!intId) return;
|
|
45
|
+
if (!map[intId]) map[intId] = [];
|
|
46
|
+
map[intId].push(obj);
|
|
47
|
+
});
|
|
48
|
+
return map;
|
|
49
|
+
}, [dataModelObjects]);
|
|
50
|
+
|
|
51
|
+
async function handleFileChange(event) {
|
|
52
|
+
const file = event.target.files?.[0];
|
|
53
|
+
if (!file) return;
|
|
54
|
+
setUploading(true);
|
|
55
|
+
setUploadResult(null);
|
|
56
|
+
const form = new FormData();
|
|
57
|
+
form.append("file", file);
|
|
58
|
+
try {
|
|
59
|
+
const res = await fetch("/api/workspace/register-resolver", { method: "POST", body: form });
|
|
60
|
+
const data = await res.json();
|
|
61
|
+
setUploadResult(res.ok ? { ok: true, ...data } : { ok: false, ...data });
|
|
62
|
+
} catch {
|
|
63
|
+
setUploadResult({ ok: false, error: "Network error" });
|
|
64
|
+
} finally {
|
|
65
|
+
setUploading(false);
|
|
66
|
+
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const resolvers = resolverData?.resolvers || [];
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<article className="workspace-readiness-section">
|
|
74
|
+
<h3>Source Resolvers</h3>
|
|
75
|
+
<div className="workspace-readiness-row">
|
|
76
|
+
<span>Files</span>
|
|
77
|
+
<code>{resolverData ? resolverData.files.length : "…"}</code>
|
|
78
|
+
</div>
|
|
79
|
+
<div className="workspace-readiness-row">
|
|
80
|
+
<span>Registered</span>
|
|
81
|
+
<code>{resolverData ? resolvers.length : "…"}</code>
|
|
82
|
+
</div>
|
|
83
|
+
<div className="workspace-readiness-row">
|
|
84
|
+
<span>Data Model objects</span>
|
|
85
|
+
<code>{dataModelObjects.length}</code>
|
|
86
|
+
</div>
|
|
87
|
+
{canSave ? (
|
|
88
|
+
<>
|
|
89
|
+
<div className="workspace-readiness-row">
|
|
90
|
+
<span>Upload resolver</span>
|
|
91
|
+
<input ref={fileInputRef} type="file" accept=".js" style={{ display: "none" }} onChange={handleFileChange} />
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
className="workspace-readiness-action"
|
|
95
|
+
disabled={uploading}
|
|
96
|
+
onClick={() => fileInputRef.current?.click()}
|
|
97
|
+
>
|
|
98
|
+
{uploading ? "Uploading…" : "Upload .js file"}
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
{uploadResult && (
|
|
102
|
+
<div className={`workspace-readiness-row resolver-upload-result ${uploadResult.ok ? "good" : "error"}`}>
|
|
103
|
+
<span>{uploadResult.ok ? "Saved" : "Error"}</span>
|
|
104
|
+
<em>{uploadResult.ok ? uploadResult.path : uploadResult.error}</em>
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
<p className="workspace-panel-hint">
|
|
108
|
+
Upload a <code>.js</code> resolver file that calls <code>registerSourceResolver()</code>.
|
|
109
|
+
</p>
|
|
110
|
+
</>
|
|
111
|
+
) : (
|
|
112
|
+
<div className="workspace-readiness-row">
|
|
113
|
+
<span>Upload</span>
|
|
114
|
+
<em>
|
|
115
|
+
Requires <code>WORKSPACE_CONFIG_ALLOW_FS_WRITE=true</code> or add resolver files manually to{" "}
|
|
116
|
+
<code>lib/adapters/integrations/resolvers/</code>.
|
|
117
|
+
</em>
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
</article>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function OwnershipPanel({ config, persistence, adapterConfig }) {
|
|
125
|
+
const persist = persistence || DEFAULT_PERSISTENCE;
|
|
126
|
+
const pipelines = Array.isArray(config?.pipelines) ? config.pipelines : [];
|
|
127
|
+
const integrations = Array.isArray(config?.integrations) ? config.integrations : [];
|
|
128
|
+
const capabilities = Array.isArray(config?.capabilities) ? config.capabilities : [];
|
|
129
|
+
return (
|
|
130
|
+
<section className="workspace-settings-card workspace-ownership-card">
|
|
131
|
+
<div className="workspace-settings-card-heading">
|
|
132
|
+
<div>
|
|
133
|
+
<h2>Ownership</h2>
|
|
134
|
+
<p>
|
|
135
|
+
Inspect-only readiness, persistence, integrations, workflows, and source resolvers for
|
|
136
|
+
this governed workspace. Workflow execution stays in <code>growthub workflow</code> /
|
|
137
|
+
<code> growthub bridge</code>; this panel never executes, never calls hosted endpoints, and
|
|
138
|
+
never exposes tokens.
|
|
139
|
+
</p>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div className="workspace-readiness">
|
|
144
|
+
<article className="workspace-readiness-section">
|
|
145
|
+
<h3>Workspace</h3>
|
|
146
|
+
<div className="workspace-readiness-row"><span>ID</span><code>{config?.id || "Unknown"}</code></div>
|
|
147
|
+
<div className="workspace-readiness-row"><span>Name</span><strong>{config?.name || "Workspace"}</strong></div>
|
|
148
|
+
<div className="workspace-readiness-row">
|
|
149
|
+
<span>Capabilities</span>
|
|
150
|
+
<span>{capabilities.length ? capabilities.join(", ") : "none"}</span>
|
|
151
|
+
</div>
|
|
152
|
+
</article>
|
|
153
|
+
|
|
154
|
+
<article className="workspace-readiness-section">
|
|
155
|
+
<h3>API</h3>
|
|
156
|
+
<div className="workspace-readiness-row"><span>PATCH allowlist</span><code>dashboards | widgetTypes | canvas | dataModel</code></div>
|
|
157
|
+
<div className="workspace-readiness-row"><span>Unknown field</span><code>400</code></div>
|
|
158
|
+
<div className="workspace-readiness-row"><span>Read-only runtime</span><code>409 + guidance</code></div>
|
|
159
|
+
<div className="workspace-readiness-row">
|
|
160
|
+
<span>Can save now</span>
|
|
161
|
+
<span className={`workspace-readiness-badge ${persist.canSave ? "good" : "warn"}`}>
|
|
162
|
+
{persist.canSave ? "yes" : "no"}
|
|
163
|
+
</span>
|
|
164
|
+
</div>
|
|
165
|
+
</article>
|
|
166
|
+
|
|
167
|
+
<article className="workspace-readiness-section">
|
|
168
|
+
<h3>Workflows</h3>
|
|
169
|
+
{pipelines.length === 0 ? (
|
|
170
|
+
<div className="workspace-readiness-row workspace-readiness-empty">
|
|
171
|
+
<em>
|
|
172
|
+
No workflows declared in <code>growthub.config.json</code>. Connect via{" "}
|
|
173
|
+
<code>growthub workflow</code> after Bridge auth.
|
|
174
|
+
</em>
|
|
175
|
+
</div>
|
|
176
|
+
) : (
|
|
177
|
+
pipelines.map((pipeline, index) => (
|
|
178
|
+
<div className="workspace-readiness-row" key={pipeline.id || index}>
|
|
179
|
+
<span>{pipeline.id || `pipeline-${index}`}</span>
|
|
180
|
+
<strong>{pipeline.name || "Untitled"}</strong>
|
|
181
|
+
</div>
|
|
182
|
+
))
|
|
183
|
+
)}
|
|
184
|
+
</article>
|
|
185
|
+
|
|
186
|
+
<article className="workspace-readiness-section">
|
|
187
|
+
<h3>Integrations</h3>
|
|
188
|
+
<div className="workspace-readiness-row"><span>Adapter</span><code>{adapterConfig?.integrationAdapter || "—"}</code></div>
|
|
189
|
+
<div className="workspace-readiness-row"><span>Deploy target</span><code>{adapterConfig?.deployTarget || "—"}</code></div>
|
|
190
|
+
{integrations.length === 0 ? (
|
|
191
|
+
<div className="workspace-readiness-row workspace-readiness-empty">
|
|
192
|
+
<em>
|
|
193
|
+
No static integrations declared. Use <code>growthub bridge agents bind</code> for hosted bindings.
|
|
194
|
+
</em>
|
|
195
|
+
</div>
|
|
196
|
+
) : (
|
|
197
|
+
integrations.map((integration, index) => (
|
|
198
|
+
<div className="workspace-readiness-row" key={integration.id || index}>
|
|
199
|
+
<span>{integration.id || `integration-${index}`}</span>
|
|
200
|
+
<strong>{integration.name || "Untitled"}</strong>
|
|
201
|
+
</div>
|
|
202
|
+
))
|
|
203
|
+
)}
|
|
204
|
+
</article>
|
|
205
|
+
|
|
206
|
+
<article className="workspace-readiness-section">
|
|
207
|
+
<h3>Persistence</h3>
|
|
208
|
+
<div className="workspace-readiness-row">
|
|
209
|
+
<span>Mode</span>
|
|
210
|
+
<span className={`workspace-readiness-badge mode-${persist.mode}`}>{persist.mode}</span>
|
|
211
|
+
</div>
|
|
212
|
+
<div className="workspace-readiness-row"><span>Reason</span><em>{persist.reason}</em></div>
|
|
213
|
+
{persist.guidance ? (
|
|
214
|
+
<div className="workspace-readiness-row"><span>Guidance</span><em>{persist.guidance}</em></div>
|
|
215
|
+
) : null}
|
|
216
|
+
</article>
|
|
217
|
+
|
|
218
|
+
<ResolverManagementSection canSave={persist.canSave} config={config} />
|
|
219
|
+
</div>
|
|
220
|
+
</section>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SettingsShell } from "../settings-shell.jsx";
|
|
2
|
+
import { readAdapterConfig } from "@/lib/adapters/env";
|
|
3
|
+
import { describePersistenceMode, readWorkspaceConfig } from "@/lib/workspace-config";
|
|
4
|
+
import { OwnershipPanel } from "./ownership-panel.jsx";
|
|
5
|
+
|
|
6
|
+
async function OwnershipSettingsPage() {
|
|
7
|
+
const config = await readWorkspaceConfig();
|
|
8
|
+
const adapterConfig = readAdapterConfig();
|
|
9
|
+
const persistence = describePersistenceMode();
|
|
10
|
+
return (
|
|
11
|
+
<SettingsShell active="/settings/ownership" eyebrow="Settings" title="Ownership">
|
|
12
|
+
<OwnershipPanel config={config} persistence={persistence} adapterConfig={adapterConfig} />
|
|
13
|
+
</SettingsShell>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
OwnershipSettingsPage as default,
|
|
19
|
+
};
|
|
@@ -4,7 +4,8 @@ import { X } from "lucide-react";
|
|
|
4
4
|
const SETTINGS_TABS = [
|
|
5
5
|
{ href: "/settings/general", label: "General" },
|
|
6
6
|
{ href: "/settings/apis-webhooks", label: "APIs & Webhooks" },
|
|
7
|
-
{ href: "/settings/apps", label: "Apps" }
|
|
7
|
+
{ href: "/settings/apps", label: "Apps" },
|
|
8
|
+
{ href: "/settings/ownership", label: "Ownership" }
|
|
8
9
|
];
|
|
9
10
|
|
|
10
11
|
function SettingsShell({ active, eyebrow, title, children, aside }) {
|
|
@@ -76,6 +76,8 @@ import {
|
|
|
76
76
|
} from "@/lib/workspace-schema";
|
|
77
77
|
import { governedWorkspaceIntegrationCatalog } from "@/lib/domain/integrations";
|
|
78
78
|
import { OBJECT_TYPE_PRESETS, listWorkspaceDataModelTables } from "@/lib/workspace-data-model";
|
|
79
|
+
import { HelperSidecar } from "./data-model/components/HelperSidecar.jsx";
|
|
80
|
+
import { WorkspaceRail } from "./workspace-rail.jsx";
|
|
79
81
|
|
|
80
82
|
const DEFAULT_CHART_TYPE = "bar-vertical";
|
|
81
83
|
const DEFAULT_FILTER_OP = "and";
|
|
@@ -266,7 +268,7 @@ const GRID_COLUMNS = 12;
|
|
|
266
268
|
const GRID_ROWS = 16;
|
|
267
269
|
const GRID_CELL_COUNT = GRID_COLUMNS * GRID_ROWS;
|
|
268
270
|
const DEFAULT_TAB_ID = "tab-default";
|
|
269
|
-
const COLLAPSED_GRID_COLUMNS = "
|
|
271
|
+
const COLLAPSED_GRID_COLUMNS = "264px minmax(0, 1fr)";
|
|
270
272
|
|
|
271
273
|
function generateId(prefix) {
|
|
272
274
|
if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) {
|
|
@@ -1003,7 +1005,7 @@ function widgetKindFill(kind) {
|
|
|
1003
1005
|
switch (kind) {
|
|
1004
1006
|
case "chart": return "#dbeafe";
|
|
1005
1007
|
case "view": return "#fef3c7";
|
|
1006
|
-
case "iframe": return "#
|
|
1008
|
+
case "iframe": return "#e0f2fe";
|
|
1007
1009
|
case "rich-text": return "#dcfce7";
|
|
1008
1010
|
default: return "#e5e7eb";
|
|
1009
1011
|
}
|
|
@@ -2213,7 +2215,7 @@ function FieldsSubPanel({ widget, dataModelTable, onChange, onBack }) {
|
|
|
2213
2215
|
<SubPanelHeader title="Fields" breadcrumb={widget.title} onBack={onBack} />
|
|
2214
2216
|
{isBound && (
|
|
2215
2217
|
<p className="workspace-panel-hint">
|
|
2216
|
-
Fields come from the bound object. Manage them on the <a href="/data-model" style={{ color: "#
|
|
2218
|
+
Fields come from the bound object. Manage them on the <a href="/data-model" style={{ color: "#3f68ff" }}>Data Model page</a>.
|
|
2217
2219
|
</p>
|
|
2218
2220
|
)}
|
|
2219
2221
|
|
|
@@ -3352,6 +3354,10 @@ function WorkspaceBuilder({ initialConfig, adapterConfig, integrationAdapter, in
|
|
|
3352
3354
|
const [configMessage, setConfigMessage] = useState("");
|
|
3353
3355
|
const [inspectorPath, setInspectorPath] = useState(SUB_PANEL_ROOT);
|
|
3354
3356
|
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
|
|
3357
|
+
const [helperOpen, setHelperOpen] = useState(false);
|
|
3358
|
+
const [helperIntent, setHelperIntent] = useState("build_dashboard");
|
|
3359
|
+
const [helperInitialPrompt, setHelperInitialPrompt] = useState("");
|
|
3360
|
+
const [helperInitialThread, setHelperInitialThread] = useState(null);
|
|
3355
3361
|
const [templateFilter, setTemplateFilter] = useState({ category: "all", tag: "all", query: "" });
|
|
3356
3362
|
const [expandedIframeWidget, setExpandedIframeWidget] = useState(null);
|
|
3357
3363
|
const [refreshing, setRefreshing] = useState(false);
|
|
@@ -4158,6 +4164,29 @@ function WorkspaceBuilder({ initialConfig, adapterConfig, integrationAdapter, in
|
|
|
4158
4164
|
|
|
4159
4165
|
const paletteCommands = useMemo(() => {
|
|
4160
4166
|
const list = [];
|
|
4167
|
+
const openHelperWith = (i, p) => {
|
|
4168
|
+
setHelperIntent(i);
|
|
4169
|
+
setHelperInitialPrompt(p);
|
|
4170
|
+
setHelperInitialThread(null);
|
|
4171
|
+
setHelperOpen(true);
|
|
4172
|
+
};
|
|
4173
|
+
list.push({
|
|
4174
|
+
id: "helper.build_dashboard", group: "Ask helper", icon: Zap, label: "Ask helper — build a dashboard",
|
|
4175
|
+
run: () => openHelperWith("build_dashboard", "Draft a dashboard for a local agency: pipeline overview, weekly revenue, and a leaderboard widget.")
|
|
4176
|
+
});
|
|
4177
|
+
list.push({
|
|
4178
|
+
id: "helper.edit_view", group: "Ask helper", icon: Zap, label: "Ask helper — improve this dashboard",
|
|
4179
|
+
disabled: !activeDashboard,
|
|
4180
|
+
run: () => openHelperWith("edit_view", `Improve the "${activeDashboard?.name || "current"}" dashboard. Suggest widget placements that match the data already in the workspace.`)
|
|
4181
|
+
});
|
|
4182
|
+
list.push({
|
|
4183
|
+
id: "helper.create_widget", group: "Ask helper", icon: Zap, label: "Ask helper — suggest widgets",
|
|
4184
|
+
run: () => openHelperWith("create_widget", "Suggest widgets that fit the data already in this workspace.")
|
|
4185
|
+
});
|
|
4186
|
+
list.push({
|
|
4187
|
+
id: "helper.repair", group: "Ask helper", icon: Zap, label: "Ask helper — repair workspace",
|
|
4188
|
+
run: () => openHelperWith("repair", "Inspect this workspace for missing references, broken bindings, or incomplete views. Propose the smallest fix for each issue.")
|
|
4189
|
+
});
|
|
4161
4190
|
list.push({
|
|
4162
4191
|
id: "dashboard.new", group: "Dashboard", icon: Plus, label: "Create dashboard", shortcut: "N",
|
|
4163
4192
|
run: () => addDashboard()
|
|
@@ -4292,27 +4321,45 @@ function WorkspaceBuilder({ initialConfig, adapterConfig, integrationAdapter, in
|
|
|
4292
4321
|
]);
|
|
4293
4322
|
|
|
4294
4323
|
return <main className="workspace-builder" onPointerDownCapture={resetWidgetSelectionOnOutsidePointer} style={builderStyle}>
|
|
4295
|
-
<
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
}
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4324
|
+
<WorkspaceRail
|
|
4325
|
+
workspaceConfig={config}
|
|
4326
|
+
authority={integrationAdapter.authority}
|
|
4327
|
+
helperOpen={helperOpen}
|
|
4328
|
+
onOpenHelper={() => {
|
|
4329
|
+
if (helperOpen) { setHelperOpen(false); return; }
|
|
4330
|
+
// Rail pill ALWAYS lands on a fresh thread (chip stack +
|
|
4331
|
+
// empty composer). Reopen specific threads via the Chat tab.
|
|
4332
|
+
setHelperIntent("build_dashboard");
|
|
4333
|
+
setHelperInitialPrompt("");
|
|
4334
|
+
setHelperInitialThread(null);
|
|
4335
|
+
setHelperOpen(true);
|
|
4336
|
+
}}
|
|
4337
|
+
onOpenThread={(row) => {
|
|
4338
|
+
setHelperInitialThread(row);
|
|
4339
|
+
setHelperOpen(true);
|
|
4340
|
+
}}
|
|
4341
|
+
onConfigChange={(nextConfig) => {
|
|
4342
|
+
if (typeof setConfig === "function") setConfig(nextConfig);
|
|
4343
|
+
}}
|
|
4344
|
+
dashboardsSlot={(
|
|
4345
|
+
<button
|
|
4346
|
+
type="button"
|
|
4347
|
+
className={workspaceView === "dashboards" ? "active workspace-nav-button" : "workspace-nav-button"}
|
|
4348
|
+
onClick={showDashboardHome}
|
|
4349
|
+
>
|
|
4350
|
+
Dashboards
|
|
4351
|
+
</button>
|
|
4352
|
+
)}
|
|
4353
|
+
managementSlot={(
|
|
4354
|
+
<button
|
|
4355
|
+
type="button"
|
|
4356
|
+
className="workspace-nav-button"
|
|
4357
|
+
onClick={() => setManagementOpen(true)}
|
|
4358
|
+
>
|
|
4359
|
+
Management
|
|
4360
|
+
</button>
|
|
4361
|
+
)}
|
|
4362
|
+
/>
|
|
4316
4363
|
|
|
4317
4364
|
<section className="workspace-surface">
|
|
4318
4365
|
<header className="workspace-toolbar">
|
|
@@ -4537,6 +4584,51 @@ function WorkspaceBuilder({ initialConfig, adapterConfig, integrationAdapter, in
|
|
|
4537
4584
|
onClose={closeManagement}
|
|
4538
4585
|
/> : null}
|
|
4539
4586
|
|
|
4587
|
+
<HelperSidecar
|
|
4588
|
+
open={helperOpen}
|
|
4589
|
+
onClose={() => setHelperOpen(false)}
|
|
4590
|
+
workspaceConfig={config}
|
|
4591
|
+
initialIntent={helperIntent}
|
|
4592
|
+
initialPrompt={helperInitialPrompt}
|
|
4593
|
+
initialThread={helperInitialThread}
|
|
4594
|
+
onOpenArtifact={(target) => {
|
|
4595
|
+
if (!target) return;
|
|
4596
|
+
// dashboards surface — focus the created dashboard inline.
|
|
4597
|
+
if (target.surface === "dashboard" && target.dashboardId) {
|
|
4598
|
+
setActiveDashboardId?.(target.dashboardId);
|
|
4599
|
+
setWorkspaceView?.("builder");
|
|
4600
|
+
setHelperOpen(false);
|
|
4601
|
+
return;
|
|
4602
|
+
}
|
|
4603
|
+
// a data-model artifact was applied — navigate to that surface.
|
|
4604
|
+
if (target.surface === "data-model" && target.source) {
|
|
4605
|
+
setHelperOpen(false);
|
|
4606
|
+
if (typeof window !== "undefined") {
|
|
4607
|
+
window.location.href = `/data-model?source=${encodeURIComponent(target.source)}`;
|
|
4608
|
+
}
|
|
4609
|
+
}
|
|
4610
|
+
}}
|
|
4611
|
+
onApplied={(updatedConfig) => {
|
|
4612
|
+
if (!updatedConfig) return;
|
|
4613
|
+
// Re-seat canvas from the dashboard the user is currently viewing.
|
|
4614
|
+
// If the helper created a new dashboard we still keep the user
|
|
4615
|
+
// anchored where they were unless they had no active dashboard yet.
|
|
4616
|
+
setConfig((current) => {
|
|
4617
|
+
const nextDashboards = Array.isArray(updatedConfig.dashboards) && updatedConfig.dashboards.length
|
|
4618
|
+
? updatedConfig.dashboards
|
|
4619
|
+
: current.dashboards;
|
|
4620
|
+
const stillActive = nextDashboards.find((d) => d?.id === resolvedActiveDashboardId);
|
|
4621
|
+
const anchor = stillActive || nextDashboards[0];
|
|
4622
|
+
return {
|
|
4623
|
+
...current,
|
|
4624
|
+
...updatedConfig,
|
|
4625
|
+
dashboards: nextDashboards,
|
|
4626
|
+
canvas: dashboardCanvasFrom(anchor, updatedConfig.canvas || current.canvas)
|
|
4627
|
+
};
|
|
4628
|
+
});
|
|
4629
|
+
}}
|
|
4630
|
+
/>
|
|
4631
|
+
|
|
4540
4632
|
{workspaceView === "builder" && panelOpen ? <aside className="workspace-widget-panel" id="widgets" aria-label="Widget configuration">
|
|
4541
4633
|
<div className="workspace-panel-title">
|
|
4542
4634
|
<button type="button" aria-label="Close widget panel" onClick={closePanel}>x</button>
|