@adminforth/agent 1.1.0 → 1.2.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/agent/middleware/openAiResponsesContinuation.ts +92 -0
- package/agent/middleware/sequenceDebug.ts +26 -0
- package/agent/simpleAgent.ts +11 -1
- package/build.log +4 -2
- package/custom/ConversationArea.vue +4 -7
- package/custom/Message.vue +8 -4
- package/custom/SessionsHistory.vue +3 -3
- package/custom/ToolRenderer.vue +5 -3
- package/custom/ToolsGroup.vue +6 -5
- package/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +78 -3
- package/custom/package.json +2 -1
- package/custom/pnpm-lock.yaml +721 -0
- package/custom/skills/data-analytics/SKILL.md +209 -0
- package/custom/useAgentStore.ts +9 -0
- package/dist/agent/middleware/openAiResponsesContinuation.js +66 -0
- package/dist/agent/middleware/sequenceDebug.js +9 -0
- package/dist/agent/simpleAgent.js +6 -2
- package/dist/custom/ConversationArea.vue +4 -7
- package/dist/custom/Message.vue +8 -4
- package/dist/custom/SessionsHistory.vue +3 -3
- package/dist/custom/ToolRenderer.vue +5 -3
- package/dist/custom/ToolsGroup.vue +6 -5
- package/dist/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +78 -3
- package/dist/custom/package.json +2 -1
- package/dist/custom/pnpm-lock.yaml +721 -0
- package/dist/custom/skills/data-analytics/SKILL.md +209 -0
- package/dist/custom/useAgentStore.ts +9 -0
- package/dist/index.js +9 -0
- package/index.ts +8 -0
- package/package.json +1 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
name: data-analytics
|
|
2
|
+
description: Analyze AdminForth resource data, summarize trends, and create charts from fetched rows.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Involved tools
|
|
6
|
+
|
|
7
|
+
Use `get_resource` first if you need to inspect resource structure and column names.
|
|
8
|
+
|
|
9
|
+
Use `get_resource_data` to fetch data for this skill. This is the main tool for loading rows for analytics, comparisons, distributions, and trend analysis.
|
|
10
|
+
|
|
11
|
+
# Instructions
|
|
12
|
+
|
|
13
|
+
When the user asks for analytics, reports, trends, comparisons, or distributions:
|
|
14
|
+
|
|
15
|
+
- Fetch the underlying rows with `get_resource_data`.
|
|
16
|
+
- Prefer narrow requests: request only the columns you need and use filters, sorting, pagination, and date ranges whenever possible.
|
|
17
|
+
- If the request is ambiguous, clarify the resource, metric, grouping, or date range before fetching data.
|
|
18
|
+
- Compute aggregates from the returned rows yourself: sums, counts, averages, min/max, grouped totals, ratios, and trend deltas.
|
|
19
|
+
- Return a short written summary with the key finding and most important numbers.
|
|
20
|
+
- If a chart would help, produce a Vega-Lite spec.
|
|
21
|
+
|
|
22
|
+
# Charts
|
|
23
|
+
|
|
24
|
+
Use Vega-Lite syntax for charts.
|
|
25
|
+
|
|
26
|
+
Return every chart as valid JSON inside a `vega-lite` fenced code block.
|
|
27
|
+
|
|
28
|
+
Every chart spec should include:
|
|
29
|
+
- `title.text`
|
|
30
|
+
- `title.subtitle`
|
|
31
|
+
- explicit axis titles when axes are used
|
|
32
|
+
- tooltips for the key fields
|
|
33
|
+
|
|
34
|
+
### Line chart
|
|
35
|
+
|
|
36
|
+
```vega-lite
|
|
37
|
+
{
|
|
38
|
+
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
|
|
39
|
+
"title": {
|
|
40
|
+
"text": "Orders by Day",
|
|
41
|
+
"subtitle": "Daily order count for the selected date range"
|
|
42
|
+
},
|
|
43
|
+
"data": {
|
|
44
|
+
"values": [
|
|
45
|
+
{ "date": "2026-04-01", "orders": 18 },
|
|
46
|
+
{ "date": "2026-04-02", "orders": 25 },
|
|
47
|
+
{ "date": "2026-04-03", "orders": 21 },
|
|
48
|
+
{ "date": "2026-04-04", "orders": 29 }
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"mark": { "type": "line", "point": true },
|
|
52
|
+
"encoding": {
|
|
53
|
+
"x": { "field": "date", "type": "temporal", "title": "Date" },
|
|
54
|
+
"y": { "field": "orders", "type": "quantitative", "title": "Orders" },
|
|
55
|
+
"tooltip": [
|
|
56
|
+
{ "field": "date", "type": "temporal", "title": "Date" },
|
|
57
|
+
{ "field": "orders", "type": "quantitative", "title": "Orders" }
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Bar chart
|
|
64
|
+
|
|
65
|
+
```vega-lite
|
|
66
|
+
{
|
|
67
|
+
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
|
|
68
|
+
"title": {
|
|
69
|
+
"text": "Revenue by Category",
|
|
70
|
+
"subtitle": "Top categories in the current filtered dataset"
|
|
71
|
+
},
|
|
72
|
+
"data": {
|
|
73
|
+
"values": [
|
|
74
|
+
{ "category": "Hardware", "revenue": 42000 },
|
|
75
|
+
{ "category": "Software", "revenue": 31500 },
|
|
76
|
+
{ "category": "Services", "revenue": 22750 }
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
"mark": "bar",
|
|
80
|
+
"encoding": {
|
|
81
|
+
"x": { "field": "category", "type": "nominal", "title": "Category", "sort": "-y" },
|
|
82
|
+
"y": { "field": "revenue", "type": "quantitative", "title": "Revenue" },
|
|
83
|
+
"tooltip": [
|
|
84
|
+
{ "field": "category", "type": "nominal", "title": "Category" },
|
|
85
|
+
{ "field": "revenue", "type": "quantitative", "title": "Revenue" }
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Area chart
|
|
92
|
+
|
|
93
|
+
```vega-lite
|
|
94
|
+
{
|
|
95
|
+
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
|
|
96
|
+
"title": {
|
|
97
|
+
"text": "Monthly Signups",
|
|
98
|
+
"subtitle": "New users accumulated across the last six months"
|
|
99
|
+
},
|
|
100
|
+
"data": {
|
|
101
|
+
"values": [
|
|
102
|
+
{ "month": "2025-11-01", "signups": 120 },
|
|
103
|
+
{ "month": "2025-12-01", "signups": 155 },
|
|
104
|
+
{ "month": "2026-01-01", "signups": 168 },
|
|
105
|
+
{ "month": "2026-02-01", "signups": 190 },
|
|
106
|
+
{ "month": "2026-03-01", "signups": 214 },
|
|
107
|
+
{ "month": "2026-04-01", "signups": 238 }
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
"mark": { "type": "area", "line": true, "point": true },
|
|
111
|
+
"encoding": {
|
|
112
|
+
"x": { "field": "month", "type": "temporal", "title": "Month" },
|
|
113
|
+
"y": { "field": "signups", "type": "quantitative", "title": "Signups" },
|
|
114
|
+
"tooltip": [
|
|
115
|
+
{ "field": "month", "type": "temporal", "title": "Month" },
|
|
116
|
+
{ "field": "signups", "type": "quantitative", "title": "Signups" }
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Scatter plot
|
|
123
|
+
|
|
124
|
+
```vega-lite
|
|
125
|
+
{
|
|
126
|
+
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
|
|
127
|
+
"title": {
|
|
128
|
+
"text": "Ad Spend vs Revenue",
|
|
129
|
+
"subtitle": "Campaign-level correlation for the selected month"
|
|
130
|
+
},
|
|
131
|
+
"data": {
|
|
132
|
+
"values": [
|
|
133
|
+
{ "campaign": "Search", "spend": 1200, "revenue": 5400 },
|
|
134
|
+
{ "campaign": "Social", "spend": 900, "revenue": 3100 },
|
|
135
|
+
{ "campaign": "Email", "spend": 350, "revenue": 2200 },
|
|
136
|
+
{ "campaign": "Affiliates", "spend": 700, "revenue": 3600 }
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
"mark": { "type": "point", "filled": true, "size": 120 },
|
|
140
|
+
"encoding": {
|
|
141
|
+
"x": { "field": "spend", "type": "quantitative", "title": "Ad Spend" },
|
|
142
|
+
"y": { "field": "revenue", "type": "quantitative", "title": "Revenue" },
|
|
143
|
+
"tooltip": [
|
|
144
|
+
{ "field": "campaign", "type": "nominal", "title": "Campaign" },
|
|
145
|
+
{ "field": "spend", "type": "quantitative", "title": "Ad Spend" },
|
|
146
|
+
{ "field": "revenue", "type": "quantitative", "title": "Revenue" }
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Pie chart
|
|
153
|
+
|
|
154
|
+
```vega-lite
|
|
155
|
+
{
|
|
156
|
+
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
|
|
157
|
+
"title": {
|
|
158
|
+
"text": "Tickets by Status",
|
|
159
|
+
"subtitle": "Share of tickets in each workflow state"
|
|
160
|
+
},
|
|
161
|
+
"data": {
|
|
162
|
+
"values": [
|
|
163
|
+
{ "status": "Open", "count": 42 },
|
|
164
|
+
{ "status": "In Progress", "count": 27 },
|
|
165
|
+
{ "status": "Resolved", "count": 58 }
|
|
166
|
+
]
|
|
167
|
+
},
|
|
168
|
+
"mark": { "type": "arc", "innerRadius": 40 },
|
|
169
|
+
"encoding": {
|
|
170
|
+
"theta": { "field": "count", "type": "quantitative", "title": "Tickets" },
|
|
171
|
+
"color": { "field": "status", "type": "nominal", "title": "Status" },
|
|
172
|
+
"tooltip": [
|
|
173
|
+
{ "field": "status", "type": "nominal", "title": "Status" },
|
|
174
|
+
{ "field": "count", "type": "quantitative", "title": "Tickets" }
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Histogram
|
|
181
|
+
|
|
182
|
+
```vega-lite
|
|
183
|
+
{
|
|
184
|
+
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
|
|
185
|
+
"title": {
|
|
186
|
+
"text": "Order Value Distribution",
|
|
187
|
+
"subtitle": "Histogram of order totals in the filtered result set"
|
|
188
|
+
},
|
|
189
|
+
"data": {
|
|
190
|
+
"values": [
|
|
191
|
+
{ "order_total": 24 },
|
|
192
|
+
{ "order_total": 31 },
|
|
193
|
+
{ "order_total": 39 },
|
|
194
|
+
{ "order_total": 42 },
|
|
195
|
+
{ "order_total": 63 },
|
|
196
|
+
{ "order_total": 78 },
|
|
197
|
+
{ "order_total": 95 }
|
|
198
|
+
]
|
|
199
|
+
},
|
|
200
|
+
"mark": "bar",
|
|
201
|
+
"encoding": {
|
|
202
|
+
"x": { "bin": true, "field": "order_total", "type": "quantitative", "title": "Order Total" },
|
|
203
|
+
"y": { "aggregate": "count", "type": "quantitative", "title": "Count" },
|
|
204
|
+
"tooltip": [
|
|
205
|
+
{ "aggregate": "count", "type": "quantitative", "title": "Count" }
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
@@ -120,6 +120,10 @@ export const useAgentStore = defineStore('agent', () => {
|
|
|
120
120
|
});
|
|
121
121
|
const blockCloseOfChat = ref(false);
|
|
122
122
|
|
|
123
|
+
function sortSessionsListByTimestamp(sessionsList: ISessionsListItem[]) {
|
|
124
|
+
return [...sessionsList].sort((a: ISessionsListItem, b: ISessionsListItem) => b.timestamp.localeCompare(a.timestamp));
|
|
125
|
+
}
|
|
126
|
+
|
|
123
127
|
async function sendMessage() {
|
|
124
128
|
const message = trimmedUserMessage.value;
|
|
125
129
|
if (!message || isResponseInProgress.value) {
|
|
@@ -128,6 +132,11 @@ export const useAgentStore = defineStore('agent', () => {
|
|
|
128
132
|
if (!currentSession.value || currentSession.value.sessionId === 'pre-session') {
|
|
129
133
|
await createNewSession(message);
|
|
130
134
|
}
|
|
135
|
+
currentSession.value.timestamp = new Date().toISOString();
|
|
136
|
+
sessionList.value = sortSessionsListByTimestamp(sessionList.value.map((s: ISessionsListItem) => s.sessionId === currentSession.value?.sessionId ? {
|
|
137
|
+
...s,
|
|
138
|
+
timestamp: currentSession.value?.timestamp || s.timestamp,
|
|
139
|
+
} : s));
|
|
131
140
|
lastMessage.value = message;
|
|
132
141
|
currentChat.value?.sendMessage({
|
|
133
142
|
text: message,
|
package/dist/index.js
CHANGED
|
@@ -71,6 +71,14 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
71
71
|
return { ok: true };
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
+
updateSessionDate(sessionId) {
|
|
75
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
+
yield this.adminforth.resource(this.options.sessionResource.resourceId).update(sessionId, {
|
|
77
|
+
[this.options.sessionResource.createdAtField]: new Date().toISOString(),
|
|
78
|
+
});
|
|
79
|
+
return { ok: true };
|
|
80
|
+
});
|
|
81
|
+
}
|
|
74
82
|
getSessionTurns(sessionId) {
|
|
75
83
|
return __awaiter(this, void 0, void 0, function* () {
|
|
76
84
|
const turns = yield this.adminforth.resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], undefined, undefined, [Sorts.ASC(this.options.turnResource.createdAtField)]);
|
|
@@ -134,6 +142,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
134
142
|
const userTimeZone = (_e = body.timeZone) !== null && _e !== void 0 ? _e : 'UTC';
|
|
135
143
|
const sessionId = body.sessionId || (adminUser === null || adminUser === void 0 ? void 0 : adminUser.pk) || (adminUser === null || adminUser === void 0 ? void 0 : adminUser.username) || 'default';
|
|
136
144
|
const turnId = yield this.createNewTurn(sessionId, prompt);
|
|
145
|
+
yield this.updateSessionDate(sessionId);
|
|
137
146
|
let fullResponse = "";
|
|
138
147
|
let isStreamClosed = false;
|
|
139
148
|
const sequenceDebugCollector = createSequenceDebugCollector();
|
package/index.ts
CHANGED
|
@@ -87,6 +87,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
87
87
|
return {ok: true};
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
private async updateSessionDate(sessionId: string) {
|
|
91
|
+
await this.adminforth.resource(this.options.sessionResource.resourceId).update(sessionId, {
|
|
92
|
+
[this.options.sessionResource.createdAtField]: new Date().toISOString(),
|
|
93
|
+
});
|
|
94
|
+
return {ok: true};
|
|
95
|
+
}
|
|
96
|
+
|
|
90
97
|
private async getSessionTurns(sessionId: string) {
|
|
91
98
|
const turns = await this.adminforth.resource(this.options.turnResource.resourceId).list(
|
|
92
99
|
[Filters.EQ(this.options.turnResource.sessionIdField, sessionId)],
|
|
@@ -150,6 +157,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
150
157
|
const userTimeZone = (body.timeZone as string | undefined) ?? 'UTC';
|
|
151
158
|
const sessionId = body.sessionId || adminUser?.pk || adminUser?.username || 'default';
|
|
152
159
|
const turnId = await this.createNewTurn(sessionId, prompt);
|
|
160
|
+
await this.updateSessionDate(sessionId);
|
|
153
161
|
let fullResponse = "";
|
|
154
162
|
let isStreamClosed = false;
|
|
155
163
|
const sequenceDebugCollector = createSequenceDebugCollector();
|