@burdenoff/microfe-bigconsole 2026.812.2 → 2026.827.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/dist/bigconsole/components/builder/inspector/LayoutSection.js +71 -0
- package/dist/bigconsole/components/builder/inspector/LayoutSection.js.map +1 -0
- package/dist/bigconsole/components/builder/inspector/index.js +1 -0
- package/dist/bigconsole/components/dashboard/DashboardCanvas.js +137 -121
- package/dist/bigconsole/components/dashboard/DashboardCanvas.js.map +1 -1
- package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js +204 -93
- package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js +259 -133
- package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js +87 -39
- package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkCard.js +88 -68
- package/dist/bigconsole/components/datasink/DataSinkCard.js.map +1 -1
- package/dist/bigconsole/components/widgets/PropertiesPanel.js +213 -199
- package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
- package/dist/bigconsole/components/workflow/WorkflowCard.js +46 -27
- package/dist/bigconsole/components/workflow/WorkflowCard.js.map +1 -1
- package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js +44 -47
- package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js.map +1 -1
- package/dist/bigconsole/customWidget/proposalSource.js +31 -24
- package/dist/bigconsole/customWidget/proposalSource.js.map +1 -1
- package/dist/bigconsole/hooks/useDashboardOperations.js +74 -64
- package/dist/bigconsole/hooks/useDashboardOperations.js.map +1 -1
- package/dist/bigconsole/hooks/useDataSinkOperations.js +2 -2
- package/dist/bigconsole/hooks/useDataSinkOperations.js.map +1 -1
- package/dist/bigconsole/pages/DashboardBuilderPage.js +1 -1
- package/dist/bigconsole/pages/DashboardsPage.js +386 -316
- package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinksPage.js +251 -206
- package/dist/bigconsole/pages/DataSinksPage.js.map +1 -1
- package/dist/bigconsole/pages/InsightsPage.js +646 -378
- package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
- package/dist/bigconsole/pages/WorkflowsPage.js +153 -108
- package/dist/bigconsole/pages/WorkflowsPage.js.map +1 -1
- package/dist/bigconsole/utils/format.js +18 -0
- package/dist/bigconsole/utils/format.js.map +1 -0
- package/dist/bigconsole/utils/widgetPositioning.js +1 -1
- package/dist/generated/wspace-operations.js +1 -0
- package/dist/generated/wspace-operations.js.map +1 -1
- package/package.json +1 -1
|
@@ -65,25 +65,31 @@ var s = ({ className: e }) => /* @__PURE__ */ i("svg", {
|
|
|
65
65
|
strokeWidth: 2,
|
|
66
66
|
d: "M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
67
67
|
})]
|
|
68
|
-
}), f = n(function({ workflow: n, onView: f, onEdit: p, onDelete: m }) {
|
|
69
|
-
let
|
|
68
|
+
}), f = n(function({ workflow: n, onView: f, onEdit: p, onDelete: m, onRun: h, isRunning: g = !1 }) {
|
|
69
|
+
let _ = n.dataSinkLinks?.length ?? n.linkedDataSinkCount ?? 0, v = r((e) => {
|
|
70
70
|
if (!e) return "Never";
|
|
71
71
|
try {
|
|
72
72
|
return o(new Date(e), { addSuffix: !0 });
|
|
73
73
|
} catch {
|
|
74
74
|
return "Unknown";
|
|
75
75
|
}
|
|
76
|
-
}, []),
|
|
76
|
+
}, []), y = r(() => {
|
|
77
77
|
f?.(n.id);
|
|
78
|
-
}, [f, n.id]),
|
|
78
|
+
}, [f, n.id]), b = r((e) => {
|
|
79
79
|
e.stopPropagation(), p?.(n.id);
|
|
80
|
-
}, [p, n.id]),
|
|
80
|
+
}, [p, n.id]), x = r((e) => {
|
|
81
81
|
e.stopPropagation(), m?.(n.id);
|
|
82
|
-
}, [m, n.id]),
|
|
82
|
+
}, [m, n.id]), S = r((e) => {
|
|
83
|
+
e.stopPropagation(), !g && h?.(n.id);
|
|
84
|
+
}, [
|
|
85
|
+
g,
|
|
86
|
+
h,
|
|
87
|
+
n.id
|
|
88
|
+
]), C = r((e) => {
|
|
83
89
|
e.stopPropagation(), f?.(n.id);
|
|
84
90
|
}, [f, n.id]);
|
|
85
91
|
return /* @__PURE__ */ a("div", {
|
|
86
|
-
onClick:
|
|
92
|
+
onClick: y,
|
|
87
93
|
className: "min-w-0 cursor-pointer overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-elevated motion-safe:transition-[box-shadow,border-color] focus-within:border-action-primary-bg hover:border-action-primary-bg hover:shadow-[var(--shadow-elevation-1)]",
|
|
88
94
|
children: [/* @__PURE__ */ a("div", {
|
|
89
95
|
className: "flex items-center justify-between gap-icon-gap border-b border-[var(--color-border-subtle)] bg-bg-sunken px-3 py-2",
|
|
@@ -108,7 +114,7 @@ var s = ({ className: e }) => /* @__PURE__ */ i("svg", {
|
|
|
108
114
|
className: "text-sm font-semibold text-text-primary",
|
|
109
115
|
children: /* @__PURE__ */ i("button", {
|
|
110
116
|
type: "button",
|
|
111
|
-
onClick:
|
|
117
|
+
onClick: C,
|
|
112
118
|
"aria-label": `Open workflow ${n.name}`,
|
|
113
119
|
className: `line-clamp-2 rounded-button text-left ${e}`,
|
|
114
120
|
children: n.name
|
|
@@ -119,21 +125,34 @@ var s = ({ className: e }) => /* @__PURE__ */ i("svg", {
|
|
|
119
125
|
})]
|
|
120
126
|
}), /* @__PURE__ */ a("div", {
|
|
121
127
|
className: "flex shrink-0 items-center gap-1",
|
|
122
|
-
children: [
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
128
|
+
children: [
|
|
129
|
+
h && /* @__PURE__ */ i("button", {
|
|
130
|
+
type: "button",
|
|
131
|
+
onClick: S,
|
|
132
|
+
disabled: g,
|
|
133
|
+
"aria-busy": g,
|
|
134
|
+
className: "inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-bg-sunken hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-60 sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-bg-surface",
|
|
135
|
+
title: g ? "Running…" : "Run Workflow",
|
|
136
|
+
"aria-label": g ? `Running workflow ${n.name}` : `Run workflow ${n.name}`,
|
|
137
|
+
children: /* @__PURE__ */ i(d, { className: `w-4 h-4 ${g ? "motion-safe:animate-pulse" : ""}` })
|
|
138
|
+
}),
|
|
139
|
+
p && /* @__PURE__ */ i("button", {
|
|
140
|
+
type: "button",
|
|
141
|
+
onClick: b,
|
|
142
|
+
className: "inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-bg-sunken hover:text-text-primary sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-bg-surface",
|
|
143
|
+
title: "Edit Workflow",
|
|
144
|
+
"aria-label": `Edit workflow ${n.name}`,
|
|
145
|
+
children: /* @__PURE__ */ i(c, { className: "w-4 h-4" })
|
|
146
|
+
}),
|
|
147
|
+
m && /* @__PURE__ */ i("button", {
|
|
148
|
+
type: "button",
|
|
149
|
+
onClick: x,
|
|
150
|
+
className: "inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-status-error-bg/10 hover:text-status-error-text sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-bg-surface",
|
|
151
|
+
title: "Delete Workflow",
|
|
152
|
+
"aria-label": `Delete workflow ${n.name}`,
|
|
153
|
+
children: /* @__PURE__ */ i(l, { className: "w-4 h-4" })
|
|
154
|
+
})
|
|
155
|
+
]
|
|
137
156
|
})]
|
|
138
157
|
}),
|
|
139
158
|
n.description && /* @__PURE__ */ i("p", {
|
|
@@ -146,12 +165,12 @@ var s = ({ className: e }) => /* @__PURE__ */ i("svg", {
|
|
|
146
165
|
className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1",
|
|
147
166
|
children: [/* @__PURE__ */ a("span", {
|
|
148
167
|
className: "flex items-center gap-1",
|
|
149
|
-
title: `${
|
|
168
|
+
title: `${_} linked DataSinks`,
|
|
150
169
|
children: [
|
|
151
170
|
/* @__PURE__ */ i(u, { className: "w-3.5 h-3.5 shrink-0" }),
|
|
152
|
-
|
|
171
|
+
_,
|
|
153
172
|
" DataSink",
|
|
154
|
-
|
|
173
|
+
_ === 1 ? "" : "s"
|
|
155
174
|
]
|
|
156
175
|
}), /* @__PURE__ */ a("span", {
|
|
157
176
|
className: "flex items-center gap-1",
|
|
@@ -160,7 +179,7 @@ var s = ({ className: e }) => /* @__PURE__ */ i("svg", {
|
|
|
160
179
|
})]
|
|
161
180
|
}), /* @__PURE__ */ i("span", {
|
|
162
181
|
title: n.lastExecutedAt ? `Last executed: ${new Date(n.lastExecutedAt).toLocaleString()}` : "Never executed",
|
|
163
|
-
children:
|
|
182
|
+
children: v(n.lastExecutedAt)
|
|
164
183
|
})]
|
|
165
184
|
})
|
|
166
185
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkflowCard.js","names":[],"sources":["../../../../src/bigconsole/components/workflow/WorkflowCard.tsx"],"sourcesContent":["/**\n * WorkflowCard Component\n *\n * Card component displaying a FluidGrids Workflow summary.\n * Shows name, status, linked DataSinks count, and execution stats.\n *\n * Emphasis (BOFF-5634): the card is a tile inside the list page's `primary`\n * results section, so it sits on `bg-bg-elevated` — one step above the section\n * surface — rather than repeating the generic panel treatment used by every\n * other block in the module. The old 96px empty media band carried no\n * information and out-weighed the title; it is now a compact identity strip.\n */\n\nimport { memo, useCallback } from 'react';\nimport { formatDistanceToNow } from 'date-fns';\nimport { WorkflowStatusBadge } from './WorkflowStatusBadge';\nimport { SECTION_FOCUS_RING } from '../common';\nimport type { FluidGridsWorkflow } from '../../types';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface WorkflowCardProps {\n workflow: FluidGridsWorkflow;\n onView?: (id: string) => void;\n onEdit?: (id: string) => void;\n onDelete?: (id: string) => void;\n}\n\n// ============================================================================\n// ICONS\n// ============================================================================\n\nconst WorkflowIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n);\n\nconst EditIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\"\n />\n </svg>\n);\n\nconst DeleteIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"\n />\n </svg>\n);\n\nconst DataSinkIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4\"\n />\n </svg>\n);\n\nconst PlayIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z\"\n />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n);\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const WorkflowCard = memo(function WorkflowCard({ workflow, onView, onEdit, onDelete }: WorkflowCardProps) {\n // Calculate linked DataSinks count\n const linkedDataSinkCount = workflow.dataSinkLinks?.length ?? workflow.linkedDataSinkCount ?? 0;\n\n // Format last executed time\n const formatLastExecuted = useCallback((dateStr: string | null): string => {\n if (!dateStr) return 'Never';\n try {\n const date = new Date(dateStr);\n return formatDistanceToNow(date, { addSuffix: true });\n } catch {\n return 'Unknown';\n }\n }, []);\n\n // Event handlers\n const handleCardClick = useCallback(() => {\n onView?.(workflow.id);\n }, [onView, workflow.id]);\n\n const handleEditClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n onEdit?.(workflow.id);\n },\n [onEdit, workflow.id]\n );\n\n const handleDeleteClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n onDelete?.(workflow.id);\n },\n [onDelete, workflow.id]\n );\n\n // The title is the real \"open this workflow\" control. The tile itself must NOT\n // carry role=\"button\": that role makes its subtree presentational, which would\n // drop the Edit/Delete buttons and the status badge out of the accessibility\n // tree. The tile keeps a plain click handler as a pointer convenience only.\n const handleTitleClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n onView?.(workflow.id);\n },\n [onView, workflow.id]\n );\n\n return (\n <div\n onClick={handleCardClick}\n className=\"min-w-0 cursor-pointer overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-elevated motion-safe:transition-[box-shadow,border-color] focus-within:border-action-primary-bg hover:border-action-primary-bg hover:shadow-[var(--shadow-elevation-1)]\"\n >\n {/* Identity strip: what kind of thing this is + its status, at a glance */}\n <div className=\"flex items-center justify-between gap-icon-gap border-b border-[var(--color-border-subtle)] bg-bg-sunken px-3 py-2\">\n <span className=\"flex min-w-0 items-center gap-icon-gap text-xs font-medium text-text-secondary\">\n <WorkflowIcon className=\"w-4 h-4 shrink-0\" />\n <span className=\"truncate\">Workflow</span>\n </span>\n <WorkflowStatusBadge status={workflow.status} size=\"sm\" />\n </div>\n\n {/* Content */}\n <div className=\"p-card-padding\">\n {/* Title and Actions */}\n <div className=\"mb-2 flex items-start justify-between gap-icon-gap\">\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n <button\n type=\"button\"\n onClick={handleTitleClick}\n aria-label={`Open workflow ${workflow.name}`}\n className={`line-clamp-2 rounded-button text-left ${SECTION_FOCUS_RING}`}\n >\n {workflow.name}\n </button>\n </h3>\n <p className=\"mt-0.5 truncate font-mono text-xs text-text-secondary\">{workflow.key}</p>\n </div>\n\n <div className=\"flex shrink-0 items-center gap-1\">\n {onEdit && (\n <button\n type=\"button\"\n onClick={handleEditClick}\n className={`inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-bg-sunken hover:text-text-primary sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] ${SECTION_FOCUS_RING}`}\n title=\"Edit Workflow\"\n aria-label={`Edit workflow ${workflow.name}`}\n >\n <EditIcon className=\"w-4 h-4\" />\n </button>\n )}\n {onDelete && (\n <button\n type=\"button\"\n onClick={handleDeleteClick}\n className={`inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-status-error-bg/10 hover:text-status-error-text sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] ${SECTION_FOCUS_RING}`}\n title=\"Delete Workflow\"\n aria-label={`Delete workflow ${workflow.name}`}\n >\n <DeleteIcon className=\"w-4 h-4\" />\n </button>\n )}\n </div>\n </div>\n\n {/* Description */}\n {workflow.description && (\n <p className=\"mb-3 line-clamp-2 text-xs text-text-secondary\">{workflow.description}</p>\n )}\n\n {/* Stats Footer */}\n <div className=\"flex flex-wrap items-center justify-between gap-x-3 gap-y-1 text-xs text-text-muted\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1\">\n {/* DataSinks Count */}\n <span className=\"flex items-center gap-1\" title={`${linkedDataSinkCount} linked DataSinks`}>\n <DataSinkIcon className=\"w-3.5 h-3.5 shrink-0\" />\n {linkedDataSinkCount} DataSink{linkedDataSinkCount !== 1 ? 's' : ''}\n </span>\n\n {/* Execution Count */}\n <span className=\"flex items-center gap-1\" title={`${workflow.executionCount} executions`}>\n <PlayIcon className=\"w-3.5 h-3.5 shrink-0\" />\n {workflow.executionCount}\n </span>\n </div>\n\n {/* Last Executed */}\n <span\n title={\n workflow.lastExecutedAt\n ? `Last executed: ${new Date(workflow.lastExecutedAt).toLocaleString()}`\n : 'Never executed'\n }\n >\n {formatLastExecuted(workflow.lastExecutedAt)}\n </span>\n </div>\n </div>\n </div>\n );\n});\n\nexport default WorkflowCard;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WorkflowCard.js","names":[],"sources":["../../../../src/bigconsole/components/workflow/WorkflowCard.tsx"],"sourcesContent":["/**\n * WorkflowCard Component\n *\n * Card component displaying a FluidGrids Workflow summary.\n * Shows name, status, linked DataSinks count, and execution stats.\n *\n * Emphasis (BOFF-5634): the card is a tile inside the list page's `primary`\n * results section, so it sits on `bg-bg-elevated` — one step above the section\n * surface — rather than repeating the generic panel treatment used by every\n * other block in the module. The old 96px empty media band carried no\n * information and out-weighed the title; it is now a compact identity strip.\n */\n\nimport { memo, useCallback } from 'react';\nimport { formatDistanceToNow } from 'date-fns';\nimport { WorkflowStatusBadge } from './WorkflowStatusBadge';\nimport { SECTION_FOCUS_RING } from '../common';\nimport type { FluidGridsWorkflow } from '../../types';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface WorkflowCardProps {\n workflow: FluidGridsWorkflow;\n onView?: (id: string) => void;\n onEdit?: (id: string) => void;\n onDelete?: (id: string) => void;\n /**\n * Trigger a run. Omitted when the actor lacks `bigconsole-workflow:execute`,\n * so the affordance is absent rather than present-and-refused.\n */\n onRun?: (id: string) => void;\n /** True while this workflow's run request is in flight. */\n isRunning?: boolean;\n}\n\n// ============================================================================\n// ICONS\n// ============================================================================\n\nconst WorkflowIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n);\n\nconst EditIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\"\n />\n </svg>\n);\n\nconst DeleteIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"\n />\n </svg>\n);\n\nconst DataSinkIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4\"\n />\n </svg>\n);\n\nconst PlayIcon = ({ className }: { className?: string }) => (\n <svg className={className} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z\"\n />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n);\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const WorkflowCard = memo(function WorkflowCard({\n workflow,\n onView,\n onEdit,\n onDelete,\n onRun,\n isRunning = false,\n}: WorkflowCardProps) {\n // Calculate linked DataSinks count\n const linkedDataSinkCount = workflow.dataSinkLinks?.length ?? workflow.linkedDataSinkCount ?? 0;\n\n // Format last executed time\n const formatLastExecuted = useCallback((dateStr: string | null): string => {\n if (!dateStr) return 'Never';\n try {\n const date = new Date(dateStr);\n return formatDistanceToNow(date, { addSuffix: true });\n } catch {\n return 'Unknown';\n }\n }, []);\n\n // Event handlers\n const handleCardClick = useCallback(() => {\n onView?.(workflow.id);\n }, [onView, workflow.id]);\n\n const handleEditClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n onEdit?.(workflow.id);\n },\n [onEdit, workflow.id]\n );\n\n const handleDeleteClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n onDelete?.(workflow.id);\n },\n [onDelete, workflow.id]\n );\n\n const handleRunClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n if (isRunning) return;\n onRun?.(workflow.id);\n },\n [isRunning, onRun, workflow.id]\n );\n\n // The title is the real \"open this workflow\" control. The tile itself must NOT\n // carry role=\"button\": that role makes its subtree presentational, which would\n // drop the Edit/Delete buttons and the status badge out of the accessibility\n // tree. The tile keeps a plain click handler as a pointer convenience only.\n const handleTitleClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n onView?.(workflow.id);\n },\n [onView, workflow.id]\n );\n\n return (\n <div\n onClick={handleCardClick}\n className=\"min-w-0 cursor-pointer overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-elevated motion-safe:transition-[box-shadow,border-color] focus-within:border-action-primary-bg hover:border-action-primary-bg hover:shadow-[var(--shadow-elevation-1)]\"\n >\n {/* Identity strip: what kind of thing this is + its status, at a glance */}\n <div className=\"flex items-center justify-between gap-icon-gap border-b border-[var(--color-border-subtle)] bg-bg-sunken px-3 py-2\">\n <span className=\"flex min-w-0 items-center gap-icon-gap text-xs font-medium text-text-secondary\">\n <WorkflowIcon className=\"w-4 h-4 shrink-0\" />\n <span className=\"truncate\">Workflow</span>\n </span>\n <WorkflowStatusBadge status={workflow.status} size=\"sm\" />\n </div>\n\n {/* Content */}\n <div className=\"p-card-padding\">\n {/* Title and Actions */}\n <div className=\"mb-2 flex items-start justify-between gap-icon-gap\">\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n <button\n type=\"button\"\n onClick={handleTitleClick}\n aria-label={`Open workflow ${workflow.name}`}\n className={`line-clamp-2 rounded-button text-left ${SECTION_FOCUS_RING}`}\n >\n {workflow.name}\n </button>\n </h3>\n <p className=\"mt-0.5 truncate font-mono text-xs text-text-secondary\">{workflow.key}</p>\n </div>\n\n <div className=\"flex shrink-0 items-center gap-1\">\n {onRun && (\n <button\n type=\"button\"\n onClick={handleRunClick}\n disabled={isRunning}\n aria-busy={isRunning}\n className={`inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-bg-sunken hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-60 sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] ${SECTION_FOCUS_RING}`}\n title={isRunning ? 'Running…' : 'Run Workflow'}\n aria-label={isRunning ? `Running workflow ${workflow.name}` : `Run workflow ${workflow.name}`}\n >\n <PlayIcon className={`w-4 h-4 ${isRunning ? 'motion-safe:animate-pulse' : ''}`} />\n </button>\n )}\n {onEdit && (\n <button\n type=\"button\"\n onClick={handleEditClick}\n className={`inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-bg-sunken hover:text-text-primary sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] ${SECTION_FOCUS_RING}`}\n title=\"Edit Workflow\"\n aria-label={`Edit workflow ${workflow.name}`}\n >\n <EditIcon className=\"w-4 h-4\" />\n </button>\n )}\n {onDelete && (\n <button\n type=\"button\"\n onClick={handleDeleteClick}\n className={`inline-flex h-control-md w-[var(--size-controlHeight-md)] items-center justify-center rounded-button text-text-secondary motion-safe:transition-colors hover:bg-status-error-bg/10 hover:text-status-error-text sm:h-control-sm sm:w-[var(--size-controlHeight-sm)] ${SECTION_FOCUS_RING}`}\n title=\"Delete Workflow\"\n aria-label={`Delete workflow ${workflow.name}`}\n >\n <DeleteIcon className=\"w-4 h-4\" />\n </button>\n )}\n </div>\n </div>\n\n {/* Description */}\n {workflow.description && (\n <p className=\"mb-3 line-clamp-2 text-xs text-text-secondary\">{workflow.description}</p>\n )}\n\n {/* Stats Footer */}\n <div className=\"flex flex-wrap items-center justify-between gap-x-3 gap-y-1 text-xs text-text-muted\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1\">\n {/* DataSinks Count */}\n <span className=\"flex items-center gap-1\" title={`${linkedDataSinkCount} linked DataSinks`}>\n <DataSinkIcon className=\"w-3.5 h-3.5 shrink-0\" />\n {linkedDataSinkCount} DataSink{linkedDataSinkCount !== 1 ? 's' : ''}\n </span>\n\n {/* Execution Count */}\n <span className=\"flex items-center gap-1\" title={`${workflow.executionCount} executions`}>\n <PlayIcon className=\"w-3.5 h-3.5 shrink-0\" />\n {workflow.executionCount}\n </span>\n </div>\n\n {/* Last Executed */}\n <span\n title={\n workflow.lastExecutedAt\n ? `Last executed: ${new Date(workflow.lastExecutedAt).toLocaleString()}`\n : 'Never executed'\n }\n >\n {formatLastExecuted(workflow.lastExecutedAt)}\n </span>\n </div>\n </div>\n </div>\n );\n});\n\nexport default WorkflowCard;\n"],"mappings":"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"}
|
|
@@ -2,44 +2,41 @@ import e from "../hooks/useWidgetOperations.js";
|
|
|
2
2
|
import t from "../hooks/useDataSinkOperations.js";
|
|
3
3
|
import n from "../hooks/useParserOperations.js";
|
|
4
4
|
import "../hooks/index.js";
|
|
5
|
-
import r from "./
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
5
|
+
import { selectActiveProposalSinks as r } from "./proposalSource.js";
|
|
6
|
+
import i from "./CustomWidgetProposalPanel.js";
|
|
7
|
+
import { memo as a, useCallback as o, useMemo as s } from "react";
|
|
8
|
+
import { useI18n as c } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
9
|
+
import { Sheet as l, SheetContent as u, SheetDescription as d, SheetHeader as f, SheetTitle as p } from "@burdenoff/fe-libs/ui";
|
|
10
|
+
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
10
11
|
//#region src/bigconsole/customWidget/CustomWidgetBuilderSheet.tsx
|
|
11
|
-
var
|
|
12
|
+
var g = {
|
|
12
13
|
x: 0,
|
|
13
14
|
y: 0,
|
|
14
15
|
width: 6,
|
|
15
16
|
height: 4
|
|
16
|
-
},
|
|
17
|
-
let { t:
|
|
18
|
-
consoleId:
|
|
19
|
-
skipFetch: !
|
|
20
|
-
}), { parsers:
|
|
21
|
-
consoleId:
|
|
22
|
-
skipFetch: !
|
|
23
|
-
}),
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
name: e.name
|
|
27
|
-
})), [S]), k = o(() => {
|
|
28
|
-
let e = new Set(T.map((e) => e.id));
|
|
29
|
-
return [...T, ...D.filter((t) => !e.has(t.id))].map((e) => ({
|
|
17
|
+
}, _ = "default", v = a(function({ isOpen: a, onClose: v, dashboardId: y, pageId: b, source: x }) {
|
|
18
|
+
let { t: S } = c(), { dataSinks: C } = t({ skipFetch: !a }), { createWidget: w } = e(b, y, { skipFetch: !0 }), T = y === _, { parsers: E, executeParser: D } = n({
|
|
19
|
+
consoleId: y,
|
|
20
|
+
skipFetch: !a
|
|
21
|
+
}), { parsers: O } = n({
|
|
22
|
+
consoleId: _,
|
|
23
|
+
skipFetch: !a || T
|
|
24
|
+
}), k = s(() => r(C), [C]), A = s(() => {
|
|
25
|
+
let e = new Set(E.map((e) => e.id));
|
|
26
|
+
return [...E, ...O.filter((t) => !e.has(t.id))].map((e) => ({
|
|
30
27
|
id: e.id,
|
|
31
28
|
name: e.name,
|
|
32
29
|
inputSinkKey: e.inputSinkKey
|
|
33
30
|
}));
|
|
34
|
-
}, [
|
|
31
|
+
}, [E, O]), j = o(async (e) => {
|
|
35
32
|
if (!e.binding.parserId) return;
|
|
36
|
-
let t = await
|
|
37
|
-
if (!t || !t.success) throw Error(t?.error ||
|
|
33
|
+
let t = await D(e.binding.parserId);
|
|
34
|
+
if (!t || !t.success) throw Error(t?.error || S("bigconsole.widget.customWidget.error.couldNotResolveBinding", "Could not resolve the binding."));
|
|
38
35
|
return t.output;
|
|
39
|
-
}, [
|
|
40
|
-
if (!await
|
|
41
|
-
pageId:
|
|
42
|
-
dashboardId:
|
|
36
|
+
}, [D, S]), M = o(async (e) => {
|
|
37
|
+
if (!await w({
|
|
38
|
+
pageId: b,
|
|
39
|
+
dashboardId: y,
|
|
43
40
|
type: e.widgetType,
|
|
44
41
|
title: e.presentation.title,
|
|
45
42
|
description: e.presentation.description,
|
|
@@ -52,18 +49,18 @@ var h = {
|
|
|
52
49
|
sourcePrompt: e.sourcePrompt,
|
|
53
50
|
proposalVersion: e.version
|
|
54
51
|
},
|
|
55
|
-
position: { ...
|
|
56
|
-
})) throw Error(
|
|
52
|
+
position: { ...g }
|
|
53
|
+
})) throw Error(S("bigconsole.widget.customWidget.error.widgetNotCreated", "The widget could not be created."));
|
|
57
54
|
}, [
|
|
58
|
-
|
|
55
|
+
w,
|
|
56
|
+
b,
|
|
59
57
|
y,
|
|
60
|
-
|
|
61
|
-
x
|
|
58
|
+
S
|
|
62
59
|
]);
|
|
63
|
-
return /* @__PURE__ */
|
|
64
|
-
open:
|
|
65
|
-
onOpenChange: (e) => !e &&
|
|
66
|
-
children: /* @__PURE__ */
|
|
60
|
+
return /* @__PURE__ */ m(l, {
|
|
61
|
+
open: a,
|
|
62
|
+
onOpenChange: (e) => !e && v(),
|
|
63
|
+
children: /* @__PURE__ */ h(u, {
|
|
67
64
|
style: {
|
|
68
65
|
width: "min(480px, 100vw)",
|
|
69
66
|
maxWidth: "100vw",
|
|
@@ -71,32 +68,32 @@ var h = {
|
|
|
71
68
|
display: "flex",
|
|
72
69
|
flexDirection: "column"
|
|
73
70
|
},
|
|
74
|
-
children: [/* @__PURE__ */
|
|
71
|
+
children: [/* @__PURE__ */ h(f, {
|
|
75
72
|
style: {
|
|
76
73
|
padding: "var(--space-cardPadding)",
|
|
77
74
|
borderBottom: "1px solid var(--color-border-subtle)",
|
|
78
75
|
backgroundColor: "var(--color-bg-surface)",
|
|
79
76
|
flexShrink: 0
|
|
80
77
|
},
|
|
81
|
-
children: [/* @__PURE__ */ p
|
|
82
|
-
}), /* @__PURE__ */
|
|
78
|
+
children: [/* @__PURE__ */ m(p, { children: S("bigconsole.widget.customWidget.sheet.title", "AI custom widget") }), /* @__PURE__ */ m(d, { children: S("bigconsole.widget.customWidget.sheet.description", "Describe a widget, review the proposal, then approve it onto the dashboard.") })]
|
|
79
|
+
}), /* @__PURE__ */ m("div", {
|
|
83
80
|
style: {
|
|
84
81
|
flex: 1,
|
|
85
82
|
minHeight: 0
|
|
86
83
|
},
|
|
87
|
-
children: /* @__PURE__ */
|
|
88
|
-
sinks:
|
|
89
|
-
parsers:
|
|
90
|
-
resolvePreviewData:
|
|
91
|
-
onApprove:
|
|
92
|
-
onClose:
|
|
93
|
-
source:
|
|
84
|
+
children: /* @__PURE__ */ m(i, {
|
|
85
|
+
sinks: k,
|
|
86
|
+
parsers: A,
|
|
87
|
+
resolvePreviewData: j,
|
|
88
|
+
onApprove: M,
|
|
89
|
+
onClose: v,
|
|
90
|
+
source: x
|
|
94
91
|
})
|
|
95
92
|
})]
|
|
96
93
|
})
|
|
97
94
|
});
|
|
98
95
|
});
|
|
99
96
|
//#endregion
|
|
100
|
-
export {
|
|
97
|
+
export { v as default };
|
|
101
98
|
|
|
102
99
|
//# sourceMappingURL=CustomWidgetBuilderSheet.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CustomWidgetBuilderSheet.js","names":[],"sources":["../../../src/bigconsole/customWidget/CustomWidgetBuilderSheet.tsx"],"sourcesContent":["/**\n * Container that wires the custom-widget proposal panel to real workspace data\n * (BOFF-5532).\n *\n * Supplies the panel with the user's actual permitted DataSinks and parsers,\n * resolves previews by running the real parser, and persists an approved\n * proposal through the normal `createWidget` path — so an approved widget is an\n * ordinary widget, editable afterwards through the standard inspector.\n */\n\nimport { type FC, memo, useCallback, useMemo } from 'react';\nimport { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nimport { useDataSinkOperations, useParserOperations, useWidgetOperations } from '../hooks';\nimport { CustomWidgetProposalPanel } from './CustomWidgetProposalPanel';\nimport type { CustomWidgetProposal } from './proposalContract';\nimport type
|
|
1
|
+
{"version":3,"file":"CustomWidgetBuilderSheet.js","names":[],"sources":["../../../src/bigconsole/customWidget/CustomWidgetBuilderSheet.tsx"],"sourcesContent":["/**\n * Container that wires the custom-widget proposal panel to real workspace data\n * (BOFF-5532).\n *\n * Supplies the panel with the user's actual permitted DataSinks and parsers,\n * resolves previews by running the real parser, and persists an approved\n * proposal through the normal `createWidget` path — so an approved widget is an\n * ordinary widget, editable afterwards through the standard inspector.\n */\n\nimport { type FC, memo, useCallback, useMemo } from 'react';\nimport { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nimport { useDataSinkOperations, useParserOperations, useWidgetOperations } from '../hooks';\nimport { CustomWidgetProposalPanel } from './CustomWidgetProposalPanel';\nimport type { CustomWidgetProposal } from './proposalContract';\nimport {\n selectActiveProposalSinks,\n type CustomWidgetProposalSource,\n type ProposalCandidateParser,\n type ProposalCandidateSink,\n} from './proposalSource';\n\nexport interface CustomWidgetBuilderSheetProps {\n isOpen: boolean;\n onClose: () => void;\n dashboardId: string;\n pageId: string;\n /** Swap for the GraphQL-backed source once BOFF-3021's backend half lands. */\n source?: CustomWidgetProposalSource;\n}\n\n/** Grid slot for an approved widget; the reviewer can move it afterwards. */\nconst DEFAULT_POSITION = { x: 0, y: 0, width: 6, height: 4 } as const;\n\n/** Sentinel consoleId under which workspace-global parsers are stored. */\nconst GLOBAL_CONSOLE_ID = 'default';\n\nexport const CustomWidgetBuilderSheet: FC<CustomWidgetBuilderSheetProps> = memo(function CustomWidgetBuilderSheet({\n isOpen,\n onClose,\n dashboardId,\n pageId,\n source,\n}) {\n const { t } = useI18n();\n const { dataSinks } = useDataSinkOperations({ skipFetch: !isOpen });\n const { createWidget } = useWidgetOperations(pageId, dashboardId, { skipFetch: true });\n\n // Parsers are either dashboard-scoped or workspace-global (`consoleId: 'default'`),\n // and the widget inspector offers both. Fetch both here too, otherwise a proposal\n // can never bind to a global parser — which is what the parser builder creates by\n // default, so the picker would look empty in a typical workspace.\n const isGlobalConsole = dashboardId === GLOBAL_CONSOLE_ID;\n const { parsers: dashboardParsers, executeParser } = useParserOperations({\n consoleId: dashboardId,\n skipFetch: !isOpen,\n });\n const { parsers: globalParsers } = useParserOperations({\n consoleId: GLOBAL_CONSOLE_ID,\n skipFetch: !isOpen || isGlobalConsole,\n });\n\n const sinks: ProposalCandidateSink[] = useMemo(() => selectActiveProposalSinks(dataSinks), [dataSinks]);\n\n const parserCandidates: ProposalCandidateParser[] = useMemo(() => {\n const seen = new Set(dashboardParsers.map((parser) => parser.id));\n const merged = [...dashboardParsers, ...globalParsers.filter((parser) => !seen.has(parser.id))];\n return merged.map((parser) => ({ id: parser.id, name: parser.name, inputSinkKey: parser.inputSinkKey }));\n }, [dashboardParsers, globalParsers]);\n\n /**\n * Resolves what the binding actually produces. Rejects on failure so the\n * panel can distinguish \"no data\" from \"you cannot read this\".\n */\n const resolvePreviewData = useCallback(\n async (proposal: CustomWidgetProposal): Promise<unknown> => {\n if (!proposal.binding.parserId) return undefined;\n const execution = await executeParser(proposal.binding.parserId);\n if (!execution || !execution.success) {\n throw new Error(\n execution?.error ||\n t('bigconsole.widget.customWidget.error.couldNotResolveBinding', 'Could not resolve the binding.')\n );\n }\n return execution.output;\n },\n [executeParser, t]\n );\n\n const handleApprove = useCallback(\n async (proposal: CustomWidgetProposal) => {\n const widget = await createWidget({\n pageId,\n dashboardId,\n type: proposal.widgetType,\n title: proposal.presentation.title,\n description: proposal.presentation.description,\n dataSinkId: proposal.binding.dataSinkId || undefined,\n parserId: proposal.binding.parserId,\n config: {\n ...proposal.presentation.config,\n ...proposal.fieldMapping,\n // Provenance, so an approved widget can be traced back to its prompt\n // and survives export/import and clone with its origin intact.\n generatedBy: proposal.generatedBy,\n sourcePrompt: proposal.sourcePrompt,\n proposalVersion: proposal.version,\n },\n position: { ...DEFAULT_POSITION },\n });\n\n if (!widget)\n throw new Error(t('bigconsole.widget.customWidget.error.widgetNotCreated', 'The widget could not be created.'));\n },\n [createWidget, pageId, dashboardId, t]\n );\n\n return (\n <Sheet open={isOpen} onOpenChange={(open) => !open && onClose()}>\n {/*\n `min(480px, 100vw)` rather than a fixed 480px + 95vw cap, and header\n padding on the `--space-*` scale rather than raw pixels, so this sheet\n follows the same rules as the widget inspector (BOFF-5636).\n */}\n <SheetContent\n style={{\n width: 'min(480px, 100vw)',\n maxWidth: '100vw',\n padding: 0,\n display: 'flex',\n flexDirection: 'column',\n }}\n >\n <SheetHeader\n style={{\n padding: 'var(--space-cardPadding)',\n borderBottom: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-surface)',\n flexShrink: 0,\n }}\n >\n <SheetTitle>{t('bigconsole.widget.customWidget.sheet.title', 'AI custom widget')}</SheetTitle>\n <SheetDescription>\n {t(\n 'bigconsole.widget.customWidget.sheet.description',\n 'Describe a widget, review the proposal, then approve it onto the dashboard.'\n )}\n </SheetDescription>\n </SheetHeader>\n\n <div style={{ flex: 1, minHeight: 0 }}>\n <CustomWidgetProposalPanel\n sinks={sinks}\n parsers={parserCandidates}\n resolvePreviewData={resolvePreviewData}\n onApprove={handleApprove}\n onClose={onClose}\n source={source}\n />\n </div>\n </SheetContent>\n </Sheet>\n );\n});\n\nexport default CustomWidgetBuilderSheet;\n"],"mappings":";;;;;;;;;;;AAkCA,IAAM,IAAmB;CAAE,GAAG;CAAG,GAAG;CAAG,OAAO;CAAG,QAAQ;CAAG,EAGtD,IAAoB,WAEb,IAA8D,EAAK,SAAkC,EAChH,WACA,YACA,gBACA,WACA,aACC;CACD,IAAM,EAAE,SAAM,GAAS,EACjB,EAAE,iBAAc,EAAsB,EAAE,WAAW,CAAC,GAAQ,CAAC,EAC7D,EAAE,oBAAiB,EAAoB,GAAQ,GAAa,EAAE,WAAW,IAAM,CAAC,EAMhF,IAAkB,MAAgB,GAClC,EAAE,SAAS,GAAkB,qBAAkB,EAAoB;EACvE,WAAW;EACX,WAAW,CAAC;EACb,CAAC,EACI,EAAE,SAAS,MAAkB,EAAoB;EACrD,WAAW;EACX,WAAW,CAAC,KAAU;EACvB,CAAC,EAEI,IAAiC,QAAc,EAA0B,EAAU,EAAE,CAAC,EAAU,CAAC,EAEjG,IAA8C,QAAc;EAChE,IAAM,IAAO,IAAI,IAAI,EAAiB,KAAK,MAAW,EAAO,GAAG,CAAC;AAEjE,SADe,CAAC,GAAG,GAAkB,GAAG,EAAc,QAAQ,MAAW,CAAC,EAAK,IAAI,EAAO,GAAG,CAAC,CAAC,CACjF,KAAK,OAAY;GAAE,IAAI,EAAO;GAAI,MAAM,EAAO;GAAM,cAAc,EAAO;GAAc,EAAE;IACvG,CAAC,GAAkB,EAAc,CAAC,EAM/B,IAAqB,EACzB,OAAO,MAAqD;AAC1D,MAAI,CAAC,EAAS,QAAQ,SAAU;EAChC,IAAM,IAAY,MAAM,EAAc,EAAS,QAAQ,SAAS;AAChE,MAAI,CAAC,KAAa,CAAC,EAAU,QAC3B,OAAU,MACR,GAAW,SACT,EAAE,+DAA+D,iCAAiC,CACrG;AAEH,SAAO,EAAU;IAEnB,CAAC,GAAe,EAAE,CACnB,EAEK,IAAgB,EACpB,OAAO,MAAmC;AAqBxC,MAAI,CApBW,MAAM,EAAa;GAChC;GACA;GACA,MAAM,EAAS;GACf,OAAO,EAAS,aAAa;GAC7B,aAAa,EAAS,aAAa;GACnC,YAAY,EAAS,QAAQ,cAAc,KAAA;GAC3C,UAAU,EAAS,QAAQ;GAC3B,QAAQ;IACN,GAAG,EAAS,aAAa;IACzB,GAAG,EAAS;IAGZ,aAAa,EAAS;IACtB,cAAc,EAAS;IACvB,iBAAiB,EAAS;IAC3B;GACD,UAAU,EAAE,GAAG,GAAkB;GAClC,CAAC,CAGA,OAAU,MAAM,EAAE,yDAAyD,mCAAmC,CAAC;IAEnH;EAAC;EAAc;EAAQ;EAAa;EAAE,CACvC;AAED,QACE,kBAAC,GAAD;EAAO,MAAM;EAAQ,eAAe,MAAS,CAAC,KAAQ,GAAS;YAM7D,kBAAC,GAAD;GACE,OAAO;IACL,OAAO;IACP,UAAU;IACV,SAAS;IACT,SAAS;IACT,eAAe;IAChB;aAPH,CASE,kBAAC,GAAD;IACE,OAAO;KACL,SAAS;KACT,cAAc;KACd,iBAAiB;KACjB,YAAY;KACb;cANH,CAQE,kBAAC,GAAD,EAAA,UAAa,EAAE,8CAA8C,mBAAmB,EAAc,CAAA,EAC9F,kBAAC,GAAD,EAAA,UACG,EACC,oDACA,8EACD,EACgB,CAAA,CACP;OAEd,kBAAC,OAAD;IAAK,OAAO;KAAE,MAAM;KAAG,WAAW;KAAG;cACnC,kBAAC,GAAD;KACS;KACP,SAAS;KACW;KACpB,WAAW;KACF;KACD;KACR,CAAA;IACE,CAAA,CACO;;EACT,CAAA;EAEV"}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
//#region src/bigconsole/customWidget/proposalSource.ts
|
|
2
|
-
|
|
2
|
+
function e(e) {
|
|
3
|
+
return e.filter((e) => !e.deletedAt).map(({ id: e, key: t, name: n }) => ({
|
|
4
|
+
id: e,
|
|
5
|
+
key: t,
|
|
6
|
+
name: n
|
|
7
|
+
}));
|
|
8
|
+
}
|
|
9
|
+
var t = [
|
|
3
10
|
{
|
|
4
11
|
type: "funnel_chart",
|
|
5
12
|
words: [
|
|
@@ -58,7 +65,7 @@ var e = [
|
|
|
58
65
|
"graph"
|
|
59
66
|
]
|
|
60
67
|
}
|
|
61
|
-
],
|
|
68
|
+
], n = [
|
|
62
69
|
{
|
|
63
70
|
chartType: "bar",
|
|
64
71
|
words: ["bar"]
|
|
@@ -85,17 +92,17 @@ var e = [
|
|
|
85
92
|
]
|
|
86
93
|
}
|
|
87
94
|
];
|
|
88
|
-
function n(t) {
|
|
89
|
-
let n = t.toLowerCase();
|
|
90
|
-
for (let { type: t, words: r } of e) if (r.some((e) => n.includes(e))) return t;
|
|
91
|
-
return "chart";
|
|
92
|
-
}
|
|
93
95
|
function r(e) {
|
|
94
96
|
let n = e.toLowerCase();
|
|
95
|
-
for (let {
|
|
97
|
+
for (let { type: e, words: r } of t) if (r.some((e) => n.includes(e))) return e;
|
|
98
|
+
return "chart";
|
|
99
|
+
}
|
|
100
|
+
function i(e) {
|
|
101
|
+
let t = e.toLowerCase();
|
|
102
|
+
for (let { chartType: e, words: r } of n) if (r.some((e) => t.includes(e))) return e;
|
|
96
103
|
return "line";
|
|
97
104
|
}
|
|
98
|
-
function
|
|
105
|
+
function a(e, t) {
|
|
99
106
|
if (t.length === 0) return;
|
|
100
107
|
let n = e.toLowerCase().split(/[^a-z0-9]+/).filter((e) => e.length > 2), r;
|
|
101
108
|
for (let e of t) {
|
|
@@ -107,7 +114,7 @@ function i(e, t) {
|
|
|
107
114
|
}
|
|
108
115
|
return r?.sink ?? t[0];
|
|
109
116
|
}
|
|
110
|
-
function
|
|
117
|
+
function o(e) {
|
|
111
118
|
switch (e) {
|
|
112
119
|
case "heatmap": return {
|
|
113
120
|
xAxisField: "column",
|
|
@@ -131,7 +138,7 @@ function a(e) {
|
|
|
131
138
|
};
|
|
132
139
|
}
|
|
133
140
|
}
|
|
134
|
-
function
|
|
141
|
+
function s(e, t) {
|
|
135
142
|
let n = {
|
|
136
143
|
showLegend: !0,
|
|
137
144
|
showTooltip: !0
|
|
@@ -139,7 +146,7 @@ function o(e, t) {
|
|
|
139
146
|
switch (e) {
|
|
140
147
|
case "chart": return {
|
|
141
148
|
...n,
|
|
142
|
-
chartType:
|
|
149
|
+
chartType: i(t),
|
|
143
150
|
showGrid: !0
|
|
144
151
|
};
|
|
145
152
|
case "funnel_chart": return {
|
|
@@ -166,39 +173,39 @@ function o(e, t) {
|
|
|
166
173
|
default: return n;
|
|
167
174
|
}
|
|
168
175
|
}
|
|
169
|
-
function
|
|
176
|
+
function c(e) {
|
|
170
177
|
let t = e.trim().replace(/^(please\s+)?(create|build|make|add|show me|show|give me)\s+(an?\s+|the\s+)?/i, "").split(/[.;\n]/)[0].trim();
|
|
171
178
|
if (!t) return "Custom widget";
|
|
172
179
|
let n = t.charAt(0).toUpperCase() + t.slice(1);
|
|
173
180
|
return n.length > 60 ? `${n.slice(0, 57)}…` : n;
|
|
174
181
|
}
|
|
175
|
-
var
|
|
182
|
+
var l = 0, u = {
|
|
176
183
|
id: "local-heuristic-v1",
|
|
177
|
-
async propose({ prompt: e, sinks: t, parsers:
|
|
178
|
-
let
|
|
179
|
-
return
|
|
184
|
+
async propose({ prompt: e, sinks: t, parsers: n }) {
|
|
185
|
+
let i = r(e), d = a(e, t), f = d ? n.find((e) => e.inputSinkKey === d.key) : void 0;
|
|
186
|
+
return l += 1, {
|
|
180
187
|
version: "1.0",
|
|
181
|
-
id: `proposal-${
|
|
188
|
+
id: `proposal-${l}`,
|
|
182
189
|
sourcePrompt: e,
|
|
183
|
-
generatedBy:
|
|
184
|
-
widgetType:
|
|
190
|
+
generatedBy: u.id,
|
|
191
|
+
widgetType: i,
|
|
185
192
|
binding: {
|
|
186
193
|
dataSinkId: d?.id ?? "",
|
|
187
194
|
dataSinkKey: d?.key,
|
|
188
195
|
parserId: f?.id,
|
|
189
196
|
parserName: f?.name
|
|
190
197
|
},
|
|
191
|
-
fieldMapping:
|
|
198
|
+
fieldMapping: o(i),
|
|
192
199
|
presentation: {
|
|
193
|
-
title:
|
|
200
|
+
title: c(e),
|
|
194
201
|
description: `Generated from: "${e}"`,
|
|
195
|
-
config:
|
|
202
|
+
config: s(i, e)
|
|
196
203
|
},
|
|
197
204
|
rationale: d ? `Matched DataSink "${d.key}"${f ? ` and parser "${f.name}"` : " (no parser found for this sink)"}.` : "No DataSink is available in this workspace — create one before saving this widget."
|
|
198
205
|
};
|
|
199
206
|
}
|
|
200
207
|
};
|
|
201
208
|
//#endregion
|
|
202
|
-
export {
|
|
209
|
+
export { u as localProposalSource, e as selectActiveProposalSinks };
|
|
203
210
|
|
|
204
211
|
//# sourceMappingURL=proposalSource.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proposalSource.js","names":[],"sources":["../../../src/bigconsole/customWidget/proposalSource.ts"],"sourcesContent":["/**\n * Proposal source — the seam between the review UI and whatever produces\n * proposals (BOFF-5532 / BOFF-3021).\n *\n * The UI only ever talks to `CustomWidgetProposalSource`. Today the default\n * implementation derives a proposal locally from the prompt and the workspace's\n * real sinks and parsers, because the backend operation that will emit typed\n * proposals is owned by BOFF-3021's backend half and does not exist yet.\n *\n * When it lands, add a `graphqlProposalSource` here that calls it and swap the\n * value passed to the panel. Nothing in the review, edit, approve or persist\n * path changes: those already run against real sinks, real parsers and the real\n * `createBigConsoleWidget` mutation.\n */\n\nimport {\n CUSTOM_WIDGET_PROPOSAL_VERSION,\n type CustomWidgetProposal,\n type ProposableWidgetType,\n type ProposalFieldMapping,\n} from './proposalContract';\n\n/** A sink the current user may bind to, as the panel knows it. */\nexport interface ProposalCandidateSink {\n id: string;\n key: string;\n name?: string | null;\n}\n\n/** A parser the current user may bind to. */\nexport interface ProposalCandidateParser {\n id: string;\n name: string;\n inputSinkKey: string;\n}\n\nexport interface ProposalRequest {\n prompt: string;\n sinks: readonly ProposalCandidateSink[];\n parsers: readonly ProposalCandidateParser[];\n}\n\nexport interface CustomWidgetProposalSource {\n /** Stable id, surfaced in the UI so reviewers know what produced a proposal. */\n readonly id: string;\n propose(request: ProposalRequest): Promise<CustomWidgetProposal>;\n}\n\n// ============================================================================\n// Local heuristic source\n// ============================================================================\n\n/**\n * Keyword → widget type. Ordered most-specific first so \"conversion funnel\"\n * matches funnel before a generic \"chart\" would win.\n */\nconst TYPE_KEYWORDS: ReadonlyArray<{ type: ProposableWidgetType; words: readonly string[] }> = [\n { type: 'funnel_chart', words: ['funnel', 'conversion', 'drop-off', 'dropoff', 'stage'] },\n { type: 'heatmap', words: ['heatmap', 'heat map', 'matrix', 'intensity', 'by hour', 'density'] },\n { type: 'retention', words: ['retention', 'cohort'] },\n { type: 'table', words: ['table', 'list of', 'rows', 'breakdown table', 'grid'] },\n { type: 'metric_card', words: ['kpi', 'single number', 'metric card', 'scorecard', 'total'] },\n { type: 'chart', words: ['chart', 'trend', 'over time', 'line', 'bar', 'area', 'pie', 'graph'] },\n];\n\nconst CHART_SUBTYPE_KEYWORDS: ReadonlyArray<{ chartType: string; words: readonly string[] }> = [\n { chartType: 'bar', words: ['bar'] },\n { chartType: 'area', words: ['area', 'cumulative'] },\n { chartType: 'pie', words: ['pie', 'composition', 'share', 'mix'] },\n { chartType: 'line', words: ['line', 'trend', 'over time'] },\n];\n\nfunction detectWidgetType(prompt: string): ProposableWidgetType {\n const lower = prompt.toLowerCase();\n for (const { type, words } of TYPE_KEYWORDS) {\n if (words.some((word) => lower.includes(word))) return type;\n }\n return 'chart';\n}\n\nfunction detectChartSubtype(prompt: string): string {\n const lower = prompt.toLowerCase();\n for (const { chartType, words } of CHART_SUBTYPE_KEYWORDS) {\n if (words.some((word) => lower.includes(word))) return chartType;\n }\n return 'line';\n}\n\n/**\n * Picks the sink whose key/name shares the most word-stems with the prompt.\n * Falls back to the first permitted sink so a proposal is always reviewable\n * rather than failing outright.\n */\nfunction pickSink(prompt: string, sinks: readonly ProposalCandidateSink[]): ProposalCandidateSink | undefined {\n if (sinks.length === 0) return undefined;\n const tokens = prompt\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((token) => token.length > 2);\n\n let best: { sink: ProposalCandidateSink; score: number } | undefined;\n for (const sink of sinks) {\n const haystack = `${sink.key} ${sink.name ?? ''}`.toLowerCase();\n const score = tokens.reduce((acc, token) => (haystack.includes(token) ? acc + 1 : acc), 0);\n if (!best || score > best.score) best = { sink, score };\n }\n return best?.sink ?? sinks[0];\n}\n\n/** Default field mapping per widget type, using the renderers' conventions. */\nfunction defaultFieldMapping(type: ProposableWidgetType): ProposalFieldMapping {\n switch (type) {\n case 'heatmap':\n return { xAxisField: 'column', yAxisField: 'row', valueField: 'value' };\n case 'funnel_chart':\n return { labelField: 'name', valueField: 'value' };\n case 'metric_card':\n return { valueField: 'value' };\n case 'table':\n return {};\n case 'retention':\n return { cohortField: 'cohort', valueField: 'value' };\n case 'chart':\n default:\n return { xAxisField: 'name', labelField: 'name', valueField: 'value' };\n }\n}\n\nfunction defaultConfig(type: ProposableWidgetType, prompt: string): Record<string, unknown> {\n const base: Record<string, unknown> = { showLegend: true, showTooltip: true };\n switch (type) {\n case 'chart':\n return { ...base, chartType: detectChartSubtype(prompt), showGrid: true };\n case 'funnel_chart':\n return { ...base, chartType: 'funnel', showLabels: true, showPercentages: true, orientation: 'vertical' };\n case 'heatmap':\n return { ...base, colorScheme: 'blue', showValues: true };\n case 'table':\n return { ...base, pageSize: 10, sortable: true };\n case 'metric_card':\n return { ...base, showTrend: true };\n default:\n return base;\n }\n}\n\n/** Turns the prompt's first clause into a widget title. */\nfunction deriveTitle(prompt: string): string {\n const cleaned = prompt\n .trim()\n .replace(/^(please\\s+)?(create|build|make|add|show me|show|give me)\\s+(an?\\s+|the\\s+)?/i, '')\n .split(/[.;\\n]/)[0]\n .trim();\n if (!cleaned) return 'Custom widget';\n const titled = cleaned.charAt(0).toUpperCase() + cleaned.slice(1);\n return titled.length > 60 ? `${titled.slice(0, 57)}…` : titled;\n}\n\nlet proposalCounter = 0;\n\n/**\n * Derives a proposal from the prompt and the workspace's real, permitted sinks\n * and parsers. Deterministic, so the review UI and its tests behave predictably.\n */\nexport const localProposalSource: CustomWidgetProposalSource = {\n id: 'local-heuristic-v1',\n\n async propose({ prompt, sinks, parsers }: ProposalRequest): Promise<CustomWidgetProposal> {\n const widgetType = detectWidgetType(prompt);\n const sink = pickSink(prompt, sinks);\n const parser = sink ? parsers.find((candidate) => candidate.inputSinkKey === sink.key) : undefined;\n\n proposalCounter += 1;\n\n return {\n version: CUSTOM_WIDGET_PROPOSAL_VERSION,\n id: `proposal-${proposalCounter}`,\n sourcePrompt: prompt,\n generatedBy: localProposalSource.id,\n widgetType,\n binding: {\n dataSinkId: sink?.id ?? '',\n dataSinkKey: sink?.key,\n parserId: parser?.id,\n parserName: parser?.name,\n },\n fieldMapping: defaultFieldMapping(widgetType),\n presentation: {\n title: deriveTitle(prompt),\n description: `Generated from: \"${prompt}\"`,\n config: defaultConfig(widgetType, prompt),\n },\n rationale: sink\n ? `Matched DataSink \"${sink.key}\"${parser ? ` and parser \"${parser.name}\"` : ' (no parser found for this sink)'}.`\n : 'No DataSink is available in this workspace — create one before saving this widget.',\n };\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"proposalSource.js","names":[],"sources":["../../../src/bigconsole/customWidget/proposalSource.ts"],"sourcesContent":["/**\n * Proposal source — the seam between the review UI and whatever produces\n * proposals (BOFF-5532 / BOFF-3021).\n *\n * The UI only ever talks to `CustomWidgetProposalSource`. Today the default\n * implementation derives a proposal locally from the prompt and the workspace's\n * real sinks and parsers, because the backend operation that will emit typed\n * proposals is owned by BOFF-3021's backend half and does not exist yet.\n *\n * When it lands, add a `graphqlProposalSource` here that calls it and swap the\n * value passed to the panel. Nothing in the review, edit, approve or persist\n * path changes: those already run against real sinks, real parsers and the real\n * `createBigConsoleWidget` mutation.\n */\n\nimport {\n CUSTOM_WIDGET_PROPOSAL_VERSION,\n type CustomWidgetProposal,\n type ProposableWidgetType,\n type ProposalFieldMapping,\n} from './proposalContract';\n\n/** A sink the current user may bind to, as the panel knows it. */\nexport interface ProposalCandidateSink {\n id: string;\n key: string;\n name?: string | null;\n}\n\n/**\n * Converts the workspace store into a proposal-safe candidate set. The API\n * excludes soft-deleted sinks, but this second boundary is intentional:\n * persisted client state can outlive an out-of-band deletion.\n */\nexport function selectActiveProposalSinks(\n sinks: ReadonlyArray<ProposalCandidateSink & { deletedAt?: string | null }>\n): ProposalCandidateSink[] {\n return sinks.filter((sink) => !sink.deletedAt).map(({ id, key, name }) => ({ id, key, name }));\n}\n\n/** A parser the current user may bind to. */\nexport interface ProposalCandidateParser {\n id: string;\n name: string;\n inputSinkKey: string;\n}\n\nexport interface ProposalRequest {\n prompt: string;\n sinks: readonly ProposalCandidateSink[];\n parsers: readonly ProposalCandidateParser[];\n}\n\nexport interface CustomWidgetProposalSource {\n /** Stable id, surfaced in the UI so reviewers know what produced a proposal. */\n readonly id: string;\n propose(request: ProposalRequest): Promise<CustomWidgetProposal>;\n}\n\n// ============================================================================\n// Local heuristic source\n// ============================================================================\n\n/**\n * Keyword → widget type. Ordered most-specific first so \"conversion funnel\"\n * matches funnel before a generic \"chart\" would win.\n */\nconst TYPE_KEYWORDS: ReadonlyArray<{ type: ProposableWidgetType; words: readonly string[] }> = [\n { type: 'funnel_chart', words: ['funnel', 'conversion', 'drop-off', 'dropoff', 'stage'] },\n { type: 'heatmap', words: ['heatmap', 'heat map', 'matrix', 'intensity', 'by hour', 'density'] },\n { type: 'retention', words: ['retention', 'cohort'] },\n { type: 'table', words: ['table', 'list of', 'rows', 'breakdown table', 'grid'] },\n { type: 'metric_card', words: ['kpi', 'single number', 'metric card', 'scorecard', 'total'] },\n { type: 'chart', words: ['chart', 'trend', 'over time', 'line', 'bar', 'area', 'pie', 'graph'] },\n];\n\nconst CHART_SUBTYPE_KEYWORDS: ReadonlyArray<{ chartType: string; words: readonly string[] }> = [\n { chartType: 'bar', words: ['bar'] },\n { chartType: 'area', words: ['area', 'cumulative'] },\n { chartType: 'pie', words: ['pie', 'composition', 'share', 'mix'] },\n { chartType: 'line', words: ['line', 'trend', 'over time'] },\n];\n\nfunction detectWidgetType(prompt: string): ProposableWidgetType {\n const lower = prompt.toLowerCase();\n for (const { type, words } of TYPE_KEYWORDS) {\n if (words.some((word) => lower.includes(word))) return type;\n }\n return 'chart';\n}\n\nfunction detectChartSubtype(prompt: string): string {\n const lower = prompt.toLowerCase();\n for (const { chartType, words } of CHART_SUBTYPE_KEYWORDS) {\n if (words.some((word) => lower.includes(word))) return chartType;\n }\n return 'line';\n}\n\n/**\n * Picks the sink whose key/name shares the most word-stems with the prompt.\n * Falls back to the first permitted sink so a proposal is always reviewable\n * rather than failing outright.\n */\nfunction pickSink(prompt: string, sinks: readonly ProposalCandidateSink[]): ProposalCandidateSink | undefined {\n if (sinks.length === 0) return undefined;\n const tokens = prompt\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((token) => token.length > 2);\n\n let best: { sink: ProposalCandidateSink; score: number } | undefined;\n for (const sink of sinks) {\n const haystack = `${sink.key} ${sink.name ?? ''}`.toLowerCase();\n const score = tokens.reduce((acc, token) => (haystack.includes(token) ? acc + 1 : acc), 0);\n if (!best || score > best.score) best = { sink, score };\n }\n return best?.sink ?? sinks[0];\n}\n\n/** Default field mapping per widget type, using the renderers' conventions. */\nfunction defaultFieldMapping(type: ProposableWidgetType): ProposalFieldMapping {\n switch (type) {\n case 'heatmap':\n return { xAxisField: 'column', yAxisField: 'row', valueField: 'value' };\n case 'funnel_chart':\n return { labelField: 'name', valueField: 'value' };\n case 'metric_card':\n return { valueField: 'value' };\n case 'table':\n return {};\n case 'retention':\n return { cohortField: 'cohort', valueField: 'value' };\n case 'chart':\n default:\n return { xAxisField: 'name', labelField: 'name', valueField: 'value' };\n }\n}\n\nfunction defaultConfig(type: ProposableWidgetType, prompt: string): Record<string, unknown> {\n const base: Record<string, unknown> = { showLegend: true, showTooltip: true };\n switch (type) {\n case 'chart':\n return { ...base, chartType: detectChartSubtype(prompt), showGrid: true };\n case 'funnel_chart':\n return { ...base, chartType: 'funnel', showLabels: true, showPercentages: true, orientation: 'vertical' };\n case 'heatmap':\n return { ...base, colorScheme: 'blue', showValues: true };\n case 'table':\n return { ...base, pageSize: 10, sortable: true };\n case 'metric_card':\n return { ...base, showTrend: true };\n default:\n return base;\n }\n}\n\n/** Turns the prompt's first clause into a widget title. */\nfunction deriveTitle(prompt: string): string {\n const cleaned = prompt\n .trim()\n .replace(/^(please\\s+)?(create|build|make|add|show me|show|give me)\\s+(an?\\s+|the\\s+)?/i, '')\n .split(/[.;\\n]/)[0]\n .trim();\n if (!cleaned) return 'Custom widget';\n const titled = cleaned.charAt(0).toUpperCase() + cleaned.slice(1);\n return titled.length > 60 ? `${titled.slice(0, 57)}…` : titled;\n}\n\nlet proposalCounter = 0;\n\n/**\n * Derives a proposal from the prompt and the workspace's real, permitted sinks\n * and parsers. Deterministic, so the review UI and its tests behave predictably.\n */\nexport const localProposalSource: CustomWidgetProposalSource = {\n id: 'local-heuristic-v1',\n\n async propose({ prompt, sinks, parsers }: ProposalRequest): Promise<CustomWidgetProposal> {\n const widgetType = detectWidgetType(prompt);\n const sink = pickSink(prompt, sinks);\n const parser = sink ? parsers.find((candidate) => candidate.inputSinkKey === sink.key) : undefined;\n\n proposalCounter += 1;\n\n return {\n version: CUSTOM_WIDGET_PROPOSAL_VERSION,\n id: `proposal-${proposalCounter}`,\n sourcePrompt: prompt,\n generatedBy: localProposalSource.id,\n widgetType,\n binding: {\n dataSinkId: sink?.id ?? '',\n dataSinkKey: sink?.key,\n parserId: parser?.id,\n parserName: parser?.name,\n },\n fieldMapping: defaultFieldMapping(widgetType),\n presentation: {\n title: deriveTitle(prompt),\n description: `Generated from: \"${prompt}\"`,\n config: defaultConfig(widgetType, prompt),\n },\n rationale: sink\n ? `Matched DataSink \"${sink.key}\"${parser ? ` and parser \"${parser.name}\"` : ' (no parser found for this sink)'}.`\n : 'No DataSink is available in this workspace — create one before saving this widget.',\n };\n },\n};\n"],"mappings":"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"}
|