inconvo 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/commands/add.js +2 -0
- package/dist/lib/thread-injector.js +86 -0
- package/package.json +1 -1
- package/templates/assistant-ui/src/components/assistant-ui/tools/inconvo-chart.tsx +54 -167
- package/templates/assistant-ui/src/components/assistant-ui/tools/inconvo-chart-colors.ts +0 -15
package/README.md
CHANGED
|
@@ -20,6 +20,8 @@ Options:
|
|
|
20
20
|
|
|
21
21
|
By default the installer writes everything under `src/`, including the tool components and their supporting `src/lib/inconvo/types.ts`. It also installs external dependencies such as `recharts` and `@tanstack/react-table` unless you opt out with `--skip-install`.
|
|
22
22
|
|
|
23
|
+
After the files are copied, the CLI attempts to update `src/components/assistant-ui/thread.tsx` by importing `InconvoTools` and rendering it inside `ThreadPrimitive.Root`. If the file is missing or has been customized heavily, you'll see a warning and can complete the insertion manually.
|
|
24
|
+
|
|
23
25
|
## Development
|
|
24
26
|
|
|
25
27
|
```bash
|
package/dist/commands/add.js
CHANGED
|
@@ -4,6 +4,7 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import { logger } from "../lib/logger.js";
|
|
5
5
|
import { confirmPrompt } from "../lib/prompt.js";
|
|
6
6
|
import { installDependencies } from "../lib/install-dependencies.js";
|
|
7
|
+
import { ensureThreadHasTools } from "../lib/thread-injector.js";
|
|
7
8
|
import { componentPacks, getPack } from "../templates/packs.js";
|
|
8
9
|
export const addCommand = new Command()
|
|
9
10
|
.name("add")
|
|
@@ -70,6 +71,7 @@ export const addCommand = new Command()
|
|
|
70
71
|
if (replaced.size) {
|
|
71
72
|
logger.info(`Overwrote ${replaced.size} file(s).`);
|
|
72
73
|
}
|
|
74
|
+
await ensureThreadHasTools(cwd);
|
|
73
75
|
if (pack.dependencies) {
|
|
74
76
|
const depList = Object.entries(pack.dependencies).map(([name, version]) => `${name}@${version}`);
|
|
75
77
|
if (!depList.length)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { logger } from "./logger.js";
|
|
4
|
+
const TOOLS_IMPORT = `import { InconvoTools } from "./tools/inconvo-tools";`;
|
|
5
|
+
const THREAD_PATH = ["src", "components", "assistant-ui", "thread.tsx"];
|
|
6
|
+
export async function ensureThreadHasTools(projectRoot) {
|
|
7
|
+
const threadPath = path.join(projectRoot, ...THREAD_PATH);
|
|
8
|
+
if (!(await fileExists(threadPath))) {
|
|
9
|
+
logger.warn(`Could not find ${path.relative(projectRoot, threadPath)}. Skipping thread update.`);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
let content = await fs.readFile(threadPath, "utf8");
|
|
13
|
+
let updated = false;
|
|
14
|
+
if (!hasToolsImport(content)) {
|
|
15
|
+
content = injectImport(content);
|
|
16
|
+
updated = true;
|
|
17
|
+
}
|
|
18
|
+
if (!content.includes("<InconvoTools")) {
|
|
19
|
+
const nextContent = injectComponentUsage(content);
|
|
20
|
+
if (nextContent) {
|
|
21
|
+
content = nextContent;
|
|
22
|
+
updated = true;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
logger.warn("Could not locate </ThreadPrimitive.Root> in thread.tsx. Please insert <InconvoTools /> manually.");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (updated) {
|
|
29
|
+
await fs.writeFile(threadPath, content, "utf8");
|
|
30
|
+
logger.success("Updated thread component to render <InconvoTools />.");
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
logger.info("Thread component already renders <InconvoTools />.");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function hasToolsImport(content) {
|
|
37
|
+
const namedImportRegex = /import\s+{[^}]*InconvoTools[^}]*}\s+from\s+["'][^"']+["']/;
|
|
38
|
+
const defaultImportRegex = /import\s+InconvoTools\s+from\s+["'][^"']+["']/;
|
|
39
|
+
return (namedImportRegex.test(content) || defaultImportRegex.test(content));
|
|
40
|
+
}
|
|
41
|
+
function injectImport(content) {
|
|
42
|
+
const lines = content.split("\n");
|
|
43
|
+
let lastImportIndex = -1;
|
|
44
|
+
for (let i = 0; i < lines.length; i++) {
|
|
45
|
+
if (lines[i].startsWith("import ")) {
|
|
46
|
+
lastImportIndex = i;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (lastImportIndex >= 0) {
|
|
50
|
+
lines.splice(lastImportIndex + 1, 0, TOOLS_IMPORT);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
lines.unshift(TOOLS_IMPORT);
|
|
54
|
+
}
|
|
55
|
+
return lines.join("\n");
|
|
56
|
+
}
|
|
57
|
+
function injectComponentUsage(content) {
|
|
58
|
+
const lines = content.split("\n");
|
|
59
|
+
const rootIndex = lines.findIndex((line) => line.includes("<ThreadPrimitive.Root"));
|
|
60
|
+
if (rootIndex === -1) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
let insertIndex = rootIndex + 1;
|
|
64
|
+
for (let i = rootIndex; i < lines.length; i++) {
|
|
65
|
+
if (lines[i].includes(">")) {
|
|
66
|
+
insertIndex = i + 1;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const baseIndent = lines[rootIndex].match(/^\s*/)?.[0] ?? "";
|
|
71
|
+
const componentLine = `${baseIndent} <InconvoTools />`;
|
|
72
|
+
lines.splice(insertIndex, 0, componentLine);
|
|
73
|
+
return lines.join("\n");
|
|
74
|
+
}
|
|
75
|
+
async function fileExists(target) {
|
|
76
|
+
try {
|
|
77
|
+
await fs.stat(target);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error && error.code === "ENOENT") {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
package/package.json
CHANGED
|
@@ -1,188 +1,75 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useMemo,
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
LineChart as RechartsLineChart,
|
|
7
|
-
Line,
|
|
8
|
-
BarChart as RechartsBarChart,
|
|
9
|
-
Bar,
|
|
10
|
-
CartesianGrid,
|
|
11
|
-
Tooltip,
|
|
12
|
-
XAxis,
|
|
13
|
-
YAxis,
|
|
14
|
-
Label,
|
|
15
|
-
Legend,
|
|
16
|
-
} from "recharts";
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { VegaEmbed } from "react-vega";
|
|
5
|
+
import type { VisualizationSpec } from "vega-embed";
|
|
17
6
|
|
|
18
|
-
import type {
|
|
19
|
-
|
|
7
|
+
import type {
|
|
8
|
+
InconvoChartData,
|
|
9
|
+
InconvoChartType,
|
|
10
|
+
InconvoChartSpec,
|
|
11
|
+
} from "~/lib/inconvo/types";
|
|
20
12
|
|
|
21
13
|
interface InconvoChartProps {
|
|
22
|
-
data
|
|
23
|
-
|
|
14
|
+
data?: InconvoChartData;
|
|
15
|
+
spec?: InconvoChartSpec;
|
|
16
|
+
variant?: InconvoChartType;
|
|
24
17
|
xLabel?: string;
|
|
25
18
|
yLabel?: string;
|
|
26
19
|
title?: string;
|
|
27
20
|
}
|
|
28
21
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
axisColor,
|
|
32
|
-
textColor,
|
|
33
|
-
xLabel,
|
|
34
|
-
yLabel,
|
|
35
|
-
labelCount,
|
|
36
|
-
}: {
|
|
37
|
-
children: ReactNode;
|
|
38
|
-
axisColor: string;
|
|
39
|
-
textColor: string;
|
|
40
|
-
xLabel?: string;
|
|
41
|
-
yLabel?: string;
|
|
42
|
-
labelCount: number;
|
|
43
|
-
}) => (
|
|
44
|
-
<>
|
|
45
|
-
<CartesianGrid strokeDasharray="3 3" stroke="var(--border)" />
|
|
46
|
-
<XAxis
|
|
47
|
-
dataKey="name"
|
|
48
|
-
stroke={axisColor}
|
|
49
|
-
tick={{ fill: axisColor, fontSize: 12 }}
|
|
50
|
-
angle={-30}
|
|
51
|
-
textAnchor="end"
|
|
52
|
-
interval={labelCount > 12 ? "preserveStartEnd" : 0}
|
|
53
|
-
>
|
|
54
|
-
{xLabel ? (
|
|
55
|
-
<Label
|
|
56
|
-
position="bottom"
|
|
57
|
-
offset={24}
|
|
58
|
-
style={{
|
|
59
|
-
fill: axisColor,
|
|
60
|
-
textAnchor: "middle",
|
|
61
|
-
}}
|
|
62
|
-
value={xLabel}
|
|
63
|
-
/>
|
|
64
|
-
) : null}
|
|
65
|
-
</XAxis>
|
|
66
|
-
<YAxis width={80} stroke={axisColor} tick={{ fill: axisColor, fontSize: 12 }}>
|
|
67
|
-
{yLabel ? (
|
|
68
|
-
<Label
|
|
69
|
-
angle={-90}
|
|
70
|
-
position="insideLeft"
|
|
71
|
-
style={{
|
|
72
|
-
fill: axisColor,
|
|
73
|
-
textAnchor: "middle",
|
|
74
|
-
}}
|
|
75
|
-
value={yLabel}
|
|
76
|
-
/>
|
|
77
|
-
) : null}
|
|
78
|
-
</YAxis>
|
|
79
|
-
<Tooltip
|
|
80
|
-
contentStyle={{
|
|
81
|
-
backgroundColor: "var(--card)",
|
|
82
|
-
border: "1px solid var(--border)",
|
|
83
|
-
borderRadius: 8,
|
|
84
|
-
color: textColor,
|
|
85
|
-
}}
|
|
86
|
-
labelStyle={{
|
|
87
|
-
color: textColor,
|
|
88
|
-
fontWeight: 600,
|
|
89
|
-
}}
|
|
90
|
-
/>
|
|
91
|
-
<Legend
|
|
92
|
-
verticalAlign="top"
|
|
93
|
-
align="right"
|
|
94
|
-
wrapperStyle={{
|
|
95
|
-
color: axisColor,
|
|
96
|
-
paddingBottom: "4px",
|
|
97
|
-
}}
|
|
98
|
-
/>
|
|
99
|
-
{children}
|
|
100
|
-
</>
|
|
101
|
-
);
|
|
22
|
+
export const InconvoChart = ({ spec: providedSpec }: InconvoChartProps) => {
|
|
23
|
+
const [error, setError] = useState<string | null>(null);
|
|
102
24
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
};
|
|
114
|
-
data.datasets.forEach((dataset) => {
|
|
115
|
-
row[dataset.name] = dataset.values[index] ?? 0;
|
|
116
|
-
});
|
|
117
|
-
return row;
|
|
118
|
-
});
|
|
119
|
-
}, [data]);
|
|
25
|
+
const resolvedSpec = useMemo<VisualizationSpec | null>(() => {
|
|
26
|
+
if (providedSpec) {
|
|
27
|
+
return {
|
|
28
|
+
$schema: "https://vega.github.io/schema/vega-lite/v5.json",
|
|
29
|
+
background: "transparent",
|
|
30
|
+
autosize: { type: "fit", contains: "padding" },
|
|
31
|
+
width: "container",
|
|
32
|
+
...providedSpec,
|
|
33
|
+
} as VisualizationSpec;
|
|
34
|
+
}
|
|
120
35
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
[data.datasets.length],
|
|
124
|
-
);
|
|
36
|
+
return null;
|
|
37
|
+
}, [providedSpec]);
|
|
125
38
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
setError(null);
|
|
41
|
+
}, [resolvedSpec]);
|
|
129
42
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
key={dataset.name}
|
|
136
|
-
type="monotone"
|
|
137
|
-
dataKey={dataset.name}
|
|
138
|
-
stroke={stroke}
|
|
139
|
-
strokeWidth={2}
|
|
140
|
-
dot={{ r: 3, strokeWidth: 2, stroke, fill: "var(--card)" }}
|
|
141
|
-
activeDot={{ r: 5, strokeWidth: 2, stroke, fill: stroke }}
|
|
142
|
-
/>
|
|
143
|
-
);
|
|
144
|
-
});
|
|
43
|
+
const handleError = (err: unknown) => {
|
|
44
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
45
|
+
console.error("Vega-Lite render error:", err);
|
|
46
|
+
setError(message);
|
|
47
|
+
};
|
|
145
48
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
49
|
+
if (!resolvedSpec) {
|
|
50
|
+
return (
|
|
51
|
+
<div className="text-sm text-muted-foreground">
|
|
52
|
+
No chart data provided.
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (error) {
|
|
58
|
+
return (
|
|
59
|
+
<div className="text-sm text-red-500">
|
|
60
|
+
Failed to render chart: {error}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
156
64
|
|
|
157
65
|
return (
|
|
158
66
|
<div className="flex w-full flex-col gap-4 text-foreground">
|
|
159
|
-
<
|
|
160
|
-
{
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
xLabel={xLabel}
|
|
166
|
-
yLabel={yLabel}
|
|
167
|
-
labelCount={data.labels.length}
|
|
168
|
-
>
|
|
169
|
-
{renderLines()}
|
|
170
|
-
</ChartScaffold>
|
|
171
|
-
</RechartsLineChart>
|
|
172
|
-
) : (
|
|
173
|
-
<RechartsBarChart data={chartData} margin={margins}>
|
|
174
|
-
<ChartScaffold
|
|
175
|
-
axisColor={axisColor}
|
|
176
|
-
textColor={textColor}
|
|
177
|
-
xLabel={xLabel}
|
|
178
|
-
yLabel={yLabel}
|
|
179
|
-
labelCount={data.labels.length}
|
|
180
|
-
>
|
|
181
|
-
{renderBars()}
|
|
182
|
-
</ChartScaffold>
|
|
183
|
-
</RechartsBarChart>
|
|
184
|
-
)}
|
|
185
|
-
</ResponsiveContainer>
|
|
67
|
+
<VegaEmbed
|
|
68
|
+
spec={resolvedSpec}
|
|
69
|
+
options={{ actions: false }}
|
|
70
|
+
onError={handleError}
|
|
71
|
+
style={{ width: "100%" }}
|
|
72
|
+
/>
|
|
186
73
|
</div>
|
|
187
74
|
);
|
|
188
75
|
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
const chartColorVars = [
|
|
2
|
-
"var(--chart-1)",
|
|
3
|
-
"var(--chart-2)",
|
|
4
|
-
"var(--chart-3)",
|
|
5
|
-
"var(--chart-4)",
|
|
6
|
-
"var(--chart-5)",
|
|
7
|
-
];
|
|
8
|
-
|
|
9
|
-
export const buildChartPalette = (seriesCount: number) => {
|
|
10
|
-
if (seriesCount <= 0) return [];
|
|
11
|
-
return Array.from({ length: seriesCount }, (_, index) => {
|
|
12
|
-
const colorIndex = index % chartColorVars.length;
|
|
13
|
-
return chartColorVars[colorIndex] ?? "var(--chart-series-primary)";
|
|
14
|
-
});
|
|
15
|
-
};
|