@svgrid/enterprise 2.0.4 → 2.2.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/README.md +18 -6
- package/dist/cdn/svgrid-enterprise.svelte-external.js +14035 -6842
- package/dist/node/studio.js +7888 -2459
- package/package.json +9 -4
- package/src/SvGridMasterDetail.svelte +24 -3
- package/src/SvGridScheduler.svelte +4410 -0
- package/src/SvPivotDesigner.svelte +1990 -1045
- package/src/SvSchemaChart.svelte +10 -9
- package/src/ai.test.ts +522 -522
- package/src/ai.ts +202 -2
- package/src/export.ts +7 -1
- package/src/index.ts +409 -384
- package/src/install.ts +10 -0
- package/src/pivot-chart.test.ts +86 -0
- package/src/pivot-chart.ts +112 -0
- package/src/scheduler.ts +37 -0
- package/src/scheduling.test.ts +194 -0
- package/src/scheduling.ts +293 -0
- package/src/sources/filters.ts +6 -0
- package/src/studio/HANDLERS-DESIGN.md +142 -0
- package/src/studio/cli.ts +7 -2
- package/src/studio/emit-project.test.ts +1447 -13
- package/src/studio/emit-project.ts +3995 -1273
- package/src/studio/emit-schema.ts +146 -29
- package/src/studio/index.ts +320 -195
- package/src/studio/project.test.ts +370 -0
- package/src/studio/project.ts +1146 -26
- package/src/studio/sample-data.ts +4 -1
- package/src/studio/samples/ats.ts +2 -2
- package/src/studio/samples/clinic.ts +4 -2
- package/src/studio/samples/crm.ts +16 -8
- package/src/studio/samples/events.ts +4 -2
- package/src/studio/samples/fleet.ts +4 -2
- package/src/studio/samples/gym.ts +4 -2
- package/src/studio/samples/hr.ts +3 -1
- package/src/studio/samples/live-data.ts +308 -308
- package/src/studio/samples/projects.ts +2 -2
- package/src/studio/samples/restaurant.ts +4 -2
- package/src/studio/samples/samples.test.ts +13 -5
- package/src/studio/samples/shared.ts +346 -305
- package/src/studio/samples/support.ts +3 -1
- package/src/studio/scaffold.test.ts +15 -1
- package/src/studio/scaffold.ts +16 -0
- package/src/studio/themes.ts +7 -0
- package/src/studio/ui-components.ts +472 -0
- package/src/sveltekit/transport.test.ts +26 -0
- package/src/sveltekit/transport.ts +50 -5
- package/dist/designer/assets/index-Dp44bTid.js +0 -939
- package/dist/designer/assets/index-RJp6x8tw.css +0 -1
- package/dist/designer/assets/jszip.min-CjMo-QGg.js +0 -2
- package/dist/designer/index.html +0 -13
package/src/ai.ts
CHANGED
|
@@ -60,7 +60,7 @@ export type AIRequest = {
|
|
|
60
60
|
maxOutputTokens?: number
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
export type AITask = 'filter' | 'smart-fill' | 'summarize' | 'classify' | 'export' | 'anomaly'
|
|
63
|
+
export type AITask = 'filter' | 'smart-fill' | 'summarize' | 'classify' | 'export' | 'anomaly' | 'chart'
|
|
64
64
|
|
|
65
65
|
let provider: AIProvider | null = null
|
|
66
66
|
|
|
@@ -807,7 +807,157 @@ export async function aiFindAnomalies<
|
|
|
807
807
|
}
|
|
808
808
|
|
|
809
809
|
// ---------------------------------------------------------------------------
|
|
810
|
-
// 7.
|
|
810
|
+
// 7. Natural-language chart ("chart this")
|
|
811
|
+
// ---------------------------------------------------------------------------
|
|
812
|
+
|
|
813
|
+
export type AIChartType = 'bar' | 'line' | 'area' | 'pie'
|
|
814
|
+
|
|
815
|
+
export type AIChartPlan = {
|
|
816
|
+
type: AIChartType
|
|
817
|
+
/** Group-by (category-axis) column field, or null. */
|
|
818
|
+
dimension: string | null
|
|
819
|
+
/** Split-by column field: one series per distinct value, or null. */
|
|
820
|
+
series: string | null
|
|
821
|
+
/** Measure (value-axis) column field, or null. */
|
|
822
|
+
measure: string | null
|
|
823
|
+
reduce: 'sum' | 'avg' | 'count'
|
|
824
|
+
stacked: boolean
|
|
825
|
+
logScale: boolean
|
|
826
|
+
timeAxis: boolean
|
|
827
|
+
valueFormat: 'number' | 'currency' | 'percent'
|
|
828
|
+
rationale: string
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
export type AIChartOptions = {
|
|
832
|
+
/** Apply the plan to the grid's chart panel (open + configure). Default false. */
|
|
833
|
+
apply?: boolean
|
|
834
|
+
signal?: AbortSignal
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Translate a natural-language request into a chart plan for the grid's built-in
|
|
839
|
+
* `charting` panel. Validates every field against the real schema, then (with
|
|
840
|
+
* `apply: true`) pushes it into the panel through `api.configureChart`.
|
|
841
|
+
*/
|
|
842
|
+
export async function aiChart<
|
|
843
|
+
TFeatures extends TableFeatures,
|
|
844
|
+
TData extends RowData,
|
|
845
|
+
>(
|
|
846
|
+
api: SvGridApi<TFeatures, TData>,
|
|
847
|
+
query: string,
|
|
848
|
+
opts: AIChartOptions = {},
|
|
849
|
+
): Promise<AIChartPlan> {
|
|
850
|
+
assertEnterpriseLicensed('AI assistant')
|
|
851
|
+
const schema = buildColumnSchema(api)
|
|
852
|
+
const prompt =
|
|
853
|
+
`You are a charting assistant for a data grid. Translate the user's request ` +
|
|
854
|
+
`into a strict-JSON chart plan the grid can render.\n\n` +
|
|
855
|
+
`Columns:\n${schemaToPromptBlock(schema)}\n\n` +
|
|
856
|
+
`Output JSON schema:\n` +
|
|
857
|
+
`{ "type": "bar"|"line"|"area"|"pie", ` +
|
|
858
|
+
`"dimension": "<a categorical column to group by>", ` +
|
|
859
|
+
`"series": "<a second categorical column to split into series, or null>", ` +
|
|
860
|
+
`"measure": "<a numeric column to aggregate>", ` +
|
|
861
|
+
`"reduce": "sum"|"avg"|"count", ` +
|
|
862
|
+
`"stacked": true|false, ` +
|
|
863
|
+
`"logScale": true|false, ` +
|
|
864
|
+
`"timeAxis": true|false, ` +
|
|
865
|
+
`"valueFormat": "number"|"currency"|"percent", ` +
|
|
866
|
+
`"rationale": "<one-sentence explanation>" }\n\n` +
|
|
867
|
+
`Rules: pick "dimension" and "series" from text/date columns and "measure" ` +
|
|
868
|
+
`from a numeric column; use only column names from the list above; ` +
|
|
869
|
+
`"stacked"/"stack" -> stacked:true; "log"/"logarithmic" -> logScale:true; ` +
|
|
870
|
+
`"over time"/"by date"/a date dimension -> timeAxis:true; ` +
|
|
871
|
+
`money/revenue/price/$ -> valueFormat:"currency"; rate/%/share -> valueFormat:"percent"; ` +
|
|
872
|
+
`else valueFormat:"number". default reduce is "sum". ` +
|
|
873
|
+
`Return JSON only, no prose.\n\n` +
|
|
874
|
+
`User request: ${query}`
|
|
875
|
+
|
|
876
|
+
const plan = await callJSON<AIChartPlan>({
|
|
877
|
+
prompt,
|
|
878
|
+
task: 'chart',
|
|
879
|
+
signal: opts.signal,
|
|
880
|
+
maxOutputTokens: 300,
|
|
881
|
+
})
|
|
882
|
+
|
|
883
|
+
const valid = new Set(schema.map((c) => c.field))
|
|
884
|
+
const numericFields = new Set(schema.filter((c) => c.type === 'number').map((c) => c.field))
|
|
885
|
+
const CHART_TYPES: AIChartType[] = ['bar', 'line', 'area', 'pie']
|
|
886
|
+
plan.type = CHART_TYPES.includes(plan.type) ? plan.type : 'bar'
|
|
887
|
+
plan.dimension = plan.dimension && valid.has(plan.dimension) ? plan.dimension : null
|
|
888
|
+
plan.series = plan.series && valid.has(plan.series) && plan.series !== plan.dimension ? plan.series : null
|
|
889
|
+
plan.measure =
|
|
890
|
+
plan.measure && numericFields.has(plan.measure)
|
|
891
|
+
? plan.measure
|
|
892
|
+
: (schema.find((c) => c.type === 'number')?.field ?? null)
|
|
893
|
+
plan.reduce = ['sum', 'avg', 'count'].includes(plan.reduce) ? plan.reduce : 'sum'
|
|
894
|
+
plan.stacked = plan.stacked === true
|
|
895
|
+
plan.logScale = plan.logScale === true
|
|
896
|
+
plan.timeAxis = plan.timeAxis === true
|
|
897
|
+
plan.valueFormat = ['number', 'currency', 'percent'].includes(plan.valueFormat) ? plan.valueFormat : 'number'
|
|
898
|
+
plan.rationale = plan.rationale ?? ''
|
|
899
|
+
|
|
900
|
+
if (opts.apply === true) {
|
|
901
|
+
const cfg = api as unknown as { configureChart?: (c: Record<string, unknown>) => void }
|
|
902
|
+
cfg.configureChart?.({
|
|
903
|
+
open: true,
|
|
904
|
+
type: plan.type,
|
|
905
|
+
dimension: plan.dimension,
|
|
906
|
+
series: plan.series,
|
|
907
|
+
measure: plan.measure,
|
|
908
|
+
reduce: plan.reduce,
|
|
909
|
+
stacked: plan.stacked,
|
|
910
|
+
logScale: plan.logScale,
|
|
911
|
+
timeAxis: plan.timeAxis,
|
|
912
|
+
valueFormat: plan.valueFormat,
|
|
913
|
+
})
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
return plan
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Wire the grid's chart-panel AI button to the model: registers a handler (via
|
|
921
|
+
* `api.setChartAiHandler`) that runs `aiChart` and returns the plan for the
|
|
922
|
+
* panel to apply + explain. Call once after `onApiReady`. `installEnterprise`
|
|
923
|
+
* calls this for you.
|
|
924
|
+
*/
|
|
925
|
+
export function enableAiCharting<
|
|
926
|
+
TFeatures extends TableFeatures,
|
|
927
|
+
TData extends RowData,
|
|
928
|
+
>(api: SvGridApi<TFeatures, TData>): void {
|
|
929
|
+
const hook = api as unknown as {
|
|
930
|
+
setChartAiHandler?: (
|
|
931
|
+
fn: ((prompt: string) => Promise<Record<string, unknown> | null>) | null,
|
|
932
|
+
) => void
|
|
933
|
+
}
|
|
934
|
+
hook.setChartAiHandler?.(async (prompt: string) => {
|
|
935
|
+
const plan = await aiChart(api, prompt)
|
|
936
|
+
return {
|
|
937
|
+
type: plan.type,
|
|
938
|
+
dimension: plan.dimension,
|
|
939
|
+
series: plan.series,
|
|
940
|
+
measure: plan.measure,
|
|
941
|
+
reduce: plan.reduce,
|
|
942
|
+
stacked: plan.stacked,
|
|
943
|
+
logScale: plan.logScale,
|
|
944
|
+
timeAxis: plan.timeAxis,
|
|
945
|
+
valueFormat: plan.valueFormat,
|
|
946
|
+
rationale: plan.rationale,
|
|
947
|
+
}
|
|
948
|
+
})
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
export function disableAiCharting<
|
|
952
|
+
TFeatures extends TableFeatures,
|
|
953
|
+
TData extends RowData,
|
|
954
|
+
>(api: SvGridApi<TFeatures, TData>): void {
|
|
955
|
+
const hook = api as unknown as { setChartAiHandler?: (fn: null) => void }
|
|
956
|
+
hook.setChartAiHandler?.(null)
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// ---------------------------------------------------------------------------
|
|
960
|
+
// 8. Mock provider for examples + tests
|
|
811
961
|
// ---------------------------------------------------------------------------
|
|
812
962
|
|
|
813
963
|
/**
|
|
@@ -838,9 +988,59 @@ export const mockAIProvider: AIProvider = async (req) => {
|
|
|
838
988
|
if (req.task === 'anomaly') {
|
|
839
989
|
return JSON.stringify(buildMockAnomaly(req.prompt))
|
|
840
990
|
}
|
|
991
|
+
if (req.task === 'chart') {
|
|
992
|
+
return JSON.stringify(buildMockChart(req.prompt))
|
|
993
|
+
}
|
|
841
994
|
return '{}'
|
|
842
995
|
}
|
|
843
996
|
|
|
997
|
+
function buildMockChart(prompt: string): AIChartPlan {
|
|
998
|
+
const q = extractUserRequest(prompt)
|
|
999
|
+
const typed = extractFieldTypes(prompt)
|
|
1000
|
+
const textFields = typed.filter((f) => f.type === 'string' || f.type === 'date').map((f) => f.name)
|
|
1001
|
+
const numberFields = typed.filter((f) => f.type === 'number').map((f) => f.name)
|
|
1002
|
+
|
|
1003
|
+
const type: AIChartType =
|
|
1004
|
+
/\bline\b|\btrend\b|over time/.test(q) ? 'line'
|
|
1005
|
+
: /\barea\b/.test(q) ? 'area'
|
|
1006
|
+
: /\bpie\b|share|proportion|breakdown/.test(q) ? 'pie'
|
|
1007
|
+
: 'bar'
|
|
1008
|
+
|
|
1009
|
+
const byMatch = q.match(/\bby\s+([a-z0-9_ ]+?)(?:\s+(?:split|stacked|grouped|by|as|,|$))/)
|
|
1010
|
+
const pickText = (hint?: string): string | null => {
|
|
1011
|
+
if (hint) {
|
|
1012
|
+
const f = textFields.find((t) => t.toLowerCase() === hint.trim() || (hint.trim().length >= 4 && t.toLowerCase().includes(hint.trim())))
|
|
1013
|
+
if (f) return f
|
|
1014
|
+
}
|
|
1015
|
+
return null
|
|
1016
|
+
}
|
|
1017
|
+
const dimension = pickText(byMatch?.[1]) ?? textFields[0] ?? null
|
|
1018
|
+
const series =
|
|
1019
|
+
/\b(split|stack(?:ed)?|by product|by category|per)\b/.test(q)
|
|
1020
|
+
? (textFields.find((t) => t !== dimension) ?? null)
|
|
1021
|
+
: null
|
|
1022
|
+
const measure = numberFields.find((n) => q.includes(n.toLowerCase())) ?? numberFields[0] ?? null
|
|
1023
|
+
const reduce: 'sum' | 'avg' | 'count' =
|
|
1024
|
+
/\b(average|avg|mean)\b/.test(q) ? 'avg' : /\b(count|number of|how many)\b/.test(q) ? 'count' : 'sum'
|
|
1025
|
+
const stacked = /\bstack(ed)?\b/.test(q)
|
|
1026
|
+
const logScale = /\b(log|logarithmic)\b/.test(q)
|
|
1027
|
+
const timeAxis = /\b(over time|by date|by day|by month|by week|time series|timeline|trend)\b/.test(q)
|
|
1028
|
+
const valueFormat: 'number' | 'currency' | 'percent' =
|
|
1029
|
+
/\b(currency|money|revenue|sales|price|cost|\$|dollar)\b/.test(q) || /(price|revenue|amount|cost|sales)/i.test(measure ?? '')
|
|
1030
|
+
? 'currency'
|
|
1031
|
+
: /\b(percent|percentage|%|rate|share)\b/.test(q)
|
|
1032
|
+
? 'percent'
|
|
1033
|
+
: 'number'
|
|
1034
|
+
|
|
1035
|
+
return {
|
|
1036
|
+
type, dimension, series, measure, reduce, stacked, logScale, timeAxis, valueFormat,
|
|
1037
|
+
rationale:
|
|
1038
|
+
`${type} chart of ${reduce}(${measure ?? 'value'}) by ${dimension ?? 'category'}` +
|
|
1039
|
+
`${series ? `, split by ${series}` : ''}${stacked ? ', stacked' : ''}` +
|
|
1040
|
+
`${logScale ? ', log scale' : ''}${timeAxis ? ', date axis' : ''} (mock - wire a real model for genuine parsing).`,
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
844
1044
|
function extractUserRequest(prompt: string): string {
|
|
845
1045
|
const m = prompt.match(/User request:\s*([\s\S]+)$/)
|
|
846
1046
|
return (m?.[1] ?? '').trim().toLowerCase()
|
package/src/export.ts
CHANGED
|
@@ -1172,7 +1172,13 @@ async function exportXlsxNative<
|
|
|
1172
1172
|
: cols.map((_, i) => i)
|
|
1173
1173
|
for (const ci of targetIdxs) {
|
|
1174
1174
|
if (f.type === 'dataBar') condFormats.push({ kind: 'dataBar', colIdx: ci, color: f.color })
|
|
1175
|
-
else
|
|
1175
|
+
else {
|
|
1176
|
+
// min/mid/max are optional on the format; fall back to the same defaults
|
|
1177
|
+
// the on-screen renderer uses (#ffffff -> #000000) so the export matches.
|
|
1178
|
+
const min = f.min ?? '#ffffff'
|
|
1179
|
+
const max = f.max ?? '#000000'
|
|
1180
|
+
condFormats.push({ kind: 'colorScale', colIdx: ci, colors: f.mid ? [min, f.mid, max] : [min, max] })
|
|
1181
|
+
}
|
|
1176
1182
|
}
|
|
1177
1183
|
}
|
|
1178
1184
|
|