@adminforth/agent 1.39.1 → 1.40.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/agent/languageDetect.ts +31 -10
- package/agent/systemPrompt.ts +4 -4
- package/build.log +2 -2
- package/custom/skills/analyze_data/SKILL.md +2 -1
- package/custom/skills/fetch_data/SKILL.md +2 -0
- package/custom/speech_recognition_frontend/AudioLines.vue +28 -82
- package/custom/speech_recognition_frontend/MicrophoneButon.vue +12 -2
- package/dist/agent/languageDetect.js +22 -7
- package/dist/agent/systemPrompt.js +1 -1
- package/dist/custom/skills/analyze_data/SKILL.md +2 -1
- package/dist/custom/skills/fetch_data/SKILL.md +2 -0
- package/dist/custom/speech_recognition_frontend/AudioLines.vue +28 -82
- package/dist/custom/speech_recognition_frontend/MicrophoneButon.vue +12 -2
- package/dist/index.js +36 -1
- package/index.ts +48 -3
- package/package.json +2 -2
package/agent/languageDetect.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { logger } from "adminforth";
|
|
2
2
|
import type { PluginOptions } from "../types.js";
|
|
3
3
|
|
|
4
|
-
export type
|
|
4
|
+
export type DetectedLanguage = {
|
|
5
5
|
language: string;
|
|
6
|
-
code: string;
|
|
6
|
+
code: string; // ISO 639-1
|
|
7
|
+
ambiguous: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type PreviousUserMessage = {
|
|
11
|
+
text: string;
|
|
7
12
|
};
|
|
8
13
|
|
|
9
14
|
const USER_LANGUAGE_OUTPUT_SCHEMA = {
|
|
@@ -19,23 +24,28 @@ const USER_LANGUAGE_OUTPUT_SCHEMA = {
|
|
|
19
24
|
},
|
|
20
25
|
code: {
|
|
21
26
|
type: "string",
|
|
22
|
-
description: "Uppercase two-letter language code, for example EN,
|
|
27
|
+
description: "Uppercase ISO 639-1 two-letter language code, for example EN, UK, FR.",
|
|
28
|
+
},
|
|
29
|
+
ambiguous: {
|
|
30
|
+
type: "boolean",
|
|
31
|
+
description: "True if the user's language cannot be confidently detected from the message.",
|
|
23
32
|
},
|
|
24
33
|
},
|
|
25
|
-
required: ["language", "code"],
|
|
34
|
+
required: ["language", "code", "ambiguous"],
|
|
26
35
|
},
|
|
27
36
|
} as const;
|
|
28
37
|
|
|
29
|
-
function parseUserLanguage(content: string | undefined):
|
|
38
|
+
function parseUserLanguage(content: string | undefined): DetectedLanguage | null {
|
|
30
39
|
if (!content) {
|
|
31
40
|
return null;
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
try {
|
|
35
|
-
const parsed = JSON.parse(content) as
|
|
44
|
+
const parsed = JSON.parse(content) as DetectedLanguage;
|
|
36
45
|
return {
|
|
37
46
|
language: parsed.language,
|
|
38
47
|
code: parsed.code,
|
|
48
|
+
ambiguous: parsed.ambiguous,
|
|
39
49
|
};
|
|
40
50
|
} catch (error) {
|
|
41
51
|
logger.warn(`Failed to parse detected user language: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -46,15 +56,26 @@ function parseUserLanguage(content: string | undefined): UserLanguage | null {
|
|
|
46
56
|
export async function detectUserLanguage(
|
|
47
57
|
completionAdapter: PluginOptions["modes"][number]["completionAdapter"],
|
|
48
58
|
prompt: string,
|
|
49
|
-
|
|
59
|
+
previousUserMessages: PreviousUserMessage[] = [],
|
|
60
|
+
): Promise<DetectedLanguage | null> {
|
|
61
|
+
const previousMessages = previousUserMessages.length
|
|
62
|
+
? [
|
|
63
|
+
"",
|
|
64
|
+
"Previous user messages:",
|
|
65
|
+
...previousUserMessages.map((message) => message.text),
|
|
66
|
+
]
|
|
67
|
+
: [];
|
|
50
68
|
const response = await completionAdapter.complete({
|
|
51
69
|
content: [
|
|
52
|
-
"Detect the language
|
|
70
|
+
"Detect the language the assistant should use for the current user message.",
|
|
71
|
+
"Use recent conversation context only to resolve short or ambiguous current messages.",
|
|
53
72
|
"Return only the requested structured output.",
|
|
54
73
|
"The language must be the full English language name.",
|
|
55
|
-
"The code must be an uppercase two-letter code like
|
|
74
|
+
"The code must be an uppercase ISO 639-1 two-letter code like UK, EN, FR.",
|
|
75
|
+
"Set ambiguous to true if the response language cannot be confidently detected from the current message or context.",
|
|
76
|
+
...previousMessages,
|
|
56
77
|
"",
|
|
57
|
-
"
|
|
78
|
+
"Current user message:",
|
|
58
79
|
prompt,
|
|
59
80
|
].join("\n"),
|
|
60
81
|
maxTokens: 80,
|
package/agent/systemPrompt.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AdminForthResource, AdminUser, IAdminForth } from "adminforth";
|
|
2
|
-
import type {
|
|
2
|
+
import type { DetectedLanguage } from "./languageDetect.js";
|
|
3
3
|
import {
|
|
4
4
|
listBundledSkillManifests,
|
|
5
5
|
listProjectSkillManifests,
|
|
@@ -46,8 +46,8 @@ export function appendCustomSystemPrompt(
|
|
|
46
46
|
return `${systemPrompt}\n\n${normalizedCustomSystemPrompt}`;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function formatLanguagePrompt(language:
|
|
50
|
-
if (!language) {
|
|
49
|
+
function formatLanguagePrompt(language: DetectedLanguage | null) {
|
|
50
|
+
if (!language || language.ambiguous) {
|
|
51
51
|
return "Respond in the user's language.";
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -70,7 +70,7 @@ export function buildAgentTurnSystemPrompt(input: {
|
|
|
70
70
|
agentSystemPrompt: string;
|
|
71
71
|
adminUser: AdminUser;
|
|
72
72
|
usernameField: string;
|
|
73
|
-
userLanguage:
|
|
73
|
+
userLanguage: DetectedLanguage | null;
|
|
74
74
|
}) {
|
|
75
75
|
return [
|
|
76
76
|
input.agentSystemPrompt,
|
package/build.log
CHANGED
|
@@ -58,5 +58,5 @@ custom/speech_recognition_frontend/voiceActivityDetection.ts
|
|
|
58
58
|
custom/speech_recognition_frontend/types/
|
|
59
59
|
custom/speech_recognition_frontend/types/voice-activity-detection.d.ts
|
|
60
60
|
|
|
61
|
-
sent 1,
|
|
62
|
-
total size is 1,
|
|
61
|
+
sent 1,655,890 bytes received 860 bytes 3,313,500.00 bytes/sec
|
|
62
|
+
total size is 1,651,974 speedup is 1.00
|
|
@@ -10,7 +10,7 @@ Then you need to select between two main tools for fetching data:
|
|
|
10
10
|
|
|
11
11
|
- Load and call `aggregate` tool to fetch data for analytics. This is the main tool for fast server-side aggregations, including filtered data, grouped metrics, and date buckets such as day, week, or month. Always prioritize this way of fetching data for analytics, as it is optimized for performance and reduces the amount of data transferred and processed in-memory.
|
|
12
12
|
|
|
13
|
-
- Load and call `get_resource_data` tool only when the requested analysis cannot be answered with `aggregate`.
|
|
13
|
+
- Load and call `get_resource_data` tool only when the requested analysis cannot be answered with `aggregate`. When using it, pass `columns` with only the fields required for the calculation so large result sets do not include unrelated row data.
|
|
14
14
|
|
|
15
15
|
# Instructions
|
|
16
16
|
|
|
@@ -19,6 +19,7 @@ When the user asks for analytics, reports, trends, comparisons, or distributions
|
|
|
19
19
|
- Fetch the requested data using `aggregate` whenever possible.
|
|
20
20
|
- If it is not possible to get the required aggregates using `aggregate`, fetch the underlying rows with `get_resource_data`.
|
|
21
21
|
- Prefer narrow requests: use filters, sorting, pagination, and date ranges whenever possible.
|
|
22
|
+
- Prefer narrow row payloads: include `columns` when only one or a few fields are needed.
|
|
22
23
|
- If the request is ambiguous, clarify the resource, metric, grouping, or date range before fetching data.
|
|
23
24
|
- Return a short written summary with the key finding and most important numbers.
|
|
24
25
|
- If the user asks for a chart, or if a chart would help and you decide to produce one, invoke the `charts` skill for chart formatting and Vega-Lite requirements.
|
|
@@ -6,6 +6,8 @@ description: Fetch one or more records. Use to find records entities. To use thi
|
|
|
6
6
|
|
|
7
7
|
You can use tool `get_resource_data` it returns one or more records and is capable of using filters
|
|
8
8
|
|
|
9
|
+
When you only need specific fields, pass `columns` with the exact resource column names you need. This keeps large result sets small. For record links or user-facing record choices, include the primary key and enough display fields, or omit `columns` when you need the full row context.
|
|
10
|
+
|
|
9
11
|
# Instructions
|
|
10
12
|
|
|
11
13
|
To find specific data record you should use filters. ILIKE filters are preferred when we are unsure the input is clear.You can combine filters with OR if you want to search multiple fields.If user queries one record you should try to fetch up to 5 records and if more then one returned return output them all to user and ask to select one. When you communicate about record with user, show its several most important fields.
|
|
@@ -1,97 +1,43 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
|
|
4
|
-
:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
'h-1': isRecording,
|
|
8
|
-
}"
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-for="(height, index) in lineHeights"
|
|
4
|
+
:key="index"
|
|
5
|
+
class="bg-white w-[0.2rem] rounded-sm transition-all duration-100 ease-out"
|
|
6
|
+
:style="{ height }"
|
|
9
7
|
/>
|
|
10
|
-
<
|
|
11
|
-
class=" bg-white w-[0.2rem] rounded-sm transition-all duration-300 ease-in-out"
|
|
12
|
-
:class="{
|
|
13
|
-
'recordingAnimation2' : showAnimation,
|
|
14
|
-
'h-4': !isRecording,
|
|
15
|
-
'h-1': isRecording,
|
|
16
|
-
}"
|
|
17
|
-
/>
|
|
18
|
-
<div
|
|
19
|
-
class=" bg-white w-[0.2rem] rounded-sm transition-all duration-300 ease-in-out"
|
|
20
|
-
:class="{
|
|
21
|
-
'recordingAnimation3' : showAnimation,
|
|
22
|
-
'h-3': !isRecording,
|
|
23
|
-
'h-1': isRecording,
|
|
24
|
-
}"
|
|
25
|
-
/>
|
|
26
|
-
<div
|
|
27
|
-
class=" bg-white w-[0.2rem] rounded-sm transition-all duration-300 ease-in-out"
|
|
28
|
-
:class="{
|
|
29
|
-
'recordingAnimation4' : showAnimation,
|
|
30
|
-
'h-2': !isRecording,
|
|
31
|
-
'h-1': isRecording,
|
|
32
|
-
}"
|
|
33
|
-
/>
|
|
34
|
-
<template v-if="isRecording">
|
|
35
|
-
<div
|
|
36
|
-
class=" bg-white w-[0.2rem] rounded-sm h-1 transition-all duration-300 ease-in-out"
|
|
37
|
-
:class="{
|
|
38
|
-
'recordingAnimation5' : showAnimation,
|
|
39
|
-
}"
|
|
40
|
-
/>
|
|
41
|
-
<p class="text-white ml-2">End</p>
|
|
42
|
-
</template>
|
|
8
|
+
<p v-if="isRecording" class="text-white ml-2">End</p>
|
|
43
9
|
</template>
|
|
44
10
|
|
|
45
|
-
|
|
46
|
-
|
|
47
11
|
<script setup lang="ts">
|
|
12
|
+
import { computed } from 'vue';
|
|
13
|
+
|
|
14
|
+
const IDLE_LINE_HEIGHTS = [0.5, 1, 0.75, 0.5];
|
|
15
|
+
const RECORDING_LINE_WEIGHTS = [0.45, 1, 0.75, 0.9, 0.55];
|
|
16
|
+
const MIN_RECORDING_HEIGHT = 0.25;
|
|
17
|
+
const MAX_RECORDING_DELTA = 0.9;
|
|
48
18
|
|
|
49
19
|
const props = defineProps<{
|
|
50
20
|
showAnimation: boolean;
|
|
51
21
|
isRecording: boolean;
|
|
22
|
+
amplitude: number;
|
|
52
23
|
}>();
|
|
53
24
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
.recordingAnimation1 {
|
|
58
|
-
animation: recordingAnimation 1s infinite;
|
|
59
|
-
height: 0.3rem;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.recordingAnimation2 {
|
|
63
|
-
animation: recordingAnimation 1s infinite;
|
|
64
|
-
animation-delay: 0.2s;
|
|
65
|
-
height: 0.5rem;
|
|
25
|
+
const normalizedAmplitude = computed(() => {
|
|
26
|
+
if (!props.isRecording || !props.showAnimation) {
|
|
27
|
+
return 0;
|
|
66
28
|
}
|
|
67
29
|
|
|
68
|
-
.
|
|
69
|
-
|
|
70
|
-
animation-delay: 0.4s;
|
|
71
|
-
height: 0.4rem;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.recordingAnimation4 {
|
|
75
|
-
animation: recordingAnimation 1s infinite;
|
|
76
|
-
animation-delay: 0.6s;
|
|
77
|
-
height: 0.5rem;
|
|
78
|
-
}
|
|
30
|
+
return Math.min(Math.max(props.amplitude, 0), 1);
|
|
31
|
+
});
|
|
79
32
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
height: 0.3rem;
|
|
33
|
+
const lineHeights = computed(() => {
|
|
34
|
+
if (!props.isRecording) {
|
|
35
|
+
return IDLE_LINE_HEIGHTS.map((height) => `${height}rem`);
|
|
84
36
|
}
|
|
85
37
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
100% {
|
|
94
|
-
transform: scaleY(1);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
</style>
|
|
38
|
+
return RECORDING_LINE_WEIGHTS.map((weight) => {
|
|
39
|
+
const height = MIN_RECORDING_HEIGHT + normalizedAmplitude.value * MAX_RECORDING_DELTA * weight;
|
|
40
|
+
return `${height}rem`;
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
</script>
|
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
>
|
|
9
9
|
<div class="w-5 h-5 flex items-center justify-center">
|
|
10
10
|
<div v-if="microphoneButtonMode === 'listen' || microphoneButtonMode === 'off'" class="flex justify-evenly items-center gap-[0.1rem]">
|
|
11
|
-
<AudioLines
|
|
11
|
+
<AudioLines
|
|
12
|
+
:showAnimation="showAudioWavesAnimation"
|
|
13
|
+
:isRecording="microphoneButtonMode === 'listen'"
|
|
14
|
+
:amplitude="audioAmplitude"
|
|
15
|
+
/>
|
|
12
16
|
</div>
|
|
13
17
|
<div v-else-if="microphoneButtonMode === 'generating'" class="flex items-center justify-center gap-2 text-white text-sm">
|
|
14
18
|
<span class="w-3 h-3 bg-white rounded-sm" />
|
|
@@ -23,7 +27,7 @@
|
|
|
23
27
|
|
|
24
28
|
<script setup lang="ts">
|
|
25
29
|
import { computed, onMounted, onBeforeUnmount, ref, watch } from 'vue';
|
|
26
|
-
import debounce from 'lodash
|
|
30
|
+
import debounce from 'lodash.debounce';
|
|
27
31
|
import { requestMicAndStartVAD, stopUserMedia, getRecorder, CALIBRATION_DURATION } from './voiceActivityDetection';
|
|
28
32
|
import { Spinner } from '@/afcl'
|
|
29
33
|
import { storeToRefs } from 'pinia';
|
|
@@ -39,6 +43,7 @@ const { stopCurrentAudioPlayback } = agentAudio;
|
|
|
39
43
|
const { agentAudioMode } = storeToRefs(agentAudio);
|
|
40
44
|
const microphoneButtonMode = ref<'off' | 'calibrating' | 'listen' | 'transcribing' | 'generating'>('off');
|
|
41
45
|
const showAudioWavesAnimation = ref(false);
|
|
46
|
+
const audioAmplitude = ref(0);
|
|
42
47
|
const hideAnimationDebounced = debounce(() => {
|
|
43
48
|
showAudioWavesAnimation.value = false;
|
|
44
49
|
}, 100);
|
|
@@ -101,12 +106,14 @@ async function onStartRecording() {
|
|
|
101
106
|
function onStopRecording() {
|
|
102
107
|
agentAudio.playBeep(600);
|
|
103
108
|
stopUserMedia();
|
|
109
|
+
audioAmplitude.value = 0;
|
|
104
110
|
showAudioWavesAnimation.value = false;
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
function resetAll() {
|
|
108
114
|
stopGenerationAndAudio();
|
|
109
115
|
microphoneButtonMode.value = 'off';
|
|
116
|
+
audioAmplitude.value = 0;
|
|
110
117
|
showAudioWavesAnimation.value = false;
|
|
111
118
|
hideAnimationDebounced.cancel();
|
|
112
119
|
sendUserRecordDebounced.cancel();
|
|
@@ -125,7 +132,10 @@ function stopRecording() {
|
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
function onAnySound(amplitude: number) {
|
|
135
|
+
audioAmplitude.value = Math.min(Math.max(amplitude, 0), 1);
|
|
136
|
+
|
|
128
137
|
if(amplitude < 0.01) {
|
|
138
|
+
audioAmplitude.value = 0;
|
|
129
139
|
showAudioWavesAnimation.value = false;
|
|
130
140
|
return;
|
|
131
141
|
}
|
|
@@ -21,10 +21,14 @@ const USER_LANGUAGE_OUTPUT_SCHEMA = {
|
|
|
21
21
|
},
|
|
22
22
|
code: {
|
|
23
23
|
type: "string",
|
|
24
|
-
description: "Uppercase two-letter language code, for example EN,
|
|
24
|
+
description: "Uppercase ISO 639-1 two-letter language code, for example EN, UK, FR.",
|
|
25
|
+
},
|
|
26
|
+
ambiguous: {
|
|
27
|
+
type: "boolean",
|
|
28
|
+
description: "True if the user's language cannot be confidently detected from the message.",
|
|
25
29
|
},
|
|
26
30
|
},
|
|
27
|
-
required: ["language", "code"],
|
|
31
|
+
required: ["language", "code", "ambiguous"],
|
|
28
32
|
},
|
|
29
33
|
};
|
|
30
34
|
function parseUserLanguage(content) {
|
|
@@ -36,6 +40,7 @@ function parseUserLanguage(content) {
|
|
|
36
40
|
return {
|
|
37
41
|
language: parsed.language,
|
|
38
42
|
code: parsed.code,
|
|
43
|
+
ambiguous: parsed.ambiguous,
|
|
39
44
|
};
|
|
40
45
|
}
|
|
41
46
|
catch (error) {
|
|
@@ -43,16 +48,26 @@ function parseUserLanguage(content) {
|
|
|
43
48
|
return null;
|
|
44
49
|
}
|
|
45
50
|
}
|
|
46
|
-
export function detectUserLanguage(
|
|
47
|
-
return __awaiter(this,
|
|
51
|
+
export function detectUserLanguage(completionAdapter_1, prompt_1) {
|
|
52
|
+
return __awaiter(this, arguments, void 0, function* (completionAdapter, prompt, previousUserMessages = []) {
|
|
53
|
+
const previousMessages = previousUserMessages.length
|
|
54
|
+
? [
|
|
55
|
+
"",
|
|
56
|
+
"Previous user messages:",
|
|
57
|
+
...previousUserMessages.map((message) => message.text),
|
|
58
|
+
]
|
|
59
|
+
: [];
|
|
48
60
|
const response = yield completionAdapter.complete({
|
|
49
61
|
content: [
|
|
50
|
-
"Detect the language
|
|
62
|
+
"Detect the language the assistant should use for the current user message.",
|
|
63
|
+
"Use recent conversation context only to resolve short or ambiguous current messages.",
|
|
51
64
|
"Return only the requested structured output.",
|
|
52
65
|
"The language must be the full English language name.",
|
|
53
|
-
"The code must be an uppercase two-letter code like
|
|
66
|
+
"The code must be an uppercase ISO 639-1 two-letter code like UK, EN, FR.",
|
|
67
|
+
"Set ambiguous to true if the response language cannot be confidently detected from the current message or context.",
|
|
68
|
+
...previousMessages,
|
|
54
69
|
"",
|
|
55
|
-
"
|
|
70
|
+
"Current user message:",
|
|
56
71
|
prompt,
|
|
57
72
|
].join("\n"),
|
|
58
73
|
maxTokens: 80,
|
|
@@ -38,7 +38,7 @@ export function appendCustomSystemPrompt(systemPrompt, customSystemPrompt) {
|
|
|
38
38
|
return `${systemPrompt}\n\n${normalizedCustomSystemPrompt}`;
|
|
39
39
|
}
|
|
40
40
|
function formatLanguagePrompt(language) {
|
|
41
|
-
if (!language) {
|
|
41
|
+
if (!language || language.ambiguous) {
|
|
42
42
|
return "Respond in the user's language.";
|
|
43
43
|
}
|
|
44
44
|
return `Respond in ${language.language} (${language.code}).`;
|
|
@@ -10,7 +10,7 @@ Then you need to select between two main tools for fetching data:
|
|
|
10
10
|
|
|
11
11
|
- Load and call `aggregate` tool to fetch data for analytics. This is the main tool for fast server-side aggregations, including filtered data, grouped metrics, and date buckets such as day, week, or month. Always prioritize this way of fetching data for analytics, as it is optimized for performance and reduces the amount of data transferred and processed in-memory.
|
|
12
12
|
|
|
13
|
-
- Load and call `get_resource_data` tool only when the requested analysis cannot be answered with `aggregate`.
|
|
13
|
+
- Load and call `get_resource_data` tool only when the requested analysis cannot be answered with `aggregate`. When using it, pass `columns` with only the fields required for the calculation so large result sets do not include unrelated row data.
|
|
14
14
|
|
|
15
15
|
# Instructions
|
|
16
16
|
|
|
@@ -19,6 +19,7 @@ When the user asks for analytics, reports, trends, comparisons, or distributions
|
|
|
19
19
|
- Fetch the requested data using `aggregate` whenever possible.
|
|
20
20
|
- If it is not possible to get the required aggregates using `aggregate`, fetch the underlying rows with `get_resource_data`.
|
|
21
21
|
- Prefer narrow requests: use filters, sorting, pagination, and date ranges whenever possible.
|
|
22
|
+
- Prefer narrow row payloads: include `columns` when only one or a few fields are needed.
|
|
22
23
|
- If the request is ambiguous, clarify the resource, metric, grouping, or date range before fetching data.
|
|
23
24
|
- Return a short written summary with the key finding and most important numbers.
|
|
24
25
|
- If the user asks for a chart, or if a chart would help and you decide to produce one, invoke the `charts` skill for chart formatting and Vega-Lite requirements.
|
|
@@ -6,6 +6,8 @@ description: Fetch one or more records. Use to find records entities. To use thi
|
|
|
6
6
|
|
|
7
7
|
You can use tool `get_resource_data` it returns one or more records and is capable of using filters
|
|
8
8
|
|
|
9
|
+
When you only need specific fields, pass `columns` with the exact resource column names you need. This keeps large result sets small. For record links or user-facing record choices, include the primary key and enough display fields, or omit `columns` when you need the full row context.
|
|
10
|
+
|
|
9
11
|
# Instructions
|
|
10
12
|
|
|
11
13
|
To find specific data record you should use filters. ILIKE filters are preferred when we are unsure the input is clear.You can combine filters with OR if you want to search multiple fields.If user queries one record you should try to fetch up to 5 records and if more then one returned return output them all to user and ask to select one. When you communicate about record with user, show its several most important fields.
|
|
@@ -1,97 +1,43 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
|
|
4
|
-
:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
'h-1': isRecording,
|
|
8
|
-
}"
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-for="(height, index) in lineHeights"
|
|
4
|
+
:key="index"
|
|
5
|
+
class="bg-white w-[0.2rem] rounded-sm transition-all duration-100 ease-out"
|
|
6
|
+
:style="{ height }"
|
|
9
7
|
/>
|
|
10
|
-
<
|
|
11
|
-
class=" bg-white w-[0.2rem] rounded-sm transition-all duration-300 ease-in-out"
|
|
12
|
-
:class="{
|
|
13
|
-
'recordingAnimation2' : showAnimation,
|
|
14
|
-
'h-4': !isRecording,
|
|
15
|
-
'h-1': isRecording,
|
|
16
|
-
}"
|
|
17
|
-
/>
|
|
18
|
-
<div
|
|
19
|
-
class=" bg-white w-[0.2rem] rounded-sm transition-all duration-300 ease-in-out"
|
|
20
|
-
:class="{
|
|
21
|
-
'recordingAnimation3' : showAnimation,
|
|
22
|
-
'h-3': !isRecording,
|
|
23
|
-
'h-1': isRecording,
|
|
24
|
-
}"
|
|
25
|
-
/>
|
|
26
|
-
<div
|
|
27
|
-
class=" bg-white w-[0.2rem] rounded-sm transition-all duration-300 ease-in-out"
|
|
28
|
-
:class="{
|
|
29
|
-
'recordingAnimation4' : showAnimation,
|
|
30
|
-
'h-2': !isRecording,
|
|
31
|
-
'h-1': isRecording,
|
|
32
|
-
}"
|
|
33
|
-
/>
|
|
34
|
-
<template v-if="isRecording">
|
|
35
|
-
<div
|
|
36
|
-
class=" bg-white w-[0.2rem] rounded-sm h-1 transition-all duration-300 ease-in-out"
|
|
37
|
-
:class="{
|
|
38
|
-
'recordingAnimation5' : showAnimation,
|
|
39
|
-
}"
|
|
40
|
-
/>
|
|
41
|
-
<p class="text-white ml-2">End</p>
|
|
42
|
-
</template>
|
|
8
|
+
<p v-if="isRecording" class="text-white ml-2">End</p>
|
|
43
9
|
</template>
|
|
44
10
|
|
|
45
|
-
|
|
46
|
-
|
|
47
11
|
<script setup lang="ts">
|
|
12
|
+
import { computed } from 'vue';
|
|
13
|
+
|
|
14
|
+
const IDLE_LINE_HEIGHTS = [0.5, 1, 0.75, 0.5];
|
|
15
|
+
const RECORDING_LINE_WEIGHTS = [0.45, 1, 0.75, 0.9, 0.55];
|
|
16
|
+
const MIN_RECORDING_HEIGHT = 0.25;
|
|
17
|
+
const MAX_RECORDING_DELTA = 0.9;
|
|
48
18
|
|
|
49
19
|
const props = defineProps<{
|
|
50
20
|
showAnimation: boolean;
|
|
51
21
|
isRecording: boolean;
|
|
22
|
+
amplitude: number;
|
|
52
23
|
}>();
|
|
53
24
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
.recordingAnimation1 {
|
|
58
|
-
animation: recordingAnimation 1s infinite;
|
|
59
|
-
height: 0.3rem;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.recordingAnimation2 {
|
|
63
|
-
animation: recordingAnimation 1s infinite;
|
|
64
|
-
animation-delay: 0.2s;
|
|
65
|
-
height: 0.5rem;
|
|
25
|
+
const normalizedAmplitude = computed(() => {
|
|
26
|
+
if (!props.isRecording || !props.showAnimation) {
|
|
27
|
+
return 0;
|
|
66
28
|
}
|
|
67
29
|
|
|
68
|
-
.
|
|
69
|
-
|
|
70
|
-
animation-delay: 0.4s;
|
|
71
|
-
height: 0.4rem;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.recordingAnimation4 {
|
|
75
|
-
animation: recordingAnimation 1s infinite;
|
|
76
|
-
animation-delay: 0.6s;
|
|
77
|
-
height: 0.5rem;
|
|
78
|
-
}
|
|
30
|
+
return Math.min(Math.max(props.amplitude, 0), 1);
|
|
31
|
+
});
|
|
79
32
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
height: 0.3rem;
|
|
33
|
+
const lineHeights = computed(() => {
|
|
34
|
+
if (!props.isRecording) {
|
|
35
|
+
return IDLE_LINE_HEIGHTS.map((height) => `${height}rem`);
|
|
84
36
|
}
|
|
85
37
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
100% {
|
|
94
|
-
transform: scaleY(1);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
</style>
|
|
38
|
+
return RECORDING_LINE_WEIGHTS.map((weight) => {
|
|
39
|
+
const height = MIN_RECORDING_HEIGHT + normalizedAmplitude.value * MAX_RECORDING_DELTA * weight;
|
|
40
|
+
return `${height}rem`;
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
</script>
|
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
>
|
|
9
9
|
<div class="w-5 h-5 flex items-center justify-center">
|
|
10
10
|
<div v-if="microphoneButtonMode === 'listen' || microphoneButtonMode === 'off'" class="flex justify-evenly items-center gap-[0.1rem]">
|
|
11
|
-
<AudioLines
|
|
11
|
+
<AudioLines
|
|
12
|
+
:showAnimation="showAudioWavesAnimation"
|
|
13
|
+
:isRecording="microphoneButtonMode === 'listen'"
|
|
14
|
+
:amplitude="audioAmplitude"
|
|
15
|
+
/>
|
|
12
16
|
</div>
|
|
13
17
|
<div v-else-if="microphoneButtonMode === 'generating'" class="flex items-center justify-center gap-2 text-white text-sm">
|
|
14
18
|
<span class="w-3 h-3 bg-white rounded-sm" />
|
|
@@ -23,7 +27,7 @@
|
|
|
23
27
|
|
|
24
28
|
<script setup lang="ts">
|
|
25
29
|
import { computed, onMounted, onBeforeUnmount, ref, watch } from 'vue';
|
|
26
|
-
import debounce from 'lodash
|
|
30
|
+
import debounce from 'lodash.debounce';
|
|
27
31
|
import { requestMicAndStartVAD, stopUserMedia, getRecorder, CALIBRATION_DURATION } from './voiceActivityDetection';
|
|
28
32
|
import { Spinner } from '@/afcl'
|
|
29
33
|
import { storeToRefs } from 'pinia';
|
|
@@ -39,6 +43,7 @@ const { stopCurrentAudioPlayback } = agentAudio;
|
|
|
39
43
|
const { agentAudioMode } = storeToRefs(agentAudio);
|
|
40
44
|
const microphoneButtonMode = ref<'off' | 'calibrating' | 'listen' | 'transcribing' | 'generating'>('off');
|
|
41
45
|
const showAudioWavesAnimation = ref(false);
|
|
46
|
+
const audioAmplitude = ref(0);
|
|
42
47
|
const hideAnimationDebounced = debounce(() => {
|
|
43
48
|
showAudioWavesAnimation.value = false;
|
|
44
49
|
}, 100);
|
|
@@ -101,12 +106,14 @@ async function onStartRecording() {
|
|
|
101
106
|
function onStopRecording() {
|
|
102
107
|
agentAudio.playBeep(600);
|
|
103
108
|
stopUserMedia();
|
|
109
|
+
audioAmplitude.value = 0;
|
|
104
110
|
showAudioWavesAnimation.value = false;
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
function resetAll() {
|
|
108
114
|
stopGenerationAndAudio();
|
|
109
115
|
microphoneButtonMode.value = 'off';
|
|
116
|
+
audioAmplitude.value = 0;
|
|
110
117
|
showAudioWavesAnimation.value = false;
|
|
111
118
|
hideAnimationDebounced.cancel();
|
|
112
119
|
sendUserRecordDebounced.cancel();
|
|
@@ -125,7 +132,10 @@ function stopRecording() {
|
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
function onAnySound(amplitude: number) {
|
|
135
|
+
audioAmplitude.value = Math.min(Math.max(amplitude, 0), 1);
|
|
136
|
+
|
|
128
137
|
if(amplitude < 0.01) {
|
|
138
|
+
audioAmplitude.value = 0;
|
|
129
139
|
showAudioWavesAnimation.value = false;
|
|
130
140
|
return;
|
|
131
141
|
}
|
package/dist/index.js
CHANGED
|
@@ -78,6 +78,16 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
78
78
|
}));
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
|
+
getPreviousUserMessages(sessionId) {
|
|
82
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
83
|
+
const turns = yield this.adminforth.resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], 2, undefined, [Sorts.DESC(this.options.turnResource.createdAtField)]);
|
|
84
|
+
return turns
|
|
85
|
+
.reverse()
|
|
86
|
+
.map((turn) => ({
|
|
87
|
+
text: turn[this.options.turnResource.promptField],
|
|
88
|
+
}));
|
|
89
|
+
});
|
|
90
|
+
}
|
|
81
91
|
getCheckpointer() {
|
|
82
92
|
if (this.checkpointer)
|
|
83
93
|
return this.checkpointer;
|
|
@@ -160,7 +170,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
160
170
|
const model = primaryModelSpec.model;
|
|
161
171
|
const summaryModel = summaryModelSpec.model;
|
|
162
172
|
const modelMiddleware = primaryModelSpec.middleware;
|
|
163
|
-
const userLanguage = yield detectUserLanguage(selectedMode.completionAdapter, input.prompt)
|
|
173
|
+
const userLanguage = yield detectUserLanguage(selectedMode.completionAdapter, input.prompt, input.previousUserMessages)
|
|
164
174
|
.catch((error) => {
|
|
165
175
|
logger.warn(`Failed to detect user language: ${error.message}`);
|
|
166
176
|
return null;
|
|
@@ -247,6 +257,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
247
257
|
runAndPersistAgentResponse(input) {
|
|
248
258
|
return __awaiter(this, void 0, void 0, function* () {
|
|
249
259
|
var _a, _b;
|
|
260
|
+
const previousUserMessages = yield this.getPreviousUserMessages(input.sessionId);
|
|
250
261
|
const turnId = yield this.createNewTurn(input.sessionId, input.prompt);
|
|
251
262
|
yield this.adminforth.resource(this.options.sessionResource.resourceId).update(input.sessionId, {
|
|
252
263
|
[this.options.sessionResource.createdAtField]: new Date().toISOString(),
|
|
@@ -260,6 +271,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
260
271
|
prompt: input.prompt,
|
|
261
272
|
sessionId: input.sessionId,
|
|
262
273
|
turnId,
|
|
274
|
+
previousUserMessages,
|
|
263
275
|
modeName: input.modeName,
|
|
264
276
|
userTimeZone: input.userTimeZone,
|
|
265
277
|
currentPage: input.currentPage,
|
|
@@ -376,14 +388,24 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
376
388
|
filename: req.file.originalname,
|
|
377
389
|
mimeType: req.file.mimetype,
|
|
378
390
|
language: "auto",
|
|
391
|
+
abortSignal,
|
|
379
392
|
});
|
|
380
393
|
}
|
|
381
394
|
catch (error) {
|
|
395
|
+
if (abortSignal.aborted) {
|
|
396
|
+
logger.info("Agent speech transcription aborted by the client");
|
|
397
|
+
stream.end();
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
382
400
|
logger.error(`Agent speech transcription failed:\n${error.message}`);
|
|
383
401
|
stream.error("Speech transcription failed. Check server logs for details.");
|
|
384
402
|
stream.end();
|
|
385
403
|
return null;
|
|
386
404
|
}
|
|
405
|
+
if (abortSignal.aborted) {
|
|
406
|
+
stream.end();
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
387
409
|
const prompt = transcription.text;
|
|
388
410
|
if (!prompt) {
|
|
389
411
|
stream.error("Speech transcription is empty");
|
|
@@ -426,19 +448,32 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
426
448
|
stream: true,
|
|
427
449
|
streamFormat: "audio",
|
|
428
450
|
format: "mp3",
|
|
451
|
+
abortSignal,
|
|
429
452
|
});
|
|
430
453
|
stream.audioStart(speech.mimeType, speech.format);
|
|
431
454
|
const reader = speech.audioStream.getReader();
|
|
455
|
+
const cancelAudioStream = () => {
|
|
456
|
+
void reader.cancel();
|
|
457
|
+
};
|
|
432
458
|
try {
|
|
459
|
+
abortSignal.addEventListener("abort", cancelAudioStream, { once: true });
|
|
433
460
|
while (true) {
|
|
461
|
+
if (abortSignal.aborted) {
|
|
462
|
+
yield reader.cancel();
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
434
465
|
const { value, done } = yield reader.read();
|
|
435
466
|
if (done) {
|
|
436
467
|
break;
|
|
437
468
|
}
|
|
469
|
+
if (abortSignal.aborted) {
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
438
472
|
stream.audioDelta(value);
|
|
439
473
|
}
|
|
440
474
|
}
|
|
441
475
|
finally {
|
|
476
|
+
abortSignal.removeEventListener("abort", cancelAudioStream);
|
|
442
477
|
reader.releaseLock();
|
|
443
478
|
}
|
|
444
479
|
stream.audioDone();
|
package/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { z } from "zod";
|
|
|
10
10
|
import { createAgentChatModel, callAgent } from "./agent/simpleAgent.js";
|
|
11
11
|
import { AdminForthCheckpointSaver } from "./agent/checkpointer.js";
|
|
12
12
|
import { createSequenceDebugCollector } from "./agent/middleware/sequenceDebug.js";
|
|
13
|
-
import { detectUserLanguage } from "./agent/languageDetect.js";
|
|
13
|
+
import { detectUserLanguage, type PreviousUserMessage } from "./agent/languageDetect.js";
|
|
14
14
|
import { prepareApiBasedTools as buildApiBasedTools } from './apiBasedTools.js';
|
|
15
15
|
import { createAgentEventStream } from "./agentResponseEvents.js";
|
|
16
16
|
import { appendCustomSystemPrompt, buildAgentSystemPrompt, buildAgentTurnSystemPrompt, DEFAULT_AGENT_SYSTEM_PROMPT} from "./agent/systemPrompt.js";
|
|
@@ -29,6 +29,7 @@ type AgentTurnRunInput = {
|
|
|
29
29
|
prompt: string;
|
|
30
30
|
sessionId: string;
|
|
31
31
|
turnId: string;
|
|
32
|
+
previousUserMessages: PreviousUserMessage[];
|
|
32
33
|
modeName?: string | null;
|
|
33
34
|
userTimeZone: string;
|
|
34
35
|
currentPage?: CurrentPageContext;
|
|
@@ -41,7 +42,7 @@ type AgentTurnRunInput = {
|
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
type RunAndPersistAgentResponseInput =
|
|
44
|
-
Omit<AgentTurnRunInput, "turnId" | "sequenceDebugCollector"> & {
|
|
45
|
+
Omit<AgentTurnRunInput, "turnId" | "sequenceDebugCollector" | "previousUserMessages"> & {
|
|
45
46
|
emitErrorResponse?: (response: string) => void;
|
|
46
47
|
failureLogMessage: string;
|
|
47
48
|
abortLogMessage: string;
|
|
@@ -116,6 +117,20 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
116
117
|
}));
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
private async getPreviousUserMessages(sessionId: string) {
|
|
121
|
+
const turns = await this.adminforth.resource(this.options.turnResource.resourceId).list(
|
|
122
|
+
[Filters.EQ(this.options.turnResource.sessionIdField, sessionId)],
|
|
123
|
+
2,
|
|
124
|
+
undefined,
|
|
125
|
+
[Sorts.DESC(this.options.turnResource.createdAtField)]
|
|
126
|
+
);
|
|
127
|
+
return turns
|
|
128
|
+
.reverse()
|
|
129
|
+
.map((turn): PreviousUserMessage => ({
|
|
130
|
+
text: turn[this.options.turnResource.promptField],
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
|
|
119
134
|
private getCheckpointer() {
|
|
120
135
|
if (this.checkpointer) return this.checkpointer;
|
|
121
136
|
|
|
@@ -199,7 +214,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
199
214
|
const summaryModel = summaryModelSpec.model;
|
|
200
215
|
const modelMiddleware = primaryModelSpec.middleware;
|
|
201
216
|
|
|
202
|
-
const userLanguage = await detectUserLanguage(selectedMode.completionAdapter, input.prompt)
|
|
217
|
+
const userLanguage = await detectUserLanguage(selectedMode.completionAdapter, input.prompt, input.previousUserMessages)
|
|
203
218
|
.catch((error) => {
|
|
204
219
|
logger.warn(`Failed to detect user language: ${error.message}`);
|
|
205
220
|
return null;
|
|
@@ -284,6 +299,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
284
299
|
}
|
|
285
300
|
|
|
286
301
|
private async runAndPersistAgentResponse(input: RunAndPersistAgentResponseInput) {
|
|
302
|
+
const previousUserMessages = await this.getPreviousUserMessages(input.sessionId);
|
|
287
303
|
const turnId = await this.createNewTurn(input.sessionId, input.prompt);
|
|
288
304
|
await this.adminforth.resource(this.options.sessionResource.resourceId).update(input.sessionId, {
|
|
289
305
|
[this.options.sessionResource.createdAtField]: new Date().toISOString(),
|
|
@@ -298,6 +314,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
298
314
|
prompt: input.prompt,
|
|
299
315
|
sessionId: input.sessionId,
|
|
300
316
|
turnId,
|
|
317
|
+
previousUserMessages,
|
|
301
318
|
modeName: input.modeName,
|
|
302
319
|
userTimeZone: input.userTimeZone,
|
|
303
320
|
currentPage: input.currentPage,
|
|
@@ -417,14 +434,26 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
417
434
|
filename: req.file.originalname,
|
|
418
435
|
mimeType: req.file.mimetype,
|
|
419
436
|
language: "auto",
|
|
437
|
+
abortSignal,
|
|
420
438
|
});
|
|
421
439
|
} catch (error) {
|
|
440
|
+
if (abortSignal.aborted) {
|
|
441
|
+
logger.info("Agent speech transcription aborted by the client");
|
|
442
|
+
stream.end();
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
|
|
422
446
|
logger.error(`Agent speech transcription failed:\n${error.message}`);
|
|
423
447
|
stream.error("Speech transcription failed. Check server logs for details.");
|
|
424
448
|
stream.end();
|
|
425
449
|
return null;
|
|
426
450
|
}
|
|
427
451
|
|
|
452
|
+
if (abortSignal.aborted) {
|
|
453
|
+
stream.end();
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
|
|
428
457
|
const prompt = transcription.text;
|
|
429
458
|
if (!prompt) {
|
|
430
459
|
stream.error("Speech transcription is empty");
|
|
@@ -476,23 +505,39 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
476
505
|
stream: true,
|
|
477
506
|
streamFormat: "audio",
|
|
478
507
|
format: "mp3",
|
|
508
|
+
abortSignal,
|
|
479
509
|
});
|
|
480
510
|
|
|
481
511
|
stream.audioStart(speech.mimeType, speech.format);
|
|
482
512
|
|
|
483
513
|
const reader = speech.audioStream.getReader();
|
|
514
|
+
const cancelAudioStream = () => {
|
|
515
|
+
void reader.cancel();
|
|
516
|
+
};
|
|
484
517
|
|
|
485
518
|
try {
|
|
519
|
+
abortSignal.addEventListener("abort", cancelAudioStream, { once: true });
|
|
520
|
+
|
|
486
521
|
while (true) {
|
|
522
|
+
if (abortSignal.aborted) {
|
|
523
|
+
await reader.cancel();
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
|
|
487
527
|
const { value, done } = await reader.read();
|
|
488
528
|
|
|
489
529
|
if (done) {
|
|
490
530
|
break;
|
|
491
531
|
}
|
|
492
532
|
|
|
533
|
+
if (abortSignal.aborted) {
|
|
534
|
+
break;
|
|
535
|
+
}
|
|
536
|
+
|
|
493
537
|
stream.audioDelta(value);
|
|
494
538
|
}
|
|
495
539
|
} finally {
|
|
540
|
+
abortSignal.removeEventListener("abort", cancelAudioStream);
|
|
496
541
|
reader.releaseLock();
|
|
497
542
|
}
|
|
498
543
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.40.1",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@langchain/core": "^1.1.40",
|
|
34
34
|
"@langchain/langgraph": "^1.2.8",
|
|
35
35
|
"@langchain/langgraph-checkpoint": "^1.0.1",
|
|
36
|
-
"adminforth": "^2.
|
|
36
|
+
"adminforth": "^2.53.5",
|
|
37
37
|
"dayjs": "^1.11.20",
|
|
38
38
|
"langchain": "^1.3.3",
|
|
39
39
|
"multer": "^2.1.1",
|