diff --git a/content/docs/references/ai/conversation.mdx b/content/docs/references/ai/conversation.mdx index 0fd508b7b..6b0437987 100644 --- a/content/docs/references/ai/conversation.mdx +++ b/content/docs/references/ai/conversation.mdx @@ -77,6 +77,7 @@ const result = ConversationAnalyticsSchema.parse(data); | **toolCallId** | `string` | optional | Tool call ID this message responds to | | **name** | `string` | optional | Name of the function/user | | **tokens** | `object` | optional | Token usage for this message | +| **cost** | `number` | optional | Cost for this message in USD | | **pinned** | `boolean` | optional | Prevent removal during pruning | | **importance** | `number` | optional | Importance score for pruning | | **embedding** | `number[]` | optional | Vector embedding for semantic search | @@ -97,6 +98,8 @@ const result = ConversationAnalyticsSchema.parse(data); | **tokenBudget** | `object` | ✅ | | | **messages** | `object[]` | optional | | | **tokens** | `object` | optional | | +| **totalTokens** | `object` | optional | Total tokens across all messages | +| **totalCost** | `number` | optional | Total cost for this session in USD | | **status** | `Enum<'active' \| 'paused' \| 'completed' \| 'archived'>` | optional | | | **createdAt** | `string` | ✅ | ISO 8601 timestamp | | **updatedAt** | `string` | ✅ | ISO 8601 timestamp | diff --git a/content/docs/references/ai/cost.mdx b/content/docs/references/ai/cost.mdx index d2cc5d1e5..6e8f3d5a6 100644 --- a/content/docs/references/ai/cost.mdx +++ b/content/docs/references/ai/cost.mdx @@ -12,15 +12,32 @@ description: Cost protocol schemas ## TypeScript Usage ```typescript -import { BillingPeriodSchema, BudgetLimitSchema, BudgetStatusSchema, BudgetTypeSchema, CostAlertSchema, CostAlertTypeSchema, CostAnalyticsSchema, CostBreakdownDimensionSchema, CostBreakdownEntrySchema, CostEntrySchema, CostMetricTypeSchema, CostOptimizationRecommendationSchema, CostQueryFiltersSchema, CostReportSchema } from '@objectstack/spec/ai'; -import type { BillingPeriod, BudgetLimit, BudgetStatus, BudgetType, CostAlert, CostAlertType, CostAnalytics, CostBreakdownDimension, CostBreakdownEntry, CostEntry, CostMetricType, CostOptimizationRecommendation, CostQueryFilters, CostReport } from '@objectstack/spec/ai'; +import { AIOperationCostSchema, BillingPeriodSchema, BudgetLimitSchema, BudgetStatusSchema, BudgetTypeSchema, CostAlertSchema, CostAlertTypeSchema, CostAnalyticsSchema, CostBreakdownDimensionSchema, CostBreakdownEntrySchema, CostEntrySchema, CostMetricTypeSchema, CostOptimizationRecommendationSchema, CostQueryFiltersSchema, CostReportSchema, TokenUsageSchema } from '@objectstack/spec/ai'; +import type { AIOperationCost, BillingPeriod, BudgetLimit, BudgetStatus, BudgetType, CostAlert, CostAlertType, CostAnalytics, CostBreakdownDimension, CostBreakdownEntry, CostEntry, CostMetricType, CostOptimizationRecommendation, CostQueryFilters, CostReport, TokenUsage } from '@objectstack/spec/ai'; // Validate data -const result = BillingPeriodSchema.parse(data); +const result = AIOperationCostSchema.parse(data); ``` --- +## AIOperationCost + +### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **operationId** | `string` | ✅ | | +| **operationType** | `Enum<'conversation' \| 'orchestration' \| 'prediction' \| 'rag' \| 'nlq'>` | ✅ | | +| **agentName** | `string` | optional | Agent that performed the operation | +| **modelId** | `string` | ✅ | | +| **tokens** | `object` | ✅ | | +| **cost** | `number` | ✅ | Cost in USD | +| **timestamp** | `string` | ✅ | | +| **metadata** | `Record` | optional | | + +--- + ## BillingPeriod ### Allowed Values @@ -316,3 +333,15 @@ const result = BillingPeriodSchema.parse(data); | **format** | `Enum<'summary' \| 'detailed' \| 'executive'>` | optional | | | **currency** | `string` | optional | | +--- + +## TokenUsage + +### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **prompt** | `integer` | ✅ | Input tokens | +| **completion** | `integer` | ✅ | Output tokens | +| **total** | `integer` | ✅ | Total tokens | + diff --git a/content/docs/references/ai/nlq.mdx b/content/docs/references/ai/nlq.mdx index deff51c9a..c6f360996 100644 --- a/content/docs/references/ai/nlq.mdx +++ b/content/docs/references/ai/nlq.mdx @@ -152,6 +152,8 @@ const result = EntitySchema.parse(data); | **totalCount** | `integer` | optional | | | **executionTime** | `number` | optional | Execution time in milliseconds | | **needsClarification** | `boolean` | ✅ | Whether query needs clarification | +| **tokens** | `object` | optional | Token usage for this query | +| **cost** | `number` | optional | Cost for this query in USD | | **suggestions** | `string[]` | optional | Query refinement suggestions | --- diff --git a/content/docs/references/ai/orchestration.mdx b/content/docs/references/ai/orchestration.mdx index bb0670c8f..6befb6970 100644 --- a/content/docs/references/ai/orchestration.mdx +++ b/content/docs/references/ai/orchestration.mdx @@ -69,6 +69,8 @@ const result = AIOrchestrationSchema.parse(data); | **tasksSucceeded** | `integer` | ✅ | Number of tasks succeeded | | **tasksFailed** | `integer` | ✅ | Number of tasks failed | | **taskResults** | `object[]` | optional | | +| **tokens** | `object` | optional | Total token usage for this execution | +| **cost** | `number` | optional | Total cost for this execution in USD | | **error** | `string` | optional | | | **startedAt** | `string` | ✅ | ISO timestamp | | **completedAt** | `string` | optional | ISO timestamp | diff --git a/content/docs/references/ai/predictive.mdx b/content/docs/references/ai/predictive.mdx index 63e6eefe4..15237a32c 100644 --- a/content/docs/references/ai/predictive.mdx +++ b/content/docs/references/ai/predictive.mdx @@ -131,6 +131,8 @@ const result = EvaluationMetricsSchema.parse(data); | **confidence** | `number` | optional | Confidence score (0-1) | | **probabilities** | `Record` | optional | Class probabilities (for classification) | | **explanation** | `object` | optional | | +| **tokens** | `object` | optional | Token usage for this prediction (if AI-powered) | +| **cost** | `number` | optional | Cost for this prediction in USD | | **metadata** | `object` | optional | | --- diff --git a/content/docs/references/ai/rag-pipeline.mdx b/content/docs/references/ai/rag-pipeline.mdx index 6456c7519..9cf0d1a03 100644 --- a/content/docs/references/ai/rag-pipeline.mdx +++ b/content/docs/references/ai/rag-pipeline.mdx @@ -157,7 +157,9 @@ const result = ChunkingStrategySchema.parse(data); | **query** | `string` | ✅ | | | **results** | `object[]` | ✅ | | | **context** | `string` | ✅ | Assembled context for LLM | -| **tokensUsed** | `integer` | optional | | +| **tokens** | `object` | optional | Token usage for this query | +| **cost** | `number` | optional | Cost for this query in USD | +| **tokensUsed** | `integer` | optional | Deprecated: use tokens.total instead | | **retrievalTime** | `number` | optional | Retrieval time in milliseconds | --- diff --git a/content/docs/references/ui/chart.mdx b/content/docs/references/ui/chart.mdx new file mode 100644 index 000000000..fc8fb4729 --- /dev/null +++ b/content/docs/references/ui/chart.mdx @@ -0,0 +1,72 @@ +--- +title: Chart +description: Chart protocol schemas +--- + +# Chart + + +**Source:** `packages/spec/src/ui/chart.zod.ts` + + +## TypeScript Usage + +```typescript +import { ChartConfigSchema, ChartTypeSchema } from '@objectstack/spec/ui'; +import type { ChartConfig, ChartType } from '@objectstack/spec/ui'; + +// Validate data +const result = ChartConfigSchema.parse(data); +``` + +--- + +## ChartConfig + +### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **type** | `Enum<'bar' \| 'horizontal-bar' \| 'column' \| 'grouped-bar' \| 'stacked-bar' \| 'line' \| 'area' \| 'stacked-area' \| 'step-line' \| 'pie' \| 'donut' \| 'funnel' \| 'scatter' \| 'bubble' \| 'treemap' \| 'sunburst' \| 'sankey' \| 'gauge' \| 'metric' \| 'kpi' \| 'choropleth' \| 'bubble-map' \| 'heatmap' \| 'radar' \| 'waterfall' \| 'box-plot' \| 'violin' \| 'table' \| 'pivot'>` | ✅ | | +| **title** | `string` | optional | Chart title | +| **description** | `string` | optional | Chart description | +| **showLegend** | `boolean` | optional | Display legend | +| **showDataLabels** | `boolean` | optional | Display data labels on chart | +| **colors** | `string[]` | optional | Custom color palette | + +--- + +## ChartType + +### Allowed Values + +* `bar` +* `horizontal-bar` +* `column` +* `grouped-bar` +* `stacked-bar` +* `line` +* `area` +* `stacked-area` +* `step-line` +* `pie` +* `donut` +* `funnel` +* `scatter` +* `bubble` +* `treemap` +* `sunburst` +* `sankey` +* `gauge` +* `metric` +* `kpi` +* `choropleth` +* `bubble-map` +* `heatmap` +* `radar` +* `waterfall` +* `box-plot` +* `violin` +* `table` +* `pivot` + diff --git a/content/docs/references/ui/dashboard.mdx b/content/docs/references/ui/dashboard.mdx index 7b537a8c6..cee58c6a5 100644 --- a/content/docs/references/ui/dashboard.mdx +++ b/content/docs/references/ui/dashboard.mdx @@ -12,38 +12,15 @@ description: Dashboard protocol schemas ## TypeScript Usage ```typescript -import { ChartTypeSchema, DashboardSchema, DashboardWidgetSchema } from '@objectstack/spec/ui'; -import type { ChartType, Dashboard, DashboardWidget } from '@objectstack/spec/ui'; +import { DashboardSchema, DashboardWidgetSchema } from '@objectstack/spec/ui'; +import type { Dashboard, DashboardWidget } from '@objectstack/spec/ui'; // Validate data -const result = ChartTypeSchema.parse(data); +const result = DashboardSchema.parse(data); ``` --- -## ChartType - -### Allowed Values - -* `metric` -* `bar` -* `line` -* `pie` -* `donut` -* `gauge` -* `funnel` -* `radar` -* `scatter` -* `heatmap` -* `pivot` -* `table` -* `list` -* `text` -* `image` -* `frame` - ---- - ## Dashboard ### Properties @@ -64,7 +41,8 @@ const result = ChartTypeSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **title** | `string` | optional | Widget title | -| **type** | `Enum<'metric' \| 'bar' \| 'line' \| 'pie' \| 'donut' \| 'gauge' \| 'funnel' \| 'radar' \| 'scatter' \| 'heatmap' \| 'pivot' \| 'table' \| 'list' \| 'text' \| 'image' \| 'frame'>` | optional | Visualization type | +| **type** | `Enum<'bar' \| 'horizontal-bar' \| 'column' \| 'grouped-bar' \| 'stacked-bar' \| 'line' \| 'area' \| 'stacked-area' \| 'step-line' \| 'pie' \| 'donut' \| 'funnel' \| 'scatter' \| 'bubble' \| 'treemap' \| 'sunburst' \| 'sankey' \| 'gauge' \| 'metric' \| 'kpi' \| 'choropleth' \| 'bubble-map' \| 'heatmap' \| 'radar' \| 'waterfall' \| 'box-plot' \| 'violin' \| 'table' \| 'pivot'>` | optional | Visualization type | +| **chartConfig** | `object` | optional | Chart visualization configuration | | **object** | `string` | optional | Data source object name | | **filter** | `any` | optional | Data filter criteria | | **categoryField** | `string` | optional | Field for grouping (X-Axis) | diff --git a/content/docs/references/ui/index.mdx b/content/docs/references/ui/index.mdx index 338b633e6..6cffc3888 100644 --- a/content/docs/references/ui/index.mdx +++ b/content/docs/references/ui/index.mdx @@ -10,6 +10,7 @@ This section contains all protocol schemas for the ui layer of ObjectStack. + diff --git a/content/docs/references/ui/meta.json b/content/docs/references/ui/meta.json index c6b917f87..d177bd776 100644 --- a/content/docs/references/ui/meta.json +++ b/content/docs/references/ui/meta.json @@ -3,6 +3,7 @@ "pages": [ "action", "app", + "chart", "component", "dashboard", "page", diff --git a/content/docs/references/ui/report.mdx b/content/docs/references/ui/report.mdx index fdaffbbc4..989a18d2a 100644 --- a/content/docs/references/ui/report.mdx +++ b/content/docs/references/ui/report.mdx @@ -46,11 +46,15 @@ const result = ReportSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **type** | `Enum<'bar' \| 'column' \| 'line' \| 'pie' \| 'donut' \| 'scatter' \| 'funnel'>` | ✅ | Chart type | -| **title** | `string` | optional | | -| **showLegend** | `boolean` | optional | | +| **type** | `Enum<'bar' \| 'horizontal-bar' \| 'column' \| 'grouped-bar' \| 'stacked-bar' \| 'line' \| 'area' \| 'stacked-area' \| 'step-line' \| 'pie' \| 'donut' \| 'funnel' \| 'scatter' \| 'bubble' \| 'treemap' \| 'sunburst' \| 'sankey' \| 'gauge' \| 'metric' \| 'kpi' \| 'choropleth' \| 'bubble-map' \| 'heatmap' \| 'radar' \| 'waterfall' \| 'box-plot' \| 'violin' \| 'table' \| 'pivot'>` | ✅ | | +| **title** | `string` | optional | Chart title | +| **description** | `string` | optional | Chart description | +| **showLegend** | `boolean` | optional | Display legend | +| **showDataLabels** | `boolean` | optional | Display data labels on chart | +| **colors** | `string[]` | optional | Custom color palette | | **xAxis** | `string` | ✅ | Grouping field for X-Axis | | **yAxis** | `string` | ✅ | Summary field for Y-Axis | +| **groupBy** | `string` | optional | Additional grouping field | --- diff --git a/packages/spec/json-schema/ai/AIOperationCost.json b/packages/spec/json-schema/ai/AIOperationCost.json new file mode 100644 index 000000000..c511eb109 --- /dev/null +++ b/packages/spec/json-schema/ai/AIOperationCost.json @@ -0,0 +1,79 @@ +{ + "$ref": "#/definitions/AIOperationCost", + "definitions": { + "AIOperationCost": { + "type": "object", + "properties": { + "operationId": { + "type": "string" + }, + "operationType": { + "type": "string", + "enum": [ + "conversation", + "orchestration", + "prediction", + "rag", + "nlq" + ] + }, + "agentName": { + "type": "string", + "description": "Agent that performed the operation" + }, + "modelId": { + "type": "string" + }, + "tokens": { + "type": "object", + "properties": { + "prompt": { + "type": "integer", + "minimum": 0, + "description": "Input tokens" + }, + "completion": { + "type": "integer", + "minimum": 0, + "description": "Output tokens" + }, + "total": { + "type": "integer", + "minimum": 0, + "description": "Total tokens" + } + }, + "required": [ + "prompt", + "completion", + "total" + ], + "additionalProperties": false + }, + "cost": { + "type": "number", + "minimum": 0, + "description": "Cost in USD" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "metadata": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "operationId", + "operationType", + "modelId", + "tokens", + "cost", + "timestamp" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ai/AIOrchestrationExecutionResult.json b/packages/spec/json-schema/ai/AIOrchestrationExecutionResult.json index 18b3d268a..6688867d6 100644 --- a/packages/spec/json-schema/ai/AIOrchestrationExecutionResult.json +++ b/packages/spec/json-schema/ai/AIOrchestrationExecutionResult.json @@ -76,6 +76,38 @@ "additionalProperties": false } }, + "tokens": { + "type": "object", + "properties": { + "prompt": { + "type": "integer", + "minimum": 0, + "description": "Input tokens" + }, + "completion": { + "type": "integer", + "minimum": 0, + "description": "Output tokens" + }, + "total": { + "type": "integer", + "minimum": 0, + "description": "Total tokens" + } + }, + "required": [ + "prompt", + "completion", + "total" + ], + "additionalProperties": false, + "description": "Total token usage for this execution" + }, + "cost": { + "type": "number", + "minimum": 0, + "description": "Total cost for this execution in USD" + }, "error": { "type": "string" }, diff --git a/packages/spec/json-schema/ai/ConversationMessage.json b/packages/spec/json-schema/ai/ConversationMessage.json index 8fedfb987..9b0579ece 100644 --- a/packages/spec/json-schema/ai/ConversationMessage.json +++ b/packages/spec/json-schema/ai/ConversationMessage.json @@ -157,12 +157,12 @@ "prompt": { "type": "integer", "minimum": 0, - "description": "Tokens in prompt" + "description": "Input tokens" }, "completion": { "type": "integer", "minimum": 0, - "description": "Tokens in completion" + "description": "Output tokens" }, "total": { "type": "integer", @@ -170,9 +170,19 @@ "description": "Total tokens" } }, + "required": [ + "prompt", + "completion", + "total" + ], "additionalProperties": false, "description": "Token usage for this message" }, + "cost": { + "type": "number", + "minimum": 0, + "description": "Cost for this message in USD" + }, "pinned": { "type": "boolean", "default": false, diff --git a/packages/spec/json-schema/ai/ConversationSession.json b/packages/spec/json-schema/ai/ConversationSession.json index b994fa482..b5da890fa 100644 --- a/packages/spec/json-schema/ai/ConversationSession.json +++ b/packages/spec/json-schema/ai/ConversationSession.json @@ -302,12 +302,12 @@ "prompt": { "type": "integer", "minimum": 0, - "description": "Tokens in prompt" + "description": "Input tokens" }, "completion": { "type": "integer", "minimum": 0, - "description": "Tokens in completion" + "description": "Output tokens" }, "total": { "type": "integer", @@ -315,9 +315,19 @@ "description": "Total tokens" } }, + "required": [ + "prompt", + "completion", + "total" + ], "additionalProperties": false, "description": "Token usage for this message" }, + "cost": { + "type": "number", + "minimum": 0, + "description": "Cost for this message in USD" + }, "pinned": { "type": "boolean", "default": false, @@ -411,6 +421,38 @@ ], "additionalProperties": false }, + "totalTokens": { + "type": "object", + "properties": { + "prompt": { + "type": "integer", + "minimum": 0, + "description": "Input tokens" + }, + "completion": { + "type": "integer", + "minimum": 0, + "description": "Output tokens" + }, + "total": { + "type": "integer", + "minimum": 0, + "description": "Total tokens" + } + }, + "required": [ + "prompt", + "completion", + "total" + ], + "additionalProperties": false, + "description": "Total tokens across all messages" + }, + "totalCost": { + "type": "number", + "minimum": 0, + "description": "Total cost for this session in USD" + }, "status": { "type": "string", "enum": [ diff --git a/packages/spec/json-schema/ai/NLQResponse.json b/packages/spec/json-schema/ai/NLQResponse.json index 615a59f3d..574ce7a8e 100644 --- a/packages/spec/json-schema/ai/NLQResponse.json +++ b/packages/spec/json-schema/ai/NLQResponse.json @@ -269,6 +269,38 @@ "type": "boolean", "description": "Whether query needs clarification" }, + "tokens": { + "type": "object", + "properties": { + "prompt": { + "type": "integer", + "minimum": 0, + "description": "Input tokens" + }, + "completion": { + "type": "integer", + "minimum": 0, + "description": "Output tokens" + }, + "total": { + "type": "integer", + "minimum": 0, + "description": "Total tokens" + } + }, + "required": [ + "prompt", + "completion", + "total" + ], + "additionalProperties": false, + "description": "Token usage for this query" + }, + "cost": { + "type": "number", + "minimum": 0, + "description": "Cost for this query in USD" + }, "suggestions": { "type": "array", "items": { diff --git a/packages/spec/json-schema/ai/PredictionResult.json b/packages/spec/json-schema/ai/PredictionResult.json index fef3097e3..b19c96715 100644 --- a/packages/spec/json-schema/ai/PredictionResult.json +++ b/packages/spec/json-schema/ai/PredictionResult.json @@ -56,6 +56,38 @@ }, "additionalProperties": false }, + "tokens": { + "type": "object", + "properties": { + "prompt": { + "type": "integer", + "minimum": 0, + "description": "Input tokens" + }, + "completion": { + "type": "integer", + "minimum": 0, + "description": "Output tokens" + }, + "total": { + "type": "integer", + "minimum": 0, + "description": "Total tokens" + } + }, + "required": [ + "prompt", + "completion", + "total" + ], + "additionalProperties": false, + "description": "Token usage for this prediction (if AI-powered)" + }, + "cost": { + "type": "number", + "minimum": 0, + "description": "Cost for this prediction in USD" + }, "metadata": { "type": "object", "properties": { diff --git a/packages/spec/json-schema/ai/RAGQueryResponse.json b/packages/spec/json-schema/ai/RAGQueryResponse.json index 32c6e01e6..e224ea002 100644 --- a/packages/spec/json-schema/ai/RAGQueryResponse.json +++ b/packages/spec/json-schema/ai/RAGQueryResponse.json @@ -88,8 +88,41 @@ "type": "string", "description": "Assembled context for LLM" }, + "tokens": { + "type": "object", + "properties": { + "prompt": { + "type": "integer", + "minimum": 0, + "description": "Input tokens" + }, + "completion": { + "type": "integer", + "minimum": 0, + "description": "Output tokens" + }, + "total": { + "type": "integer", + "minimum": 0, + "description": "Total tokens" + } + }, + "required": [ + "prompt", + "completion", + "total" + ], + "additionalProperties": false, + "description": "Token usage for this query" + }, + "cost": { + "type": "number", + "minimum": 0, + "description": "Cost for this query in USD" + }, "tokensUsed": { - "type": "integer" + "type": "integer", + "description": "Deprecated: use tokens.total instead" }, "retrievalTime": { "type": "number", diff --git a/packages/spec/json-schema/ai/TokenUsage.json b/packages/spec/json-schema/ai/TokenUsage.json new file mode 100644 index 000000000..4810e4c46 --- /dev/null +++ b/packages/spec/json-schema/ai/TokenUsage.json @@ -0,0 +1,32 @@ +{ + "$ref": "#/definitions/TokenUsage", + "definitions": { + "TokenUsage": { + "type": "object", + "properties": { + "prompt": { + "type": "integer", + "minimum": 0, + "description": "Input tokens" + }, + "completion": { + "type": "integer", + "minimum": 0, + "description": "Output tokens" + }, + "total": { + "type": "integer", + "minimum": 0, + "description": "Total tokens" + } + }, + "required": [ + "prompt", + "completion", + "total" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ui/ChartConfig.json b/packages/spec/json-schema/ui/ChartConfig.json new file mode 100644 index 000000000..112ffbc92 --- /dev/null +++ b/packages/spec/json-schema/ui/ChartConfig.json @@ -0,0 +1,74 @@ +{ + "$ref": "#/definitions/ChartConfig", + "definitions": { + "ChartConfig": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "bar", + "horizontal-bar", + "column", + "grouped-bar", + "stacked-bar", + "line", + "area", + "stacked-area", + "step-line", + "pie", + "donut", + "funnel", + "scatter", + "bubble", + "treemap", + "sunburst", + "sankey", + "gauge", + "metric", + "kpi", + "choropleth", + "bubble-map", + "heatmap", + "radar", + "waterfall", + "box-plot", + "violin", + "table", + "pivot" + ] + }, + "title": { + "type": "string", + "description": "Chart title" + }, + "description": { + "type": "string", + "description": "Chart description" + }, + "showLegend": { + "type": "boolean", + "default": true, + "description": "Display legend" + }, + "showDataLabels": { + "type": "boolean", + "default": false, + "description": "Display data labels on chart" + }, + "colors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Custom color palette" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/spec/json-schema/ui/ChartType.json b/packages/spec/json-schema/ui/ChartType.json index 9114da43e..fe550d0b2 100644 --- a/packages/spec/json-schema/ui/ChartType.json +++ b/packages/spec/json-schema/ui/ChartType.json @@ -4,22 +4,35 @@ "ChartType": { "type": "string", "enum": [ - "metric", "bar", + "horizontal-bar", + "column", + "grouped-bar", + "stacked-bar", "line", + "area", + "stacked-area", + "step-line", "pie", "donut", - "gauge", "funnel", - "radar", "scatter", + "bubble", + "treemap", + "sunburst", + "sankey", + "gauge", + "metric", + "kpi", + "choropleth", + "bubble-map", "heatmap", - "pivot", + "radar", + "waterfall", + "box-plot", + "violin", "table", - "list", - "text", - "image", - "frame" + "pivot" ] } }, diff --git a/packages/spec/json-schema/ui/Dashboard.json b/packages/spec/json-schema/ui/Dashboard.json index bcdf91df8..e1eab378b 100644 --- a/packages/spec/json-schema/ui/Dashboard.json +++ b/packages/spec/json-schema/ui/Dashboard.json @@ -29,26 +29,108 @@ "type": { "type": "string", "enum": [ - "metric", "bar", + "horizontal-bar", + "column", + "grouped-bar", + "stacked-bar", "line", + "area", + "stacked-area", + "step-line", "pie", "donut", - "gauge", "funnel", - "radar", "scatter", + "bubble", + "treemap", + "sunburst", + "sankey", + "gauge", + "metric", + "kpi", + "choropleth", + "bubble-map", "heatmap", - "pivot", + "radar", + "waterfall", + "box-plot", + "violin", "table", - "list", - "text", - "image", - "frame" + "pivot" ], "default": "metric", "description": "Visualization type" }, + "chartConfig": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "bar", + "horizontal-bar", + "column", + "grouped-bar", + "stacked-bar", + "line", + "area", + "stacked-area", + "step-line", + "pie", + "donut", + "funnel", + "scatter", + "bubble", + "treemap", + "sunburst", + "sankey", + "gauge", + "metric", + "kpi", + "choropleth", + "bubble-map", + "heatmap", + "radar", + "waterfall", + "box-plot", + "violin", + "table", + "pivot" + ] + }, + "title": { + "type": "string", + "description": "Chart title" + }, + "description": { + "type": "string", + "description": "Chart description" + }, + "showLegend": { + "type": "boolean", + "default": true, + "description": "Display legend" + }, + "showDataLabels": { + "type": "boolean", + "default": false, + "description": "Display data labels on chart" + }, + "colors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Custom color palette" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Chart visualization configuration" + }, "object": { "type": "string", "description": "Data source object name" diff --git a/packages/spec/json-schema/ui/DashboardWidget.json b/packages/spec/json-schema/ui/DashboardWidget.json index d12142238..5b8c23bf6 100644 --- a/packages/spec/json-schema/ui/DashboardWidget.json +++ b/packages/spec/json-schema/ui/DashboardWidget.json @@ -11,26 +11,108 @@ "type": { "type": "string", "enum": [ - "metric", "bar", + "horizontal-bar", + "column", + "grouped-bar", + "stacked-bar", "line", + "area", + "stacked-area", + "step-line", "pie", "donut", - "gauge", "funnel", - "radar", "scatter", + "bubble", + "treemap", + "sunburst", + "sankey", + "gauge", + "metric", + "kpi", + "choropleth", + "bubble-map", "heatmap", - "pivot", + "radar", + "waterfall", + "box-plot", + "violin", "table", - "list", - "text", - "image", - "frame" + "pivot" ], "default": "metric", "description": "Visualization type" }, + "chartConfig": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "bar", + "horizontal-bar", + "column", + "grouped-bar", + "stacked-bar", + "line", + "area", + "stacked-area", + "step-line", + "pie", + "donut", + "funnel", + "scatter", + "bubble", + "treemap", + "sunburst", + "sankey", + "gauge", + "metric", + "kpi", + "choropleth", + "bubble-map", + "heatmap", + "radar", + "waterfall", + "box-plot", + "violin", + "table", + "pivot" + ] + }, + "title": { + "type": "string", + "description": "Chart title" + }, + "description": { + "type": "string", + "description": "Chart description" + }, + "showLegend": { + "type": "boolean", + "default": true, + "description": "Display legend" + }, + "showDataLabels": { + "type": "boolean", + "default": false, + "description": "Display data labels on chart" + }, + "colors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Custom color palette" + } + }, + "required": [ + "type" + ], + "additionalProperties": false, + "description": "Chart visualization configuration" + }, "object": { "type": "string", "description": "Data source object name" diff --git a/packages/spec/json-schema/ui/Report.json b/packages/spec/json-schema/ui/Report.json index 24cd89c78..318305d42 100644 --- a/packages/spec/json-schema/ui/Report.json +++ b/packages/spec/json-schema/ui/Report.json @@ -166,21 +166,60 @@ "type": "string", "enum": [ "bar", + "horizontal-bar", "column", + "grouped-bar", + "stacked-bar", "line", + "area", + "stacked-area", + "step-line", "pie", "donut", + "funnel", "scatter", - "funnel" - ], - "description": "Chart type" + "bubble", + "treemap", + "sunburst", + "sankey", + "gauge", + "metric", + "kpi", + "choropleth", + "bubble-map", + "heatmap", + "radar", + "waterfall", + "box-plot", + "violin", + "table", + "pivot" + ] }, "title": { - "type": "string" + "type": "string", + "description": "Chart title" + }, + "description": { + "type": "string", + "description": "Chart description" }, "showLegend": { "type": "boolean", - "default": true + "default": true, + "description": "Display legend" + }, + "showDataLabels": { + "type": "boolean", + "default": false, + "description": "Display data labels on chart" + }, + "colors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Custom color palette" }, "xAxis": { "type": "string", @@ -189,6 +228,10 @@ "yAxis": { "type": "string", "description": "Summary field for Y-Axis" + }, + "groupBy": { + "type": "string", + "description": "Additional grouping field" } }, "required": [ diff --git a/packages/spec/json-schema/ui/ReportChart.json b/packages/spec/json-schema/ui/ReportChart.json index c27463dbe..e616ab45b 100644 --- a/packages/spec/json-schema/ui/ReportChart.json +++ b/packages/spec/json-schema/ui/ReportChart.json @@ -8,21 +8,60 @@ "type": "string", "enum": [ "bar", + "horizontal-bar", "column", + "grouped-bar", + "stacked-bar", "line", + "area", + "stacked-area", + "step-line", "pie", "donut", + "funnel", "scatter", - "funnel" - ], - "description": "Chart type" + "bubble", + "treemap", + "sunburst", + "sankey", + "gauge", + "metric", + "kpi", + "choropleth", + "bubble-map", + "heatmap", + "radar", + "waterfall", + "box-plot", + "violin", + "table", + "pivot" + ] }, "title": { - "type": "string" + "type": "string", + "description": "Chart title" + }, + "description": { + "type": "string", + "description": "Chart description" }, "showLegend": { "type": "boolean", - "default": true + "default": true, + "description": "Display legend" + }, + "showDataLabels": { + "type": "boolean", + "default": false, + "description": "Display data labels on chart" + }, + "colors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Custom color palette" }, "xAxis": { "type": "string", @@ -31,6 +70,10 @@ "yAxis": { "type": "string", "description": "Summary field for Y-Axis" + }, + "groupBy": { + "type": "string", + "description": "Additional grouping field" } }, "required": [ diff --git a/packages/spec/src/ai/conversation.zod.ts b/packages/spec/src/ai/conversation.zod.ts index 279e9feca..93ed0926e 100644 --- a/packages/spec/src/ai/conversation.zod.ts +++ b/packages/spec/src/ai/conversation.zod.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { TokenUsageSchema } from './cost.zod'; /** * AI Conversation Memory Protocol @@ -81,11 +82,8 @@ export const ConversationMessageSchema = z.object({ /** Metadata */ name: z.string().optional().describe('Name of the function/user'), - tokens: z.object({ - prompt: z.number().int().nonnegative().optional().describe('Tokens in prompt'), - completion: z.number().int().nonnegative().optional().describe('Tokens in completion'), - total: z.number().int().nonnegative().optional().describe('Total tokens'), - }).optional().describe('Token usage for this message'), + tokens: TokenUsageSchema.optional().describe('Token usage for this message'), + cost: z.number().nonnegative().optional().describe('Cost for this message in USD'), /** Context Management */ pinned: z.boolean().optional().default(false).describe('Prevent removal during pruning'), @@ -196,6 +194,8 @@ export const ConversationSessionSchema = z.object({ /** Token Tracking */ tokens: TokenUsageStatsSchema.optional(), + totalTokens: TokenUsageSchema.optional().describe('Total tokens across all messages'), + totalCost: z.number().nonnegative().optional().describe('Total cost for this session in USD'), /** Session Status */ status: z.enum(['active', 'paused', 'completed', 'archived']).default('active'), diff --git a/packages/spec/src/ai/cost.test.ts b/packages/spec/src/ai/cost.test.ts index bf9c10418..593e61116 100644 --- a/packages/spec/src/ai/cost.test.ts +++ b/packages/spec/src/ai/cost.test.ts @@ -1,5 +1,7 @@ import { describe, it, expect } from 'vitest'; import { + TokenUsageSchema, + AIOperationCostSchema, CostMetricTypeSchema, BillingPeriodSchema, CostEntrySchema, @@ -14,12 +16,130 @@ import { CostOptimizationRecommendationSchema, CostReportSchema, CostQueryFiltersSchema, + type TokenUsage, + type AIOperationCost, type CostEntry, type BudgetLimit, type CostAlert, type CostReport, } from './cost.zod'; +describe('TokenUsageSchema', () => { + it('should accept valid token usage', () => { + const usage: TokenUsage = { + prompt: 1500, + completion: 800, + total: 2300, + }; + expect(() => TokenUsageSchema.parse(usage)).not.toThrow(); + }); + + it('should require all fields', () => { + expect(() => TokenUsageSchema.parse({ + prompt: 1500, + completion: 800, + // missing total + })).toThrow(); + }); + + it('should reject negative values', () => { + expect(() => TokenUsageSchema.parse({ + prompt: -100, + completion: 800, + total: 700, + })).toThrow(); + }); +}); + +describe('AIOperationCostSchema', () => { + it('should accept minimal AI operation cost', () => { + const cost: AIOperationCost = { + operationId: 'op-123', + operationType: 'conversation', + modelId: 'gpt-4-turbo', + tokens: { + prompt: 1500, + completion: 800, + total: 2300, + }, + cost: 0.05, + timestamp: '2024-01-15T10:00:00Z', + }; + expect(() => AIOperationCostSchema.parse(cost)).not.toThrow(); + }); + + it('should accept all operation types', () => { + const types = ['conversation', 'orchestration', 'prediction', 'rag', 'nlq'] as const; + + types.forEach(type => { + const cost: AIOperationCost = { + operationId: 'op-123', + operationType: type, + modelId: 'gpt-4-turbo', + tokens: { + prompt: 1500, + completion: 800, + total: 2300, + }, + cost: 0.05, + timestamp: '2024-01-15T10:00:00Z', + }; + expect(() => AIOperationCostSchema.parse(cost)).not.toThrow(); + }); + }); + + it('should accept AI operation cost with agent name and metadata', () => { + const cost: AIOperationCost = { + operationId: 'op-456', + operationType: 'orchestration', + agentName: 'support_agent', + modelId: 'gpt-4-turbo', + tokens: { + prompt: 2000, + completion: 1200, + total: 3200, + }, + cost: 0.08, + timestamp: '2024-01-15T11:00:00Z', + metadata: { + workflowId: 'wf-789', + recordId: 'case-123', + }, + }; + expect(() => AIOperationCostSchema.parse(cost)).not.toThrow(); + }); + + it('should reject invalid timestamp', () => { + expect(() => AIOperationCostSchema.parse({ + operationId: 'op-123', + operationType: 'conversation', + modelId: 'gpt-4-turbo', + tokens: { + prompt: 1500, + completion: 800, + total: 2300, + }, + cost: 0.05, + timestamp: 'invalid-timestamp', + })).toThrow(); + }); + + it('should reject negative cost', () => { + expect(() => AIOperationCostSchema.parse({ + operationId: 'op-123', + operationType: 'conversation', + modelId: 'gpt-4-turbo', + tokens: { + prompt: 1500, + completion: 800, + total: 2300, + }, + cost: -0.05, + timestamp: '2024-01-15T10:00:00Z', + })).toThrow(); + }); +}); + describe('CostMetricTypeSchema', () => { it('should accept all valid metric types', () => { const types = ['token', 'request', 'character', 'second', 'image', 'embedding'] as const; diff --git a/packages/spec/src/ai/cost.zod.ts b/packages/spec/src/ai/cost.zod.ts index 6db5c6936..42e47c5a5 100644 --- a/packages/spec/src/ai/cost.zod.ts +++ b/packages/spec/src/ai/cost.zod.ts @@ -7,6 +7,35 @@ import { z } from 'zod'; * Provides cost optimization, budget enforcement, and financial reporting. */ +/** + * Token Usage Schema + * Standardized across all AI operations + */ +export const TokenUsageSchema = z.object({ + prompt: z.number().int().nonnegative().describe('Input tokens'), + completion: z.number().int().nonnegative().describe('Output tokens'), + total: z.number().int().nonnegative().describe('Total tokens'), +}); + +export type TokenUsage = z.infer; + +/** + * AI Operation Cost Schema + * Unified cost tracking for all AI operations + */ +export const AIOperationCostSchema = z.object({ + operationId: z.string(), + operationType: z.enum(['conversation', 'orchestration', 'prediction', 'rag', 'nlq']), + agentName: z.string().optional().describe('Agent that performed the operation'), + modelId: z.string(), + tokens: TokenUsageSchema, + cost: z.number().nonnegative().describe('Cost in USD'), + timestamp: z.string().datetime(), + metadata: z.record(z.any()).optional(), +}); + +export type AIOperationCost = z.infer; + /** * Cost Metric Type */ @@ -34,6 +63,7 @@ export const BillingPeriodSchema = z.enum([ /** * Cost Entry + * Extended from AIOperationCostSchema with additional tracking fields */ export const CostEntrySchema = z.object({ /** Identity */ diff --git a/packages/spec/src/ai/nlq.zod.ts b/packages/spec/src/ai/nlq.zod.ts index 4711ed61e..c24528c0e 100644 --- a/packages/spec/src/ai/nlq.zod.ts +++ b/packages/spec/src/ai/nlq.zod.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { TokenUsageSchema } from './cost.zod'; /** * Natural Language Query (NLQ) Protocol @@ -161,6 +162,10 @@ export const NLQResponseSchema = z.object({ executionTime: z.number().optional().describe('Execution time in milliseconds'), needsClarification: z.boolean().describe('Whether query needs clarification'), + /** Cost Tracking */ + tokens: TokenUsageSchema.optional().describe('Token usage for this query'), + cost: z.number().nonnegative().optional().describe('Cost for this query in USD'), + /** Suggestions */ suggestions: z.array(z.string()).optional().describe('Query refinement suggestions'), }); diff --git a/packages/spec/src/ai/orchestration.zod.ts b/packages/spec/src/ai/orchestration.zod.ts index 50c4f51a2..6e007b380 100644 --- a/packages/spec/src/ai/orchestration.zod.ts +++ b/packages/spec/src/ai/orchestration.zod.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { TokenUsageSchema } from './cost.zod'; /** * AI Agentic Orchestration Protocol @@ -219,6 +220,8 @@ export const AIOrchestrationExecutionResultSchema = z.object({ modelUsed: z.string().optional(), tokensUsed: z.number().optional(), })).optional(), + tokens: TokenUsageSchema.optional().describe('Total token usage for this execution'), + cost: z.number().nonnegative().optional().describe('Total cost for this execution in USD'), error: z.string().optional(), startedAt: z.string().describe('ISO timestamp'), completedAt: z.string().optional().describe('ISO timestamp'), diff --git a/packages/spec/src/ai/predictive.zod.ts b/packages/spec/src/ai/predictive.zod.ts index 497bffa5e..a839220fe 100644 --- a/packages/spec/src/ai/predictive.zod.ts +++ b/packages/spec/src/ai/predictive.zod.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { TokenUsageSchema } from './cost.zod'; /** * Predictive Analytics Protocol @@ -259,6 +260,8 @@ export const PredictionResultSchema = z.object({ })).optional(), reasoning: z.string().optional(), }).optional(), + tokens: TokenUsageSchema.optional().describe('Token usage for this prediction (if AI-powered)'), + cost: z.number().nonnegative().optional().describe('Cost for this prediction in USD'), metadata: z.object({ executionTime: z.number().optional().describe('Execution time in milliseconds'), timestamp: z.string().optional().describe('ISO timestamp'), diff --git a/packages/spec/src/ai/rag-pipeline.zod.ts b/packages/spec/src/ai/rag-pipeline.zod.ts index 5789fe713..1f16da42a 100644 --- a/packages/spec/src/ai/rag-pipeline.zod.ts +++ b/packages/spec/src/ai/rag-pipeline.zod.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { TokenUsageSchema } from './cost.zod'; /** * RAG (Retrieval-Augmented Generation) Pipeline Protocol @@ -251,7 +252,9 @@ export const RAGQueryResponseSchema = z.object({ chunkId: z.string().optional(), })), context: z.string().describe('Assembled context for LLM'), - tokensUsed: z.number().int().optional(), + tokens: TokenUsageSchema.optional().describe('Token usage for this query'), + cost: z.number().nonnegative().optional().describe('Cost for this query in USD'), + tokensUsed: z.number().int().optional().describe('Deprecated: use tokens.total instead'), retrievalTime: z.number().optional().describe('Retrieval time in milliseconds'), }); diff --git a/packages/spec/src/ui/chart.test.ts b/packages/spec/src/ui/chart.test.ts new file mode 100644 index 000000000..33a46875a --- /dev/null +++ b/packages/spec/src/ui/chart.test.ts @@ -0,0 +1,210 @@ +import { describe, it, expect } from 'vitest'; +import { + ChartTypeSchema, + ChartConfigSchema, + type ChartType, + type ChartConfig, +} from './chart.zod'; + +describe('ChartTypeSchema', () => { + it('should accept all comparison chart types', () => { + const types = ['bar', 'horizontal-bar', 'column', 'grouped-bar', 'stacked-bar'] as const; + + types.forEach(type => { + expect(() => ChartTypeSchema.parse(type)).not.toThrow(); + }); + }); + + it('should accept all trend chart types', () => { + const types = ['line', 'area', 'stacked-area', 'step-line'] as const; + + types.forEach(type => { + expect(() => ChartTypeSchema.parse(type)).not.toThrow(); + }); + }); + + it('should accept all distribution chart types', () => { + const types = ['pie', 'donut', 'funnel'] as const; + + types.forEach(type => { + expect(() => ChartTypeSchema.parse(type)).not.toThrow(); + }); + }); + + it('should accept all relationship chart types', () => { + const types = ['scatter', 'bubble'] as const; + + types.forEach(type => { + expect(() => ChartTypeSchema.parse(type)).not.toThrow(); + }); + }); + + it('should accept all composition chart types', () => { + const types = ['treemap', 'sunburst', 'sankey'] as const; + + types.forEach(type => { + expect(() => ChartTypeSchema.parse(type)).not.toThrow(); + }); + }); + + it('should accept all performance chart types', () => { + const types = ['gauge', 'metric', 'kpi'] as const; + + types.forEach(type => { + expect(() => ChartTypeSchema.parse(type)).not.toThrow(); + }); + }); + + it('should accept all geo chart types', () => { + const types = ['choropleth', 'bubble-map'] as const; + + types.forEach(type => { + expect(() => ChartTypeSchema.parse(type)).not.toThrow(); + }); + }); + + it('should accept all advanced chart types', () => { + const types = ['heatmap', 'radar', 'waterfall', 'box-plot', 'violin'] as const; + + types.forEach(type => { + expect(() => ChartTypeSchema.parse(type)).not.toThrow(); + }); + }); + + it('should accept all tabular chart types', () => { + const types = ['table', 'pivot'] as const; + + types.forEach(type => { + expect(() => ChartTypeSchema.parse(type)).not.toThrow(); + }); + }); + + it('should reject invalid chart type', () => { + expect(() => ChartTypeSchema.parse('invalid-chart')).toThrow(); + }); +}); + +describe('ChartConfigSchema', () => { + it('should accept minimal chart config', () => { + const config: ChartConfig = { + type: 'bar', + }; + const result = ChartConfigSchema.parse(config); + expect(result.type).toBe('bar'); + expect(result.showLegend).toBe(true); + expect(result.showDataLabels).toBe(false); + }); + + it('should accept full chart config', () => { + const config: ChartConfig = { + type: 'line', + title: 'Sales Trend', + description: 'Monthly sales performance', + showLegend: true, + showDataLabels: true, + colors: ['#FF6384', '#36A2EB', '#FFCE56'], + }; + expect(() => ChartConfigSchema.parse(config)).not.toThrow(); + }); + + it('should apply default values', () => { + const config: ChartConfig = { + type: 'pie', + title: 'Revenue by Region', + }; + const result = ChartConfigSchema.parse(config); + expect(result.showLegend).toBe(true); + expect(result.showDataLabels).toBe(false); + }); + + it('should allow custom colors', () => { + const config: ChartConfig = { + type: 'donut', + colors: ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'], + }; + const result = ChartConfigSchema.parse(config); + expect(result.colors).toHaveLength(4); + }); +}); + +describe('Real-World Chart Configuration Examples', () => { + it('should accept bar chart for comparison', () => { + const config: ChartConfig = { + type: 'bar', + title: 'Sales by Product Category', + description: 'Comparison of sales across different product categories', + showLegend: true, + showDataLabels: true, + colors: ['#4e79a7', '#f28e2c', '#e15759'], + }; + expect(() => ChartConfigSchema.parse(config)).not.toThrow(); + }); + + it('should accept line chart for trends', () => { + const config: ChartConfig = { + type: 'line', + title: 'Revenue Trend', + description: 'Monthly revenue over the past year', + showLegend: true, + showDataLabels: false, + }; + expect(() => ChartConfigSchema.parse(config)).not.toThrow(); + }); + + it('should accept pie chart for distribution', () => { + const config: ChartConfig = { + type: 'pie', + title: 'Market Share', + description: 'Market share by competitor', + showLegend: true, + showDataLabels: true, + }; + expect(() => ChartConfigSchema.parse(config)).not.toThrow(); + }); + + it('should accept gauge for performance metrics', () => { + const config: ChartConfig = { + type: 'gauge', + title: 'Customer Satisfaction Score', + description: 'Current satisfaction rating (0-100)', + showLegend: false, + colors: ['#22c55e', '#eab308', '#ef4444'], + }; + expect(() => ChartConfigSchema.parse(config)).not.toThrow(); + }); + + it('should accept heatmap for correlation analysis', () => { + const config: ChartConfig = { + type: 'heatmap', + title: 'User Activity Heatmap', + description: 'Hourly user activity by day of week', + showLegend: true, + showDataLabels: false, + colors: ['#440154', '#31688e', '#35b779', '#fde724'], + }; + expect(() => ChartConfigSchema.parse(config)).not.toThrow(); + }); + + it('should accept funnel chart for conversion tracking', () => { + const config: ChartConfig = { + type: 'funnel', + title: 'Sales Funnel', + description: 'Conversion rates at each stage', + showLegend: false, + showDataLabels: true, + }; + expect(() => ChartConfigSchema.parse(config)).not.toThrow(); + }); + + it('should accept waterfall chart for financial analysis', () => { + const config: ChartConfig = { + type: 'waterfall', + title: 'Profit & Loss Breakdown', + description: 'Revenue, costs, and profit components', + showLegend: false, + showDataLabels: true, + colors: ['#22c55e', '#ef4444', '#6366f1'], + }; + expect(() => ChartConfigSchema.parse(config)).not.toThrow(); + }); +}); diff --git a/packages/spec/src/ui/chart.zod.ts b/packages/spec/src/ui/chart.zod.ts new file mode 100644 index 000000000..fe963f741 --- /dev/null +++ b/packages/spec/src/ui/chart.zod.ts @@ -0,0 +1,78 @@ +import { z } from 'zod'; + +/** + * Unified Chart Type Taxonomy + * + * Shared by Dashboard and Report widgets. + * Provides a comprehensive set of chart types for data visualization. + */ + +/** + * Chart Type Enum + * Categorized by visualization purpose + */ +export const ChartTypeSchema = z.enum([ + // Comparison + 'bar', + 'horizontal-bar', + 'column', + 'grouped-bar', + 'stacked-bar', + + // Trend + 'line', + 'area', + 'stacked-area', + 'step-line', + + // Distribution + 'pie', + 'donut', + 'funnel', + + // Relationship + 'scatter', + 'bubble', + + // Composition + 'treemap', + 'sunburst', + 'sankey', + + // Performance + 'gauge', + 'metric', + 'kpi', + + // Geo + 'choropleth', + 'bubble-map', + + // Advanced + 'heatmap', + 'radar', + 'waterfall', + 'box-plot', + 'violin', + + // Tabular + 'table', + 'pivot', +]); + +export type ChartType = z.infer; + +/** + * Chart Configuration Base + * Common configuration for all chart types + */ +export const ChartConfigSchema = z.object({ + type: ChartTypeSchema, + title: z.string().optional().describe('Chart title'), + description: z.string().optional().describe('Chart description'), + showLegend: z.boolean().optional().default(true).describe('Display legend'), + showDataLabels: z.boolean().optional().default(false).describe('Display data labels on chart'), + colors: z.array(z.string()).optional().describe('Custom color palette'), +}); + +export type ChartConfig = z.infer; diff --git a/packages/spec/src/ui/dashboard.test.ts b/packages/spec/src/ui/dashboard.test.ts index 258cacaec..8dcff0144 100644 --- a/packages/spec/src/ui/dashboard.test.ts +++ b/packages/spec/src/ui/dashboard.test.ts @@ -2,24 +2,24 @@ import { describe, it, expect } from 'vitest'; import { DashboardSchema, DashboardWidgetSchema, - ChartType, Dashboard, type Dashboard as DashboardType, type DashboardWidget, } from './dashboard.zod'; +import { ChartTypeSchema } from './chart.zod'; -describe('ChartType', () => { +describe('ChartTypeSchema', () => { it('should accept all chart types', () => { - const types = ['metric', 'bar', 'line', 'pie', 'funnel', 'table', 'text']; + const types = ['metric', 'bar', 'line', 'pie', 'funnel', 'table', 'bubble', 'gauge', 'heatmap']; types.forEach(type => { - expect(() => ChartType.parse(type)).not.toThrow(); + expect(() => ChartTypeSchema.parse(type)).not.toThrow(); }); }); it('should reject invalid chart types', () => { - expect(() => ChartType.parse('bubble')).toThrow(); - expect(() => ChartType.parse('invalid')).toThrow(); + expect(() => ChartTypeSchema.parse('invalid-chart')).toThrow(); + expect(() => ChartTypeSchema.parse('unknown')).toThrow(); }); }); @@ -148,10 +148,10 @@ describe('DashboardWidgetSchema', () => { expect(() => DashboardWidgetSchema.parse(widget)).not.toThrow(); }); - it('should accept text widget', () => { + it('should accept metric widget for text/markdown content', () => { const widget: DashboardWidget = { title: 'Welcome Message', - type: 'text', + type: 'metric', layout: { x: 0, y: 0, w: 12, h: 2 }, options: { content: '# Welcome to Sales Dashboard\n\nThis dashboard shows...', @@ -427,7 +427,7 @@ describe('DashboardSchema', () => { }, { title: 'Welcome', - type: 'text', + type: 'metric', layout: { x: 0, y: 7, w: 12, h: 1 }, options: { content: '**Last updated:** {NOW()}', diff --git a/packages/spec/src/ui/dashboard.zod.ts b/packages/spec/src/ui/dashboard.zod.ts index 3e1d081f5..b2b980b5d 100644 --- a/packages/spec/src/ui/dashboard.zod.ts +++ b/packages/spec/src/ui/dashboard.zod.ts @@ -1,30 +1,6 @@ import { z } from 'zod'; import { FilterConditionSchema } from '../data/filter.zod'; - -/** - * Chart Type Enum - */ -export const ChartType = z.enum([ - // Analysis - 'metric', // KPI / Big Number - 'bar', // Bar / Column - 'line', // Line / Area - 'pie', // Pie - 'donut', // Donut - 'gauge', // Gauge / Speedometer - 'funnel', // Conversion Funnel - 'radar', // Spider / Radar - 'scatter', // Scatter Plot - 'heatmap', // Heatmap - 'pivot', // Pivot Table (Cross-tab) - - // Content - 'table', // Data Grid - 'list', // Simple List - 'text', // Markdown / HTML - 'image', // Static Image - 'frame', // IFrame / Embed -]); +import { ChartTypeSchema, ChartConfigSchema } from './chart.zod'; /** * Dashboard Widget Schema @@ -35,7 +11,10 @@ export const DashboardWidgetSchema = z.object({ title: z.string().optional().describe('Widget title'), /** Visualization Type */ - type: ChartType.default('metric').describe('Visualization type'), + type: ChartTypeSchema.default('metric').describe('Visualization type'), + + /** Chart Configuration */ + chartConfig: ChartConfigSchema.optional().describe('Chart visualization configuration'), /** Data Source Object */ object: z.string().optional().describe('Data source object name'), diff --git a/packages/spec/src/ui/index.ts b/packages/spec/src/ui/index.ts index ae4257133..32334c233 100644 --- a/packages/spec/src/ui/index.ts +++ b/packages/spec/src/ui/index.ts @@ -5,8 +5,10 @@ * - App, Page, View (Grid/Kanban) * - Dashboard (Widgets), Report * - Action (Triggers) + * - Chart (Unified Visualization Types) */ +export * from './chart.zod'; export * from './app.zod'; export * from './view.zod'; export * from './dashboard.zod'; diff --git a/packages/spec/src/ui/report.test.ts b/packages/spec/src/ui/report.test.ts index f240fe07b..1ff1a6e33 100644 --- a/packages/spec/src/ui/report.test.ts +++ b/packages/spec/src/ui/report.test.ts @@ -155,7 +155,10 @@ describe('ReportChartSchema', () => { }); it('should accept different chart types', () => { - const types: Array = ['bar', 'column', 'line', 'pie', 'donut', 'scatter', 'funnel']; + const types: Array = [ + 'bar', 'column', 'line', 'pie', 'donut', 'scatter', 'funnel', + 'area', 'gauge', 'heatmap', 'waterfall', 'metric' + ]; types.forEach(type => { const chart = ReportChartSchema.parse({ @@ -169,7 +172,7 @@ describe('ReportChartSchema', () => { it('should reject invalid chart type', () => { expect(() => ReportChartSchema.parse({ - type: 'area', + type: 'invalid-chart-type', xAxis: 'x', yAxis: 'y', })).toThrow(); diff --git a/packages/spec/src/ui/report.zod.ts b/packages/spec/src/ui/report.zod.ts index 504f75bdb..cbd6b1ff9 100644 --- a/packages/spec/src/ui/report.zod.ts +++ b/packages/spec/src/ui/report.zod.ts @@ -1,5 +1,6 @@ import { z } from 'zod'; import { FilterConditionSchema } from '../data/filter.zod'; +import { ChartConfigSchema } from './chart.zod'; /** * Report Type Enum @@ -31,14 +32,13 @@ export const ReportGroupingSchema = z.object({ /** * Report Chart Schema - * Embedded visualization configuration. + * Embedded visualization configuration using unified chart taxonomy. */ -export const ReportChartSchema = z.object({ - type: z.enum(['bar', 'column', 'line', 'pie', 'donut', 'scatter', 'funnel']).describe('Chart type'), - title: z.string().optional(), - showLegend: z.boolean().default(true), +export const ReportChartSchema = ChartConfigSchema.extend({ + /** Report-specific chart configuration */ xAxis: z.string().describe('Grouping field for X-Axis'), yAxis: z.string().describe('Summary field for Y-Axis'), + groupBy: z.string().optional().describe('Additional grouping field'), }); /**