mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-01 15:32:11 +08:00
Chart skill
This commit is contained in:
@@ -7,9 +7,7 @@ description: Use when the user asks to visualize data with charts, graphs, or pl
|
||||
|
||||
The `chart` tool is ALWAYS available in this environment. When the user asks to visualize data (charts, graphs, plots), you MUST call the `chart` tool. Never output the config as text, never say the tool is unavailable, never suggest external renderers. Always use the tool call — it is the only correct response for data visualization requests. Do NOT repeat or echo the config JSON in your text response.
|
||||
|
||||
Use the `chart` tool when:
|
||||
- The user asks for a chart, graph, or plot of data (bar, line, scatter, pie, time series, etc.)
|
||||
- Presenting numerical data that would be clearer visually than as a table or prose (trends, comparisons, distributions)
|
||||
Use the `chart` tool only when the user explicitly asks for a chart, graph, or plot. Only use these supported Chart.js v4 types: `area`, `bar`, `bubble`, `pie`, `doughnut`, `line`, `mixed`, `polarArea`, `radar`, `scatter`. Do not use it for any other type.
|
||||
|
||||
Use mermaid fenced code blocks (` ```mermaid `) when:
|
||||
- The user asks for a diagram, flowchart, sequence diagram, ER diagram, or architecture diagram
|
||||
@@ -37,6 +35,17 @@ Bar chart:
|
||||
}
|
||||
```
|
||||
|
||||
Area chart (line with fill):
|
||||
```json
|
||||
{
|
||||
"type": "line",
|
||||
"data": {
|
||||
"labels": ["Jan", "Feb", "Mar", "Apr"],
|
||||
"datasets": [{ "label": "Value", "data": [10, 28, 19, 45], "fill": true }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Line chart:
|
||||
```json
|
||||
{
|
||||
@@ -83,4 +92,64 @@ Pie chart:
|
||||
}
|
||||
```
|
||||
|
||||
Doughnut chart:
|
||||
```json
|
||||
{
|
||||
"type": "doughnut",
|
||||
"data": {
|
||||
"labels": ["A", "B", "C"],
|
||||
"datasets": [{ "data": [30, 50, 20] }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Radar chart:
|
||||
```json
|
||||
{
|
||||
"type": "radar",
|
||||
"data": {
|
||||
"labels": ["Speed", "Power", "Agility", "Stamina"],
|
||||
"datasets": [{ "label": "Player", "data": [80, 60, 90, 70] }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Bubble chart:
|
||||
```json
|
||||
{
|
||||
"type": "bubble",
|
||||
"data": {
|
||||
"datasets": [{
|
||||
"label": "Group A",
|
||||
"data": [{ "x": 10, "y": 20, "r": 8 }, { "x": 15, "y": 10, "r": 5 }]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Polar area chart:
|
||||
```json
|
||||
{
|
||||
"type": "polarArea",
|
||||
"data": {
|
||||
"labels": ["A", "B", "C", "D"],
|
||||
"datasets": [{ "data": [11, 16, 7, 14] }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Mixed chart (bar + line):
|
||||
```json
|
||||
{
|
||||
"type": "bar",
|
||||
"data": {
|
||||
"labels": ["Jan", "Feb", "Mar"],
|
||||
"datasets": [
|
||||
{ "type": "bar", "label": "Revenue", "data": [100, 120, 90] },
|
||||
{ "type": "line", "label": "Trend", "data": [95, 115, 100] }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You may customize colors by setting `backgroundColor` and `borderColor` arrays on datasets. The renderer handles sizing — do not set width or height.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
import { createSignal, onCleanup, onMount } from "solid-js"
|
||||
import { createEffect, createSignal, onCleanup } from "solid-js"
|
||||
import { BasicTool } from "./basic-tool"
|
||||
import type { ToolProps } from "./message-part"
|
||||
import { busy } from "./tool-utils"
|
||||
|
||||
function getThemeColors() {
|
||||
const style = getComputedStyle(document.documentElement)
|
||||
@@ -21,7 +22,7 @@ type ChartConfig = {
|
||||
labels?: string[]
|
||||
datasets: {
|
||||
label?: string
|
||||
data: number[] | { x: number | string; y: number }[]
|
||||
data: number[] | { x: number | string; y: number; r?: number }[]
|
||||
backgroundColor?: string | string[]
|
||||
borderColor?: string | string[]
|
||||
[key: string]: unknown
|
||||
@@ -31,55 +32,57 @@ type ChartConfig = {
|
||||
}
|
||||
|
||||
export function ChartTool(props: ToolProps) {
|
||||
let canvas: HTMLCanvasElement | undefined
|
||||
const [canvas, setCanvas] = createSignal<HTMLCanvasElement>()
|
||||
const [error, setError] = createSignal<string>()
|
||||
|
||||
onMount(async () => {
|
||||
if (!canvas) return
|
||||
let rendered = false
|
||||
|
||||
createEffect(() => {
|
||||
const el = canvas()
|
||||
const raw = props.output
|
||||
if (!el || !raw || busy(props.status) || rendered) return
|
||||
|
||||
let config: ChartConfig
|
||||
try {
|
||||
config = JSON.parse(props.output ?? "{}")
|
||||
const parsed = JSON.parse(raw)
|
||||
if (!parsed || typeof parsed !== "object" || !parsed.type || !parsed.data) {
|
||||
setError("Invalid chart config — must include type and data")
|
||||
return
|
||||
}
|
||||
config = parsed
|
||||
} catch {
|
||||
setError("Could not parse chart config")
|
||||
// not valid JSON (e.g. mermaid or error string) — skip silently
|
||||
return
|
||||
}
|
||||
|
||||
if (!config || typeof config !== "object" || !config.type || !config.data) {
|
||||
setError("Invalid chart config — must include type and data")
|
||||
return
|
||||
}
|
||||
rendered = true
|
||||
|
||||
try {
|
||||
const { Chart, registerables } = await import("chart.js")
|
||||
Chart.register(...registerables)
|
||||
const colors = getThemeColors()
|
||||
const isPolar = config.type === "pie" || config.type === "doughnut" || config.type === "polarArea"
|
||||
|
||||
const colors = getThemeColors()
|
||||
|
||||
const isPolar = config.type === "pie" || config.type === "doughnut" || config.type === "polarArea"
|
||||
|
||||
// apply kilo series colors to datasets that don't have their own colors
|
||||
config.data.datasets = config.data.datasets.map((dataset, i) => {
|
||||
if (dataset.backgroundColor) return dataset
|
||||
// pie/doughnut need one color per slice
|
||||
if (isPolar) {
|
||||
const data = dataset.data as unknown[]
|
||||
return {
|
||||
...dataset,
|
||||
backgroundColor: data.map((_, j) => colors.series[j % colors.series.length]),
|
||||
borderColor: data.map((_, j) => colors.series[j % colors.series.length]),
|
||||
}
|
||||
}
|
||||
const datasets = config.data.datasets.map((dataset, i) => {
|
||||
if (dataset.backgroundColor) return dataset
|
||||
if (isPolar) {
|
||||
const data = dataset.data as unknown[]
|
||||
return {
|
||||
backgroundColor: colors.series[i % colors.series.length],
|
||||
borderColor: colors.series[i % colors.series.length],
|
||||
...dataset,
|
||||
backgroundColor: data.map((_, j) => colors.series[j % colors.series.length]),
|
||||
borderColor: data.map((_, j) => colors.series[j % colors.series.length]),
|
||||
}
|
||||
})
|
||||
}
|
||||
return {
|
||||
backgroundColor: colors.series[i % colors.series.length],
|
||||
borderColor: colors.series[i % colors.series.length],
|
||||
...dataset,
|
||||
}
|
||||
})
|
||||
|
||||
const chart = new Chart(canvas!, {
|
||||
import("chart.js").then(({ Chart, registerables }) => {
|
||||
if (!el.isConnected) return
|
||||
Chart.register(...registerables)
|
||||
const chart = new Chart(el, {
|
||||
type: config.type as any,
|
||||
data: config.data as any,
|
||||
data: { ...config.data, datasets } as any,
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
@@ -103,11 +106,10 @@ export function ChartTool(props: ToolProps) {
|
||||
...config.options,
|
||||
},
|
||||
})
|
||||
|
||||
onCleanup(() => chart.destroy())
|
||||
} catch (e) {
|
||||
}).catch((e: unknown) => {
|
||||
setError(e instanceof Error ? e.message : "Failed to render chart")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
@@ -122,7 +124,7 @@ export function ChartTool(props: ToolProps) {
|
||||
defaultOpen={props.defaultOpen ?? true}
|
||||
>
|
||||
<div data-component="chart-container">
|
||||
{error() ? <div data-slot="chart-error">{error()}</div> : <canvas ref={canvas} data-slot="chart-render" />}
|
||||
{error() ? <div data-slot="chart-error">{error()}</div> : <canvas ref={setCanvas} data-slot="chart-render" />}
|
||||
</div>
|
||||
</BasicTool>
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ const Parameters = Schema.Struct({
|
||||
description: "Optional subtitle shown below the title",
|
||||
}),
|
||||
spec: Schema.String.annotate({
|
||||
description: "A valid Chart.js v4 JSON configuration string describing the chart to render",
|
||||
description: "A Chart.js v4 configuration serialized as a JSON string. Pass the JSON object as a plain string value — do not nest objects, do not escape quotes manually. Example: '{\"type\":\"bar\",\"data\":{\"labels\":[\"A\",\"B\"],\"datasets\":[{\"data\":[1,2]}]}}'",
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -25,7 +25,7 @@ export const ChartTool = Tool.define(
|
||||
Effect.gen(function* () {
|
||||
return {
|
||||
description:
|
||||
"Render a data visualization chart using a Chart.js v4 config. Use this when the user asks to visualize data as a chart, graph, or plot (bar, line, scatter, pie, etc.). Do NOT use this for diagrams, flowcharts, sequence diagrams, or any mermaid content — write those as mermaid fenced code blocks in your text response instead.",
|
||||
"Render a data visualization chart using a Chart.js v4 config. Use this when the user explicitly asks for a chart, graph, or plot. Supported types: area, bar, bubble, pie, doughnut, line, mixed, polarArea, radar, scatter. Do NOT use this for diagrams, flowcharts, sequence diagrams, or any mermaid content — write those as mermaid fenced code blocks in your text response instead. After calling this tool, do NOT output the JSON spec or raw data in your text response — the chart is the response.",
|
||||
parameters: Parameters,
|
||||
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) =>
|
||||
Effect.gen(function* () {
|
||||
@@ -40,11 +40,19 @@ export const ChartTool = Tool.define(
|
||||
} catch {
|
||||
return {
|
||||
title: params.title,
|
||||
output: `Invalid chart spec: could not parse JSON. Please provide a valid Chart.js v4 JSON string.`,
|
||||
output: `Invalid chart spec: could not parse JSON. If you are trying to render a diagram or mermaid chart, do NOT use the chart tool — write a mermaid fenced code block in your text response instead.`,
|
||||
metadata: { title: params.title, description: params.description, error: "invalid-json" } as Meta,
|
||||
}
|
||||
}
|
||||
|
||||
if (!spec || typeof spec !== "object" || !("type" in spec) || !("data" in spec)) {
|
||||
return {
|
||||
title: params.title,
|
||||
output: `Invalid chart spec: must be a Chart.js v4 config with "type" and "data" fields. If you are trying to render a mermaid diagram, do NOT use the chart tool — write a mermaid fenced code block in your text response instead.`,
|
||||
metadata: { title: params.title, description: params.description, error: "invalid-spec" } as Meta,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: params.title,
|
||||
output: JSON.stringify(spec),
|
||||
|
||||
Reference in New Issue
Block a user