@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
|
+
```
|
package/custom/useAgentStore.ts
CHANGED
|
@@ -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,
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { AIMessage } from "@langchain/core/messages";
|
|
11
|
+
import { createMiddleware } from "langchain";
|
|
12
|
+
function getTurnKey(context) {
|
|
13
|
+
return `${context.sessionId}:${context.turnId}`;
|
|
14
|
+
}
|
|
15
|
+
function getResponseId(message) {
|
|
16
|
+
var _a;
|
|
17
|
+
const metadata = message.response_metadata;
|
|
18
|
+
return (_a = metadata === null || metadata === void 0 ? void 0 : metadata.id) !== null && _a !== void 0 ? _a : null;
|
|
19
|
+
}
|
|
20
|
+
function getPreviousResponseId(modelSettings) {
|
|
21
|
+
return modelSettings === null || modelSettings === void 0 ? void 0 : modelSettings.previous_response_id;
|
|
22
|
+
}
|
|
23
|
+
function getContinuationMessages(messages, previousResponseId) {
|
|
24
|
+
var _a;
|
|
25
|
+
let continuationStartIndex = null;
|
|
26
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
27
|
+
const message = messages[index];
|
|
28
|
+
if (AIMessage.isInstance(message) &&
|
|
29
|
+
((_a = message.response_metadata) === null || _a === void 0 ? void 0 : _a.id) ===
|
|
30
|
+
previousResponseId) {
|
|
31
|
+
continuationStartIndex = index + 1;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (continuationStartIndex === null) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return messages.slice(continuationStartIndex);
|
|
39
|
+
}
|
|
40
|
+
export function createOpenAiResponsesContinuationMiddleware() {
|
|
41
|
+
const responseIdsByTurn = new Map();
|
|
42
|
+
return createMiddleware({
|
|
43
|
+
name: "OpenAiResponsesContinuationMiddleware",
|
|
44
|
+
wrapModelCall(request, handler) {
|
|
45
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
46
|
+
var _a;
|
|
47
|
+
const context = request.runtime.context;
|
|
48
|
+
const turnKey = getTurnKey(context);
|
|
49
|
+
const previousResponseId = (_a = getPreviousResponseId(request.modelSettings)) !== null && _a !== void 0 ? _a : responseIdsByTurn.get(turnKey);
|
|
50
|
+
const continuationMessages = previousResponseId
|
|
51
|
+
? getContinuationMessages(request.messages, previousResponseId)
|
|
52
|
+
: null;
|
|
53
|
+
const response = yield handler(previousResponseId && continuationMessages
|
|
54
|
+
? Object.assign(Object.assign({}, request), { messages: continuationMessages, modelSettings: Object.assign(Object.assign({}, request.modelSettings), { previous_response_id: previousResponseId }) }) : request);
|
|
55
|
+
const responseId = getResponseId(response);
|
|
56
|
+
if (responseId) {
|
|
57
|
+
responseIdsByTurn.set(turnKey, responseId);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
responseIdsByTurn.delete(turnKey);
|
|
61
|
+
}
|
|
62
|
+
return response;
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -27,6 +27,8 @@ function createPendingSequenceDebug(sequenceId) {
|
|
|
27
27
|
prompt: "",
|
|
28
28
|
reasoning: "",
|
|
29
29
|
text: "",
|
|
30
|
+
cachedTokens: 0,
|
|
31
|
+
responseId: null,
|
|
30
32
|
toolCalls: [],
|
|
31
33
|
pendingToolCalls: 0,
|
|
32
34
|
resultType: null,
|
|
@@ -47,6 +49,8 @@ function finalizeSequenceDebug(sequence) {
|
|
|
47
49
|
prompt: sequence.prompt,
|
|
48
50
|
reasoning: sequence.reasoning,
|
|
49
51
|
text: sequence.text,
|
|
52
|
+
cachedTokens: sequence.cachedTokens,
|
|
53
|
+
responseId: sequence.responseId,
|
|
50
54
|
toolCalls: sequence.toolCalls.map((_a) => {
|
|
51
55
|
var { completed: _completed } = _a, toolCall = __rest(_a, ["completed"]);
|
|
52
56
|
return toolCall;
|
|
@@ -95,6 +99,7 @@ function hasToolCallSignal(message) {
|
|
|
95
99
|
message.additional_kwargs.tool_calls.length > 0));
|
|
96
100
|
}
|
|
97
101
|
function extractSequenceResponseDebug(message) {
|
|
102
|
+
var _a, _b, _c, _d, _e;
|
|
98
103
|
const blocks = getMessageBlocks(message);
|
|
99
104
|
const reasoning = blocks
|
|
100
105
|
.filter((block) => (block === null || block === void 0 ? void 0 : block.type) === "reasoning")
|
|
@@ -107,6 +112,8 @@ function extractSequenceResponseDebug(message) {
|
|
|
107
112
|
return {
|
|
108
113
|
reasoning,
|
|
109
114
|
text: textFromBlocks || (typeof message.content === "string" ? message.content : ""),
|
|
115
|
+
cachedTokens: (_c = (_b = (_a = message.usage_metadata) === null || _a === void 0 ? void 0 : _a.input_token_details) === null || _b === void 0 ? void 0 : _b.cache_read) !== null && _c !== void 0 ? _c : 0,
|
|
116
|
+
responseId: (_e = (_d = message.response_metadata) === null || _d === void 0 ? void 0 : _d.id) !== null && _e !== void 0 ? _e : null,
|
|
110
117
|
resultType: hasToolCallSignal(message) ? "tool_calls" : "final_text",
|
|
111
118
|
};
|
|
112
119
|
}
|
|
@@ -142,6 +149,8 @@ export function createSequenceDebugCollector() {
|
|
|
142
149
|
const sequenceDebug = ensureSequenceDebug();
|
|
143
150
|
sequenceDebug.reasoning = params.reasoning;
|
|
144
151
|
sequenceDebug.text = params.text;
|
|
152
|
+
sequenceDebug.cachedTokens = params.cachedTokens;
|
|
153
|
+
sequenceDebug.responseId = params.responseId;
|
|
145
154
|
sequenceDebug.resultType = params.resultType;
|
|
146
155
|
if (sequenceDebug.resultType === "final_text" &&
|
|
147
156
|
sequenceDebug.pendingToolCalls === 0) {
|
|
@@ -16,6 +16,7 @@ import { ChatOpenAI } from "@langchain/openai";
|
|
|
16
16
|
import { createAgentTools } from "./tools/index.js";
|
|
17
17
|
import { createApiBasedToolsMiddleware } from "./middleware/apiBasedTools.js";
|
|
18
18
|
import { createSequenceDebugMiddleware, } from "./middleware/sequenceDebug.js";
|
|
19
|
+
import { createOpenAiResponsesContinuationMiddleware } from "./middleware/openAiResponsesContinuation.js";
|
|
19
20
|
const checkpointer = new MemorySaver();
|
|
20
21
|
export const contextSchema = z.object({
|
|
21
22
|
adminUser: z.custom(),
|
|
@@ -121,7 +122,8 @@ export function createAgentChatModel(params) {
|
|
|
121
122
|
const model = (_c = (_b = params.modelName) !== null && _b !== void 0 ? _b : options.model) !== null && _c !== void 0 ? _c : "gpt-5-nano";
|
|
122
123
|
const baseURL = (_d = options.baseURL) !== null && _d !== void 0 ? _d : options.baseUrl;
|
|
123
124
|
const reasoning = normalizeReasoning(params.reasoning);
|
|
124
|
-
|
|
125
|
+
// @ts-ignore
|
|
126
|
+
return new ChatOpenAI(Object.assign(Object.assign(Object.assign({ apiKey: options.openAiApiKey, model, maxTokens: params.maxTokens, useResponsesApi: true, outputVersion: "v1", promptCacheKey: `adminforth-agent:${model}:system-v1:tools-v1`, promptCacheRetention: "in_memory" }, (reasoning ? { reasoning } : {})), (typeof options.timeoutMs === "number"
|
|
125
127
|
? { timeout: options.timeoutMs }
|
|
126
128
|
: {})), (baseURL
|
|
127
129
|
? {
|
|
@@ -136,13 +138,15 @@ export function callAgent(params) {
|
|
|
136
138
|
const { name, model, summaryModel, messages, adminUser, apiBasedTools, customComponentsDir, sessionId, turnId, userTimeZone, emitToolCallEvent, sequenceDebugSink, } = params;
|
|
137
139
|
const tools = yield createAgentTools(customComponentsDir, apiBasedTools);
|
|
138
140
|
const apiBasedToolsMiddleware = createApiBasedToolsMiddleware(apiBasedTools);
|
|
141
|
+
const openAiResponsesContinuationMiddleware = createOpenAiResponsesContinuationMiddleware();
|
|
139
142
|
const sequenceDebugMiddleware = createSequenceDebugMiddleware(sequenceDebugSink);
|
|
140
143
|
const middleware = [
|
|
141
144
|
apiBasedToolsMiddleware,
|
|
145
|
+
openAiResponsesContinuationMiddleware,
|
|
142
146
|
sequenceDebugMiddleware,
|
|
143
147
|
summarizationMiddleware({
|
|
144
148
|
model: summaryModel,
|
|
145
|
-
trigger: { tokens: 1024 *
|
|
149
|
+
trigger: { tokens: 1024 * 128 },
|
|
146
150
|
keep: { messages: 10 },
|
|
147
151
|
}),
|
|
148
152
|
];
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
</div>
|
|
19
19
|
<AutoScrollContainer
|
|
20
|
-
enabled
|
|
20
|
+
:enabled="!showScrollToBottomButton"
|
|
21
21
|
class="flex flex-col overflow-y-auto border-t border-gray-200 dark:border-gray-700"
|
|
22
22
|
ref="scrollContainer"
|
|
23
|
+
:threshold="10"
|
|
23
24
|
behavior="smooth"
|
|
24
25
|
>
|
|
25
26
|
|
|
@@ -57,8 +58,8 @@
|
|
|
57
58
|
v-if="props.messages.length === 0"
|
|
58
59
|
class="flex-1 flex flex-col items-center justify-center text-gray-400 tracking-widest text-xl font-medium"
|
|
59
60
|
>
|
|
60
|
-
<p>Start the conversation</p>
|
|
61
|
-
<p class="tracking-normal text-base text">Give any input to begin</p>
|
|
61
|
+
<p>{{ $t('Start the conversation') }}</p>
|
|
62
|
+
<p class="tracking-normal text-base text">{{ $t('Give any input to begin') }}</p>
|
|
62
63
|
</div>
|
|
63
64
|
</AutoScrollContainer>
|
|
64
65
|
</template>
|
|
@@ -151,20 +152,16 @@ const groupToolCallParts = (message: IMessage) => {
|
|
|
151
152
|
if(!part?.toolInfo) {
|
|
152
153
|
continue;
|
|
153
154
|
}
|
|
154
|
-
console.log('part', part);
|
|
155
155
|
if (part.toolInfo.toolName === currentToolName) {
|
|
156
|
-
console.log('grouping part with tool name', currentToolName);
|
|
157
156
|
groupedParts[groupedParts.length - 1].groupedTools.push(part);
|
|
158
157
|
continue;
|
|
159
158
|
}
|
|
160
159
|
currentToolName = part.toolInfo.toolName;
|
|
161
|
-
console.log('starting new group with tool name', currentToolName);
|
|
162
160
|
groupedParts.push({
|
|
163
161
|
title: currentToolName,
|
|
164
162
|
groupedTools: [part]
|
|
165
163
|
});
|
|
166
164
|
}
|
|
167
|
-
console.log('groupedParts', groupedParts);
|
|
168
165
|
return groupedParts;
|
|
169
166
|
}
|
|
170
167
|
|
package/dist/custom/Message.vue
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
class="max-w-[80%] flex px-4
|
|
3
|
+
class="max-w-[80%] flex px-4 m-2 rounded-xl border border-gray-200 dark:border-gray-700"
|
|
4
4
|
@click="handleMarkdownLinkClick"
|
|
5
|
-
:class="
|
|
5
|
+
:class="[
|
|
6
|
+
hasVegaLite ? 'w-full' : '',
|
|
7
|
+
props.role === 'user' ? 'bg-lightListTableHeading dark:bg-darkListTableHeading self-end'
|
|
6
8
|
: isTypeReasoning || isTypeToolCall ? 'bg-transparent border-none self-start'
|
|
7
|
-
: 'bg-blue-100 dark:bg-blue-700/10 self-start'
|
|
9
|
+
: 'bg-blue-100 dark:bg-blue-700/10 self-start'
|
|
10
|
+
]"
|
|
8
11
|
>
|
|
9
12
|
<IncremarkContent
|
|
10
|
-
class="text-wrap break-words max-w-full"
|
|
13
|
+
class="text-wrap break-words w-full max-w-full"
|
|
11
14
|
v-if="content && props.type === 'text'"
|
|
12
15
|
:content="content"
|
|
13
16
|
:is-finished="isFinished"
|
|
@@ -88,6 +91,7 @@
|
|
|
88
91
|
const content = computed(() => props.message)
|
|
89
92
|
const isFinished = computed(() => props.state === 'done')
|
|
90
93
|
const isThoughtsExpanded = ref(false)
|
|
94
|
+
const hasVegaLite = computed(() => props.type === 'text' && props.message.includes('```vega-lite'))
|
|
91
95
|
|
|
92
96
|
const isTypeReasoning = computed(() => props.type === 'reasoning')
|
|
93
97
|
const isTypeToolCall = computed(() => props.type === 'data-tool-call')
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
"
|
|
7
7
|
>
|
|
8
8
|
<h3 :class="h3Style">{{ $t('Chat history') }}</h3>
|
|
9
|
-
<Button @click="agentStore.createPreSession()" :disabled="agentStore.isResponseInProgress" class="w-[360px] mx-4 my-2 mb-4 rounded-3xl text-gray-800 dark:text-gray-200">
|
|
9
|
+
<Button @click="agentStore.createPreSession(); agentStore.setSessionHistoryOpen(false)" :disabled="agentStore.isResponseInProgress" class="w-[360px] mx-4 my-2 mb-4 rounded-3xl text-gray-800 dark:text-gray-200">
|
|
10
10
|
<IconPlusOutline class="w-5 h-5" />
|
|
11
11
|
{{ $t('New chat') }}
|
|
12
12
|
</Button>
|
|
13
13
|
<div class="w-full border-b border-gray-200 dark:border-gray-700"/>
|
|
14
14
|
<div class="absolute w-full h-full flex flex-col items-center justify-center bg-gray-100/50 dark:bg-gray-700/50 z-10" v-if="agentStore.isResponseInProgress">
|
|
15
15
|
<Spinner class="w-8 h-8" v-if="agentStore.isResponseInProgress" />
|
|
16
|
-
<p class="mt-2 text-gray-800 dark:text-gray-200">
|
|
16
|
+
<p class="mt-2 text-gray-800 dark:text-gray-200">{{ $t('Generation in progress...') }}</p>
|
|
17
17
|
</div>
|
|
18
18
|
<div v-for="group in groupedSessions" :key="group.dayKey" class="w-full py-2">
|
|
19
19
|
<div class="px-4 pb-2 text-xs font-semibold uppercase tracking-[0.2em] text-gray-500 dark:text-gray-400">
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
v-if="!groupedSessions || groupedSessions.length === 0"
|
|
39
39
|
class="w-full h-full flex items-center justify-center text-gray-800 dark:text-gray-200"
|
|
40
40
|
>
|
|
41
|
-
There
|
|
41
|
+
{{ $t('There are no previous chat sessions') }}
|
|
42
42
|
</p>
|
|
43
43
|
</div>
|
|
44
44
|
</template>
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
class="inline-flex m-2 max-w-[80%] flex-col gap-3 rounded-xl p-2 cursor-pointer text-lightListTableHeadingText dark:text-darkListTableHeadingText hover:opacity-75"
|
|
4
|
+
@click="isInputOutputExpanded = !isInputOutputExpanded"
|
|
5
|
+
>
|
|
3
6
|
<div class="flex items-center gap-3">
|
|
4
7
|
<div class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-white/70 dark:bg-blue-700/20">
|
|
5
8
|
<Spinner v-if="isRunning" class="h-4 w-4" />
|
|
@@ -18,8 +21,7 @@
|
|
|
18
21
|
<IconAngleDownOutline
|
|
19
22
|
v-if="hasToolSections"
|
|
20
23
|
:class="isInputOutputExpanded ? 'rotate-180' : 'rotate-0'"
|
|
21
|
-
class="cursor-pointer transition-transform duration-200 hover:scale-105
|
|
22
|
-
@click="isInputOutputExpanded = !isInputOutputExpanded"
|
|
24
|
+
class="cursor-pointer transition-transform duration-200 hover:scale-105"
|
|
23
25
|
/>
|
|
24
26
|
</div>
|
|
25
27
|
<transition name="expand">
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<template v-for="group in props.toolGroup" :key="group.title">
|
|
3
|
-
<div v-if="group.groupedTools.length > 1" class="
|
|
4
|
-
<div class="flex items-center gap-2 p-2 m-2 cursor-pointer hover:opacity-75 break-all font-mono text-sm leading-5" @click="toggleGroup(group.title)">
|
|
5
|
-
-
|
|
3
|
+
<div v-if="group.groupedTools.length > 1" class="flex flex-col">
|
|
4
|
+
<div class="flex items-center gap-2 p-2 m-2 cursor-pointer hover:opacity-75 break-all font-mono text-sm leading-5 text-lightListTableHeadingText dark:text-darkListTableHeadingText" @click="toggleGroup(group.title)">
|
|
5
|
+
<IconMinusOutline class="w-6 h-6 p-1"/>
|
|
6
|
+
{{ group.title }} {{ 'x' + group.groupedTools.length }}
|
|
6
7
|
<IconAngleDownOutline
|
|
7
8
|
class="transition-transform duration-200 hover:scale-105 hover:opacity-75"
|
|
8
9
|
:class="expandedGroups.includes(group.title) ? 'rotate-180' : 'rotate-0'"
|
|
@@ -10,7 +11,7 @@
|
|
|
10
11
|
</div>
|
|
11
12
|
<transition name="expand">
|
|
12
13
|
<div v-show="expandedGroups.includes(group.title)" class="flex flex-col">
|
|
13
|
-
<ToolRenderer v-for="part in group.groupedTools" :key="part.text + part.type" :data="part" />
|
|
14
|
+
<ToolRenderer v-for="part in group.groupedTools" :key="part.text + part.type" :data="part" class="ml-8"/>
|
|
14
15
|
</div>
|
|
15
16
|
</transition>
|
|
16
17
|
</div>
|
|
@@ -24,7 +25,7 @@ import { Tool } from 'langchain';
|
|
|
24
25
|
import ToolRenderer from './ToolRenderer.vue';
|
|
25
26
|
import type { IPart } from './types';
|
|
26
27
|
import { ref } from 'vue';
|
|
27
|
-
import { IconAngleDownOutline } from '@iconify-prerendered/vue-flowbite';
|
|
28
|
+
import { IconAngleDownOutline, IconMinusOutline } from '@iconify-prerendered/vue-flowbite';
|
|
28
29
|
|
|
29
30
|
const props = defineProps<{
|
|
30
31
|
toolGroup: {
|