@agent-native/core 0.144.0 → 0.144.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +1 -1
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +36 -14
- package/corpus/templates/clips/app/components/player/player-controls.tsx +0 -3
- package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -33
- package/corpus/templates/clips/app/components/player/video-player.tsx +58 -11
- package/corpus/templates/clips/changelog/2026-08-06-trimmed-clips-play-on-a-clean-timeline-without-cut-markers.md +6 -0
- package/corpus/templates/dispatch/AGENTS.md +8 -1
- package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -0
- package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +30 -15
- package/corpus/templates/dispatch/app/i18n-data.ts +131 -0
- package/corpus/templates/dispatch/app/routes/admin._index.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.agents.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.approvals.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.apps.$appId.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.apps.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.audit.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.automations.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.destinations.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.dreams.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.identities.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.integrations.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.messaging.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.metrics.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.new-app.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.operations.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.thread-debug.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.transactional-email.$appId.$id.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.transactional-email.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.vault.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.workspace.tsx +1 -0
- package/corpus/templates/dispatch/changelog/2026-08-06-dispatch-keeps-everyday-navigation-focused-while-management-.md +6 -0
- package/corpus/templates/slides/AGENTS.md +10 -12
- package/corpus/templates/slides/actions/export-pptx.ts +68 -0
- package/corpus/templates/slides/actions/get-deck.ts +12 -0
- package/corpus/templates/slides/actions/import-file.ts +130 -55
- package/corpus/templates/slides/actions/import-pptx.ts +108 -8
- package/corpus/templates/slides/actions/patch-deck.ts +51 -0
- package/corpus/templates/slides/actions/update-slide.ts +29 -3
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +11 -0
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +72 -11
- package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +89 -0
- package/corpus/templates/slides/app/pages/Index.tsx +85 -18
- package/corpus/templates/slides/changelog/2026-08-06-uploaded-decks-keep-their-slides-and-visuals-when-you-improv.md +6 -0
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +13 -2
- package/corpus/templates/slides/server/lib/chat-attachments.ts +2 -2
- package/corpus/templates/slides/server/lib/source-import.ts +167 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +8 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/jobs/scheduler.js +79 -23
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/realtime-token.d.ts +1 -1
- package/docs/content/creating-templates.mdx +1 -1
- package/docs/content/getting-started-actions.mdx +38 -189
- package/docs/content/getting-started-database.mdx +80 -164
- package/docs/content/getting-started-pages.mdx +44 -155
- package/docs/content/getting-started.mdx +26 -106
- package/package.json +1 -1
|
@@ -1,84 +1,44 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Add an Action"
|
|
3
|
-
description: "Define your first action and render its result as a
|
|
3
|
+
description: "Define your first action and render its result as a chart directly inside the chat."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Add an Action
|
|
7
7
|
|
|
8
|
-
This is part two of the Getting Started series. In [Getting Started](/docs/getting-started)
|
|
8
|
+
This is part two of the Getting Started series. In [Getting Started](/docs/getting-started)
|
|
9
|
+
you created a Chat app. Here you'll define a typed text analyzer and render its
|
|
10
|
+
result in the chat.
|
|
9
11
|
|
|
10
12
|
## Add an action {#add-an-action}
|
|
11
13
|
|
|
12
|
-
An action is a typed operation that both
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
schedule. You define them once and call them from anywhere.
|
|
14
|
+
An action is a typed operation that both the agent and your UI can call. Actions
|
|
15
|
+
live in the `actions/` directory and are the shared operation surface for chat,
|
|
16
|
+
React components, the CLI, and other callers.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
The Chat template includes a `hello` action at `actions/hello.ts`:
|
|
20
|
-
|
|
21
|
-
```ts filename="actions/hello.ts"
|
|
22
|
-
import { defineAction } from "@agent-native/core/action";
|
|
23
|
-
import { z } from "zod";
|
|
24
|
-
|
|
25
|
-
export default defineAction({
|
|
26
|
-
description: "Return a friendly greeting.",
|
|
27
|
-
schema: z.object({
|
|
28
|
-
name: z.string().default("world").describe("Name to greet"),
|
|
29
|
-
}),
|
|
30
|
-
http: { method: "GET" },
|
|
31
|
-
run: async ({ name }) => {
|
|
32
|
-
return { message: `Hello, ${name}!` };
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Run it from the terminal (inside your `my-app/` directory):
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
pnpm action hello --name Alice
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Or open your app at `http://localhost:8080` and ask the agent in the chat there:
|
|
44
|
-
|
|
45
|
-
> Use the hello action with the name Alice.
|
|
46
|
-
|
|
47
|
-
### Add your own action
|
|
48
|
-
|
|
49
|
-
Replace the starter action with the first real operation in your domain. This example
|
|
50
|
-
counts words, sentences, and paragraphs in any text you pass it. It computes
|
|
51
|
-
everything locally, so there's nothing to configure and no external service to connect.
|
|
52
|
-
|
|
53
|
-
Create a new file called `analyze-text.ts` in your `actions/` directory:
|
|
18
|
+
Create `actions/analyze-text.ts`:
|
|
54
19
|
|
|
55
20
|
```ts filename="actions/analyze-text.ts"
|
|
56
21
|
import { defineAction } from "@agent-native/core/action";
|
|
22
|
+
import { ACTION_CHAT_UI_DATA_CHART_RENDERER } from "@agent-native/core/action-ui";
|
|
23
|
+
import {
|
|
24
|
+
createDataChartWidgetResult,
|
|
25
|
+
dataChartWidgetResultSchema,
|
|
26
|
+
} from "@agent-native/core/data-widgets";
|
|
57
27
|
import { z } from "zod";
|
|
58
28
|
|
|
59
|
-
const textStatsSchema = z.object({
|
|
60
|
-
title: z.string(),
|
|
61
|
-
points: z.array(z.object({ label: z.string(), value: z.number() })),
|
|
62
|
-
});
|
|
63
|
-
|
|
64
29
|
export default defineAction({
|
|
65
30
|
description: "Count words, sentences, and paragraphs in a block of text.",
|
|
66
31
|
schema: z.object({
|
|
67
|
-
text: z
|
|
68
|
-
.string()
|
|
69
|
-
.default(
|
|
70
|
-
"The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.",
|
|
71
|
-
),
|
|
32
|
+
text: z.string().default("The quick brown fox jumps over the lazy dog."),
|
|
72
33
|
}),
|
|
73
|
-
outputSchema:
|
|
34
|
+
outputSchema: dataChartWidgetResultSchema,
|
|
74
35
|
chatUI: {
|
|
75
|
-
renderer:
|
|
36
|
+
renderer: ACTION_CHAT_UI_DATA_CHART_RENDERER,
|
|
76
37
|
title: "Text stats",
|
|
77
38
|
},
|
|
78
39
|
readOnly: true,
|
|
79
|
-
run: async ({ text }) =>
|
|
80
|
-
|
|
81
|
-
points: [
|
|
40
|
+
run: async ({ text }) => {
|
|
41
|
+
const points = [
|
|
82
42
|
{ label: "Characters", value: text.length },
|
|
83
43
|
{ label: "Words", value: text.split(/\s+/).filter(Boolean).length },
|
|
84
44
|
{
|
|
@@ -89,147 +49,36 @@ export default defineAction({
|
|
|
89
49
|
label: "Paragraphs",
|
|
90
50
|
value: text.split(/\n\n+/).filter(Boolean).length,
|
|
91
51
|
},
|
|
92
|
-
]
|
|
93
|
-
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
return createDataChartWidgetResult({
|
|
55
|
+
title: "Text statistics",
|
|
56
|
+
chartSeries: {
|
|
57
|
+
type: "bar",
|
|
58
|
+
xKey: "label",
|
|
59
|
+
series: [{ key: "value", label: "Count" }],
|
|
60
|
+
data: points,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
},
|
|
94
64
|
});
|
|
95
65
|
```
|
|
96
66
|
|
|
97
|
-
|
|
67
|
+
Run it from the terminal:
|
|
98
68
|
|
|
99
69
|
```bash
|
|
100
70
|
pnpm action analyze-text --text "Hello world. How are you today?"
|
|
101
71
|
```
|
|
102
72
|
|
|
103
|
-
Or
|
|
73
|
+
Or ask the agent in chat:
|
|
104
74
|
|
|
105
75
|
> Run the analyze-text action on "Hello world. How are you today?"
|
|
106
76
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
scheduled jobs, and webhooks.
|
|
111
|
-
|
|
112
|
-
TIP: Any time you want the agent to call a specific action without ambiguity, phrasing it as "Run the `<action-name>` action" is most reliable. Natural-language prompts work well once the agent has enough context about your app's domain. For a brand-new app with no data or context yet, explicit is safer.
|
|
113
|
-
|
|
114
|
-
## Render the result inline {#render-inline}
|
|
115
|
-
|
|
116
|
-
When the agent runs `analyze-text`, it returns structured data: a title and an
|
|
117
|
-
array of counts. By default the agent will describe that data in prose: "The
|
|
118
|
-
text has 9 words, 2 sentences..." and so on. That works, but you can
|
|
119
|
-
also render the result as a real UI component (a bar chart, a table, a card)
|
|
120
|
-
directly inside the chat transcript, right where the agent responded.
|
|
121
|
-
|
|
122
|
-
This is what `chatUI.renderer` in the action does. It's a label that says "when
|
|
123
|
-
this action's result appears in chat, hand it to this React component instead of
|
|
124
|
-
summarizing it in text." The component receives the validated action output as
|
|
125
|
-
props and renders whatever you want.
|
|
126
|
-
|
|
127
|
-
In the next step, you'll create `app/chat-renderers.tsx`, but first, add one import line
|
|
128
|
-
to `app/root.tsx` so it runs on startup:
|
|
129
|
-
|
|
130
|
-
```ts filename="app/root.tsx"
|
|
131
|
-
import "./chat-renderers";
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
Add it alongside your other imports at the top of the file. That's the only
|
|
135
|
-
change to `root.tsx`. The import just ensures the file runs and registers the
|
|
136
|
-
renderer. Now create the renderer file:
|
|
137
|
-
|
|
138
|
-
```tsx filename="app/chat-renderers.tsx"
|
|
139
|
-
import {
|
|
140
|
-
registerActionChatRenderer,
|
|
141
|
-
type ToolRendererProps,
|
|
142
|
-
} from "@agent-native/core/client/chat";
|
|
143
|
-
|
|
144
|
-
type TextStatsResult = {
|
|
145
|
-
title: string;
|
|
146
|
-
points: Array<{ label: string; value: number }>;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const MAX_BAR_PX = 80;
|
|
150
|
-
|
|
151
|
-
function TextStatsChart({ context }: ToolRendererProps) {
|
|
152
|
-
const result = context.resultJson as TextStatsResult;
|
|
153
|
-
const max = Math.max(...result.points.map((point) => point.value), 1);
|
|
154
|
-
return (
|
|
155
|
-
<section className="rounded-lg border bg-card p-4">
|
|
156
|
-
<h3 className="text-sm font-medium">{result.title}</h3>
|
|
157
|
-
<div className="mt-4 flex items-end gap-2">
|
|
158
|
-
{result.points.map((point) => (
|
|
159
|
-
<div
|
|
160
|
-
key={point.label}
|
|
161
|
-
className="flex flex-1 flex-col items-center gap-2"
|
|
162
|
-
>
|
|
163
|
-
<div
|
|
164
|
-
className="w-full rounded-t bg-blue-500"
|
|
165
|
-
style={{
|
|
166
|
-
height: `${Math.max(Math.round((point.value / max) * MAX_BAR_PX), 2)}px`,
|
|
167
|
-
}}
|
|
168
|
-
/>
|
|
169
|
-
<span className="text-xs text-muted-foreground">{point.label}</span>
|
|
170
|
-
</div>
|
|
171
|
-
))}
|
|
172
|
-
</div>
|
|
173
|
-
</section>
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
registerActionChatRenderer({
|
|
178
|
-
id: "text.stats-chart",
|
|
179
|
-
renderer: "text.stats-chart",
|
|
180
|
-
Component: TextStatsChart,
|
|
181
|
-
});
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
Once the renderer is registered, the agent's response looks like this. Instead
|
|
185
|
-
of a paragraph of text, your React component renders directly inside the chat
|
|
186
|
-
transcript:
|
|
187
|
-
|
|
188
|
-
<WireframeBlock id="doc-block-inline-result-wireframe">
|
|
189
|
-
<Screen
|
|
190
|
-
surface="desktop"
|
|
191
|
-
html={
|
|
192
|
-
"<div style='min-height:340px;box-sizing:border-box;padding:24px;display:flex;justify-content:center;align-items:center;background:var(--wf-bg)'><div style='width:min(640px,100%);display:flex;flex-direction:column;gap:14px'><div class='wf-card' data-rough style='align-self:flex-end;max-width:70%;padding:12px 14px'><strong>User</strong><p style='margin:6px 0 0'>Run the analyze-text action on \"Hello world. How are you today?\"</p></div><div class='wf-card' data-rough style='align-self:flex-start;width:min(480px,100%);padding:14px'><strong>Agent</strong><p class='wf-muted' style='margin:6px 0 12px'>Rendered with text.stats-chart.</p><section class='wf-card' data-rough style='padding:14px'><h3 style='margin:0 0 12px;font-size:14px'>Text statistics</h3><div data-rough='line:bottom' style='height:104px;display:flex;align-items:end;gap:8px;border-bottom:1.4px solid var(--wf-line);padding-bottom:4px'><div style='flex:1;display:flex;flex-direction:column;align-items:center;gap:6px'><div data-rough style='height:80px;width:100%;background:color-mix(in srgb, var(--wf-accent) 36%, transparent);border:1.4px solid var(--wf-accent);border-radius:8px 8px 3px 3px'></div><span class='wf-muted'>Characters</span></div><div style='flex:1;display:flex;flex-direction:column;align-items:center;gap:6px'><div data-rough style='height:18px;width:100%;background:color-mix(in srgb, var(--wf-accent) 30%, transparent);border:1.4px solid var(--wf-accent);border-radius:8px 8px 3px 3px'></div><span class='wf-muted'>Words</span></div><div style='flex:1;display:flex;flex-direction:column;align-items:center;gap:6px'><div data-rough style='height:2px;width:100%;background:color-mix(in srgb, var(--wf-accent) 24%, transparent);border:1.4px solid var(--wf-accent);border-radius:8px 8px 3px 3px'></div><span class='wf-muted'>Sentences</span></div><div style='flex:1;display:flex;flex-direction:column;align-items:center;gap:6px'><div data-rough style='height:2px;width:100%;background:color-mix(in srgb, var(--wf-accent) 24%, transparent);border:1.4px solid var(--wf-accent);border-radius:8px 8px 3px 3px'></div><span class='wf-muted'>Paragraphs</span></div></div></section></div></div></div>"
|
|
193
|
-
}
|
|
194
|
-
/>
|
|
195
|
-
</WireframeBlock>
|
|
196
|
-
|
|
197
|
-
Use this step when the result belongs where the agent is speaking:
|
|
198
|
-
|
|
199
|
-
- setup summaries
|
|
200
|
-
- short reports
|
|
201
|
-
- approvals
|
|
202
|
-
- tables or charts small enough to inspect inline
|
|
203
|
-
- links into durable app views
|
|
204
|
-
|
|
205
|
-
For reusable generic outputs, the framework also ships built-in
|
|
206
|
-
`data-chart` and `data-table` renderers, plus `data-insights` for combined
|
|
207
|
-
summary/chart/table cards. See [Native Chat UI](/docs/native-chat-ui). For
|
|
208
|
-
temporary controls the agent creates at runtime, see
|
|
209
|
-
[Generative UI](/docs/generative-ui).
|
|
210
|
-
|
|
211
|
-
## What's next {#next}
|
|
212
|
-
|
|
213
|
-
<Cards>
|
|
214
|
-
|
|
215
|
-
### [Persist Data in SQL](/docs/getting-started-database)
|
|
216
|
-
|
|
217
|
-
Save action results to a database so the agent can reference them across
|
|
218
|
-
conversations. Next in the series.
|
|
219
|
-
|
|
220
|
-
### [Actions](/docs/actions)
|
|
221
|
-
|
|
222
|
-
Schemas, auth, approvals, hooks, and transport — the full reference for what
|
|
223
|
-
actions can do.
|
|
224
|
-
|
|
225
|
-
### [Native Chat UI](/docs/native-chat-ui)
|
|
226
|
-
|
|
227
|
-
Built-in renderers for tables, charts, and typed cards — beyond the custom
|
|
228
|
-
renderer you just built.
|
|
229
|
-
|
|
230
|
-
### [Key Concepts](/docs/key-concepts)
|
|
77
|
+
The built-in `data-chart` renderer turns the structured result into a chart in
|
|
78
|
+
the transcript. There is no renderer file or `app/root.tsx` import to add. For
|
|
79
|
+
custom native components, see [Native Chat UI](/docs/native-chat-ui).
|
|
231
80
|
|
|
232
|
-
|
|
233
|
-
awareness.
|
|
81
|
+
## Continue
|
|
234
82
|
|
|
235
|
-
|
|
83
|
+
Continue with [Persist Data in SQL](/docs/getting-started-database) to save
|
|
84
|
+
analysis results for later.
|
|
@@ -5,25 +5,20 @@ description: "Save action results to a SQL database so the agent can reference t
|
|
|
5
5
|
|
|
6
6
|
# Persist Data in SQL
|
|
7
7
|
|
|
8
|
-
This is part three of the Getting Started series. In [Add an Action](/docs/getting-started-actions)
|
|
8
|
+
This is part three of the Getting Started series. In [Add an Action](/docs/getting-started-actions)
|
|
9
|
+
you defined `analyze-text`. Here you'll add a table and two actions so the
|
|
10
|
+
results persist.
|
|
9
11
|
|
|
10
|
-
##
|
|
12
|
+
## Add a table {#persist-data}
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
reference later, and no way to build a page around the data. Persisting to SQL
|
|
15
|
-
fixes that: the agent writes results to a table, and both the agent and your UI
|
|
16
|
-
can read them back at any time.
|
|
14
|
+
The Chat template uses a local SQLite file by default, so no database server is
|
|
15
|
+
needed for this tutorial. Add these three server files:
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
1. `server/plugins/db.ts` creates the table with an additive migration.
|
|
18
|
+
2. `server/db/schema.ts` describes the table for typed queries.
|
|
19
|
+
3. `server/db/index.ts` creates the app's typed database client.
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
The Chat template doesn't include a database plugin by default. Create
|
|
25
|
-
`server/plugins/db.ts` to initialize it. This is what runs migrations and
|
|
26
|
-
makes the database available to your actions:
|
|
21
|
+
Create `server/plugins/db.ts`:
|
|
27
22
|
|
|
28
23
|
```ts filename="server/plugins/db.ts"
|
|
29
24
|
import { runMigrations } from "@agent-native/core/db";
|
|
@@ -32,12 +27,15 @@ export default runMigrations(
|
|
|
32
27
|
[
|
|
33
28
|
{
|
|
34
29
|
version: 1,
|
|
30
|
+
name: "create-text-analyses",
|
|
35
31
|
sql: `CREATE TABLE IF NOT EXISTS text_analyses (
|
|
36
32
|
id TEXT PRIMARY KEY,
|
|
37
33
|
input TEXT NOT NULL,
|
|
38
34
|
char_count INTEGER NOT NULL,
|
|
39
35
|
word_count INTEGER NOT NULL,
|
|
40
36
|
sentence_count INTEGER NOT NULL,
|
|
37
|
+
paragraph_count INTEGER NOT NULL,
|
|
38
|
+
owner_email TEXT NOT NULL,
|
|
41
39
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
42
40
|
)`,
|
|
43
41
|
},
|
|
@@ -46,14 +44,7 @@ export default runMigrations(
|
|
|
46
44
|
);
|
|
47
45
|
```
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
tables later, append a new version object. Never edit existing ones.
|
|
51
|
-
|
|
52
|
-
### Define the schema
|
|
53
|
-
|
|
54
|
-
Create `server/db/schema.ts`. The `server/db/` directory may not exist yet,
|
|
55
|
-
so create it if needed. This file describes your tables using typed helpers so
|
|
56
|
-
your actions get full TypeScript autocomplete:
|
|
47
|
+
Create `server/db/schema.ts`:
|
|
57
48
|
|
|
58
49
|
```ts filename="server/db/schema.ts"
|
|
59
50
|
import { integer, now, table, text } from "@agent-native/core/db/schema";
|
|
@@ -64,190 +55,115 @@ export const textAnalyses = table("text_analyses", {
|
|
|
64
55
|
charCount: integer("char_count").notNull(),
|
|
65
56
|
wordCount: integer("word_count").notNull(),
|
|
66
57
|
sentenceCount: integer("sentence_count").notNull(),
|
|
58
|
+
paragraphCount: integer("paragraph_count").notNull(),
|
|
59
|
+
ownerEmail: text("owner_email").notNull(),
|
|
67
60
|
createdAt: text("created_at").notNull().default(now()),
|
|
68
61
|
});
|
|
69
62
|
```
|
|
70
63
|
|
|
71
|
-
|
|
72
|
-
`sqliteTable`, `pgTable`, or dialect-specific imports. They pick the configured
|
|
73
|
-
SQL backend automatically, so the same schema runs locally on SQLite and in
|
|
74
|
-
production on any supported provider.
|
|
64
|
+
Create `server/db/index.ts`:
|
|
75
65
|
|
|
76
|
-
|
|
66
|
+
```ts filename="server/db/index.ts"
|
|
67
|
+
import { createGetDb } from "@agent-native/core/db";
|
|
68
|
+
import * as schema from "./schema.js";
|
|
77
69
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Look for these two lines in the terminal output. They confirm the table was created:
|
|
83
|
-
|
|
84
|
-
```
|
|
85
|
-
[db] Applying 1 migration(s) on SQLite/libsql…
|
|
86
|
-
[db] Applied migration v1 (1 statement)
|
|
70
|
+
export const getDb = createGetDb(schema);
|
|
71
|
+
export { schema };
|
|
87
72
|
```
|
|
88
73
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
can be ignored.
|
|
74
|
+
Restart `pnpm dev` once so the migration runs. Keep future migrations additive,
|
|
75
|
+
and give each one a new `version` and `name`.
|
|
92
76
|
|
|
93
|
-
|
|
77
|
+
## Add save and list actions
|
|
94
78
|
|
|
95
|
-
|
|
96
|
-
`actions
|
|
97
|
-
create them yourself, one file per operation. The agent and your UI will call
|
|
98
|
-
them the same way they call any other action.
|
|
99
|
-
|
|
100
|
-
**`actions/save-text-analysis.ts`** writes a result row to the database.
|
|
101
|
-
Call this after running `analyze-text` to make the result durable:
|
|
79
|
+
Keep `analyze-text` read-only and make saving explicit with a separate action.
|
|
80
|
+
Create `actions/save-text-analysis.ts`:
|
|
102
81
|
|
|
103
82
|
```ts filename="actions/save-text-analysis.ts"
|
|
104
83
|
import { defineAction } from "@agent-native/core/action";
|
|
105
|
-
import { getDbExec } from "@agent-native/core/db";
|
|
106
84
|
import { z } from "zod";
|
|
107
85
|
|
|
86
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
87
|
+
|
|
108
88
|
export default defineAction({
|
|
109
89
|
description: "Save a text analysis result to the database.",
|
|
110
90
|
schema: z.object({
|
|
111
|
-
input: z.string(),
|
|
91
|
+
input: z.string().min(1),
|
|
112
92
|
charCount: z.number(),
|
|
113
93
|
wordCount: z.number(),
|
|
114
94
|
sentenceCount: z.number(),
|
|
95
|
+
paragraphCount: z.number(),
|
|
115
96
|
}),
|
|
116
|
-
run: async (
|
|
97
|
+
run: async (
|
|
98
|
+
{ input, charCount, wordCount, sentenceCount, paragraphCount },
|
|
99
|
+
ctx,
|
|
100
|
+
) => {
|
|
101
|
+
const ownerEmail = ctx?.userEmail;
|
|
102
|
+
if (!ownerEmail) throw new Error("Sign in before saving analyses.");
|
|
103
|
+
|
|
117
104
|
const id = crypto.randomUUID();
|
|
118
|
-
await
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
105
|
+
await getDb().insert(schema.textAnalyses).values({
|
|
106
|
+
id,
|
|
107
|
+
input,
|
|
108
|
+
charCount,
|
|
109
|
+
wordCount,
|
|
110
|
+
sentenceCount,
|
|
111
|
+
paragraphCount,
|
|
112
|
+
ownerEmail,
|
|
122
113
|
});
|
|
114
|
+
|
|
123
115
|
return { id };
|
|
124
116
|
},
|
|
125
117
|
});
|
|
126
118
|
```
|
|
127
119
|
|
|
128
|
-
|
|
129
|
-
call this to summarize past analyses, and your UI can use it to populate a page:
|
|
120
|
+
Create `actions/list-text-analyses.ts`:
|
|
130
121
|
|
|
131
122
|
```ts filename="actions/list-text-analyses.ts"
|
|
132
123
|
import { defineAction } from "@agent-native/core/action";
|
|
133
|
-
import {
|
|
124
|
+
import { desc, eq } from "drizzle-orm";
|
|
134
125
|
import { z } from "zod";
|
|
135
126
|
|
|
136
|
-
|
|
137
|
-
description: "List all saved text analyses, newest first.",
|
|
138
|
-
schema: z.object({}),
|
|
139
|
-
run: async () => {
|
|
140
|
-
const result = await getDbExec().execute(
|
|
141
|
-
`SELECT id, input, char_count, word_count, sentence_count, created_at
|
|
142
|
-
FROM text_analyses
|
|
143
|
-
ORDER BY created_at DESC`,
|
|
144
|
-
);
|
|
145
|
-
return result.rows;
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
**`actions/delete-text-analysis.ts`** removes a row by id:
|
|
151
|
-
|
|
152
|
-
```ts filename="actions/delete-text-analysis.ts"
|
|
153
|
-
import { defineAction } from "@agent-native/core/action";
|
|
154
|
-
import { getDbExec } from "@agent-native/core/db";
|
|
155
|
-
import { z } from "zod";
|
|
127
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
156
128
|
|
|
157
129
|
export default defineAction({
|
|
158
|
-
description: "
|
|
159
|
-
schema: z.object({
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
130
|
+
description: "List the signed-in user's saved text analyses, newest first.",
|
|
131
|
+
schema: z.object({}),
|
|
132
|
+
http: { method: "GET" },
|
|
133
|
+
readOnly: true,
|
|
134
|
+
run: async (_, ctx) => {
|
|
135
|
+
const ownerEmail = ctx?.userEmail;
|
|
136
|
+
if (!ownerEmail) throw new Error("Sign in before listing analyses.");
|
|
137
|
+
|
|
138
|
+
return getDb()
|
|
139
|
+
.select({
|
|
140
|
+
id: schema.textAnalyses.id,
|
|
141
|
+
input: schema.textAnalyses.input,
|
|
142
|
+
charCount: schema.textAnalyses.charCount,
|
|
143
|
+
wordCount: schema.textAnalyses.wordCount,
|
|
144
|
+
sentenceCount: schema.textAnalyses.sentenceCount,
|
|
145
|
+
paragraphCount: schema.textAnalyses.paragraphCount,
|
|
146
|
+
createdAt: schema.textAnalyses.createdAt,
|
|
147
|
+
})
|
|
148
|
+
.from(schema.textAnalyses)
|
|
149
|
+
.where(eq(schema.textAnalyses.ownerEmail, ownerEmail))
|
|
150
|
+
.orderBy(desc(schema.textAnalyses.createdAt));
|
|
166
151
|
},
|
|
167
152
|
});
|
|
168
153
|
```
|
|
169
154
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
```bash
|
|
174
|
-
pnpm action list-text-analyses
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
You should see an empty array. The table exists and the action works; there's
|
|
178
|
-
just nothing saved yet:
|
|
179
|
-
|
|
180
|
-
```
|
|
181
|
-
[]
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
Data is saved to `data/app.db`, a SQLite file in your project directory that
|
|
185
|
-
gets created automatically on first run. In production you'd point
|
|
186
|
-
`DATABASE_URL` at a hosted database instead, but locally this file is all you
|
|
187
|
-
need.
|
|
188
|
-
|
|
189
|
-
To save something, first run `analyze-text` to get the counts:
|
|
190
|
-
|
|
191
|
-
```bash
|
|
192
|
-
pnpm action analyze-text --text "Hello world"
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
You'll see output like:
|
|
196
|
-
|
|
197
|
-
```
|
|
198
|
-
{
|
|
199
|
-
title: 'Text statistics',
|
|
200
|
-
points: [
|
|
201
|
-
{ label: 'Characters', value: 11 },
|
|
202
|
-
{ label: 'Words', value: 2 },
|
|
203
|
-
{ label: 'Sentences', value: 1 },
|
|
204
|
-
{ label: 'Paragraphs', value: 1 }
|
|
205
|
-
]
|
|
206
|
-
}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
Then pass those values to `save-text-analysis`:
|
|
210
|
-
|
|
211
|
-
```bash
|
|
212
|
-
pnpm action save-text-analysis \
|
|
213
|
-
--input "Hello world" \
|
|
214
|
-
--charCount 11 \
|
|
215
|
-
--wordCount 2 \
|
|
216
|
-
--sentenceCount 1
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
Now run `list-text-analyses` again and you'll see the saved row:
|
|
220
|
-
|
|
221
|
-
```bash
|
|
222
|
-
pnpm action list-text-analyses
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
Or ask the agent in the chat at `http://localhost:8080` to do both steps at once:
|
|
226
|
-
|
|
227
|
-
> Run analyze-text on "Hello world", then save the result.
|
|
228
|
-
|
|
229
|
-
## What's next {#next}
|
|
230
|
-
|
|
231
|
-
<Cards>
|
|
232
|
-
|
|
233
|
-
### [Add a Page](/docs/getting-started-pages)
|
|
234
|
-
|
|
235
|
-
Build a React route that displays your saved analyses and wire it into the
|
|
236
|
-
sidebar. Next in the series.
|
|
237
|
-
|
|
238
|
-
### [Database](/docs/database)
|
|
155
|
+
The actions use the typed Drizzle client for app reads and writes. Raw SQL is
|
|
156
|
+
only needed here for the additive migration.
|
|
239
157
|
|
|
240
|
-
|
|
241
|
-
for SQL in Agent Native.
|
|
158
|
+
## Try it
|
|
242
159
|
|
|
243
|
-
|
|
160
|
+
In chat, ask the agent to do the whole flow:
|
|
244
161
|
|
|
245
|
-
|
|
246
|
-
actions can do.
|
|
162
|
+
> Run analyze-text on "Hello world", save the result with save-text-analysis, then list my saved text analyses.
|
|
247
163
|
|
|
248
|
-
|
|
164
|
+
The saved row is now available to the agent and to the page you'll build next.
|
|
249
165
|
|
|
250
|
-
|
|
251
|
-
awareness.
|
|
166
|
+
## Continue
|
|
252
167
|
|
|
253
|
-
|
|
168
|
+
Continue with [Add a Page](/docs/getting-started-pages) to display the saved
|
|
169
|
+
rows in a route.
|