semiotic 3.4.2 → 3.5.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/CLAUDE.md +114 -9
- package/README.md +45 -4
- package/ai/behaviorContracts.cjs +311 -0
- package/ai/chartSuggestions.cjs +291 -0
- package/ai/cli.js +255 -30
- package/ai/componentMetadata.cjs +107 -0
- package/ai/dist/mcp-server.js +907 -227
- package/ai/schema.json +3954 -2537
- package/ai/system-prompt.md +23 -4
- package/dist/components/LinkedCharts.d.ts +5 -1
- package/dist/components/Tooltip/Tooltip.d.ts +1 -1
- package/dist/components/charts/custom/NetworkCustomChart.d.ts +64 -0
- package/dist/components/charts/custom/OrdinalCustomChart.d.ts +71 -0
- package/dist/components/charts/custom/XYCustomChart.d.ts +59 -0
- package/dist/components/charts/geo/ChoroplethMap.d.ts +93 -2
- package/dist/components/charts/geo/DistanceCartogram.d.ts +51 -4
- package/dist/components/charts/geo/FlowMap.d.ts +55 -0
- package/dist/components/charts/geo/ProportionalSymbolMap.d.ts +53 -0
- package/dist/components/charts/index.d.ts +6 -0
- package/dist/components/charts/network/ChordDiagram.d.ts +34 -2
- package/dist/components/charts/network/CirclePack.d.ts +36 -1
- package/dist/components/charts/network/ForceDirectedGraph.d.ts +130 -2
- package/dist/components/charts/network/OrbitDiagram.d.ts +37 -0
- package/dist/components/charts/network/SankeyDiagram.d.ts +51 -2
- package/dist/components/charts/network/TreeDiagram.d.ts +37 -2
- package/dist/components/charts/network/Treemap.d.ts +36 -2
- package/dist/components/charts/ordinal/BarChart.d.ts +113 -1
- package/dist/components/charts/ordinal/BoxPlot.d.ts +33 -0
- package/dist/components/charts/ordinal/DonutChart.d.ts +36 -0
- package/dist/components/charts/ordinal/DotPlot.d.ts +33 -0
- package/dist/components/charts/ordinal/FunnelChart.d.ts +40 -0
- package/dist/components/charts/ordinal/GaugeChart.d.ts +45 -0
- package/dist/components/charts/ordinal/GroupedBarChart.d.ts +40 -0
- package/dist/components/charts/ordinal/Histogram.d.ts +97 -0
- package/dist/components/charts/ordinal/LikertChart.d.ts +44 -0
- package/dist/components/charts/ordinal/PieChart.d.ts +90 -1
- package/dist/components/charts/ordinal/RidgelinePlot.d.ts +29 -0
- package/dist/components/charts/ordinal/StackedBarChart.d.ts +40 -0
- package/dist/components/charts/ordinal/SwarmPlot.d.ts +38 -0
- package/dist/components/charts/ordinal/SwimlaneChart.d.ts +62 -0
- package/dist/components/charts/ordinal/ViolinPlot.d.ts +34 -0
- package/dist/components/charts/realtime/RealtimeHeatmap.d.ts +22 -4
- package/dist/components/charts/realtime/RealtimeHistogram.d.ts +5 -2
- package/dist/components/charts/realtime/RealtimeLineChart.d.ts +24 -3
- package/dist/components/charts/realtime/RealtimeSwarmChart.d.ts +12 -0
- package/dist/components/charts/realtime/RealtimeWaterfallChart.d.ts +14 -0
- package/dist/components/charts/realtime/defaultRealtimeTooltip.d.ts +67 -0
- package/dist/components/charts/realtime/resolveWindowSize.d.ts +26 -0
- package/dist/components/charts/shared/chartSpecs.d.ts +91 -0
- package/dist/components/charts/shared/colorPalettes.d.ts +62 -0
- package/dist/components/charts/shared/colorUtils.d.ts +9 -10
- package/dist/components/charts/shared/numberFormat.d.ts +58 -0
- package/dist/components/charts/shared/sparseArray.d.ts +27 -0
- package/dist/components/charts/shared/streamPropsHelpers.d.ts +113 -0
- package/dist/components/charts/shared/timeFormat.d.ts +60 -0
- package/dist/components/charts/shared/useChartSetup.d.ts +8 -0
- package/dist/components/charts/shared/useCustomChartSetup.d.ts +84 -0
- package/dist/components/charts/shared/useFrameImperativeHandle.d.ts +28 -0
- package/dist/components/charts/shared/useOrdinalStreaming.d.ts +6 -19
- package/dist/components/charts/shared/useStreamingLegend.d.ts +27 -11
- package/dist/components/charts/shared/validateProps.d.ts +2 -2
- package/dist/components/charts/shared/validationMap.d.ts +2 -1
- package/dist/components/charts/shared/withChartWrapper.d.ts +13 -4
- package/dist/components/charts/xy/AreaChart.d.ts +44 -1
- package/dist/components/charts/xy/BubbleChart.d.ts +4 -0
- package/dist/components/charts/xy/CandlestickChart.d.ts +37 -6
- package/dist/components/charts/xy/ConnectedScatterplot.d.ts +28 -0
- package/dist/components/charts/xy/Heatmap.d.ts +4 -0
- package/dist/components/charts/xy/LineChart.d.ts +12 -0
- package/dist/components/charts/xy/MinimapChart.d.ts +58 -0
- package/dist/components/charts/xy/MultiAxisLineChart.d.ts +27 -0
- package/dist/components/charts/xy/QuadrantChart.d.ts +21 -0
- package/dist/components/charts/xy/Scatterplot.d.ts +38 -2
- package/dist/components/charts/xy/ScatterplotMatrix.d.ts +16 -0
- package/dist/components/charts/xy/StackedAreaChart.d.ts +61 -1
- package/dist/components/realtime/types.d.ts +2 -4
- package/dist/components/recipes/bullet.d.ts +86 -0
- package/dist/components/recipes/calendar.d.ts +43 -0
- package/dist/components/recipes/dagre.d.ts +56 -0
- package/dist/components/recipes/flextree.d.ts +55 -0
- package/dist/components/recipes/marimekko.d.ts +55 -0
- package/dist/components/recipes/parallelCoordinates.d.ts +97 -0
- package/dist/components/recipes/recipeUtils.d.ts +27 -0
- package/dist/components/recipes/waffle.d.ts +46 -0
- package/dist/components/semiotic-ai.d.ts +4 -0
- package/dist/components/semiotic-network.d.ts +3 -0
- package/dist/components/semiotic-ordinal.d.ts +3 -0
- package/dist/components/semiotic-recipes.d.ts +24 -0
- package/dist/components/semiotic-xy.d.ts +3 -0
- package/dist/components/semiotic.d.ts +2 -2
- package/dist/components/server/renderToStaticSVG.d.ts +8 -2
- package/dist/components/server/serverChartConfigs.d.ts +47 -1
- package/dist/components/server/staticAnnotations.d.ts +6 -0
- package/dist/components/store/ObservationStore.d.ts +1 -3
- package/dist/components/store/SelectionStore.d.ts +1 -3
- package/dist/components/store/ThemeStore.d.ts +4 -4
- package/dist/components/store/TooltipStore.d.ts +1 -3
- package/dist/components/store/createStore.d.ts +4 -2
- package/dist/components/stream/CanvasHitTester.d.ts +10 -8
- package/dist/components/stream/DataSourceAdapter.d.ts +9 -0
- package/dist/components/stream/GeoPipelineStore.d.ts +9 -0
- package/dist/components/stream/GeoTileRenderer.d.ts +14 -0
- package/dist/components/stream/NetworkPipelineStore.d.ts +25 -0
- package/dist/components/stream/OrdinalPipelineStore.d.ts +12 -0
- package/dist/components/stream/PipelineStore.d.ts +51 -0
- package/dist/components/stream/SVGOverlay.d.ts +12 -0
- package/dist/components/stream/SceneGraph.d.ts +15 -1
- package/dist/components/stream/SceneToSVG.d.ts +1 -1
- package/dist/components/stream/categoryDomain.d.ts +4 -0
- package/dist/components/stream/composeOverlays.d.ts +15 -0
- package/dist/components/stream/customLayout.d.ts +76 -0
- package/dist/components/stream/customLayoutPalette.d.ts +29 -0
- package/dist/components/stream/geoTypes.d.ts +13 -0
- package/dist/components/stream/hoverUtils.d.ts +4 -10
- package/dist/components/stream/networkCustomLayout.d.ts +67 -0
- package/dist/components/stream/networkTypes.d.ts +45 -0
- package/dist/components/stream/ordinalCustomLayout.d.ts +84 -0
- package/dist/components/stream/ordinalTypes.d.ts +35 -1
- package/dist/components/stream/renderers/barFunnelCanvasRenderer.d.ts +9 -1
- package/dist/components/stream/renderers/canvasRenderHelpers.d.ts +92 -0
- package/dist/components/stream/sampleCurvePath.d.ts +9 -0
- package/dist/components/stream/types.d.ts +44 -1
- package/dist/components/stream/useHydration.d.ts +89 -0
- package/dist/components/stream/useStableShallow.d.ts +1 -0
- package/dist/components/stream/xySceneBuilders/types.d.ts +4 -0
- package/dist/geo.min.js +2 -1
- package/dist/geo.module.min.js +2 -1
- package/dist/network.min.js +2 -1
- package/dist/network.module.min.js +2 -1
- package/dist/ordinal.min.js +2 -1
- package/dist/ordinal.module.min.js +2 -1
- package/dist/realtime.min.js +2 -1
- package/dist/realtime.module.min.js +2 -1
- package/dist/semiotic-ai.d.ts +69 -65
- package/dist/semiotic-ai.min.js +2 -1
- package/dist/semiotic-ai.module.min.js +2 -1
- package/dist/semiotic-data.d.ts +4 -4
- package/dist/semiotic-geo.d.ts +15 -15
- package/dist/semiotic-network.d.ts +19 -16
- package/dist/semiotic-ordinal.d.ts +31 -28
- package/dist/semiotic-realtime.d.ts +17 -17
- package/dist/semiotic-recipes.d.ts +24 -0
- package/dist/semiotic-recipes.min.js +1 -0
- package/dist/semiotic-recipes.module.min.js +1 -0
- package/dist/semiotic-server.d.ts +6 -6
- package/dist/semiotic-statisticalOverlays-C3DsOgr_.js +1 -0
- package/dist/semiotic-themes.d.ts +3 -3
- package/dist/semiotic-themes.min.js +2 -1
- package/dist/semiotic-themes.module.min.js +2 -1
- package/dist/semiotic-utils.d.ts +23 -23
- package/dist/semiotic-utils.min.js +2 -1
- package/dist/semiotic-utils.module.min.js +2 -1
- package/dist/semiotic-xy.d.ts +27 -24
- package/dist/semiotic.d.ts +63 -63
- package/dist/semiotic.min.js +2 -1
- package/dist/semiotic.module.min.js +2 -1
- package/dist/server.min.js +1 -1
- package/dist/server.module.min.js +1 -1
- package/dist/test-utils/canvasMock.d.ts +34 -5
- package/dist/xy.min.js +2 -1
- package/dist/xy.module.min.js +2 -1
- package/package.json +38 -17
- package/dist/semiotic-statisticalOverlays-Ckd_jM8z.js +0 -1
package/ai/dist/mcp-server.js
CHANGED
|
@@ -6799,6 +6799,697 @@ var require_dist = __commonJS({
|
|
|
6799
6799
|
}
|
|
6800
6800
|
});
|
|
6801
6801
|
|
|
6802
|
+
// ai/componentMetadata.cjs
|
|
6803
|
+
var require_componentMetadata = __commonJS({
|
|
6804
|
+
"ai/componentMetadata.cjs"(exports2, module2) {
|
|
6805
|
+
"use strict";
|
|
6806
|
+
var CATEGORY_ORDER = ["xy", "ordinal", "network", "geo", "realtime"];
|
|
6807
|
+
var COMPONENTS_BY_CATEGORY = {
|
|
6808
|
+
xy: [
|
|
6809
|
+
"LineChart",
|
|
6810
|
+
"AreaChart",
|
|
6811
|
+
"StackedAreaChart",
|
|
6812
|
+
"Scatterplot",
|
|
6813
|
+
"QuadrantChart",
|
|
6814
|
+
"MultiAxisLineChart",
|
|
6815
|
+
"CandlestickChart",
|
|
6816
|
+
"BubbleChart",
|
|
6817
|
+
"Heatmap",
|
|
6818
|
+
"ConnectedScatterplot",
|
|
6819
|
+
"ScatterplotMatrix",
|
|
6820
|
+
"MinimapChart"
|
|
6821
|
+
],
|
|
6822
|
+
ordinal: [
|
|
6823
|
+
"BarChart",
|
|
6824
|
+
"StackedBarChart",
|
|
6825
|
+
"LikertChart",
|
|
6826
|
+
"GroupedBarChart",
|
|
6827
|
+
"SwarmPlot",
|
|
6828
|
+
"BoxPlot",
|
|
6829
|
+
"Histogram",
|
|
6830
|
+
"ViolinPlot",
|
|
6831
|
+
"RidgelinePlot",
|
|
6832
|
+
"DotPlot",
|
|
6833
|
+
"PieChart",
|
|
6834
|
+
"DonutChart",
|
|
6835
|
+
"GaugeChart",
|
|
6836
|
+
"FunnelChart",
|
|
6837
|
+
"SwimlaneChart"
|
|
6838
|
+
],
|
|
6839
|
+
network: [
|
|
6840
|
+
"ForceDirectedGraph",
|
|
6841
|
+
"SankeyDiagram",
|
|
6842
|
+
"ChordDiagram",
|
|
6843
|
+
"TreeDiagram",
|
|
6844
|
+
"Treemap",
|
|
6845
|
+
"CirclePack",
|
|
6846
|
+
"OrbitDiagram"
|
|
6847
|
+
],
|
|
6848
|
+
geo: [
|
|
6849
|
+
"ChoroplethMap",
|
|
6850
|
+
"ProportionalSymbolMap",
|
|
6851
|
+
"FlowMap",
|
|
6852
|
+
"DistanceCartogram"
|
|
6853
|
+
],
|
|
6854
|
+
realtime: [
|
|
6855
|
+
"RealtimeLineChart",
|
|
6856
|
+
"RealtimeHistogram",
|
|
6857
|
+
"RealtimeSwarmChart",
|
|
6858
|
+
"RealtimeWaterfallChart",
|
|
6859
|
+
"RealtimeHeatmap"
|
|
6860
|
+
]
|
|
6861
|
+
};
|
|
6862
|
+
var COMPONENT_TO_CATEGORY = /* @__PURE__ */ new Map();
|
|
6863
|
+
for (const [category, names] of Object.entries(COMPONENTS_BY_CATEGORY)) {
|
|
6864
|
+
for (const name of names) {
|
|
6865
|
+
COMPONENT_TO_CATEGORY.set(name, category);
|
|
6866
|
+
}
|
|
6867
|
+
}
|
|
6868
|
+
function schemaEntries(schema2) {
|
|
6869
|
+
return schema2.tools.map((tool) => tool.function);
|
|
6870
|
+
}
|
|
6871
|
+
function categoryForComponent(name) {
|
|
6872
|
+
const category = COMPONENT_TO_CATEGORY.get(name);
|
|
6873
|
+
if (!category) {
|
|
6874
|
+
throw new Error(`No AI component metadata category for "${name}"`);
|
|
6875
|
+
}
|
|
6876
|
+
return category;
|
|
6877
|
+
}
|
|
6878
|
+
function importPathForCategory(category) {
|
|
6879
|
+
return category === "geo" ? "semiotic/geo" : `semiotic/${category}`;
|
|
6880
|
+
}
|
|
6881
|
+
function metadataForComponent2(entryOrName) {
|
|
6882
|
+
const name = typeof entryOrName === "string" ? entryOrName : entryOrName.name;
|
|
6883
|
+
const category = categoryForComponent(name);
|
|
6884
|
+
return {
|
|
6885
|
+
name,
|
|
6886
|
+
category,
|
|
6887
|
+
importPath: importPathForCategory(category),
|
|
6888
|
+
renderable: category !== "realtime",
|
|
6889
|
+
description: typeof entryOrName === "string" ? void 0 : entryOrName.description
|
|
6890
|
+
};
|
|
6891
|
+
}
|
|
6892
|
+
function findComponent(schema2, name) {
|
|
6893
|
+
const entries = schemaEntries(schema2);
|
|
6894
|
+
const exact = entries.find((entry) => entry.name === name);
|
|
6895
|
+
if (exact) return exact;
|
|
6896
|
+
const lower = name.toLowerCase();
|
|
6897
|
+
return entries.find((entry) => entry.name.toLowerCase() === lower);
|
|
6898
|
+
}
|
|
6899
|
+
function componentIndexFromSchema2(schema2) {
|
|
6900
|
+
const components = schemaEntries(schema2).map(metadataForComponent2);
|
|
6901
|
+
const categories = {};
|
|
6902
|
+
for (const category of CATEGORY_ORDER) {
|
|
6903
|
+
categories[category] = [];
|
|
6904
|
+
}
|
|
6905
|
+
for (const component of components) {
|
|
6906
|
+
categories[component.category].push(component.name);
|
|
6907
|
+
}
|
|
6908
|
+
for (const names of Object.values(categories)) {
|
|
6909
|
+
names.sort();
|
|
6910
|
+
}
|
|
6911
|
+
return {
|
|
6912
|
+
version: schema2.version,
|
|
6913
|
+
totalComponents: components.length,
|
|
6914
|
+
renderableComponents: components.filter((component) => component.renderable).length,
|
|
6915
|
+
browserOnlyComponents: components.filter((component) => !component.renderable).length,
|
|
6916
|
+
categories,
|
|
6917
|
+
components
|
|
6918
|
+
};
|
|
6919
|
+
}
|
|
6920
|
+
module2.exports = {
|
|
6921
|
+
CATEGORY_ORDER,
|
|
6922
|
+
COMPONENTS_BY_CATEGORY,
|
|
6923
|
+
categoryForComponent,
|
|
6924
|
+
componentIndexFromSchema: componentIndexFromSchema2,
|
|
6925
|
+
findComponent,
|
|
6926
|
+
importPathForCategory,
|
|
6927
|
+
metadataForComponent: metadataForComponent2,
|
|
6928
|
+
schemaEntries
|
|
6929
|
+
};
|
|
6930
|
+
}
|
|
6931
|
+
});
|
|
6932
|
+
|
|
6933
|
+
// ai/chartSuggestions.cjs
|
|
6934
|
+
var require_chartSuggestions = __commonJS({
|
|
6935
|
+
"ai/chartSuggestions.cjs"(exports2, module2) {
|
|
6936
|
+
"use strict";
|
|
6937
|
+
var VALID_INTENTS = [
|
|
6938
|
+
"comparison",
|
|
6939
|
+
"trend",
|
|
6940
|
+
"distribution",
|
|
6941
|
+
"relationship",
|
|
6942
|
+
"composition",
|
|
6943
|
+
"geographic",
|
|
6944
|
+
"network",
|
|
6945
|
+
"hierarchy"
|
|
6946
|
+
];
|
|
6947
|
+
var MAX_SAMPLE_SIZE = 5;
|
|
6948
|
+
function summarizeFields(data, keys) {
|
|
6949
|
+
const numericFields = [];
|
|
6950
|
+
const stringFields = [];
|
|
6951
|
+
const dateFields = [];
|
|
6952
|
+
const geoFields = {};
|
|
6953
|
+
const networkFields = {};
|
|
6954
|
+
const hierarchyFields = {};
|
|
6955
|
+
for (const key of keys) {
|
|
6956
|
+
const values = data.map((d) => d[key]).filter((v) => v != null);
|
|
6957
|
+
if (values.length === 0) continue;
|
|
6958
|
+
const first = values[0];
|
|
6959
|
+
if (typeof first === "number") {
|
|
6960
|
+
numericFields.push(key);
|
|
6961
|
+
} else if (typeof first === "string") {
|
|
6962
|
+
if (/^\d{4}[-/]\d{2}/.test(first) && !Number.isNaN(Date.parse(first))) {
|
|
6963
|
+
dateFields.push(key);
|
|
6964
|
+
} else {
|
|
6965
|
+
stringFields.push(key);
|
|
6966
|
+
}
|
|
6967
|
+
}
|
|
6968
|
+
const kl = key.toLowerCase();
|
|
6969
|
+
if (kl === "lat" || kl === "latitude") geoFields.lat = key;
|
|
6970
|
+
if (kl === "lon" || kl === "lng" || kl === "longitude") geoFields.lon = key;
|
|
6971
|
+
if (kl === "source" || kl === "from") networkFields.source = key;
|
|
6972
|
+
if (kl === "target" || kl === "to") networkFields.target = key;
|
|
6973
|
+
if (kl === "value" || kl === "weight" || kl === "amount") networkFields.value = key;
|
|
6974
|
+
if (kl === "children" || kl === "values") hierarchyFields.children = key;
|
|
6975
|
+
if (kl === "parent") hierarchyFields.parent = key;
|
|
6976
|
+
}
|
|
6977
|
+
return {
|
|
6978
|
+
keys,
|
|
6979
|
+
numericFields,
|
|
6980
|
+
stringFields,
|
|
6981
|
+
dateFields,
|
|
6982
|
+
geoFields,
|
|
6983
|
+
networkFields,
|
|
6984
|
+
hierarchyFields
|
|
6985
|
+
};
|
|
6986
|
+
}
|
|
6987
|
+
function jsxString(value) {
|
|
6988
|
+
return JSON.stringify(String(value));
|
|
6989
|
+
}
|
|
6990
|
+
function jsxExpression(value) {
|
|
6991
|
+
return `{${value}}`;
|
|
6992
|
+
}
|
|
6993
|
+
function uniqueNetworkNodes(data, sourceField, targetField) {
|
|
6994
|
+
const ids = /* @__PURE__ */ new Set();
|
|
6995
|
+
for (const datum of data) {
|
|
6996
|
+
const source = datum[sourceField];
|
|
6997
|
+
const target = datum[targetField];
|
|
6998
|
+
if (source != null) ids.add(source);
|
|
6999
|
+
if (target != null) ids.add(target);
|
|
7000
|
+
}
|
|
7001
|
+
return Array.from(ids).map((id) => ({ id }));
|
|
7002
|
+
}
|
|
7003
|
+
function suggestCharts2(args = {}) {
|
|
7004
|
+
const data = args.data;
|
|
7005
|
+
const intent = args.intent;
|
|
7006
|
+
if (intent && !VALID_INTENTS.includes(intent)) {
|
|
7007
|
+
return {
|
|
7008
|
+
ok: false,
|
|
7009
|
+
error: `Unknown intent "${intent}". Expected one of: ${VALID_INTENTS.join(", ")}.`
|
|
7010
|
+
};
|
|
7011
|
+
}
|
|
7012
|
+
if (!data || !Array.isArray(data) || data.length === 0) {
|
|
7013
|
+
return {
|
|
7014
|
+
ok: false,
|
|
7015
|
+
error: "Pass { data: [{ ... }, ...] } with 1-5 sample data objects. Optionally include intent: 'comparison' | 'trend' | 'distribution' | 'relationship' | 'composition' | 'geographic' | 'network' | 'hierarchy'."
|
|
7016
|
+
};
|
|
7017
|
+
}
|
|
7018
|
+
if (data.length > MAX_SAMPLE_SIZE) {
|
|
7019
|
+
return {
|
|
7020
|
+
ok: false,
|
|
7021
|
+
error: `Pass 1-${MAX_SAMPLE_SIZE} sample data objects; received ${data.length}. Use a representative sample instead of the full dataset.`
|
|
7022
|
+
};
|
|
7023
|
+
}
|
|
7024
|
+
const sample = data[0];
|
|
7025
|
+
if (!sample || typeof sample !== "object" || Array.isArray(sample)) {
|
|
7026
|
+
return {
|
|
7027
|
+
ok: false,
|
|
7028
|
+
error: "Data items must be objects with key-value pairs."
|
|
7029
|
+
};
|
|
7030
|
+
}
|
|
7031
|
+
const keys = Object.keys(sample);
|
|
7032
|
+
const fields = summarizeFields(data, keys);
|
|
7033
|
+
const suggestions = [];
|
|
7034
|
+
const { numericFields, stringFields, dateFields, geoFields, networkFields, hierarchyFields } = fields;
|
|
7035
|
+
const hasTime = dateFields.length > 0;
|
|
7036
|
+
const hasCat = stringFields.length > 0;
|
|
7037
|
+
const hasNum = numericFields.length > 0;
|
|
7038
|
+
const hasGeo = Boolean(geoFields.lat && geoFields.lon);
|
|
7039
|
+
const hasNetwork = Boolean(networkFields.source && networkFields.target);
|
|
7040
|
+
const hasHierarchy = Boolean(hierarchyFields.children || hierarchyFields.parent);
|
|
7041
|
+
if (hasNetwork && (!intent || intent === "network")) {
|
|
7042
|
+
const src = networkFields.source;
|
|
7043
|
+
const tgt = networkFields.target;
|
|
7044
|
+
if (networkFields.value) {
|
|
7045
|
+
suggestions.push({
|
|
7046
|
+
component: "SankeyDiagram",
|
|
7047
|
+
confidence: "high",
|
|
7048
|
+
reason: `Data has ${src}->${tgt} with ${networkFields.value} - ideal for flow visualization`,
|
|
7049
|
+
props: { edges: jsxExpression("data"), sourceAccessor: jsxString(src), targetAccessor: jsxString(tgt), valueAccessor: jsxString(networkFields.value) }
|
|
7050
|
+
});
|
|
7051
|
+
}
|
|
7052
|
+
const nodes = uniqueNetworkNodes(data, src, tgt);
|
|
7053
|
+
suggestions.push({
|
|
7054
|
+
component: "ForceDirectedGraph",
|
|
7055
|
+
confidence: networkFields.value ? "medium" : "high",
|
|
7056
|
+
reason: `Data has ${src}->${tgt} edges - force layout shows network structure. ForceDirectedGraph requires explicit nodes, derived here from unique source/target IDs.`,
|
|
7057
|
+
setup: [`const nodes = ${JSON.stringify(nodes, null, 2)}`],
|
|
7058
|
+
derivedData: { nodes },
|
|
7059
|
+
props: { nodes: jsxExpression("nodes"), edges: jsxExpression("data"), nodeIDAccessor: jsxString("id"), sourceAccessor: jsxString(src), targetAccessor: jsxString(tgt) }
|
|
7060
|
+
});
|
|
7061
|
+
}
|
|
7062
|
+
if (hasHierarchy && hierarchyFields.children && Array.isArray(sample[hierarchyFields.children]) && (!intent || intent === "hierarchy")) {
|
|
7063
|
+
const childrenAccessor = hierarchyFields.children;
|
|
7064
|
+
suggestions.push({
|
|
7065
|
+
component: "Treemap",
|
|
7066
|
+
confidence: "high",
|
|
7067
|
+
reason: `Data has nested ${childrenAccessor} structure - treemap shows hierarchical proportions. Use data[0] as the root node from the provided sample.`,
|
|
7068
|
+
props: { data: jsxExpression("data[0]"), childrenAccessor: jsxString(childrenAccessor), ...numericFields[0] ? { valueAccessor: jsxString(numericFields[0]) } : {} }
|
|
7069
|
+
});
|
|
7070
|
+
suggestions.push({
|
|
7071
|
+
component: "TreeDiagram",
|
|
7072
|
+
confidence: "medium",
|
|
7073
|
+
reason: `Tree layout shows hierarchical relationships. Use data[0] as the root node from the provided sample.`,
|
|
7074
|
+
props: { data: jsxExpression("data[0]"), childrenAccessor: jsxString(childrenAccessor) }
|
|
7075
|
+
});
|
|
7076
|
+
}
|
|
7077
|
+
if (hasGeo && (!intent || intent === "geographic")) {
|
|
7078
|
+
const sizeField = numericFields.find((f) => f !== geoFields.lat && f !== geoFields.lon);
|
|
7079
|
+
suggestions.push({
|
|
7080
|
+
component: "ProportionalSymbolMap",
|
|
7081
|
+
confidence: "high",
|
|
7082
|
+
reason: `Data has ${geoFields.lat}/${geoFields.lon} coordinates - map shows spatial distribution`,
|
|
7083
|
+
props: { points: jsxExpression("data"), xAccessor: jsxString(geoFields.lon), yAccessor: jsxString(geoFields.lat), ...sizeField ? { sizeBy: jsxString(sizeField) } : {} }
|
|
7084
|
+
});
|
|
7085
|
+
}
|
|
7086
|
+
if (hasTime && hasNum && (!intent || intent === "trend")) {
|
|
7087
|
+
const timeField = dateFields[0];
|
|
7088
|
+
const valueField = numericFields[0];
|
|
7089
|
+
suggestions.push({
|
|
7090
|
+
component: "LineChart",
|
|
7091
|
+
confidence: "high",
|
|
7092
|
+
reason: `Data has dates (${timeField}) and numeric values (${valueField}) - line chart shows trends over time`,
|
|
7093
|
+
props: { data: jsxExpression("data"), xAccessor: jsxString(timeField), yAccessor: jsxString(valueField), ...hasCat ? { lineBy: jsxString(stringFields[0]), colorBy: jsxString(stringFields[0]) } : {} }
|
|
7094
|
+
});
|
|
7095
|
+
if (hasCat) {
|
|
7096
|
+
suggestions.push({
|
|
7097
|
+
component: "StackedAreaChart",
|
|
7098
|
+
confidence: "medium",
|
|
7099
|
+
reason: `Multiple categories (${stringFields[0]}) over time - stacked area shows composition trends`,
|
|
7100
|
+
props: { data: jsxExpression("data"), xAccessor: jsxString(timeField), yAccessor: jsxString(valueField), areaBy: jsxString(stringFields[0]), colorBy: jsxString(stringFields[0]) }
|
|
7101
|
+
});
|
|
7102
|
+
}
|
|
7103
|
+
}
|
|
7104
|
+
if (hasCat && hasNum && (!intent || intent === "comparison" || intent === "composition" || intent === "distribution")) {
|
|
7105
|
+
const catField = stringFields[0];
|
|
7106
|
+
const valField = numericFields[0];
|
|
7107
|
+
if (!intent || intent === "comparison") {
|
|
7108
|
+
suggestions.push({
|
|
7109
|
+
component: "BarChart",
|
|
7110
|
+
confidence: hasTime ? "medium" : "high",
|
|
7111
|
+
reason: `Categorical field (${catField}) with values (${valField}) - bar chart for comparison`,
|
|
7112
|
+
props: { data: jsxExpression("data"), categoryAccessor: jsxString(catField), valueAccessor: jsxString(valField) }
|
|
7113
|
+
});
|
|
7114
|
+
}
|
|
7115
|
+
if (stringFields.length >= 2 && (!intent || intent === "composition")) {
|
|
7116
|
+
suggestions.push({
|
|
7117
|
+
component: "StackedBarChart",
|
|
7118
|
+
confidence: "medium",
|
|
7119
|
+
reason: `Two categorical fields (${stringFields.join(", ")}) - stacked bar shows composition within categories`,
|
|
7120
|
+
props: { data: jsxExpression("data"), categoryAccessor: jsxString(catField), valueAccessor: jsxString(valField), stackBy: jsxString(stringFields[1]) }
|
|
7121
|
+
});
|
|
7122
|
+
}
|
|
7123
|
+
if (!intent || intent === "distribution") {
|
|
7124
|
+
suggestions.push({
|
|
7125
|
+
component: "Histogram",
|
|
7126
|
+
confidence: "medium",
|
|
7127
|
+
reason: `Numeric distribution of ${valField} - histogram shows value spread`,
|
|
7128
|
+
props: { data: jsxExpression("data"), categoryAccessor: jsxString(catField), valueAccessor: jsxString(valField) }
|
|
7129
|
+
});
|
|
7130
|
+
}
|
|
7131
|
+
if (!intent || intent === "composition") {
|
|
7132
|
+
const uniqueCats = new Set(data.map((d) => d[catField])).size;
|
|
7133
|
+
if (uniqueCats <= 8) {
|
|
7134
|
+
suggestions.push({
|
|
7135
|
+
component: "DonutChart",
|
|
7136
|
+
confidence: "medium",
|
|
7137
|
+
reason: `${uniqueCats} categories - donut chart shows proportional composition`,
|
|
7138
|
+
props: { data: jsxExpression("data"), categoryAccessor: jsxString(catField), valueAccessor: jsxString(valField) }
|
|
7139
|
+
});
|
|
7140
|
+
}
|
|
7141
|
+
}
|
|
7142
|
+
}
|
|
7143
|
+
if (numericFields.length >= 2 && (!intent || intent === "relationship")) {
|
|
7144
|
+
const xField = numericFields[0];
|
|
7145
|
+
const yField = numericFields[1];
|
|
7146
|
+
suggestions.push({
|
|
7147
|
+
component: "Scatterplot",
|
|
7148
|
+
confidence: "high",
|
|
7149
|
+
reason: `Two numeric fields (${xField}, ${yField}) - scatterplot shows relationships`,
|
|
7150
|
+
props: { data: jsxExpression("data"), xAccessor: jsxString(xField), yAccessor: jsxString(yField), ...hasCat ? { colorBy: jsxString(stringFields[0]) } : {}, ...numericFields[2] ? { sizeBy: jsxString(numericFields[2]) } : {} }
|
|
7151
|
+
});
|
|
7152
|
+
if (numericFields.length >= 3) {
|
|
7153
|
+
suggestions.push({
|
|
7154
|
+
component: "BubbleChart",
|
|
7155
|
+
confidence: "medium",
|
|
7156
|
+
reason: "Three numeric fields - bubble chart adds size dimension to scatter",
|
|
7157
|
+
props: { data: jsxExpression("data"), xAccessor: jsxString(xField), yAccessor: jsxString(yField), sizeBy: jsxString(numericFields[2]) }
|
|
7158
|
+
});
|
|
7159
|
+
}
|
|
7160
|
+
}
|
|
7161
|
+
if (stringFields.length >= 2 && numericFields.length >= 1 && (!intent || intent === "relationship" || intent === "distribution" || intent === "composition")) {
|
|
7162
|
+
const xField = stringFields[0];
|
|
7163
|
+
const yField = stringFields[1];
|
|
7164
|
+
const valueField = numericFields[0];
|
|
7165
|
+
suggestions.push({
|
|
7166
|
+
component: "Heatmap",
|
|
7167
|
+
confidence: "medium",
|
|
7168
|
+
reason: `Two categorical fields (${xField}, ${yField}) plus numeric values (${valueField}) - heatmap shows intensity across dimensions`,
|
|
7169
|
+
props: { data: jsxExpression("data"), xAccessor: jsxString(xField), yAccessor: jsxString(yField), valueAccessor: jsxString(valueField) }
|
|
7170
|
+
});
|
|
7171
|
+
}
|
|
7172
|
+
return {
|
|
7173
|
+
ok: true,
|
|
7174
|
+
intent,
|
|
7175
|
+
fieldSummary: `Fields: ${keys.join(", ")} (${numericFields.length} numeric, ${stringFields.length} categorical, ${dateFields.length} date)`,
|
|
7176
|
+
fields,
|
|
7177
|
+
suggestions
|
|
7178
|
+
};
|
|
7179
|
+
}
|
|
7180
|
+
function formatSuggestionReport2(result) {
|
|
7181
|
+
if (!result.ok) return result.error;
|
|
7182
|
+
if (result.suggestions.length === 0) {
|
|
7183
|
+
return `Could not confidently recommend a chart type.
|
|
7184
|
+
|
|
7185
|
+
${result.fieldSummary}
|
|
7186
|
+
|
|
7187
|
+
Try providing intent ('${VALID_INTENTS.join("', '")}') to narrow recommendations, or use getSchema to browse available components.`;
|
|
7188
|
+
}
|
|
7189
|
+
const lines = result.suggestions.map((suggestion, i) => {
|
|
7190
|
+
const propsStr = Object.entries(suggestion.props).map(([k, v]) => `${k}=${v}`).join(" ");
|
|
7191
|
+
const setup = suggestion.setup ? `${suggestion.setup.join("\n")}
|
|
7192
|
+
` : "";
|
|
7193
|
+
return `${i + 1}. **${suggestion.component}** (${suggestion.confidence} confidence)
|
|
7194
|
+
${suggestion.reason}
|
|
7195
|
+
\`\`\`tsx
|
|
7196
|
+
${setup}<${suggestion.component} ${propsStr} />
|
|
7197
|
+
\`\`\``;
|
|
7198
|
+
});
|
|
7199
|
+
const themingTip = `
|
|
7200
|
+
---
|
|
7201
|
+
**Styling**: All charts respond to CSS custom properties on any ancestor element:
|
|
7202
|
+
\`\`\`css
|
|
7203
|
+
.my-theme {
|
|
7204
|
+
--semiotic-bg: #fff;
|
|
7205
|
+
--semiotic-text: #333;
|
|
7206
|
+
--semiotic-text-secondary: #666;
|
|
7207
|
+
--semiotic-grid: #e0e0e0;
|
|
7208
|
+
--semiotic-border: #e0e0e0;
|
|
7209
|
+
--semiotic-font-family: sans-serif;
|
|
7210
|
+
--semiotic-tooltip-bg: rgba(0,0,0,0.85);
|
|
7211
|
+
--semiotic-tooltip-text: white;
|
|
7212
|
+
--semiotic-tooltip-radius: 6px;
|
|
7213
|
+
}
|
|
7214
|
+
\`\`\`
|
|
7215
|
+
Or use \`<ThemeProvider theme="dark">\` / \`<ThemeProvider theme={{ colors: {...}, typography: {...} }}>\`.
|
|
7216
|
+
For accessibility, use \`colorScheme={COLOR_BLIND_SAFE_CATEGORICAL}\` (import from \`semiotic/themes\`) - 8-color palette safe for all forms of color blindness.`;
|
|
7217
|
+
return lines.join("\n\n") + themingTip;
|
|
7218
|
+
}
|
|
7219
|
+
module2.exports = {
|
|
7220
|
+
VALID_INTENTS,
|
|
7221
|
+
formatSuggestionReport: formatSuggestionReport2,
|
|
7222
|
+
suggestCharts: suggestCharts2
|
|
7223
|
+
};
|
|
7224
|
+
}
|
|
7225
|
+
});
|
|
7226
|
+
|
|
7227
|
+
// ai/behaviorContracts.cjs
|
|
7228
|
+
var require_behaviorContracts = __commonJS({
|
|
7229
|
+
"ai/behaviorContracts.cjs"(exports2, module2) {
|
|
7230
|
+
"use strict";
|
|
7231
|
+
var path2 = require("path");
|
|
7232
|
+
var fs2 = require("fs");
|
|
7233
|
+
var DOC_MARKER_START = "<!-- semiotic-behavior-contracts:start -->";
|
|
7234
|
+
var DOC_MARKER_END = "<!-- semiotic-behavior-contracts:end -->";
|
|
7235
|
+
function loadStaticDataComponentsFromSchema() {
|
|
7236
|
+
const candidates = [
|
|
7237
|
+
path2.join(__dirname, "schema.json"),
|
|
7238
|
+
path2.join(__dirname, "..", "schema.json")
|
|
7239
|
+
];
|
|
7240
|
+
for (const schemaPath2 of candidates) {
|
|
7241
|
+
try {
|
|
7242
|
+
const schema2 = JSON.parse(fs2.readFileSync(schemaPath2, "utf8"));
|
|
7243
|
+
const out = /* @__PURE__ */ new Set();
|
|
7244
|
+
for (const tool of schema2.tools || []) {
|
|
7245
|
+
const required2 = tool.function?.parameters?.required || [];
|
|
7246
|
+
if (required2.includes("data")) out.add(tool.function.name);
|
|
7247
|
+
}
|
|
7248
|
+
if (out.size > 0) return out;
|
|
7249
|
+
} catch {
|
|
7250
|
+
}
|
|
7251
|
+
}
|
|
7252
|
+
return /* @__PURE__ */ new Set();
|
|
7253
|
+
}
|
|
7254
|
+
var REQUIRED_COMBINATIONS = [
|
|
7255
|
+
{
|
|
7256
|
+
component: "StackedAreaChart",
|
|
7257
|
+
required: ["areaBy"],
|
|
7258
|
+
staticRequired: ["data", "areaBy"],
|
|
7259
|
+
pushRequired: ["areaBy"],
|
|
7260
|
+
summary: "Stacked areas need a flat data array plus areaBy to identify the stacked series."
|
|
7261
|
+
},
|
|
7262
|
+
{
|
|
7263
|
+
component: "BubbleChart",
|
|
7264
|
+
required: ["sizeBy"],
|
|
7265
|
+
staticRequired: ["data", "sizeBy"],
|
|
7266
|
+
pushRequired: ["sizeBy"],
|
|
7267
|
+
summary: "Bubbles need sizeBy in addition to x/y accessors so radius encodes data rather than a constant point size."
|
|
7268
|
+
},
|
|
7269
|
+
{
|
|
7270
|
+
component: "StackedBarChart",
|
|
7271
|
+
required: ["stackBy"],
|
|
7272
|
+
staticRequired: ["data", "stackBy"],
|
|
7273
|
+
pushRequired: ["stackBy"],
|
|
7274
|
+
summary: "Stacked bars need stackBy to split each category into stack segments."
|
|
7275
|
+
},
|
|
7276
|
+
{
|
|
7277
|
+
component: "GroupedBarChart",
|
|
7278
|
+
required: ["groupBy"],
|
|
7279
|
+
staticRequired: ["data", "groupBy"],
|
|
7280
|
+
pushRequired: ["groupBy"],
|
|
7281
|
+
summary: "Grouped bars need groupBy to split each category into side-by-side bars."
|
|
7282
|
+
},
|
|
7283
|
+
{
|
|
7284
|
+
component: "SwimlaneChart",
|
|
7285
|
+
required: ["subcategoryAccessor"],
|
|
7286
|
+
staticRequired: ["data", "subcategoryAccessor"],
|
|
7287
|
+
pushRequired: ["subcategoryAccessor"],
|
|
7288
|
+
summary: "Swimlanes need subcategoryAccessor; colorBy defaults to the same field when not provided."
|
|
7289
|
+
},
|
|
7290
|
+
{
|
|
7291
|
+
component: "GaugeChart",
|
|
7292
|
+
required: ["value"],
|
|
7293
|
+
staticRequired: ["value"],
|
|
7294
|
+
pushRequired: [],
|
|
7295
|
+
summary: "GaugeChart is value-only. thresholds, min, max, sweep, and arcWidth are optional."
|
|
7296
|
+
},
|
|
7297
|
+
{
|
|
7298
|
+
component: "ForceDirectedGraph",
|
|
7299
|
+
required: ["nodes", "edges"],
|
|
7300
|
+
staticRequired: ["nodes", "edges"],
|
|
7301
|
+
pushRequired: ["nodes", "edges"],
|
|
7302
|
+
summary: "ForceDirectedGraph schema/rendering requires nodes and edges. If an agent infers nodes from edge endpoints, it must materialize a nodes array before returning code."
|
|
7303
|
+
}
|
|
7304
|
+
];
|
|
7305
|
+
var PUSH_MODE_COMPONENTS = [
|
|
7306
|
+
"LineChart",
|
|
7307
|
+
"AreaChart",
|
|
7308
|
+
"StackedAreaChart",
|
|
7309
|
+
"Scatterplot",
|
|
7310
|
+
"BubbleChart",
|
|
7311
|
+
"ConnectedScatterplot",
|
|
7312
|
+
"BarChart",
|
|
7313
|
+
"StackedBarChart",
|
|
7314
|
+
"GroupedBarChart",
|
|
7315
|
+
"SwarmPlot",
|
|
7316
|
+
"BoxPlot",
|
|
7317
|
+
"Histogram",
|
|
7318
|
+
"ViolinPlot",
|
|
7319
|
+
"RidgelinePlot",
|
|
7320
|
+
"DotPlot",
|
|
7321
|
+
"PieChart",
|
|
7322
|
+
"DonutChart",
|
|
7323
|
+
"LikertChart",
|
|
7324
|
+
"SwimlaneChart",
|
|
7325
|
+
"ForceDirectedGraph",
|
|
7326
|
+
"SankeyDiagram",
|
|
7327
|
+
"ChordDiagram",
|
|
7328
|
+
"ProportionalSymbolMap",
|
|
7329
|
+
"DistanceCartogram"
|
|
7330
|
+
];
|
|
7331
|
+
var STATIC_DATA_COMPONENTS = loadStaticDataComponentsFromSchema();
|
|
7332
|
+
var BEHAVIOR_CONTRACTS2 = [
|
|
7333
|
+
{
|
|
7334
|
+
id: "props.data-required-by-usage-mode",
|
|
7335
|
+
category: "required-props",
|
|
7336
|
+
title: "Data required by usage mode",
|
|
7337
|
+
severity: "error",
|
|
7338
|
+
appliesTo: {
|
|
7339
|
+
components: PUSH_MODE_COMPONENTS
|
|
7340
|
+
},
|
|
7341
|
+
summary: "Static usage (`renderChart`, MCP previews, SSR snapshots, and copy/paste examples with immediate data) requires data in props. React push mode selects live ingestion by omitting data and mutating through a ref.",
|
|
7342
|
+
agentAction: 'Pass usageMode="push" to `semiotic-ai --doctor` when validating ref-based JSX with no data prop. Keep usageMode="static" or omit it for renderChart/MCP/static configs where data must be present.'
|
|
7343
|
+
},
|
|
7344
|
+
{
|
|
7345
|
+
id: "color.category-precedence",
|
|
7346
|
+
category: "color",
|
|
7347
|
+
title: "Categorical color precedence",
|
|
7348
|
+
severity: "info",
|
|
7349
|
+
appliesTo: {
|
|
7350
|
+
propsAny: ["colorBy", "colorScheme"]
|
|
7351
|
+
},
|
|
7352
|
+
summary: "When colorBy is set, CategoryColorProvider/LinkedCharts category maps win for mapped categories. Unmapped categories fall back to explicit colorScheme, then ThemeProvider colors.categorical, then the built-in categorical fallback.",
|
|
7353
|
+
agentAction: "Use colorBy for categorical encodings. Use CategoryColorProvider or LinkedCharts for cross-chart consistency, colorScheme for per-chart fallback palettes, and avoid frameProps style functions unless intentionally bypassing HOC color resolution."
|
|
7354
|
+
},
|
|
7355
|
+
{
|
|
7356
|
+
id: "props.required-combinations",
|
|
7357
|
+
category: "required-props",
|
|
7358
|
+
title: "Required prop combinations",
|
|
7359
|
+
severity: "error",
|
|
7360
|
+
appliesTo: {
|
|
7361
|
+
components: REQUIRED_COMBINATIONS.map((entry) => entry.component)
|
|
7362
|
+
},
|
|
7363
|
+
summary: "Some chart families need semantic props beyond data. These combinations are enforced by validation/schema for static configs and remain required in push mode unless explicitly noted.",
|
|
7364
|
+
agentAction: "Before returning code, check the selected component against the required combinations list. For push mode, omit data but keep semantic props such as areaBy, sizeBy, stackBy, and groupBy.",
|
|
7365
|
+
combinations: REQUIRED_COMBINATIONS
|
|
7366
|
+
},
|
|
7367
|
+
{
|
|
7368
|
+
id: "streaming.push-mode-data",
|
|
7369
|
+
category: "streaming",
|
|
7370
|
+
title: "Push mode omits data",
|
|
7371
|
+
severity: "warning",
|
|
7372
|
+
appliesTo: {
|
|
7373
|
+
components: PUSH_MODE_COMPONENTS
|
|
7374
|
+
},
|
|
7375
|
+
summary: "HOC push mode is selected by omitting the data prop entirely. Passing data={[]} is static empty data and can clear/reinitialize the frame on render.",
|
|
7376
|
+
agentAction: "For live charts, create a ref, omit data, then call ref.current.push() or pushMany(). For static renderChart/MCP snapshots, provide data because renderChart cannot push later."
|
|
7377
|
+
},
|
|
7378
|
+
{
|
|
7379
|
+
id: "streaming.ref-mutations-require-id-accessors",
|
|
7380
|
+
category: "streaming",
|
|
7381
|
+
title: "Ref mutations need stable IDs",
|
|
7382
|
+
severity: "warning",
|
|
7383
|
+
appliesTo: {
|
|
7384
|
+
components: PUSH_MODE_COMPONENTS
|
|
7385
|
+
},
|
|
7386
|
+
summary: "push() and pushMany() can append without IDs, but remove(id) and update(id, updater) require a stable ID accessor: pointIdAccessor for XY/realtime charts, dataIdAccessor for ordinal charts, and nodeIDAccessor/edgeIdAccessor for network operations.",
|
|
7387
|
+
agentAction: "When generating code that calls remove() or update(), include the matching ID accessor and make sure pushed rows carry that ID field."
|
|
7388
|
+
},
|
|
7389
|
+
{
|
|
7390
|
+
id: "rendering.renderchart-static-props",
|
|
7391
|
+
category: "rendering",
|
|
7392
|
+
title: "renderChart uses static props only",
|
|
7393
|
+
severity: "warning",
|
|
7394
|
+
appliesTo: {},
|
|
7395
|
+
summary: "MCP renderChart and semiotic/server renderChart render a single static SVG/PNG snapshot. Browser-only realtime components and future ref pushes are not renderable through that path.",
|
|
7396
|
+
agentAction: "Use renderChart only with renderable HOC components and complete static data. For live behavior, return React code with a ref and do not promise MCP-rendered output."
|
|
7397
|
+
}
|
|
7398
|
+
];
|
|
7399
|
+
function hasOwn(value, key) {
|
|
7400
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
7401
|
+
}
|
|
7402
|
+
function normalizeProps(props) {
|
|
7403
|
+
return props && typeof props === "object" && !Array.isArray(props) ? props : {};
|
|
7404
|
+
}
|
|
7405
|
+
function appliesToComponent(contract, component) {
|
|
7406
|
+
if (!component) return !contract.appliesTo?.components;
|
|
7407
|
+
const components = contract.appliesTo?.components;
|
|
7408
|
+
return !components || components.includes(component);
|
|
7409
|
+
}
|
|
7410
|
+
function appliesToProps(contract, props) {
|
|
7411
|
+
const propsAny = contract.appliesTo?.propsAny;
|
|
7412
|
+
if (!propsAny || propsAny.length === 0) return true;
|
|
7413
|
+
return propsAny.some((prop) => hasOwn(props, prop) && props[prop] !== void 0);
|
|
7414
|
+
}
|
|
7415
|
+
function behaviorContractsFor2({ component, props } = {}) {
|
|
7416
|
+
const normalizedProps = normalizeProps(props);
|
|
7417
|
+
return BEHAVIOR_CONTRACTS2.filter(
|
|
7418
|
+
(contract) => appliesToComponent(contract, component) && appliesToProps(contract, normalizedProps)
|
|
7419
|
+
);
|
|
7420
|
+
}
|
|
7421
|
+
function normalizeUsageMode2(usageMode) {
|
|
7422
|
+
if (usageMode === "push") return "push";
|
|
7423
|
+
if (usageMode === "static" || usageMode === "renderChart" || usageMode === "server") return "static";
|
|
7424
|
+
return "static";
|
|
7425
|
+
}
|
|
7426
|
+
function dataRequiredForUsageMode2(component, usageMode) {
|
|
7427
|
+
if (!STATIC_DATA_COMPONENTS.has(component)) return false;
|
|
7428
|
+
if (normalizeUsageMode2(usageMode) === "push" && PUSH_MODE_COMPONENTS.includes(component)) return false;
|
|
7429
|
+
return true;
|
|
7430
|
+
}
|
|
7431
|
+
function requiredCombinationsFor(component) {
|
|
7432
|
+
return REQUIRED_COMBINATIONS.filter((entry) => !component || entry.component === component);
|
|
7433
|
+
}
|
|
7434
|
+
function formatRequiredCombination(entry) {
|
|
7435
|
+
const staticRequired = entry.staticRequired || entry.required;
|
|
7436
|
+
const pushRequired = entry.pushRequired || entry.required.filter((prop) => prop !== "data");
|
|
7437
|
+
const pushText = pushRequired.length > 0 ? pushRequired.join(" + ") : "not supported";
|
|
7438
|
+
return `${entry.component}: static ${staticRequired.join(" + ")}; push ${pushText}. ${entry.summary}`;
|
|
7439
|
+
}
|
|
7440
|
+
function formatDoctorBehaviorContracts2(contracts) {
|
|
7441
|
+
if (!contracts || contracts.length === 0) return "";
|
|
7442
|
+
const lines = ["Behavior contracts:"];
|
|
7443
|
+
for (const contract of contracts) {
|
|
7444
|
+
lines.push(` - [${contract.id}] ${contract.summary}`);
|
|
7445
|
+
if (contract.combinations) {
|
|
7446
|
+
for (const combo of contract.combinations) {
|
|
7447
|
+
lines.push(` ${formatRequiredCombination(combo)}`);
|
|
7448
|
+
}
|
|
7449
|
+
}
|
|
7450
|
+
lines.push(` Action: ${contract.agentAction}`);
|
|
7451
|
+
}
|
|
7452
|
+
return lines.join("\n");
|
|
7453
|
+
}
|
|
7454
|
+
function formatBehaviorContractsMarkdown({ compact = false } = {}) {
|
|
7455
|
+
const lines = [
|
|
7456
|
+
compact ? "## Behavior Contracts" : "## AI Behavior Contracts",
|
|
7457
|
+
"",
|
|
7458
|
+
DOC_MARKER_START,
|
|
7459
|
+
"",
|
|
7460
|
+
"These rules are generated from `ai/behaviorContracts.cjs` and are consumed by `semiotic-ai --doctor`, MCP resources, and docs checks.",
|
|
7461
|
+
""
|
|
7462
|
+
];
|
|
7463
|
+
for (const contract of BEHAVIOR_CONTRACTS2) {
|
|
7464
|
+
lines.push(`- **${contract.title}** (\`${contract.id}\`): ${contract.summary}`);
|
|
7465
|
+
if (!compact) {
|
|
7466
|
+
lines.push(` Agent action: ${contract.agentAction}`);
|
|
7467
|
+
}
|
|
7468
|
+
if (contract.combinations) {
|
|
7469
|
+
const combos = contract.combinations.map(formatRequiredCombination).join(" ");
|
|
7470
|
+
lines.push(` Required combinations: ${combos}`);
|
|
7471
|
+
}
|
|
7472
|
+
}
|
|
7473
|
+
lines.push("", DOC_MARKER_END);
|
|
7474
|
+
return lines.join("\n");
|
|
7475
|
+
}
|
|
7476
|
+
module2.exports = {
|
|
7477
|
+
BEHAVIOR_CONTRACTS: BEHAVIOR_CONTRACTS2,
|
|
7478
|
+
DOC_MARKER_END,
|
|
7479
|
+
DOC_MARKER_START,
|
|
7480
|
+
PUSH_MODE_COMPONENTS,
|
|
7481
|
+
REQUIRED_COMBINATIONS,
|
|
7482
|
+
STATIC_DATA_COMPONENTS,
|
|
7483
|
+
behaviorContractsFor: behaviorContractsFor2,
|
|
7484
|
+
dataRequiredForUsageMode: dataRequiredForUsageMode2,
|
|
7485
|
+
formatBehaviorContractsMarkdown,
|
|
7486
|
+
formatDoctorBehaviorContracts: formatDoctorBehaviorContracts2,
|
|
7487
|
+
normalizeUsageMode: normalizeUsageMode2,
|
|
7488
|
+
requiredCombinationsFor
|
|
7489
|
+
};
|
|
7490
|
+
}
|
|
7491
|
+
});
|
|
7492
|
+
|
|
6802
7493
|
// node_modules/zod/v3/helpers/util.js
|
|
6803
7494
|
var util;
|
|
6804
7495
|
(function(util2) {
|
|
@@ -31460,9 +32151,12 @@ var COMPONENT_REGISTRY = {
|
|
|
31460
32151
|
Scatterplot: { component: import_ai.Scatterplot, category: "xy" },
|
|
31461
32152
|
BubbleChart: { component: import_ai.BubbleChart, category: "xy" },
|
|
31462
32153
|
Heatmap: { component: import_ai.Heatmap, category: "xy" },
|
|
32154
|
+
ScatterplotMatrix: { component: import_ai.ScatterplotMatrix, category: "xy" },
|
|
32155
|
+
MinimapChart: { component: import_ai.MinimapChart, category: "xy" },
|
|
31463
32156
|
ConnectedScatterplot: { component: import_ai.ConnectedScatterplot, category: "xy" },
|
|
31464
32157
|
QuadrantChart: { component: import_ai.QuadrantChart, category: "xy" },
|
|
31465
32158
|
MultiAxisLineChart: { component: import_ai.MultiAxisLineChart, category: "xy" },
|
|
32159
|
+
CandlestickChart: { component: import_ai.CandlestickChart, category: "xy" },
|
|
31466
32160
|
BarChart: { component: import_ai.BarChart, category: "ordinal" },
|
|
31467
32161
|
StackedBarChart: { component: import_ai.StackedBarChart, category: "ordinal" },
|
|
31468
32162
|
GroupedBarChart: { component: import_ai.GroupedBarChart, category: "ordinal" },
|
|
@@ -31474,7 +32168,10 @@ var COMPONENT_REGISTRY = {
|
|
|
31474
32168
|
RidgelinePlot: { component: import_ai.RidgelinePlot, category: "ordinal" },
|
|
31475
32169
|
PieChart: { component: import_ai.PieChart, category: "ordinal" },
|
|
31476
32170
|
DonutChart: { component: import_ai.DonutChart, category: "ordinal" },
|
|
32171
|
+
GaugeChart: { component: import_ai.GaugeChart, category: "ordinal" },
|
|
31477
32172
|
FunnelChart: { component: import_ai.FunnelChart, category: "ordinal" },
|
|
32173
|
+
LikertChart: { component: import_ai.LikertChart, category: "ordinal" },
|
|
32174
|
+
SwimlaneChart: { component: import_ai.SwimlaneChart, category: "ordinal" },
|
|
31478
32175
|
ForceDirectedGraph: { component: import_ai.ForceDirectedGraph, category: "network" },
|
|
31479
32176
|
ChordDiagram: { component: import_ai.ChordDiagram, category: "network" },
|
|
31480
32177
|
SankeyDiagram: { component: import_ai.SankeyDiagram, category: "network" },
|
|
@@ -31523,27 +32220,72 @@ ${errors.join("\n")}`
|
|
|
31523
32220
|
|
|
31524
32221
|
// ai/mcp-server.ts
|
|
31525
32222
|
var import_ai3 = require("semiotic/ai");
|
|
32223
|
+
var {
|
|
32224
|
+
componentIndexFromSchema,
|
|
32225
|
+
metadataForComponent
|
|
32226
|
+
} = require_componentMetadata();
|
|
32227
|
+
var {
|
|
32228
|
+
formatSuggestionReport,
|
|
32229
|
+
suggestCharts
|
|
32230
|
+
} = require_chartSuggestions();
|
|
32231
|
+
var {
|
|
32232
|
+
BEHAVIOR_CONTRACTS,
|
|
32233
|
+
behaviorContractsFor,
|
|
32234
|
+
dataRequiredForUsageMode,
|
|
32235
|
+
formatDoctorBehaviorContracts,
|
|
32236
|
+
normalizeUsageMode
|
|
32237
|
+
} = require_behaviorContracts();
|
|
31526
32238
|
var schemaPath = path.resolve(__dirname, "../schema.json");
|
|
31527
32239
|
var schema = JSON.parse(fs.readFileSync(schemaPath, "utf-8"));
|
|
31528
32240
|
var schemaByComponent = {};
|
|
31529
32241
|
for (const tool of schema.tools) {
|
|
31530
32242
|
schemaByComponent[tool.function.name] = tool.function;
|
|
31531
32243
|
}
|
|
32244
|
+
var allComponentNames = Object.keys(schemaByComponent).sort();
|
|
31532
32245
|
var componentNames = Object.keys(COMPONENT_REGISTRY).sort();
|
|
31533
32246
|
var REPO = "nteract/semiotic";
|
|
32247
|
+
function aiFilePath(fileName) {
|
|
32248
|
+
return path.resolve(__dirname, "..", fileName);
|
|
32249
|
+
}
|
|
32250
|
+
function readAIFile(fileName) {
|
|
32251
|
+
return fs.readFileSync(aiFilePath(fileName), "utf-8");
|
|
32252
|
+
}
|
|
32253
|
+
function componentIndexJSON() {
|
|
32254
|
+
return JSON.stringify(componentIndexFromSchema(schema), null, 2);
|
|
32255
|
+
}
|
|
32256
|
+
function textResource(uri, mimeType, text) {
|
|
32257
|
+
return {
|
|
32258
|
+
contents: [{
|
|
32259
|
+
uri: uri.href,
|
|
32260
|
+
mimeType,
|
|
32261
|
+
text
|
|
32262
|
+
}]
|
|
32263
|
+
};
|
|
32264
|
+
}
|
|
32265
|
+
function promptMessage(text) {
|
|
32266
|
+
return {
|
|
32267
|
+
messages: [{
|
|
32268
|
+
role: "user",
|
|
32269
|
+
content: {
|
|
32270
|
+
type: "text",
|
|
32271
|
+
text
|
|
32272
|
+
}
|
|
32273
|
+
}]
|
|
32274
|
+
};
|
|
32275
|
+
}
|
|
31534
32276
|
async function getSchemaHandler(args) {
|
|
31535
32277
|
const component = args.component;
|
|
31536
32278
|
if (!component) {
|
|
31537
|
-
const
|
|
31538
|
-
const renderable = new Set(Object.keys(COMPONENT_REGISTRY));
|
|
31539
|
-
const list = all.map((name) => renderable.has(name) ? `${name} [renderable]` : name);
|
|
32279
|
+
const list = allComponentNames.map((name) => metadataForComponent(name).renderable ? `${name} [renderable]` : name);
|
|
31540
32280
|
return {
|
|
31541
|
-
content: [{ type: "text", text: `Available components (${
|
|
32281
|
+
content: [{ type: "text", text: `Available components (${allComponentNames.length}):
|
|
31542
32282
|
${list.join(", ")}
|
|
31543
32283
|
|
|
31544
32284
|
Components marked [renderable] can be rendered to SVG via renderChart (pass theme parameter for styled output). Others (Realtime*) require a browser environment.
|
|
31545
32285
|
|
|
31546
|
-
|
|
32286
|
+
For full agent context, read MCP resources: semiotic://schema, semiotic://components, semiotic://behavior-contracts, semiotic://system-prompt, semiotic://examples.
|
|
32287
|
+
|
|
32288
|
+
All charts support CSS custom properties for theming (--semiotic-bg, --semiotic-text, --semiotic-grid, etc.) and <ThemeProvider>. Use COLOR_BLIND_SAFE_CATEGORICAL (import from semiotic/themes) for accessible color palettes.
|
|
31547
32289
|
|
|
31548
32290
|
Pass { component: '<name>' } to get the prop schema for a specific component.` }]
|
|
31549
32291
|
};
|
|
@@ -31556,226 +32298,25 @@ Pass { component: '<name>' } to get the prop schema for a specific component.` }
|
|
|
31556
32298
|
isError: true
|
|
31557
32299
|
};
|
|
31558
32300
|
}
|
|
31559
|
-
const renderableNote =
|
|
32301
|
+
const renderableNote = metadataForComponent(component).renderable ? "This component can be rendered to SVG via renderChart." : "This component requires a browser environment and cannot be rendered via renderChart.";
|
|
32302
|
+
const contracts = behaviorContractsFor({ component, props: {} });
|
|
32303
|
+
const contractText = contracts.length > 0 ? `
|
|
32304
|
+
|
|
32305
|
+
Behavior contracts:
|
|
32306
|
+
${JSON.stringify(contracts, null, 2)}` : "";
|
|
31560
32307
|
return {
|
|
31561
32308
|
content: [{ type: "text", text: `${renderableNote}
|
|
31562
32309
|
|
|
31563
|
-
${JSON.stringify(entry, null, 2)}` }]
|
|
32310
|
+
${JSON.stringify(entry, null, 2)}${contractText}` }]
|
|
31564
32311
|
};
|
|
31565
32312
|
}
|
|
31566
32313
|
async function suggestChartHandler(args) {
|
|
31567
|
-
const
|
|
31568
|
-
const
|
|
31569
|
-
if (!
|
|
31570
|
-
return {
|
|
31571
|
-
content: [{ type: "text", text: "Pass { data: [{ ... }, ...] } with 1-5 sample data objects. Optionally include intent: 'comparison' | 'trend' | 'distribution' | 'relationship' | 'composition' | 'geographic' | 'network' | 'hierarchy'." }],
|
|
31572
|
-
isError: true
|
|
31573
|
-
};
|
|
31574
|
-
}
|
|
31575
|
-
const sample = data[0];
|
|
31576
|
-
if (!sample || typeof sample !== "object") {
|
|
31577
|
-
return {
|
|
31578
|
-
content: [{ type: "text", text: "Data items must be objects with key-value pairs." }],
|
|
31579
|
-
isError: true
|
|
31580
|
-
};
|
|
31581
|
-
}
|
|
31582
|
-
const keys = Object.keys(sample);
|
|
31583
|
-
const suggestions = [];
|
|
31584
|
-
const numericFields = [];
|
|
31585
|
-
const stringFields = [];
|
|
31586
|
-
const dateFields = [];
|
|
31587
|
-
const geoFields = {};
|
|
31588
|
-
const networkFields = {};
|
|
31589
|
-
const hierarchyFields = {};
|
|
31590
|
-
for (const key of keys) {
|
|
31591
|
-
const values = data.map((d) => d[key]).filter((v) => v != null);
|
|
31592
|
-
if (values.length === 0) continue;
|
|
31593
|
-
const first = values[0];
|
|
31594
|
-
if (typeof first === "number") {
|
|
31595
|
-
numericFields.push(key);
|
|
31596
|
-
} else if (typeof first === "string") {
|
|
31597
|
-
if (/^\d{4}[-/]\d{2}/.test(first) && !isNaN(Date.parse(first))) {
|
|
31598
|
-
dateFields.push(key);
|
|
31599
|
-
} else {
|
|
31600
|
-
stringFields.push(key);
|
|
31601
|
-
}
|
|
31602
|
-
}
|
|
31603
|
-
const kl = key.toLowerCase();
|
|
31604
|
-
if (kl === "lat" || kl === "latitude") geoFields.lat = key;
|
|
31605
|
-
if (kl === "lon" || kl === "lng" || kl === "longitude") geoFields.lon = key;
|
|
31606
|
-
if (kl === "source" || kl === "from") networkFields.source = key;
|
|
31607
|
-
if (kl === "target" || kl === "to") networkFields.target = key;
|
|
31608
|
-
if (kl === "value" || kl === "weight" || kl === "amount") networkFields.value = key;
|
|
31609
|
-
if (kl === "children" || kl === "values") hierarchyFields.children = key;
|
|
31610
|
-
if (kl === "parent") hierarchyFields.parent = key;
|
|
31611
|
-
}
|
|
31612
|
-
const hasTime = dateFields.length > 0;
|
|
31613
|
-
const hasCat = stringFields.length > 0;
|
|
31614
|
-
const hasNum = numericFields.length > 0;
|
|
31615
|
-
const hasGeo = geoFields.lat && geoFields.lon;
|
|
31616
|
-
const hasNetwork = networkFields.source && networkFields.target;
|
|
31617
|
-
const hasHierarchy = hierarchyFields.children || hierarchyFields.parent;
|
|
31618
|
-
if (hasNetwork && (!intent || intent === "network")) {
|
|
31619
|
-
const src = networkFields.source;
|
|
31620
|
-
const tgt = networkFields.target;
|
|
31621
|
-
if (networkFields.value) {
|
|
31622
|
-
suggestions.push({
|
|
31623
|
-
component: "SankeyDiagram",
|
|
31624
|
-
confidence: "high",
|
|
31625
|
-
reason: `Data has ${src}\u2192${tgt} with ${networkFields.value} \u2014 ideal for flow visualization`,
|
|
31626
|
-
props: { edges: "data", sourceAccessor: `"${src}"`, targetAccessor: `"${tgt}"`, valueAccessor: `"${networkFields.value}"` }
|
|
31627
|
-
});
|
|
31628
|
-
}
|
|
31629
|
-
suggestions.push({
|
|
31630
|
-
component: "ForceDirectedGraph",
|
|
31631
|
-
confidence: networkFields.value ? "medium" : "high",
|
|
31632
|
-
reason: `Data has ${src}\u2192${tgt} edges \u2014 force layout shows network structure. Nodes are auto-inferred from edges when not provided.`,
|
|
31633
|
-
props: { edges: "data", sourceAccessor: `"${src}"`, targetAccessor: `"${tgt}"` }
|
|
31634
|
-
});
|
|
31635
|
-
}
|
|
31636
|
-
if (hasHierarchy && (!intent || intent === "hierarchy")) {
|
|
31637
|
-
suggestions.push({
|
|
31638
|
-
component: "Treemap",
|
|
31639
|
-
confidence: "high",
|
|
31640
|
-
reason: `Data has nested ${hierarchyFields.children || "parent"} structure \u2014 treemap shows hierarchical proportions`,
|
|
31641
|
-
props: { data: "rootObject", childrenAccessor: `"${hierarchyFields.children || "children"}"`, ...numericFields[0] ? { valueAccessor: `"${numericFields[0]}"` } : {} }
|
|
31642
|
-
});
|
|
31643
|
-
suggestions.push({
|
|
31644
|
-
component: "TreeDiagram",
|
|
31645
|
-
confidence: "medium",
|
|
31646
|
-
reason: "Tree layout shows hierarchical relationships",
|
|
31647
|
-
props: { data: "rootObject", childrenAccessor: `"${hierarchyFields.children || "children"}"` }
|
|
31648
|
-
});
|
|
31649
|
-
}
|
|
31650
|
-
if (hasGeo && (!intent || intent === "geographic")) {
|
|
31651
|
-
const sizeField = numericFields.find((f) => f !== geoFields.lat && f !== geoFields.lon);
|
|
31652
|
-
suggestions.push({
|
|
31653
|
-
component: "ProportionalSymbolMap",
|
|
31654
|
-
confidence: "high",
|
|
31655
|
-
reason: `Data has ${geoFields.lat}/${geoFields.lon} coordinates \u2014 map shows spatial distribution`,
|
|
31656
|
-
props: { points: "data", xAccessor: `"${geoFields.lon}"`, yAccessor: `"${geoFields.lat}"`, ...sizeField ? { sizeBy: `"${sizeField}"` } : {} }
|
|
31657
|
-
});
|
|
31658
|
-
}
|
|
31659
|
-
if (hasTime && hasNum && (!intent || intent === "trend")) {
|
|
31660
|
-
const timeField = dateFields[0];
|
|
31661
|
-
const valueField = numericFields[0];
|
|
31662
|
-
suggestions.push({
|
|
31663
|
-
component: "LineChart",
|
|
31664
|
-
confidence: "high",
|
|
31665
|
-
reason: `Data has dates (${timeField}) and numeric values (${valueField}) \u2014 line chart shows trends over time`,
|
|
31666
|
-
props: { data: "data", xAccessor: `"${timeField}"`, yAccessor: `"${valueField}"`, ...hasCat ? { lineBy: `"${stringFields[0]}"`, colorBy: `"${stringFields[0]}"` } : {} }
|
|
31667
|
-
});
|
|
31668
|
-
if (hasCat) {
|
|
31669
|
-
suggestions.push({
|
|
31670
|
-
component: "StackedAreaChart",
|
|
31671
|
-
confidence: "medium",
|
|
31672
|
-
reason: `Multiple categories (${stringFields[0]}) over time \u2014 stacked area shows composition trends`,
|
|
31673
|
-
props: { data: "data", xAccessor: `"${timeField}"`, yAccessor: `"${valueField}"`, areaBy: `"${stringFields[0]}"`, colorBy: `"${stringFields[0]}"` }
|
|
31674
|
-
});
|
|
31675
|
-
}
|
|
32314
|
+
const result = suggestCharts(args);
|
|
32315
|
+
const content = [{ type: "text", text: formatSuggestionReport(result) }];
|
|
32316
|
+
if (!result.ok) {
|
|
32317
|
+
return { content, isError: true, structuredContent: result };
|
|
31676
32318
|
}
|
|
31677
|
-
|
|
31678
|
-
const catField = stringFields[0];
|
|
31679
|
-
const valField = numericFields[0];
|
|
31680
|
-
if (!intent || intent === "comparison") {
|
|
31681
|
-
suggestions.push({
|
|
31682
|
-
component: "BarChart",
|
|
31683
|
-
confidence: hasTime ? "medium" : "high",
|
|
31684
|
-
reason: `Categorical field (${catField}) with values (${valField}) \u2014 bar chart for comparison`,
|
|
31685
|
-
props: { data: "data", categoryAccessor: `"${catField}"`, valueAccessor: `"${valField}"` }
|
|
31686
|
-
});
|
|
31687
|
-
}
|
|
31688
|
-
if (stringFields.length >= 2 && (!intent || intent === "composition")) {
|
|
31689
|
-
suggestions.push({
|
|
31690
|
-
component: "StackedBarChart",
|
|
31691
|
-
confidence: "medium",
|
|
31692
|
-
reason: `Two categorical fields (${stringFields.join(", ")}) \u2014 stacked bar shows composition within categories`,
|
|
31693
|
-
props: { data: "data", categoryAccessor: `"${catField}"`, valueAccessor: `"${valField}"`, stackBy: `"${stringFields[1]}"` }
|
|
31694
|
-
});
|
|
31695
|
-
}
|
|
31696
|
-
if (!intent || intent === "distribution") {
|
|
31697
|
-
suggestions.push({
|
|
31698
|
-
component: "Histogram",
|
|
31699
|
-
confidence: "medium",
|
|
31700
|
-
reason: `Numeric distribution of ${valField} \u2014 histogram shows value spread`,
|
|
31701
|
-
props: { data: "data", categoryAccessor: `"${catField}"`, valueAccessor: `"${valField}"` }
|
|
31702
|
-
});
|
|
31703
|
-
}
|
|
31704
|
-
if (!intent || intent === "composition") {
|
|
31705
|
-
const uniqueCats = new Set(data.map((d) => d[catField])).size;
|
|
31706
|
-
if (uniqueCats <= 8) {
|
|
31707
|
-
suggestions.push({
|
|
31708
|
-
component: "DonutChart",
|
|
31709
|
-
confidence: "medium",
|
|
31710
|
-
reason: `${uniqueCats} categories \u2014 donut chart shows proportional composition`,
|
|
31711
|
-
props: { data: "data", categoryAccessor: `"${catField}"`, valueAccessor: `"${valField}"` }
|
|
31712
|
-
});
|
|
31713
|
-
}
|
|
31714
|
-
}
|
|
31715
|
-
}
|
|
31716
|
-
if (numericFields.length >= 2 && (!intent || intent === "relationship")) {
|
|
31717
|
-
const xField = numericFields[0];
|
|
31718
|
-
const yField = numericFields[1];
|
|
31719
|
-
suggestions.push({
|
|
31720
|
-
component: "Scatterplot",
|
|
31721
|
-
confidence: "high",
|
|
31722
|
-
reason: `Two numeric fields (${xField}, ${yField}) \u2014 scatterplot shows relationships`,
|
|
31723
|
-
props: { data: "data", xAccessor: `"${xField}"`, yAccessor: `"${yField}"`, ...hasCat ? { colorBy: `"${stringFields[0]}"` } : {}, ...numericFields[2] ? { sizeBy: `"${numericFields[2]}"` } : {} }
|
|
31724
|
-
});
|
|
31725
|
-
if (numericFields.length >= 3) {
|
|
31726
|
-
suggestions.push({
|
|
31727
|
-
component: "BubbleChart",
|
|
31728
|
-
confidence: "medium",
|
|
31729
|
-
reason: `Three numeric fields \u2014 bubble chart adds size dimension to scatter`,
|
|
31730
|
-
props: { data: "data", xAccessor: `"${xField}"`, yAccessor: `"${yField}"`, sizeBy: `"${numericFields[2]}"` }
|
|
31731
|
-
});
|
|
31732
|
-
}
|
|
31733
|
-
if (numericFields.length >= 2 && hasCat) {
|
|
31734
|
-
suggestions.push({
|
|
31735
|
-
component: "Heatmap",
|
|
31736
|
-
confidence: "medium",
|
|
31737
|
-
reason: `Numeric values across dimensions \u2014 heatmap shows density/intensity`,
|
|
31738
|
-
props: { data: "data", xAccessor: `"${xField}"`, yAccessor: `"${hasCat ? stringFields[0] : yField}"`, valueAccessor: `"${hasCat ? numericFields[0] : numericFields[2] || yField}"` }
|
|
31739
|
-
});
|
|
31740
|
-
}
|
|
31741
|
-
}
|
|
31742
|
-
if (suggestions.length === 0) {
|
|
31743
|
-
const fieldSummary = `Fields: ${keys.join(", ")} (${numericFields.length} numeric, ${stringFields.length} categorical, ${dateFields.length} date)`;
|
|
31744
|
-
return {
|
|
31745
|
-
content: [{ type: "text", text: `Could not confidently recommend a chart type.
|
|
31746
|
-
|
|
31747
|
-
${fieldSummary}
|
|
31748
|
-
|
|
31749
|
-
Try providing intent ('comparison', 'trend', 'distribution', 'relationship', 'composition', 'geographic', 'network', 'hierarchy') to narrow recommendations, or use getSchema to browse available components.` }]
|
|
31750
|
-
};
|
|
31751
|
-
}
|
|
31752
|
-
const lines = suggestions.map((s, i) => {
|
|
31753
|
-
const propsStr = Object.entries(s.props).map(([k, v]) => `${k}=${v}`).join(" ");
|
|
31754
|
-
return `${i + 1}. **${s.component}** (${s.confidence} confidence)
|
|
31755
|
-
${s.reason}
|
|
31756
|
-
\`<${s.component} ${propsStr} />\``;
|
|
31757
|
-
});
|
|
31758
|
-
const themingTip = `
|
|
31759
|
-
---
|
|
31760
|
-
**Styling**: All charts respond to CSS custom properties on any ancestor element:
|
|
31761
|
-
\`\`\`css
|
|
31762
|
-
.my-theme {
|
|
31763
|
-
--semiotic-bg: #fff; /* chart background */
|
|
31764
|
-
--semiotic-text: #333; /* primary text */
|
|
31765
|
-
--semiotic-text-secondary: #666; /* tick labels */
|
|
31766
|
-
--semiotic-grid: #e0e0e0; /* grid lines */
|
|
31767
|
-
--semiotic-border: #e0e0e0; /* axis lines, borders */
|
|
31768
|
-
--semiotic-font-family: sans-serif;
|
|
31769
|
-
--semiotic-tooltip-bg: rgba(0,0,0,0.85);
|
|
31770
|
-
--semiotic-tooltip-text: white;
|
|
31771
|
-
--semiotic-tooltip-radius: 6px;
|
|
31772
|
-
}
|
|
31773
|
-
\`\`\`
|
|
31774
|
-
Or use \`<ThemeProvider theme="dark">\` / \`<ThemeProvider theme={{ colors: {...}, typography: {...} }}>\`.
|
|
31775
|
-
For accessibility, use \`colorScheme={COLOR_BLIND_SAFE_CATEGORICAL}\` (import from \`semiotic\`) \u2014 8-color palette safe for all forms of color blindness.`;
|
|
31776
|
-
return {
|
|
31777
|
-
content: [{ type: "text", text: lines.join("\n\n") + themingTip }]
|
|
31778
|
-
};
|
|
32319
|
+
return { content, structuredContent: result };
|
|
31779
32320
|
}
|
|
31780
32321
|
async function renderChartHandler(args) {
|
|
31781
32322
|
const component = args.component;
|
|
@@ -31789,6 +32330,12 @@ async function renderChartHandler(args) {
|
|
|
31789
32330
|
};
|
|
31790
32331
|
}
|
|
31791
32332
|
if (!COMPONENT_REGISTRY[component]) {
|
|
32333
|
+
if (schemaByComponent[component]) {
|
|
32334
|
+
return {
|
|
32335
|
+
content: [{ type: "text", text: `Component "${component}" is known but cannot be rendered via renderChart. It requires a browser/live environment. Renderable components: ${componentNames.join(", ")}` }],
|
|
32336
|
+
isError: true
|
|
32337
|
+
};
|
|
32338
|
+
}
|
|
31792
32339
|
return {
|
|
31793
32340
|
content: [{ type: "text", text: `Unknown component "${component}". Available: ${componentNames.join(", ")}` }],
|
|
31794
32341
|
isError: true
|
|
@@ -31841,9 +32388,16 @@ ${svg}` }],
|
|
|
31841
32388
|
content: [{ type: "text", text: svg }]
|
|
31842
32389
|
};
|
|
31843
32390
|
}
|
|
32391
|
+
function filterUsageModeDiagnoses(component, usageMode, diagnoses) {
|
|
32392
|
+
if (dataRequiredForUsageMode(component, usageMode)) return diagnoses;
|
|
32393
|
+
return diagnoses.filter(
|
|
32394
|
+
(d) => d.code !== "VALIDATION" || d.message !== `"data" is required for ${component}.`
|
|
32395
|
+
);
|
|
32396
|
+
}
|
|
31844
32397
|
async function diagnoseConfigHandler(args) {
|
|
31845
32398
|
const component = args.component;
|
|
31846
32399
|
const props = args.props ?? {};
|
|
32400
|
+
const usageMode = normalizeUsageMode(args.usageMode);
|
|
31847
32401
|
if (!component) {
|
|
31848
32402
|
return {
|
|
31849
32403
|
content: [{ type: "text", text: "Missing 'component' field. Provide { component: 'LineChart', props: { ... } }." }],
|
|
@@ -31851,21 +32405,31 @@ async function diagnoseConfigHandler(args) {
|
|
|
31851
32405
|
};
|
|
31852
32406
|
}
|
|
31853
32407
|
const result = (0, import_ai3.diagnoseConfig)(component, props);
|
|
31854
|
-
|
|
31855
|
-
|
|
32408
|
+
const diagnoses = filterUsageModeDiagnoses(component, usageMode, result.diagnoses);
|
|
32409
|
+
const ok = diagnoses.every((d) => d.severity === "warning");
|
|
32410
|
+
const usageModeNote = usageMode === "push" ? "Usage mode: push (data prop may be omitted; use a ref to push data).\n\n" : "";
|
|
32411
|
+
if (ok) {
|
|
32412
|
+
const warnings = diagnoses.filter((d) => d.severity === "warning");
|
|
31856
32413
|
const msg = warnings.length > 0 ? `Configuration looks good with ${warnings.length} warning(s):
|
|
31857
32414
|
${warnings.map((w) => `\u26A0 [${w.code}] ${w.message}
|
|
31858
32415
|
Fix: ${w.fix}`).join("\n")}` : `\u2713 Configuration looks good \u2014 no issues detected.`;
|
|
31859
|
-
|
|
32416
|
+
const contracts = formatDoctorBehaviorContracts(behaviorContractsFor({ component, props }));
|
|
32417
|
+
return { content: [{ type: "text", text: `${usageModeNote}${contracts ? `${msg}
|
|
32418
|
+
|
|
32419
|
+
${contracts}` : msg}` }] };
|
|
31860
32420
|
}
|
|
31861
|
-
const lines =
|
|
32421
|
+
const lines = diagnoses.map((d) => {
|
|
31862
32422
|
const icon = d.severity === "error" ? "\u2717" : "\u26A0";
|
|
31863
32423
|
const fixLine = d.fix ? `
|
|
31864
32424
|
Fix: ${d.fix}` : "";
|
|
31865
32425
|
return `${icon} [${d.code}] ${d.message}${fixLine}`;
|
|
31866
32426
|
});
|
|
31867
32427
|
return {
|
|
31868
|
-
content: [{ type: "text", text:
|
|
32428
|
+
content: [{ type: "text", text: [
|
|
32429
|
+
usageModeNote.trim(),
|
|
32430
|
+
lines.join("\n"),
|
|
32431
|
+
formatDoctorBehaviorContracts(behaviorContractsFor({ component, props }))
|
|
32432
|
+
].filter(Boolean).join("\n\n") }],
|
|
31869
32433
|
isError: true
|
|
31870
32434
|
};
|
|
31871
32435
|
}
|
|
@@ -31974,6 +32538,120 @@ function createServer2() {
|
|
|
31974
32538
|
name: "semiotic",
|
|
31975
32539
|
version: schema.version || "3.0.0"
|
|
31976
32540
|
});
|
|
32541
|
+
srv.registerResource(
|
|
32542
|
+
"semiotic-schema",
|
|
32543
|
+
"semiotic://schema",
|
|
32544
|
+
{
|
|
32545
|
+
title: "Semiotic Component Schema",
|
|
32546
|
+
description: "Machine-readable JSON schema for all Semiotic AI chart components.",
|
|
32547
|
+
mimeType: "application/json"
|
|
32548
|
+
},
|
|
32549
|
+
(uri) => textResource(uri, "application/json", JSON.stringify(schema, null, 2))
|
|
32550
|
+
);
|
|
32551
|
+
srv.registerResource(
|
|
32552
|
+
"semiotic-components",
|
|
32553
|
+
"semiotic://components",
|
|
32554
|
+
{
|
|
32555
|
+
title: "Semiotic Component Index",
|
|
32556
|
+
description: "Renderable/browser-only component index with MCP categories.",
|
|
32557
|
+
mimeType: "application/json"
|
|
32558
|
+
},
|
|
32559
|
+
(uri) => textResource(uri, "application/json", componentIndexJSON())
|
|
32560
|
+
);
|
|
32561
|
+
srv.registerResource(
|
|
32562
|
+
"semiotic-behavior-contracts",
|
|
32563
|
+
"semiotic://behavior-contracts",
|
|
32564
|
+
{
|
|
32565
|
+
title: "Semiotic AI Behavior Contracts",
|
|
32566
|
+
description: "Agent-visible semantic rules for color precedence, required prop combinations, streaming refs, and renderability.",
|
|
32567
|
+
mimeType: "application/json"
|
|
32568
|
+
},
|
|
32569
|
+
(uri) => textResource(uri, "application/json", JSON.stringify({
|
|
32570
|
+
version: schema.version,
|
|
32571
|
+
contracts: BEHAVIOR_CONTRACTS
|
|
32572
|
+
}, null, 2))
|
|
32573
|
+
);
|
|
32574
|
+
srv.registerResource(
|
|
32575
|
+
"semiotic-system-prompt",
|
|
32576
|
+
"semiotic://system-prompt",
|
|
32577
|
+
{
|
|
32578
|
+
title: "Semiotic AI System Prompt",
|
|
32579
|
+
description: "Compact implementation guidance for AI assistants building with Semiotic.",
|
|
32580
|
+
mimeType: "text/markdown"
|
|
32581
|
+
},
|
|
32582
|
+
(uri) => textResource(uri, "text/markdown", readAIFile("system-prompt.md"))
|
|
32583
|
+
);
|
|
32584
|
+
srv.registerResource(
|
|
32585
|
+
"semiotic-examples",
|
|
32586
|
+
"semiotic://examples",
|
|
32587
|
+
{
|
|
32588
|
+
title: "Semiotic AI Examples",
|
|
32589
|
+
description: "Copy-paste examples for common Semiotic chart data shapes.",
|
|
32590
|
+
mimeType: "text/markdown"
|
|
32591
|
+
},
|
|
32592
|
+
(uri) => textResource(uri, "text/markdown", readAIFile("examples.md"))
|
|
32593
|
+
);
|
|
32594
|
+
srv.registerPrompt(
|
|
32595
|
+
"build-semiotic-chart",
|
|
32596
|
+
{
|
|
32597
|
+
title: "Build a Semiotic chart",
|
|
32598
|
+
description: "Workflow for choosing a chart, validating props, and rendering a preview.",
|
|
32599
|
+
argsSchema: {
|
|
32600
|
+
intent: external_exports3.string().optional().describe("Visualization intent, e.g. trend, comparison, distribution, relationship, composition, network, hierarchy."),
|
|
32601
|
+
dataDescription: external_exports3.string().optional().describe("Brief description of the data fields and sample rows."),
|
|
32602
|
+
component: external_exports3.string().optional().describe("Optional preferred Semiotic component name.")
|
|
32603
|
+
}
|
|
32604
|
+
},
|
|
32605
|
+
(args) => promptMessage([
|
|
32606
|
+
"Build a production-ready Semiotic visualization.",
|
|
32607
|
+
"",
|
|
32608
|
+
`Intent: ${args.intent || "not specified"}`,
|
|
32609
|
+
`Data: ${args.dataDescription || "not specified"}`,
|
|
32610
|
+
`Preferred component: ${args.component || "not specified"}`,
|
|
32611
|
+
"",
|
|
32612
|
+
"Use this MCP workflow:",
|
|
32613
|
+
"1. Read semiotic://system-prompt for compact API rules and pitfalls.",
|
|
32614
|
+
"2. Read semiotic://behavior-contracts for semantic rules that schema shape alone cannot express.",
|
|
32615
|
+
"3. If no component is specified, call suggestChart with 1-5 representative sample rows and the intent.",
|
|
32616
|
+
"4. Call getSchema for the selected component before writing JSX or renderChart props.",
|
|
32617
|
+
'5. Call diagnoseConfig with usageMode="static" for renderChart/static data, or usageMode="push" for ref-based React code that intentionally omits data.',
|
|
32618
|
+
"6. Fix all diagnoseConfig errors before presenting code.",
|
|
32619
|
+
"7. If the component is renderable and has static data, call renderChart once to verify it returns SVG.",
|
|
32620
|
+
"8. Prefer sub-path imports such as semiotic/xy, semiotic/ordinal, semiotic/network, semiotic/geo, or semiotic/ai depending on the surrounding code.",
|
|
32621
|
+
"",
|
|
32622
|
+
"Return the final JSX or renderChart call plus any assumptions about fields, accessors, or aggregation."
|
|
32623
|
+
].join("\n"))
|
|
32624
|
+
);
|
|
32625
|
+
srv.registerPrompt(
|
|
32626
|
+
"debug-semiotic-chart",
|
|
32627
|
+
{
|
|
32628
|
+
title: "Debug a Semiotic chart",
|
|
32629
|
+
description: "Workflow for diagnosing bad props, rendering failures, and chart-quality issues.",
|
|
32630
|
+
argsSchema: {
|
|
32631
|
+
component: external_exports3.string().optional().describe("Semiotic component name, e.g. BarChart."),
|
|
32632
|
+
problem: external_exports3.string().optional().describe("Observed failure, warning, or visual issue."),
|
|
32633
|
+
props: external_exports3.string().optional().describe("Relevant chart props as JSON or a short summary.")
|
|
32634
|
+
}
|
|
32635
|
+
},
|
|
32636
|
+
(args) => promptMessage([
|
|
32637
|
+
"Debug this Semiotic chart with the MCP server.",
|
|
32638
|
+
"",
|
|
32639
|
+
`Component: ${args.component || "not specified"}`,
|
|
32640
|
+
`Problem: ${args.problem || "not specified"}`,
|
|
32641
|
+
`Props: ${args.props || "not provided"}`,
|
|
32642
|
+
"",
|
|
32643
|
+
"Use this MCP workflow:",
|
|
32644
|
+
"1. Call getSchema for the component and compare the provided props against required props and accessor names.",
|
|
32645
|
+
"2. Read semiotic://behavior-contracts for semantic rules around colors, required combinations, streaming refs, and renderability.",
|
|
32646
|
+
'3. Call diagnoseConfig with usageMode="push" if the code intentionally omits data for a ref-push HOC; otherwise use usageMode="static".',
|
|
32647
|
+
"4. Treat diagnoseConfig errors as blockers and warnings as review items.",
|
|
32648
|
+
"5. If renderable and static data is available, call renderChart with a minimal reproduction to separate configuration issues from rendering bugs.",
|
|
32649
|
+
"6. Check semiotic://examples for a nearby working pattern before inventing new props.",
|
|
32650
|
+
"7. If the result looks like a Semiotic bug, call reportIssue with the component, props summary, diagnoseConfig output, and renderChart result.",
|
|
32651
|
+
"",
|
|
32652
|
+
"Return the smallest safe fix first, then mention any follow-up cleanup or issue-reporting step."
|
|
32653
|
+
].join("\n"))
|
|
32654
|
+
);
|
|
31977
32655
|
srv.tool(
|
|
31978
32656
|
"getSchema",
|
|
31979
32657
|
`Return the prop schema for a Semiotic chart component. Pass { component: '<name>' } to get its props, or omit component to list all available components. Components marked [renderable] can be passed to renderChart for static SVG output.`,
|
|
@@ -31991,7 +32669,7 @@ function createServer2() {
|
|
|
31991
32669
|
);
|
|
31992
32670
|
srv.tool(
|
|
31993
32671
|
"renderChart",
|
|
31994
|
-
`Render a Semiotic chart to static SVG or PNG. Returns SVG string (default) or Base64-encoded PNG image. Optionally pass theme CSS custom properties (--semiotic-bg, --semiotic-text, etc.) to style the output. PNG requires the 'sharp' package to be installed. Available components: ${componentNames.join(", ")}.`,
|
|
32672
|
+
`Render a Semiotic chart to static SVG or PNG. This is a static snapshot path: props must include data immediately, and ref/push-mode charts cannot be rendered through this tool. Returns SVG string (default) or Base64-encoded PNG image. Optionally pass theme CSS custom properties (--semiotic-bg, --semiotic-text, etc.) to style the output. PNG requires the 'sharp' package to be installed. Available components: ${componentNames.join(", ")}.`,
|
|
31995
32673
|
{
|
|
31996
32674
|
component: external_exports3.string().describe("Chart component name, e.g. 'LineChart', 'BarChart'"),
|
|
31997
32675
|
props: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional().describe("Chart props object, e.g. { data: [...], xAccessor: 'x' }."),
|
|
@@ -32002,10 +32680,11 @@ function createServer2() {
|
|
|
32002
32680
|
);
|
|
32003
32681
|
srv.tool(
|
|
32004
32682
|
"diagnoseConfig",
|
|
32005
|
-
"Diagnose a Semiotic chart configuration for common problems (empty data, bad dimensions, missing accessors, wrong data shape, color contrast issues, etc). Checks WCAG color contrast ratios and suggests COLOR_BLIND_SAFE_CATEGORICAL for accessibility. Returns a human-readable diagnostic report with actionable fixes.",
|
|
32683
|
+
"Diagnose a Semiotic chart configuration for common problems (empty data, bad dimensions, missing accessors, wrong data shape, color contrast issues, etc). Pass usageMode='push' for ref-based React HOCs that intentionally omit data; omit usageMode or pass 'static' for renderChart/MCP/server configs where data is required. Checks WCAG color contrast ratios and suggests COLOR_BLIND_SAFE_CATEGORICAL for accessibility. Returns a human-readable diagnostic report with actionable fixes.",
|
|
32006
32684
|
{
|
|
32007
32685
|
component: external_exports3.string().describe("Chart component name, e.g. 'LineChart'"),
|
|
32008
|
-
props: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional().describe("Chart props object, e.g. { data: [...], xAccessor: 'x' }.")
|
|
32686
|
+
props: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional().describe("Chart props object, e.g. { data: [...], xAccessor: 'x' }."),
|
|
32687
|
+
usageMode: external_exports3.enum(["static", "push", "renderChart", "server"]).optional().describe("Validation mode. Use 'push' for ref-based React HOCs that omit data; use 'static' or omit for renderChart/MCP/static data configs.")
|
|
32009
32688
|
},
|
|
32010
32689
|
diagnoseConfigHandler
|
|
32011
32690
|
);
|
|
@@ -32073,7 +32752,8 @@ async function main() {
|
|
|
32073
32752
|
});
|
|
32074
32753
|
httpServer.listen(port, () => {
|
|
32075
32754
|
console.error(`Semiotic MCP server (HTTP) listening on http://localhost:${port}`);
|
|
32076
|
-
console.error("Tools: getSchema, suggestChart, renderChart, diagnoseConfig, reportIssue");
|
|
32755
|
+
console.error("Tools: getSchema, suggestChart, renderChart, diagnoseConfig, reportIssue, applyTheme");
|
|
32756
|
+
console.error("Resources: semiotic://schema, semiotic://components, semiotic://behavior-contracts, semiotic://system-prompt, semiotic://examples");
|
|
32077
32757
|
});
|
|
32078
32758
|
} else {
|
|
32079
32759
|
const srv = createServer2();
|