From 8908e0785f5d718aabddb46414b81681686f7d3c Mon Sep 17 00:00:00 2001
From: nitin <142569587+ehconitin@users.noreply.github.com>
Date: Fri, 16 Jan 2026 02:25:47 +0530
Subject: [PATCH] [Dashboards] [ai] update dashboard tools schema and skill
with correct config field names (#17180)
---
...reate-standard-flat-skill-metadata.util.ts | 98 +++++++++++++++----
.../tools/create-complete-dashboard.tool.ts | 27 ++---
.../dashboard/tools/schemas/widget.schema.ts | 44 +++++----
.../tools/update-dashboard-widget.tool.ts | 15 ++-
4 files changed, 133 insertions(+), 51 deletions(-)
diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-flat-skill-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-flat-skill-metadata.util.ts
index d5ed649c7d..faa9556b8a 100644
--- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-flat-skill-metadata.util.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-flat-skill-metadata.util.ts
@@ -146,41 +146,101 @@ Before creating any GRAPH widget, you MUST:
GRAPH widgets require real UUIDs from the workspace metadata, NOT made-up values.
-## Widget Configuration
+## Understanding User Language
-### GRAPH - AGGREGATE (KPI numbers)
-Shows a single aggregated value (count, sum, average).
-Required:
-- objectMetadataId: UUID of the object (e.g., opportunity)
-- configuration.graphType: "AGGREGATE"
-- configuration.aggregateFieldMetadataId: UUID of field to aggregate
-- configuration.aggregateOperation: "COUNT", "SUM", "AVG", "MIN", "MAX"
+Users describe charts using UI terminology. Here's how to translate:
-### GRAPH - BAR/LINE Charts
-Shows data grouped by a dimension.
+### Bar/Line Chart Settings
+
+**Data section:**
+- "Source" / "change the object": objectMetadataId
+- "Filter" / "filter the data": configuration.filter
+
+**X axis section (primary grouping - the bars/categories):**
+- "X axis" / "data on display" / "categories": primaryAxisGroupByFieldMetadataId
+- "Date granularity" (on X axis): primaryAxisDateGranularity
+- "Sort by" (on X axis): primaryAxisOrderBy
+
+**Y axis section (what's being measured + optional secondary grouping):**
+- "Y axis" / "data on display" / "measure" / "metric": aggregateFieldMetadataId + aggregateOperation
+- "Group by" / "stacking" / "colors" / "breakdown": secondaryAxisGroupByFieldMetadataId
+- "Date granularity" (on Group by): secondaryAxisDateGranularity
+- "Sort by" (on Group by): secondaryAxisOrderBy
+- "Cumulative" / "running total": cumulative
+- "Min range" / "Max range": rangeMin, rangeMax
+
+**Style section:**
+- "Stacked" / "stacked bars": layout stays same, it's about secondaryAxisGroupByFieldMetadataId
+- "Data labels" / "show values": displayDataLabel
+- "Legend" / "show legend": displayLegend
+
+### CRITICAL: "Remove groupby" / "remove stacking" / "unstacked"
+When users say this for bar/line charts, they mean remove the SECONDARY grouping (the colors/stacking).
+- Set secondaryAxisGroupByFieldMetadataId to null
+- Keep the chart type (BAR_CHART/LINE_CHART)
+- Keep primaryAxisGroupByFieldMetadataId (the X axis categories)
+- DO NOT convert to AGGREGATE_CHART unless user explicitly asks for "just a number" or "KPI"
+
+### Pie Chart Settings
+- "Each slice represents" / "slices": groupByFieldMetadataId
+- "Hide empty category": hideEmptyCategory
+- "Show value in center": showValueInCenter
+
+### Aggregate Chart Settings (KPI numbers)
+- "Prefix" (e.g., "$"): prefix
+- "Suffix" (e.g., "%"): suffix
+
+## Widget Configuration Types
+
+### AGGREGATE_CHART (KPI number widget)
+Shows a single aggregated value.
Required:
- objectMetadataId: UUID of the object
-- configuration.graphType: "VERTICAL_BAR", "HORIZONTAL_BAR", or "LINE"
+- configuration.configurationType: "AGGREGATE_CHART"
+- configuration.aggregateFieldMetadataId: UUID of field to aggregate
+- configuration.aggregateOperation: "COUNT", "SUM", "AVG", "MIN", "MAX"
+Optional: prefix, suffix, displayDataLabel, filter
+
+### BAR_CHART
+Shows data grouped by categories with optional secondary grouping.
+Required:
+- objectMetadataId: UUID of the object
+- configuration.configurationType: "BAR_CHART"
- configuration.aggregateFieldMetadataId: field to aggregate
- configuration.aggregateOperation: aggregation type
-- configuration.primaryAxisGroupByFieldMetadataId: field to group by (x-axis)
+- configuration.primaryAxisGroupByFieldMetadataId: X axis categories
+- configuration.layout: "VERTICAL" or "HORIZONTAL"
+Optional: secondaryAxisGroupByFieldMetadataId (for stacking/colors), displayDataLabel, displayLegend, filter
-### GRAPH - PIE Charts
+### LINE_CHART
+Shows trends over a dimension.
+Required:
+- objectMetadataId: UUID of the object
+- configuration.configurationType: "LINE_CHART"
+- configuration.aggregateFieldMetadataId: field to aggregate
+- configuration.aggregateOperation: aggregation type
+- configuration.primaryAxisGroupByFieldMetadataId: X axis (usually date)
+Optional: secondaryAxisGroupByFieldMetadataId (for multiple lines), cumulative, displayDataLabel, filter
+
+### PIE_CHART
Shows data distribution as slices.
Required:
- objectMetadataId: UUID of the object
-- configuration.graphType: "PIE"
+- configuration.configurationType: "PIE_CHART"
- configuration.aggregateFieldMetadataId: field to aggregate
- configuration.aggregateOperation: aggregation type
-- configuration.groupByFieldMetadataId: field to slice by
+- configuration.groupByFieldMetadataId: field to slice by (NOTE: different field name than bar/line!)
+Optional: displayDataLabel, hideEmptyCategory, showValueInCenter, filter
### IFRAME
Embeds external content:
+- configuration.configurationType: "IFRAME"
- configuration.url: "https://..."
### STANDALONE_RICH_TEXT
Text content widget:
-- configuration.body: "Your text here"
+- configuration.configurationType: "STANDALONE_RICH_TEXT"
+- configuration.body: rich text content
## Grid System
@@ -197,14 +257,16 @@ Text content widget:
1. Ask user what data they want to visualize
2. Load list_object_metadata_items to discover available objects and fields
3. Create dashboard with appropriate widgets using real field IDs
-4. Use get_dashboard to verify creation
+4. Use get_dashboard to verify creation and see current configuration
+5. When modifying, first understand current config before making changes
## Best Practices
- Place KPIs at the top (row 0)
- Group related charts together
- Use consistent heights within rows
-- Start simple, add complexity as needed`,
+- Start simple, add complexity as needed
+- When user asks to modify a chart, clarify if they want to change settings OR change chart type`,
isCustom: false,
},
}),
diff --git a/packages/twenty-server/src/modules/dashboard/tools/create-complete-dashboard.tool.ts b/packages/twenty-server/src/modules/dashboard/tools/create-complete-dashboard.tool.ts
index 8641c61db2..d8c645f651 100644
--- a/packages/twenty-server/src/modules/dashboard/tools/create-complete-dashboard.tool.ts
+++ b/packages/twenty-server/src/modules/dashboard/tools/create-complete-dashboard.tool.ts
@@ -60,24 +60,25 @@ GRID SYSTEM:
WIDGET TYPES:
-1. GRAPH with graphType "AGGREGATE" (KPI number):
- - Requires: objectMetadataId, configuration.graphType, configuration.aggregateFieldMetadataId, configuration.aggregateOperation
- - Example: { type: "GRAPH", objectMetadataId: "", configuration: { graphType: "AGGREGATE", aggregateFieldMetadataId: "", aggregateOperation: "SUM" } }
+1. GRAPH with configurationType "AGGREGATE_CHART" (KPI number):
+ - Requires: objectMetadataId, configuration.configurationType, configuration.aggregateFieldMetadataId, configuration.aggregateOperation
+ - Example: { type: "GRAPH", objectMetadataId: "", configuration: { configurationType: "AGGREGATE_CHART", aggregateFieldMetadataId: "", aggregateOperation: "SUM" } }
-2. GRAPH with graphType "VERTICAL_BAR" or "HORIZONTAL_BAR":
+2. GRAPH with configurationType "BAR_CHART":
+ - Additional required: configuration.primaryAxisGroupByFieldMetadataId, configuration.layout ("VERTICAL" or "HORIZONTAL")
+ - Example: { type: "GRAPH", objectMetadataId: "", configuration: { configurationType: "BAR_CHART", aggregateFieldMetadataId: "", aggregateOperation: "COUNT", primaryAxisGroupByFieldMetadataId: "", layout: "VERTICAL" } }
+
+3. GRAPH with configurationType "LINE_CHART":
- Additional required: configuration.primaryAxisGroupByFieldMetadataId
- - Example: { graphType: "VERTICAL_BAR", aggregateFieldMetadataId: "", aggregateOperation: "COUNT", primaryAxisGroupByFieldMetadataId: "" }
+ - Example: { type: "GRAPH", objectMetadataId: "", configuration: { configurationType: "LINE_CHART", aggregateFieldMetadataId: "", aggregateOperation: "SUM", primaryAxisGroupByFieldMetadataId: "" } }
-3. GRAPH with graphType "LINE":
- - Same as bar charts, good for time series
+4. GRAPH with configurationType "PIE_CHART":
+ - Additional required: configuration.groupByFieldMetadataId (note: different field name!)
+ - Example: { type: "GRAPH", objectMetadataId: "", configuration: { configurationType: "PIE_CHART", aggregateFieldMetadataId: "", aggregateOperation: "COUNT", groupByFieldMetadataId: "" } }
-4. GRAPH with graphType "PIE":
- - Requires: objectMetadataId, aggregateFieldMetadataId, aggregateOperation, groupByFieldMetadataId
- - Example: { graphType: "PIE", aggregateFieldMetadataId: "", aggregateOperation: "COUNT", groupByFieldMetadataId: "" }
+5. IFRAME: { type: "IFRAME", configuration: { configurationType: "IFRAME", url: "https://..." } }
-5. IFRAME: { type: "IFRAME", configuration: { url: "https://..." } }
-
-6. STANDALONE_RICH_TEXT: { type: "STANDALONE_RICH_TEXT", configuration: { body: "..." } }
+6. STANDALONE_RICH_TEXT: { type: "STANDALONE_RICH_TEXT", configuration: { configurationType: "STANDALONE_RICH_TEXT", body: { ... } } }
AGGREGATION OPERATIONS: COUNT, SUM, AVG, MIN, MAX, COUNT_EMPTY, COUNT_NOT_EMPTY`,
inputSchema: createCompleteDashboardSchema,
diff --git a/packages/twenty-server/src/modules/dashboard/tools/schemas/widget.schema.ts b/packages/twenty-server/src/modules/dashboard/tools/schemas/widget.schema.ts
index 3d3699a548..2245b732e9 100644
--- a/packages/twenty-server/src/modules/dashboard/tools/schemas/widget.schema.ts
+++ b/packages/twenty-server/src/modules/dashboard/tools/schemas/widget.schema.ts
@@ -1,7 +1,7 @@
import { z } from 'zod';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
-import { GraphType } from 'src/engine/metadata-modules/page-layout-widget/enums/graph-type.enum';
+import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
export const gridPositionSchema = z.object({
@@ -28,7 +28,7 @@ export const widgetTypeSchema = z.enum([
// Graph configuration schema for AGGREGATE type (KPI numbers)
const aggregateChartConfigSchema = z.object({
- graphType: z.literal(GraphType.AGGREGATE_CHART),
+ configurationType: z.literal(WidgetConfigurationType.AGGREGATE_CHART),
aggregateFieldMetadataId: z
.string()
.uuid()
@@ -48,7 +48,7 @@ const aggregateChartConfigSchema = z.object({
// Graph configuration schema for BAR charts
const barChartConfigSchema = z
.object({
- graphType: z.literal(GraphType.BAR_CHART),
+ configurationType: z.literal(WidgetConfigurationType.BAR_CHART),
aggregateFieldMetadataId: z
.string()
.uuid()
@@ -87,8 +87,6 @@ const barChartConfigSchema = z
displayLegend: z.boolean().optional().default(true),
layout: z
.enum(['VERTICAL', 'HORIZONTAL'])
- .optional()
- .default('VERTICAL')
.describe('Layout orientation for bar charts'),
filter: z.record(z.string(), z.unknown()).optional(),
})
@@ -118,7 +116,7 @@ const barChartConfigSchema = z
// Graph configuration schema for LINE charts
const lineChartConfigSchema = z
.object({
- graphType: z.literal(GraphType.LINE_CHART),
+ configurationType: z.literal(WidgetConfigurationType.LINE_CHART),
aggregateFieldMetadataId: z.string().uuid(),
aggregateOperation: z.nativeEnum(AggregateOperations),
primaryAxisGroupByFieldMetadataId: z.string().uuid(),
@@ -176,7 +174,7 @@ const lineChartConfigSchema = z
// Graph configuration schema for PIE charts
const pieChartConfigSchema = z
.object({
- graphType: z.literal(GraphType.PIE_CHART),
+ configurationType: z.literal(WidgetConfigurationType.PIE_CHART),
aggregateFieldMetadataId: z.string().uuid(),
aggregateOperation: z.nativeEnum(AggregateOperations),
groupByFieldMetadataId: z
@@ -211,25 +209,37 @@ const pieChartConfigSchema = z
// Iframe configuration
const iframeConfigSchema = z.object({
- url: z.string().url().describe('URL to embed'),
+ configurationType: z.literal(WidgetConfigurationType.IFRAME),
+ url: z.string().url().optional().describe('URL to embed'),
});
// Rich text configuration
const richTextConfigSchema = z.object({
- body: z.string().optional().describe('Rich text content'),
+ configurationType: z.literal(WidgetConfigurationType.STANDALONE_RICH_TEXT),
+ body: z.unknown().optional().describe('Rich text content (RichTextV2Body)'),
});
-export const graphConfigurationSchema = z.discriminatedUnion('graphType', [
- aggregateChartConfigSchema,
- barChartConfigSchema,
- lineChartConfigSchema,
- pieChartConfigSchema,
-]);
+export const graphConfigurationSchema = z.discriminatedUnion(
+ 'configurationType',
+ [
+ aggregateChartConfigSchema,
+ barChartConfigSchema,
+ lineChartConfigSchema,
+ pieChartConfigSchema,
+ ],
+);
export const widgetConfigurationSchema = z
- .union([graphConfigurationSchema, iframeConfigSchema, richTextConfigSchema])
+ .discriminatedUnion('configurationType', [
+ aggregateChartConfigSchema,
+ barChartConfigSchema,
+ lineChartConfigSchema,
+ pieChartConfigSchema,
+ iframeConfigSchema,
+ richTextConfigSchema,
+ ])
.optional()
.describe('Widget configuration - structure depends on widget type');
// Export enums for documentation
-export { AggregateOperations, GraphType as GraphType };
+export { AggregateOperations, WidgetConfigurationType };
diff --git a/packages/twenty-server/src/modules/dashboard/tools/update-dashboard-widget.tool.ts b/packages/twenty-server/src/modules/dashboard/tools/update-dashboard-widget.tool.ts
index 57cf2d0435..315a48b7e7 100644
--- a/packages/twenty-server/src/modules/dashboard/tools/update-dashboard-widget.tool.ts
+++ b/packages/twenty-server/src/modules/dashboard/tools/update-dashboard-widget.tool.ts
@@ -1,4 +1,4 @@
-import { isDefined } from 'twenty-shared/utils';
+import { isDefined, isEmptyObject } from 'twenty-shared/utils';
import { z } from 'zod';
import { type WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
@@ -24,7 +24,7 @@ const updateDashboardWidgetSchema = z.object({
.uuid()
.optional()
.describe('New object metadata ID'),
- configuration: widgetConfigurationSchema,
+ configuration: widgetConfigurationSchema.optional(),
});
export const createUpdateDashboardWidgetTool = (
@@ -54,7 +54,16 @@ Only provide fields you want to change - others remain unchanged.`,
try {
const { widgetId, ...updates } = parameters;
const updateData = Object.fromEntries(
- Object.entries(updates).filter(([, value]) => isDefined(value)),
+ Object.entries(updates).filter(([key, value]) => {
+ if (!isDefined(value)) {
+ return false;
+ }
+ if (key === 'configuration' && isEmptyObject(value)) {
+ return false;
+ }
+
+ return true;
+ }),
);
const widget = await deps.pageLayoutWidgetService.update({