@inkindcards/semantic-layer 2.2.5 → 2.3.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.
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { S as SemanticField, d as FieldType, Q as QueryResult } from './types-c5qj4BKB.cjs';
2
+ import { S as SemanticField, d as FieldType, Q as QueryResult, T as TimeGrain } from './types-c5qj4BKB.cjs';
3
3
  import React__default from 'react';
4
4
 
5
5
  interface MetricPickerProps {
@@ -84,20 +84,51 @@ interface FieldMatch {
84
84
  suggestedField: SemanticField | null;
85
85
  confidence: MatchConfidence;
86
86
  }
87
+ interface ChartAnalysis {
88
+ dimensionColumns: string[];
89
+ metricColumns: string[];
90
+ }
91
+ /**
92
+ * Best-effort match of a single column name against the catalog.
93
+ */
94
+ declare function matchField(column: string, catalog: SemanticField[]): {
95
+ field: SemanticField | null;
96
+ confidence: MatchConfidence;
97
+ };
87
98
  /**
88
99
  * Match an array of column names against the semantic layer field catalog.
89
100
  * Uses three layers: exact name match, normalized match, then display name match.
90
101
  */
91
102
  declare function matchFields(columns: string[], catalog: SemanticField[]): FieldMatch[];
103
+ /**
104
+ * Analyze chart data to classify columns as dimensions (string/date values)
105
+ * or metrics (numeric values). Used by the inspector modal to build the
106
+ * query builder UI instead of a flat column-to-field mapping.
107
+ */
108
+ declare function analyzeChartData(columns: string[], sampleValues: Record<string, unknown>[]): ChartAnalysis;
92
109
 
93
110
  interface FieldMapping {
94
111
  column: string;
95
112
  field: SemanticField | null;
96
113
  }
114
+ interface DimensionMapping {
115
+ column: string;
116
+ field: SemanticField;
117
+ }
118
+ interface MigrationConfig {
119
+ componentLabel: string;
120
+ dimensions: DimensionMapping[];
121
+ metrics: SemanticField[];
122
+ grain: TimeGrain | null;
123
+ originalMetricColumns: string[];
124
+ }
97
125
  /**
98
126
  * Generate a ready-to-paste Lovable prompt that tells the AI how to migrate
99
- * a component from its current data to a useSemanticQuery() call.
127
+ * a component from hardcoded data to a useSemanticQuery() call.
128
+ *
129
+ * Understands the distinction between dimensions (groupBy) and metrics (values),
130
+ * and produces correct query config with data reshaping instructions.
100
131
  */
101
- declare function generateMigrationPrompt(componentLabel: string, mappings: FieldMapping[]): string;
132
+ declare function generateMigrationPrompt(config: MigrationConfig): string;
102
133
 
103
- export { AdminDashboard, type AdminDashboardProps, DataCatalog, type DataCatalogProps, DataInspector, type DataInspectorProps, type FieldMapping, type FieldMatch, Inspectable, type InspectableProps, type MatchConfidence, MetricPicker, type MetricPickerProps, ResultsTable, type ResultsTableProps, generateMigrationPrompt, matchFields };
134
+ export { AdminDashboard, type AdminDashboardProps, type ChartAnalysis, DataCatalog, type DataCatalogProps, DataInspector, type DataInspectorProps, type DimensionMapping, type FieldMapping, type FieldMatch, Inspectable, type InspectableProps, type MatchConfidence, MetricPicker, type MetricPickerProps, type MigrationConfig, ResultsTable, type ResultsTableProps, analyzeChartData, generateMigrationPrompt, matchField, matchFields };
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { S as SemanticField, d as FieldType, Q as QueryResult } from './types-c5qj4BKB.js';
2
+ import { S as SemanticField, d as FieldType, Q as QueryResult, T as TimeGrain } from './types-c5qj4BKB.js';
3
3
  import React__default from 'react';
4
4
 
5
5
  interface MetricPickerProps {
@@ -84,20 +84,51 @@ interface FieldMatch {
84
84
  suggestedField: SemanticField | null;
85
85
  confidence: MatchConfidence;
86
86
  }
87
+ interface ChartAnalysis {
88
+ dimensionColumns: string[];
89
+ metricColumns: string[];
90
+ }
91
+ /**
92
+ * Best-effort match of a single column name against the catalog.
93
+ */
94
+ declare function matchField(column: string, catalog: SemanticField[]): {
95
+ field: SemanticField | null;
96
+ confidence: MatchConfidence;
97
+ };
87
98
  /**
88
99
  * Match an array of column names against the semantic layer field catalog.
89
100
  * Uses three layers: exact name match, normalized match, then display name match.
90
101
  */
91
102
  declare function matchFields(columns: string[], catalog: SemanticField[]): FieldMatch[];
103
+ /**
104
+ * Analyze chart data to classify columns as dimensions (string/date values)
105
+ * or metrics (numeric values). Used by the inspector modal to build the
106
+ * query builder UI instead of a flat column-to-field mapping.
107
+ */
108
+ declare function analyzeChartData(columns: string[], sampleValues: Record<string, unknown>[]): ChartAnalysis;
92
109
 
93
110
  interface FieldMapping {
94
111
  column: string;
95
112
  field: SemanticField | null;
96
113
  }
114
+ interface DimensionMapping {
115
+ column: string;
116
+ field: SemanticField;
117
+ }
118
+ interface MigrationConfig {
119
+ componentLabel: string;
120
+ dimensions: DimensionMapping[];
121
+ metrics: SemanticField[];
122
+ grain: TimeGrain | null;
123
+ originalMetricColumns: string[];
124
+ }
97
125
  /**
98
126
  * Generate a ready-to-paste Lovable prompt that tells the AI how to migrate
99
- * a component from its current data to a useSemanticQuery() call.
127
+ * a component from hardcoded data to a useSemanticQuery() call.
128
+ *
129
+ * Understands the distinction between dimensions (groupBy) and metrics (values),
130
+ * and produces correct query config with data reshaping instructions.
100
131
  */
101
- declare function generateMigrationPrompt(componentLabel: string, mappings: FieldMapping[]): string;
132
+ declare function generateMigrationPrompt(config: MigrationConfig): string;
102
133
 
103
- export { AdminDashboard, type AdminDashboardProps, DataCatalog, type DataCatalogProps, DataInspector, type DataInspectorProps, type FieldMapping, type FieldMatch, Inspectable, type InspectableProps, type MatchConfidence, MetricPicker, type MetricPickerProps, ResultsTable, type ResultsTableProps, generateMigrationPrompt, matchFields };
134
+ export { AdminDashboard, type AdminDashboardProps, type ChartAnalysis, DataCatalog, type DataCatalogProps, DataInspector, type DataInspectorProps, type DimensionMapping, type FieldMapping, type FieldMatch, Inspectable, type InspectableProps, type MatchConfidence, MetricPicker, type MetricPickerProps, type MigrationConfig, ResultsTable, type ResultsTableProps, analyzeChartData, generateMigrationPrompt, matchField, matchFields };
@@ -996,48 +996,83 @@ function matchDisplayName(column, catalog) {
996
996
  if (lower.length < 3) return null;
997
997
  return catalog.find((f) => f.displayName.toLowerCase().includes(lower)) ?? catalog.find((f) => f.description.toLowerCase().includes(lower)) ?? null;
998
998
  }
999
+ function matchField(column, catalog) {
1000
+ const exact = matchExact(column, catalog);
1001
+ if (exact) return { field: exact, confidence: "exact" };
1002
+ const normalized = matchNormalized(column, catalog);
1003
+ if (normalized) return { field: normalized, confidence: "fuzzy" };
1004
+ const display = matchDisplayName(column, catalog);
1005
+ if (display) return { field: display, confidence: "fuzzy" };
1006
+ return { field: null, confidence: "manual" };
1007
+ }
999
1008
  function matchFields(columns, catalog) {
1000
1009
  return columns.map((column) => {
1001
- const exact = matchExact(column, catalog);
1002
- if (exact) return { column, suggestedField: exact, confidence: "exact" };
1003
- const normalized = matchNormalized(column, catalog);
1004
- if (normalized) return { column, suggestedField: normalized, confidence: "fuzzy" };
1005
- const display = matchDisplayName(column, catalog);
1006
- if (display) return { column, suggestedField: display, confidence: "fuzzy" };
1007
- return { column, suggestedField: null, confidence: "manual" };
1010
+ const { field, confidence } = matchField(column, catalog);
1011
+ return { column, suggestedField: field, confidence };
1008
1012
  });
1009
1013
  }
1014
+ function analyzeChartData(columns, sampleValues) {
1015
+ const dimensionColumns = [];
1016
+ const metricColumns = [];
1017
+ for (const col of columns) {
1018
+ const values = sampleValues.map((row) => row[col]).filter((v) => v !== null && v !== void 0);
1019
+ if (values.length === 0) {
1020
+ dimensionColumns.push(col);
1021
+ continue;
1022
+ }
1023
+ const numericCount = values.filter((v) => typeof v === "number" || typeof v === "string" && !isNaN(Number(v)) && v.trim() !== "").length;
1024
+ const isNumeric = numericCount / values.length > 0.5;
1025
+ if (isNumeric) {
1026
+ metricColumns.push(col);
1027
+ } else {
1028
+ dimensionColumns.push(col);
1029
+ }
1030
+ }
1031
+ return { dimensionColumns, metricColumns };
1032
+ }
1010
1033
 
1011
1034
  // src/components/prompt-generator.ts
1012
- function generateMigrationPrompt(componentLabel, mappings) {
1013
- const resolved = mappings.filter((m) => m.field !== null);
1014
- if (resolved.length === 0) {
1035
+ function generateMigrationPrompt(config) {
1036
+ const { componentLabel, dimensions, metrics, grain, originalMetricColumns } = config;
1037
+ if (dimensions.length === 0 && metrics.length === 0) {
1015
1038
  return `Replace the sample/hardcoded data in the "${componentLabel}" component with live data from the semantic layer using useSemanticQuery. Use the DataCatalog to find the right field names.`;
1016
1039
  }
1017
- const metrics = resolved.filter((m) => m.field.type === "metric").map((m) => m.field.name);
1018
- const timeDims = resolved.filter((m) => m.field.type === "time_dimension").map((m) => m.field.name);
1019
- const dims = resolved.filter((m) => m.field.type === "dimension").map((m) => m.field.name);
1020
- const groupBy = [...timeDims, ...dims];
1021
1040
  const parts = [
1022
- `Replace the sample/hardcoded data in the "${componentLabel}" component with a useSemanticQuery call using:`
1041
+ `Replace the sample/hardcoded data in the "${componentLabel}" component with a useSemanticQuery call using:`,
1042
+ ""
1023
1043
  ];
1024
1044
  if (metrics.length > 0) {
1025
- parts.push(` metrics: [${metrics.map((n) => `'${n}'`).join(", ")}]`);
1045
+ parts.push(` metrics: [${metrics.map((m) => `'${m.name}'`).join(", ")}]`);
1026
1046
  }
1027
- if (groupBy.length > 0) {
1047
+ if (dimensions.length > 0) {
1048
+ const groupBy = dimensions.map((d) => d.field.name);
1028
1049
  parts.push(` groupBy: [${groupBy.map((n) => `'${n}'`).join(", ")}]`);
1029
1050
  }
1030
- if (timeDims.length > 0) {
1031
- parts.push(` grain: 'month'`);
1051
+ if (grain) {
1052
+ parts.push(` grain: '${grain}'`);
1032
1053
  }
1033
1054
  parts.push("");
1034
- parts.push("Map the query result columns to the component's props:");
1035
- for (const m of resolved) {
1036
- parts.push(` "${m.column}" \u2192 data column "${m.field.name}"`);
1055
+ const sampleRow = [];
1056
+ for (const d of dimensions) {
1057
+ sampleRow.push(`${d.field.name}: "..."`);
1058
+ }
1059
+ for (const m of metrics) {
1060
+ sampleRow.push(`${m.name}: 1234`);
1061
+ }
1062
+ parts.push("The query returns rows like:");
1063
+ parts.push(` { ${sampleRow.join(", ")} }`);
1064
+ parts.push("");
1065
+ parts.push("Map to the chart's expected format:");
1066
+ for (const d of dimensions) {
1067
+ parts.push(` X-axis / category: "${d.field.name}" (was "${d.column}" in the hardcoded data)`);
1068
+ }
1069
+ if (metrics.length > 0 && originalMetricColumns.length > 0) {
1070
+ parts.push(` Series / values: each metric (${metrics.map((m) => `"${m.name}"`).join(", ")}) becomes a line/area/bar in the chart`);
1071
+ parts.push(` (replaces the old columns: ${originalMetricColumns.map((c) => `"${c}"`).join(", ")})`);
1037
1072
  }
1038
1073
  parts.push("");
1039
1074
  parts.push(
1040
- "Remove the old hardcoded/sample data and any fetch logic it used. Use the data, isLoading, and error values from useSemanticQuery to render the component."
1075
+ "Remove the old hardcoded/sample data and any fetch logic it used. Use the data, isLoading, and error values from useSemanticQuery to render the component. Show a loading spinner while isLoading is true. Show an error message if error is not null."
1041
1076
  );
1042
1077
  return parts.join("\n");
1043
1078
  }
@@ -1146,31 +1181,54 @@ function InspectorModal({
1146
1181
  entry,
1147
1182
  onClose
1148
1183
  }) {
1149
- const { fields: catalog } = useMetrics();
1150
- const initialMatches = useMemo(
1151
- () => matchFields(entry.columns, catalog),
1152
- [entry.columns, catalog]
1153
- );
1154
- const [mappings, setMappings] = useState(
1155
- () => new Map(initialMatches.map((m) => [m.column, m.suggestedField]))
1184
+ const { fields: catalog, error: catalogError, isLoading: catalogLoading } = useMetrics();
1185
+ const analysis = useMemo(
1186
+ () => analyzeChartData(entry.columns, entry.sampleValues),
1187
+ [entry.columns, entry.sampleValues]
1156
1188
  );
1189
+ const dimensions = catalog.filter((f) => f.type === "dimension" || f.type === "time_dimension");
1190
+ const metrics = catalog.filter((f) => f.type === "metric");
1191
+ const initialDimMappings = useMemo(() => {
1192
+ const map = /* @__PURE__ */ new Map();
1193
+ for (const col of analysis.dimensionColumns) {
1194
+ const { field } = matchField(col, dimensions);
1195
+ map.set(col, field);
1196
+ }
1197
+ return map;
1198
+ }, [analysis.dimensionColumns, dimensions]);
1199
+ const [dimMappings, setDimMappings] = useState(initialDimMappings);
1157
1200
  useEffect(() => {
1158
- setMappings(new Map(initialMatches.map((m) => [m.column, m.suggestedField])));
1159
- }, [initialMatches]);
1201
+ setDimMappings(initialDimMappings);
1202
+ }, [initialDimMappings]);
1203
+ const [selectedMetrics, setSelectedMetrics] = useState([]);
1204
+ const hasTimeDim = Array.from(dimMappings.values()).some((f) => f?.type === "time_dimension");
1205
+ const [grain, setGrain] = useState("month");
1160
1206
  const [copied, setCopied] = useState(false);
1161
- const handleFieldChange = useCallback((column, field) => {
1162
- setMappings((prev) => {
1207
+ const handleDimChange = useCallback((col, field) => {
1208
+ setDimMappings((prev) => {
1163
1209
  const next = new Map(prev);
1164
- next.set(column, field);
1210
+ next.set(col, field);
1165
1211
  return next;
1166
1212
  });
1167
1213
  }, []);
1214
+ const handleToggleMetric = useCallback((field) => {
1215
+ setSelectedMetrics(
1216
+ (prev) => prev.some((m) => m.name === field.name) ? prev.filter((m) => m.name !== field.name) : [...prev, field]
1217
+ );
1218
+ }, []);
1168
1219
  const handleGenerate = useCallback(async () => {
1169
- const fieldMappings = entry.columns.map((col) => ({
1170
- column: col,
1171
- field: mappings.get(col) ?? null
1172
- }));
1173
- const prompt = generateMigrationPrompt(entry.label, fieldMappings);
1220
+ const resolvedDims = [];
1221
+ for (const [col, field] of dimMappings) {
1222
+ if (field) resolvedDims.push({ column: col, field });
1223
+ }
1224
+ const migrationConfig = {
1225
+ componentLabel: entry.label,
1226
+ dimensions: resolvedDims,
1227
+ metrics: selectedMetrics,
1228
+ grain: hasTimeDim && grain ? grain : null,
1229
+ originalMetricColumns: analysis.metricColumns
1230
+ };
1231
+ const prompt = generateMigrationPrompt(migrationConfig);
1174
1232
  try {
1175
1233
  await navigator.clipboard.writeText(prompt);
1176
1234
  setCopied(true);
@@ -1185,12 +1243,8 @@ function InspectorModal({
1185
1243
  setCopied(true);
1186
1244
  setTimeout(() => setCopied(false), 2e3);
1187
1245
  }
1188
- }, [entry, mappings]);
1189
- const confidenceMap = useMemo(() => {
1190
- const map = /* @__PURE__ */ new Map();
1191
- for (const m of initialMatches) map.set(m.column, m.confidence);
1192
- return map;
1193
- }, [initialMatches]);
1246
+ }, [entry, dimMappings, selectedMetrics, grain, hasTimeDim, analysis.metricColumns]);
1247
+ const canGenerate = selectedMetrics.length > 0 || Array.from(dimMappings.values()).some(Boolean);
1194
1248
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1195
1249
  /* @__PURE__ */ jsx("div", { onClick: onClose, style: backdropStyle }),
1196
1250
  /* @__PURE__ */ jsxs("div", { style: modalStyle, children: [
@@ -1204,92 +1258,175 @@ function InspectorModal({
1204
1258
  ] }),
1205
1259
  /* @__PURE__ */ jsx("button", { onClick: onClose, style: closeBtnStyle, children: "\xD7" })
1206
1260
  ] }),
1207
- entry.sampleValues.length > 0 && /* @__PURE__ */ jsxs("div", { style: sectionStyle, children: [
1208
- /* @__PURE__ */ jsx("div", { style: sectionTitleStyle, children: "Current Data Preview" }),
1209
- /* @__PURE__ */ jsx("div", { style: { overflow: "auto" }, children: /* @__PURE__ */ jsxs("table", { style: previewTableStyle, children: [
1210
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: entry.columns.map((col) => /* @__PURE__ */ jsx("th", { style: previewThStyle, children: col }, col)) }) }),
1211
- /* @__PURE__ */ jsx("tbody", { children: entry.sampleValues.map((row, i) => /* @__PURE__ */ jsx("tr", { children: entry.columns.map((col) => /* @__PURE__ */ jsx("td", { style: previewTdStyle, children: row[col] === null || row[col] === void 0 ? "\u2014" : String(row[col]) }, col)) }, i)) })
1212
- ] }) })
1213
- ] }),
1214
- /* @__PURE__ */ jsxs("div", { style: sectionStyle, children: [
1215
- /* @__PURE__ */ jsx("div", { style: sectionTitleStyle, children: "Field Mapping" }),
1216
- /* @__PURE__ */ jsxs("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 13 }, children: [
1217
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1218
- /* @__PURE__ */ jsx("th", { style: mappingThStyle, children: "Current Column" }),
1219
- /* @__PURE__ */ jsx("th", { style: mappingThStyle, children: "Confidence" }),
1220
- /* @__PURE__ */ jsx("th", { style: mappingThStyle, children: "Semantic Layer Field" })
1261
+ /* @__PURE__ */ jsxs("div", { style: { overflow: "auto", flex: 1 }, children: [
1262
+ entry.sampleValues.length > 0 && /* @__PURE__ */ jsxs("div", { style: sectionStyle, children: [
1263
+ /* @__PURE__ */ jsx("div", { style: sectionTitleStyle, children: "Current Data Preview" }),
1264
+ /* @__PURE__ */ jsx("div", { style: { overflow: "auto" }, children: /* @__PURE__ */ jsxs("table", { style: previewTableStyle, children: [
1265
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: entry.columns.map((col) => /* @__PURE__ */ jsx("th", { style: previewThStyle, children: col }, col)) }) }),
1266
+ /* @__PURE__ */ jsx("tbody", { children: entry.sampleValues.map((row, i) => /* @__PURE__ */ jsx("tr", { children: entry.columns.map((col) => /* @__PURE__ */ jsx("td", { style: previewTdStyle, children: row[col] === null || row[col] === void 0 ? "\u2014" : String(row[col]) }, col)) }, i)) })
1221
1267
  ] }) }),
1222
- /* @__PURE__ */ jsx("tbody", { children: entry.columns.map((col) => /* @__PURE__ */ jsx(
1223
- MappingRow,
1224
- {
1225
- column: col,
1226
- confidence: confidenceMap.get(col) ?? "manual",
1227
- selectedField: mappings.get(col) ?? null,
1228
- catalog,
1229
- onChange: (field) => handleFieldChange(col, field)
1230
- },
1231
- col
1232
- )) })
1268
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: 8, fontSize: 11, color: "#9ca3af" }, children: [
1269
+ "Detected: ",
1270
+ analysis.dimensionColumns.length,
1271
+ " dimension column",
1272
+ analysis.dimensionColumns.length !== 1 ? "s" : "",
1273
+ " (",
1274
+ analysis.dimensionColumns.join(", ") || "none",
1275
+ ")",
1276
+ " \xB7 ",
1277
+ analysis.metricColumns.length,
1278
+ " value column",
1279
+ analysis.metricColumns.length !== 1 ? "s" : "",
1280
+ " (",
1281
+ analysis.metricColumns.join(", ") || "none",
1282
+ ")"
1283
+ ] })
1284
+ ] }),
1285
+ catalog.length === 0 && /* @__PURE__ */ jsx("div", { style: { ...sectionStyle, textAlign: "center" }, children: /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#9ca3af" }, children: catalogLoading ? "Loading catalog..." : catalogError ? `Error loading catalog: ${catalogError}` : "No semantic layer catalog available. Connect to the gateway to enable smart matching." }) }),
1286
+ catalog.length > 0 && /* @__PURE__ */ jsxs("div", { style: sectionStyle, children: [
1287
+ /* @__PURE__ */ jsx("div", { style: sectionTitleStyle, children: "Dimensions (Group By)" }),
1288
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "#9ca3af", marginBottom: 8 }, children: "What categories define the rows? (X-axis, grouping)" }),
1289
+ analysis.dimensionColumns.length === 0 ? /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#9ca3af", fontStyle: "italic" }, children: "No dimension columns detected in the data." }) : /* @__PURE__ */ jsxs("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 13 }, children: [
1290
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1291
+ /* @__PURE__ */ jsx("th", { style: mappingThStyle, children: "Current Column" }),
1292
+ /* @__PURE__ */ jsx("th", { style: mappingThStyle, children: "Sample Values" }),
1293
+ /* @__PURE__ */ jsx("th", { style: mappingThStyle, children: "Semantic Layer Dimension" })
1294
+ ] }) }),
1295
+ /* @__PURE__ */ jsx("tbody", { children: analysis.dimensionColumns.map((col) => {
1296
+ const sampleVals = entry.sampleValues.map((row) => row[col]).filter((v) => v !== null && v !== void 0).slice(0, 4).map(String);
1297
+ return /* @__PURE__ */ jsxs("tr", { children: [
1298
+ /* @__PURE__ */ jsx("td", { style: mappingTdStyle, children: /* @__PURE__ */ jsx("code", { style: { fontSize: 12, color: "#6366f1", fontFamily: "monospace" }, children: col }) }),
1299
+ /* @__PURE__ */ jsxs("td", { style: { ...mappingTdStyle, fontSize: 11, color: "#9ca3af" }, children: [
1300
+ sampleVals.join(", "),
1301
+ sampleVals.length >= 4 ? ", ..." : ""
1302
+ ] }),
1303
+ /* @__PURE__ */ jsx("td", { style: mappingTdStyle, children: /* @__PURE__ */ jsxs(
1304
+ "select",
1305
+ {
1306
+ value: dimMappings.get(col)?.name ?? "",
1307
+ onChange: (e) => {
1308
+ const field = dimensions.find((f) => f.name === e.target.value) ?? null;
1309
+ handleDimChange(col, field);
1310
+ },
1311
+ style: selectStyle2,
1312
+ children: [
1313
+ /* @__PURE__ */ jsx("option", { value: "", children: "-- select dimension --" }),
1314
+ dimensions.map((f) => /* @__PURE__ */ jsxs("option", { value: f.name, children: [
1315
+ f.displayName,
1316
+ " (",
1317
+ f.type,
1318
+ ") \u2014 ",
1319
+ f.name
1320
+ ] }, f.name))
1321
+ ]
1322
+ }
1323
+ ) })
1324
+ ] }, col);
1325
+ }) })
1326
+ ] }),
1327
+ hasTimeDim && /* @__PURE__ */ jsxs("div", { style: { marginTop: 10, display: "flex", alignItems: "center", gap: 8 }, children: [
1328
+ /* @__PURE__ */ jsx("label", { style: { fontSize: 12, fontWeight: 600, color: "#374151" }, children: "Time Grain:" }),
1329
+ /* @__PURE__ */ jsxs(
1330
+ "select",
1331
+ {
1332
+ value: grain,
1333
+ onChange: (e) => setGrain(e.target.value),
1334
+ style: { ...selectStyle2, width: "auto" },
1335
+ children: [
1336
+ /* @__PURE__ */ jsx("option", { value: "day", children: "Day" }),
1337
+ /* @__PURE__ */ jsx("option", { value: "week", children: "Week" }),
1338
+ /* @__PURE__ */ jsx("option", { value: "month", children: "Month" }),
1339
+ /* @__PURE__ */ jsx("option", { value: "quarter", children: "Quarter" }),
1340
+ /* @__PURE__ */ jsx("option", { value: "year", children: "Year" })
1341
+ ]
1342
+ }
1343
+ )
1344
+ ] })
1233
1345
  ] }),
1234
- catalog.length === 0 && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#9ca3af", marginTop: 8, textAlign: "center" }, children: "No semantic layer catalog available. Connect to the gateway to enable smart matching." })
1346
+ catalog.length > 0 && /* @__PURE__ */ jsxs("div", { style: sectionStyle, children: [
1347
+ /* @__PURE__ */ jsx("div", { style: sectionTitleStyle, children: "Metrics (Values)" }),
1348
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 11, color: "#9ca3af", marginBottom: 8 }, children: [
1349
+ "Which metrics should be queried? These become the series/values in the chart.",
1350
+ analysis.metricColumns.length > 0 && /* @__PURE__ */ jsxs("span", { children: [
1351
+ " (replaces: ",
1352
+ analysis.metricColumns.join(", "),
1353
+ ")"
1354
+ ] })
1355
+ ] }),
1356
+ metrics.length === 0 ? /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#9ca3af", fontStyle: "italic" }, children: "No metrics available in the catalog." }) : /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 4, maxHeight: 200, overflow: "auto" }, children: metrics.map((m) => {
1357
+ const isSelected = selectedMetrics.some((s) => s.name === m.name);
1358
+ return /* @__PURE__ */ jsxs(
1359
+ "label",
1360
+ {
1361
+ style: {
1362
+ display: "flex",
1363
+ alignItems: "center",
1364
+ gap: 8,
1365
+ padding: "4px 8px",
1366
+ borderRadius: 4,
1367
+ cursor: "pointer",
1368
+ backgroundColor: isSelected ? "#eff6ff" : "transparent",
1369
+ border: isSelected ? "1px solid #bfdbfe" : "1px solid transparent",
1370
+ fontSize: 12
1371
+ },
1372
+ children: [
1373
+ /* @__PURE__ */ jsx(
1374
+ "input",
1375
+ {
1376
+ type: "checkbox",
1377
+ checked: isSelected,
1378
+ onChange: () => handleToggleMetric(m),
1379
+ style: { margin: 0 }
1380
+ }
1381
+ ),
1382
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500, color: "#111827" }, children: m.displayName }),
1383
+ /* @__PURE__ */ jsx("code", { style: { fontSize: 10, color: "#6366f1", fontFamily: "monospace" }, children: m.name }),
1384
+ m.category && /* @__PURE__ */ jsx("span", { style: { marginLeft: "auto", fontSize: 10, color: "#9ca3af" }, children: m.category })
1385
+ ]
1386
+ },
1387
+ m.name
1388
+ );
1389
+ }) }),
1390
+ selectedMetrics.length > 0 && /* @__PURE__ */ jsxs("div", { style: { marginTop: 8, fontSize: 11, color: "#6b7280" }, children: [
1391
+ "Selected: ",
1392
+ selectedMetrics.map((m) => m.displayName).join(", ")
1393
+ ] })
1394
+ ] }),
1395
+ canGenerate && /* @__PURE__ */ jsxs("div", { style: sectionStyle, children: [
1396
+ /* @__PURE__ */ jsx("div", { style: sectionTitleStyle, children: "Query Preview" }),
1397
+ /* @__PURE__ */ jsx("pre", { style: {
1398
+ fontSize: 11,
1399
+ fontFamily: "monospace",
1400
+ backgroundColor: "#f9fafb",
1401
+ border: "1px solid #e5e7eb",
1402
+ borderRadius: 6,
1403
+ padding: "8px 12px",
1404
+ whiteSpace: "pre-wrap",
1405
+ color: "#374151",
1406
+ margin: 0
1407
+ }, children: `useSemanticQuery({${selectedMetrics.length > 0 ? `
1408
+ metrics: [${selectedMetrics.map((m) => `'${m.name}'`).join(", ")}],` : ""}${Array.from(dimMappings.values()).some(Boolean) ? `
1409
+ groupBy: [${Array.from(dimMappings.values()).filter(Boolean).map((f) => `'${f.name}'`).join(", ")}],` : ""}${hasTimeDim && grain ? `
1410
+ grain: '${grain}',` : ""}
1411
+ })` })
1412
+ ] })
1235
1413
  ] }),
1236
- /* @__PURE__ */ jsx("div", { style: { padding: "12px 20px", borderTop: "1px solid #e5e7eb" }, children: /* @__PURE__ */ jsx("button", { onClick: handleGenerate, style: generateBtnStyle, children: copied ? "Copied to clipboard!" : "Generate Migration Prompt" }) })
1414
+ /* @__PURE__ */ jsx("div", { style: { padding: "12px 20px", borderTop: "1px solid #e5e7eb" }, children: /* @__PURE__ */ jsx(
1415
+ "button",
1416
+ {
1417
+ onClick: handleGenerate,
1418
+ disabled: !canGenerate,
1419
+ style: {
1420
+ ...generateBtnStyle,
1421
+ opacity: canGenerate ? 1 : 0.5,
1422
+ cursor: canGenerate ? "pointer" : "not-allowed"
1423
+ },
1424
+ children: copied ? "Copied to clipboard!" : "Generate Migration Prompt"
1425
+ }
1426
+ ) })
1237
1427
  ] })
1238
1428
  ] });
1239
1429
  }
1240
- function MappingRow({
1241
- column,
1242
- confidence,
1243
- selectedField,
1244
- catalog,
1245
- onChange
1246
- }) {
1247
- const badgeColors = {
1248
- exact: { bg: "#dcfce7", color: "#15803d" },
1249
- fuzzy: { bg: "#fef9c3", color: "#a16207" },
1250
- manual: { bg: "#fee2e2", color: "#dc2626" }
1251
- };
1252
- const badge = badgeColors[confidence] ?? badgeColors.manual;
1253
- return /* @__PURE__ */ jsxs("tr", { children: [
1254
- /* @__PURE__ */ jsx("td", { style: mappingTdStyle, children: /* @__PURE__ */ jsx("code", { style: { fontSize: 12, color: "#6366f1", fontFamily: "monospace" }, children: column }) }),
1255
- /* @__PURE__ */ jsx("td", { style: { ...mappingTdStyle, textAlign: "center" }, children: /* @__PURE__ */ jsx(
1256
- "span",
1257
- {
1258
- style: {
1259
- display: "inline-block",
1260
- padding: "1px 6px",
1261
- fontSize: 10,
1262
- fontWeight: 600,
1263
- borderRadius: 4,
1264
- backgroundColor: badge.bg,
1265
- color: badge.color
1266
- },
1267
- children: confidence
1268
- }
1269
- ) }),
1270
- /* @__PURE__ */ jsx("td", { style: mappingTdStyle, children: catalog.length > 0 ? /* @__PURE__ */ jsxs(
1271
- "select",
1272
- {
1273
- value: selectedField?.name ?? "",
1274
- onChange: (e) => {
1275
- const field = catalog.find((f) => f.name === e.target.value) ?? null;
1276
- onChange(field);
1277
- },
1278
- style: selectStyle2,
1279
- children: [
1280
- /* @__PURE__ */ jsx("option", { value: "", children: "-- none --" }),
1281
- catalog.map((f) => /* @__PURE__ */ jsxs("option", { value: f.name, children: [
1282
- f.displayName,
1283
- " (",
1284
- f.type,
1285
- ") \u2014 ",
1286
- f.name
1287
- ] }, f.name))
1288
- ]
1289
- }
1290
- ) : /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#9ca3af" }, children: selectedField ? selectedField.name : "No catalog" }) })
1291
- ] });
1292
- }
1293
1430
  var backdropStyle = {
1294
1431
  position: "fixed",
1295
1432
  inset: 0,
@@ -1302,8 +1439,8 @@ var modalStyle = {
1302
1439
  left: "50%",
1303
1440
  transform: "translate(-50%, -50%)",
1304
1441
  zIndex: 100001,
1305
- width: "min(640px, 90vw)",
1306
- maxHeight: "80vh",
1442
+ width: "min(700px, 90vw)",
1443
+ maxHeight: "85vh",
1307
1444
  backgroundColor: "#fff",
1308
1445
  borderRadius: 12,
1309
1446
  boxShadow: "0 8px 30px rgba(0,0,0,0.2)",
@@ -1329,9 +1466,7 @@ var closeBtnStyle = {
1329
1466
  lineHeight: 1
1330
1467
  };
1331
1468
  var sectionStyle = {
1332
- padding: "12px 20px",
1333
- overflow: "auto",
1334
- flex: 1
1469
+ padding: "12px 20px"
1335
1470
  };
1336
1471
  var sectionTitleStyle = {
1337
1472
  fontSize: 11,
@@ -1339,7 +1474,7 @@ var sectionTitleStyle = {
1339
1474
  textTransform: "uppercase",
1340
1475
  letterSpacing: "0.05em",
1341
1476
  color: "#6b7280",
1342
- marginBottom: 8
1477
+ marginBottom: 4
1343
1478
  };
1344
1479
  var previewTableStyle = {
1345
1480
  width: "100%",
@@ -1394,10 +1529,9 @@ var generateBtnStyle = {
1394
1529
  color: "#fff",
1395
1530
  backgroundColor: "#3b82f6",
1396
1531
  border: "none",
1397
- borderRadius: 8,
1398
- cursor: "pointer"
1532
+ borderRadius: 8
1399
1533
  };
1400
1534
 
1401
- export { AdminDashboard, DataCatalog, DataInspector, Inspectable, MetricPicker, ResultsTable, generateMigrationPrompt, matchFields };
1535
+ export { AdminDashboard, DataCatalog, DataInspector, Inspectable, MetricPicker, ResultsTable, analyzeChartData, generateMigrationPrompt, matchField, matchFields };
1402
1536
  //# sourceMappingURL=components.js.map
1403
1537
  //# sourceMappingURL=components.js.map