@adminforth/agent 1.35.0 → 1.37.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/build.log CHANGED
@@ -8,6 +8,7 @@ custom/ChatSurface.vue
8
8
  custom/CustomAutoScrollContainer.vue
9
9
  custom/SessionsHistory.vue
10
10
  custom/chat.ts
11
+ custom/env.d.ts
11
12
  custom/package.json
12
13
  custom/pnpm-lock.yaml
13
14
  custom/tsconfig.json
@@ -16,6 +17,12 @@ custom/utils.ts
16
17
  custom/composables/
17
18
  custom/composables/useAgentStore.ts
18
19
  custom/composables/useAgentTransitions.ts
20
+ custom/composables/agentStore/
21
+ custom/composables/agentStore/constants.ts
22
+ custom/composables/agentStore/pageContext.ts
23
+ custom/composables/agentStore/useAgentChat.ts
24
+ custom/composables/agentStore/useAgentPlaceholder.ts
25
+ custom/composables/agentStore/useAgentSessions.ts
19
26
  custom/conversation_area/
20
27
  custom/conversation_area/ConversationArea.vue
21
28
  custom/conversation_area/MessageRenderer.vue
@@ -40,5 +47,5 @@ custom/skills/fetch_data/SKILL.md
40
47
  custom/skills/mutate_data/
41
48
  custom/skills/mutate_data/SKILL.md
42
49
 
43
- sent 210,681 bytes received 581 bytes 422,524.00 bytes/sec
44
- total size is 208,279 speedup is 0.99
50
+ sent 216,524 bytes received 699 bytes 434,446.00 bytes/sec
51
+ total size is 213,674 speedup is 0.98
@@ -170,6 +170,7 @@
170
170
  </div>
171
171
  </div>
172
172
  <Button
173
+ v-if="!agentStore.isResponseInProgress"
173
174
  class="absolute right-4 bottom-2 !p-0 h-9 w-9"
174
175
  @click="sendMessage"
175
176
  :disabled="!agentStore.trimmedUserMessage || agentStore.isResponseInProgress"
@@ -179,6 +180,15 @@
179
180
  text-white"
180
181
  />
181
182
  </Button>
183
+ <Button
184
+ v-else
185
+ class="absolute right-4 bottom-2 !p-0 h-9 w-9"
186
+ @click="stopCurrentRequest"
187
+ >
188
+ <div
189
+ class="w-3 h-3 bg-white rounded-sm"
190
+ />
191
+ </Button>
182
192
  </div>
183
193
  </div>
184
194
  </div>
@@ -231,6 +241,7 @@ onClickOutside(modeMenu, () => { isModeMenuOpen.value = false; });
231
241
 
232
242
  onMounted(async () => {
233
243
  agentStore.setAvailableModes(props.meta.modes, props.meta.defaultModeName);
244
+ agentStore.setCurrentGenerationModeFromLocalStorage();
234
245
  agentStore.regisrerTextInput(textInput.value);
235
246
  window.addEventListener('resize', updateHeight)
236
247
  textInput.value?.focus();
@@ -316,6 +327,10 @@ async function sendMessage() {
316
327
  conversationArea.value?.handleSendMessage();
317
328
  }
318
329
 
330
+ function stopCurrentRequest() {
331
+ agentStore.abortCurrentChatRequestAndAddSystemMessage();
332
+ }
333
+
319
334
  function updateHeight() {
320
335
  dvh.value = Math.round(window.visualViewport?.height || window.innerHeight);
321
336
  }
@@ -0,0 +1,12 @@
1
+ export type AgentMode = {
2
+ name: string;
3
+ };
4
+
5
+ export const DEFAULT_CHAT_WIDTH = 30;
6
+ export const MAX_WIDTH = 60;
7
+ export const MIN_WIDTH = 25;
8
+
9
+ export const DEFAULT_TEXTAREA_PLACEHOLDER = 'Type a message...';
10
+ export const PLACEHOLDER_TYPING_DELAY_MS = 60;
11
+ export const PLACEHOLDER_DELETING_DELAY_MS = 35;
12
+ export const PLACEHOLDER_HOLD_DELAY_MS = 3000;
@@ -0,0 +1,8 @@
1
+ export function getCurrentPageContext() {
2
+ return {
3
+ path: window.location.pathname,
4
+ fullPath: `${window.location.pathname}${window.location.search}${window.location.hash}`,
5
+ title: document.title,
6
+ url: window.location.href,
7
+ };
8
+ }
@@ -0,0 +1,69 @@
1
+ import { DefaultChatTransport } from 'ai';
2
+ import { shallowRef, type Ref } from 'vue';
3
+ import { Chat } from '../../chat';
4
+ import { getCurrentPageContext } from './pageContext';
5
+
6
+ type AgentImportMeta = ImportMeta & {
7
+ env: {
8
+ VITE_ADMINFORTH_PUBLIC_PATH?: string;
9
+ };
10
+ };
11
+
12
+ type CreateAgentChatManagerOptions = {
13
+ lastMessage: Ref<string>;
14
+ activeModeName: Ref<string | null>;
15
+ };
16
+
17
+ export function createAgentChatManager({
18
+ lastMessage,
19
+ activeModeName,
20
+ }: CreateAgentChatManagerOptions) {
21
+ const chats = new Map<string, Chat<any>>();
22
+ const currentChat = shallowRef<Chat<any> | null>();
23
+
24
+ function setCurrentChat(sessionId: string) {
25
+ if (chats.has(sessionId)) {
26
+ currentChat.value = chats.get(sessionId) || null;
27
+ } else {
28
+ const newChat = new Chat({
29
+ transport: new DefaultChatTransport({
30
+ api: `${(import.meta as AgentImportMeta).env.VITE_ADMINFORTH_PUBLIC_PATH || ''}/adminapi/v1/agent/response`,
31
+ credentials: 'include',
32
+ prepareSendMessagesRequest({ messages }: any) {
33
+ const message = lastMessage.value;
34
+ const body = {
35
+ message,
36
+ sessionId,
37
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
38
+ mode: activeModeName.value,
39
+ currentPage: getCurrentPageContext(),
40
+ };
41
+
42
+ return {
43
+ headers: {
44
+ Accept: 'text/event-stream',
45
+ 'x-vercel-ai-ui-message-stream': 'v1',
46
+ },
47
+ body
48
+ };
49
+ }
50
+ }),
51
+ onError(error: unknown) {
52
+ console.error('Chat error:', error);
53
+ },
54
+ });
55
+ chats.set(sessionId, newChat);
56
+ currentChat.value = newChat;
57
+ }
58
+ }
59
+
60
+ function abortCurrentChatRequest() {
61
+ currentChat.value?.stop();
62
+ }
63
+
64
+ return {
65
+ currentChat,
66
+ setCurrentChat,
67
+ abortCurrentChatRequest,
68
+ };
69
+ }
@@ -0,0 +1,142 @@
1
+ import { ref, watch, type Ref } from 'vue';
2
+ import { callAdminForthApi } from '@/utils';
3
+ import {
4
+ DEFAULT_TEXTAREA_PLACEHOLDER,
5
+ PLACEHOLDER_DELETING_DELAY_MS,
6
+ PLACEHOLDER_HOLD_DELAY_MS,
7
+ PLACEHOLDER_TYPING_DELAY_MS,
8
+ } from './constants';
9
+
10
+ type CreateAgentPlaceholderControllerOptions = {
11
+ userMessageInput: Ref<unknown>;
12
+ };
13
+
14
+ export function createAgentPlaceholderController({
15
+ userMessageInput,
16
+ }: CreateAgentPlaceholderControllerOptions) {
17
+ const userMessagePlaceholder = ref(DEFAULT_TEXTAREA_PLACEHOLDER);
18
+ const placeholderMessages = ref<string[]>([]);
19
+ const hasTypedMessageInPageSession = ref(false);
20
+
21
+ let placeholderAnimationTimer: ReturnType<typeof setTimeout> | null = null;
22
+
23
+ function clearPlaceholderAnimationTimer() {
24
+ if (placeholderAnimationTimer !== null) {
25
+ clearTimeout(placeholderAnimationTimer);
26
+ placeholderAnimationTimer = null;
27
+ }
28
+ }
29
+
30
+ function resetPlaceholder() {
31
+ clearPlaceholderAnimationTimer();
32
+ userMessagePlaceholder.value = DEFAULT_TEXTAREA_PLACEHOLDER;
33
+ }
34
+
35
+ function stopPlaceholderAnimation() {
36
+ resetPlaceholder();
37
+ }
38
+
39
+ function startPlaceholderAnimation(messages: string[]) {
40
+ clearPlaceholderAnimationTimer();
41
+
42
+ if (!messages.length) {
43
+ userMessagePlaceholder.value = DEFAULT_TEXTAREA_PLACEHOLDER;
44
+ return;
45
+ }
46
+
47
+ let messageIndex = 0;
48
+ let visibleLength = 0;
49
+ let isDeleting = false;
50
+
51
+ const animate = () => {
52
+ const currentMessage = messages[messageIndex];
53
+
54
+ if (!currentMessage) {
55
+ resetPlaceholder();
56
+ return;
57
+ }
58
+
59
+ if (!isDeleting) {
60
+ visibleLength += 1;
61
+ userMessagePlaceholder.value = currentMessage.slice(0, visibleLength);
62
+
63
+ if (visibleLength >= currentMessage.length) {
64
+ isDeleting = true;
65
+ placeholderAnimationTimer = setTimeout(animate, PLACEHOLDER_HOLD_DELAY_MS);
66
+ return;
67
+ }
68
+
69
+ placeholderAnimationTimer = setTimeout(animate, PLACEHOLDER_TYPING_DELAY_MS);
70
+ return;
71
+ }
72
+
73
+ visibleLength -= 1;
74
+ userMessagePlaceholder.value = currentMessage.slice(0, Math.max(visibleLength, 0));
75
+
76
+ if (visibleLength <= 0) {
77
+ isDeleting = false;
78
+ messageIndex = (messageIndex + 1) % messages.length;
79
+ placeholderAnimationTimer = setTimeout(animate, PLACEHOLDER_TYPING_DELAY_MS);
80
+ return;
81
+ }
82
+
83
+ placeholderAnimationTimer = setTimeout(animate, PLACEHOLDER_DELETING_DELAY_MS);
84
+ };
85
+
86
+ animate();
87
+ }
88
+
89
+ async function fetchPlaceholderMessages() {
90
+ if (hasTypedMessageInPageSession.value) {
91
+ stopPlaceholderAnimation();
92
+ return;
93
+ }
94
+
95
+ try {
96
+ const res = await callAdminForthApi({
97
+ method: 'POST',
98
+ path: '/agent/get-placeholder-messages',
99
+ });
100
+
101
+ if (res.error) {
102
+ console.error('Error fetching placeholder messages:', res.error);
103
+ placeholderMessages.value = [];
104
+ resetPlaceholder();
105
+ return;
106
+ }
107
+
108
+ placeholderMessages.value = Array.isArray(res.messages)
109
+ ? res.messages.filter((message: unknown): message is string => typeof message === 'string' && message.length > 0)
110
+ : [];
111
+
112
+ if (!placeholderMessages.value.length) {
113
+ resetPlaceholder();
114
+ return;
115
+ }
116
+
117
+ startPlaceholderAnimation(placeholderMessages.value);
118
+ } catch (error) {
119
+ console.error('Error fetching placeholder messages', error);
120
+ placeholderMessages.value = [];
121
+ resetPlaceholder();
122
+ }
123
+ }
124
+
125
+ watch(userMessageInput, (newVal: unknown) => {
126
+ if (hasTypedMessageInPageSession.value) {
127
+ return;
128
+ }
129
+
130
+ if (typeof newVal === 'string' && newVal.trim() !== '') {
131
+ hasTypedMessageInPageSession.value = true;
132
+ stopPlaceholderAnimation();
133
+ }
134
+ });
135
+
136
+ return {
137
+ userMessagePlaceholder,
138
+ hasTypedMessageInPageSession,
139
+ fetchPlaceholderMessages,
140
+ stopPlaceholderAnimation,
141
+ };
142
+ }
@@ -0,0 +1,272 @@
1
+ import type { ComputedRef, Ref, ShallowRef } from 'vue';
2
+ import { callAdminForthApi } from '@/utils';
3
+ import type { Chat } from '../../chat';
4
+ import type { IAgentSession, ISessionsListItem, IPart } from '../../types';
5
+ import { useI18n } from 'vue-i18n';
6
+
7
+ type AdminforthLike = {
8
+ confirm(options: { message: string; yes: string; no: string }): Promise<boolean>;
9
+ };
10
+
11
+ type CreateAgentSessionManagerOptions = {
12
+ activeSessionId: Ref<string | null>;
13
+ currentSession: Ref<IAgentSession | null>;
14
+ sessionList: Ref<ISessionsListItem[]>;
15
+ sessions: Ref<Record<string, IAgentSession>>;
16
+ currentChat: ShallowRef<Chat<any> | null | undefined>;
17
+ trimmedUserMessage: ComputedRef<string>;
18
+ isResponseInProgress: ComputedRef<boolean>;
19
+ userMessageInput: Ref<any>;
20
+ lastMessage: Ref<string>;
21
+ blockCloseOfChat: Ref<boolean>;
22
+ adminforth: AdminforthLike;
23
+ setCurrentChat: (sessionId: string) => void;
24
+ };
25
+
26
+ export function createAgentSessionManager({
27
+ activeSessionId,
28
+ currentSession,
29
+ sessionList,
30
+ sessions,
31
+ currentChat,
32
+ trimmedUserMessage,
33
+ isResponseInProgress,
34
+ userMessageInput,
35
+ lastMessage,
36
+ blockCloseOfChat,
37
+ adminforth,
38
+ setCurrentChat,
39
+ }: CreateAgentSessionManagerOptions) {
40
+ function sortSessionsListByTimestamp(sessionsListToSort: ISessionsListItem[]) {
41
+ return [...sessionsListToSort].sort((a: ISessionsListItem, b: ISessionsListItem) => b.timestamp.localeCompare(a.timestamp));
42
+ }
43
+
44
+ const { t } = useI18n();
45
+ function saveCurrentSessionInCache() {
46
+ if (currentSession.value) {
47
+ currentSession.value.messages = currentChat.value?.messages.map((m: any) => ({
48
+ role: m.role,
49
+ text: m.parts.map((p: IPart) => p.type === 'text' ? p.text : '').join(''),
50
+ })) || [];
51
+ sessions.value[currentSession.value.sessionId] = currentSession.value;
52
+ }
53
+ }
54
+
55
+ async function fetchSession(sessionId: string) {
56
+ try {
57
+ const res = await callAdminForthApi({
58
+ method: 'POST',
59
+ path: '/agent/get-session-info',
60
+ body: { sessionId },
61
+ });
62
+ if (res.error) {
63
+ console.error('Error fetching session:', res.error);
64
+ return;
65
+ }
66
+ sessions.value[sessionId] = res.session;
67
+ setCurrentChat(sessionId);
68
+ } catch (error) {
69
+ console.error('Error fetching session', error);
70
+ }
71
+ }
72
+
73
+ async function setActiveSession(sessionId: string) {
74
+ activeSessionId.value = sessionId;
75
+ saveCurrentSessionInCache();
76
+ if (!sessions.value[sessionId]) {
77
+ await fetchSession(sessionId);
78
+ }
79
+ currentSession.value = sessions.value[sessionId];
80
+ setCurrentChat(sessionId);
81
+ if (currentChat.value.messages.length === 0) {
82
+ currentChat.value.messages = currentSession.value?.messages.map((m: any) => ({
83
+ role: m.role,
84
+ parts:[{
85
+ type: 'text',
86
+ text: m.text,
87
+ state: 'done',
88
+ }]
89
+ }));
90
+ }
91
+ }
92
+
93
+ async function deletePreSession() {
94
+ sessionList.value = sessionList.value.filter((s: ISessionsListItem) => s.sessionId !== 'pre-session');
95
+ if (activeSessionId.value === 'pre-session') {
96
+ activeSessionId.value = null;
97
+ currentSession.value = null;
98
+ }
99
+ }
100
+
101
+ async function createNewSession(triggerMessage?: string) {
102
+ try {
103
+ const res = await callAdminForthApi({
104
+ method: 'POST',
105
+ path: '/agent/create-session',
106
+ body: {
107
+ triggerMessage
108
+ },
109
+ });
110
+ if (res.error) {
111
+ console.error('Error creating new session:', res.error);
112
+ return;
113
+ }
114
+ deletePreSession();
115
+ sessions.value[res.sessionId] = res;
116
+ sessionList.value.unshift({
117
+ sessionId: res.sessionId,
118
+ title: res.title,
119
+ timestamp: new Date().toISOString(),
120
+ });
121
+ setActiveSession(res.sessionId);
122
+ } catch (error) {
123
+ console.error('Error creating new session', error);
124
+ }
125
+ }
126
+
127
+ async function sendMessage() {
128
+ const message = trimmedUserMessage.value;
129
+ if (!message || isResponseInProgress.value) {
130
+ return;
131
+ }
132
+ if (!currentSession.value || currentSession.value.sessionId === 'pre-session') {
133
+ await createNewSession(message);
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));
140
+ lastMessage.value = message;
141
+ currentChat.value?.sendMessage({
142
+ text: message,
143
+ });
144
+ userMessageInput.value = '';
145
+ }
146
+
147
+ async function createPreSession() {
148
+ saveCurrentSessionInCache();
149
+ if (!sessionList.value.some((s: ISessionsListItem) => s.sessionId === 'pre-session')) {
150
+ sessionList.value.unshift({
151
+ sessionId: 'pre-session',
152
+ title: 'New Session',
153
+ timestamp: new Date().toISOString(),
154
+ });
155
+ }
156
+
157
+ activeSessionId.value = 'pre-session';
158
+ currentSession.value = {
159
+ sessionId: 'pre-session',
160
+ title: 'New Session',
161
+ timestamp: new Date().toISOString(),
162
+ messages: [],
163
+ };
164
+ sessions.value['pre-session'] = currentSession.value;
165
+ setCurrentChat('pre-session');
166
+ }
167
+
168
+ async function deleteSession(sessionId: string) {
169
+ if (sessionId === 'pre-session') {
170
+ deletePreSession();
171
+ return;
172
+ }
173
+ blockCloseOfChat.value = true;
174
+ const isConfirmed = await adminforth.confirm({title: t('Are you sure, that you want to delete this session?'), message: t('This process is irreversible.'), yes: 'Yes', no: 'No'});
175
+ blockCloseOfChat.value = false;
176
+ if (!isConfirmed) {
177
+ return;
178
+ }
179
+ try {
180
+ const res = await callAdminForthApi({
181
+ method: 'POST',
182
+ path: '/agent/delete-session',
183
+ body: { sessionId },
184
+ });
185
+ if (res.error) {
186
+ console.error('Error deleting session:', res.error);
187
+ return;
188
+ }
189
+ delete sessions.value[sessionId];
190
+ sessionList.value = sessionList.value.filter((s: ISessionsListItem) => s.sessionId !== sessionId);
191
+ if (activeSessionId.value === sessionId) {
192
+ activeSessionId.value = null;
193
+ currentSession.value = null;
194
+ }
195
+ } catch (error) {
196
+ console.error('Error deleting session', error);
197
+ }
198
+ if(sessionId === activeSessionId.value) {
199
+ activeSessionId.value = sessionList.value.length > 0 ? sessionList.value[0].sessionId : null;
200
+ if (activeSessionId.value) {
201
+ currentSession.value = sessions.value[activeSessionId.value] || null;
202
+ } else {
203
+ currentSession.value = null;
204
+ }
205
+ }
206
+ }
207
+
208
+ async function fetchSessionsList() {
209
+ try {
210
+ const res = await callAdminForthApi({
211
+ method: 'POST',
212
+ path: '/agent/get-sessions',
213
+ body: {
214
+ limit: 100,
215
+ },
216
+ });
217
+ if (res.error) {
218
+ console.error('Error fetching sessions list:', res.error);
219
+ return;
220
+ }
221
+ sessionList.value = res.sessions;
222
+ } catch (error) {
223
+ console.error('Error fetching sessions list', error);
224
+ }
225
+ }
226
+
227
+ function addDebugMessage(message: string) {
228
+ const debugMessage = {
229
+ role: 'assistant',
230
+ parts: [{
231
+ type: 'text',
232
+ text: message,
233
+ state: 'done',
234
+ }]
235
+ };
236
+ currentChat.value?.messages.push(debugMessage);
237
+ }
238
+
239
+ function addSystemMessage(message: string) {
240
+ const systemMessage = {
241
+ role: 'system',
242
+ parts: [{
243
+ type: 'text',
244
+ text: message,
245
+ state: 'done',
246
+ }]
247
+ };
248
+ currentChat.value?.messages.push(systemMessage);
249
+ try {
250
+ const res = callAdminForthApi({
251
+ method: 'POST',
252
+ path: '/agent/add-system-message-to-turns',
253
+ body: {
254
+ sessionId: activeSessionId.value,
255
+ systemMessage: message,
256
+ },
257
+ });
258
+ } catch (error) {
259
+ console.error('Error adding system message', error);
260
+ }
261
+ }
262
+
263
+ return {
264
+ sendMessage,
265
+ createPreSession,
266
+ setActiveSession,
267
+ fetchSessionsList,
268
+ deleteSession,
269
+ addDebugMessage,
270
+ addSystemMessage,
271
+ };
272
+ }