@adminforth/agent 1.14.1 → 1.15.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
@@ -31,5 +31,5 @@ custom/skills/fetch_data/SKILL.md
31
31
  custom/skills/mutate_data/
32
32
  custom/skills/mutate_data/SKILL.md
33
33
 
34
- sent 180,483 bytes received 436 bytes 361,838.00 bytes/sec
35
- total size is 178,690 speedup is 0.99
34
+ sent 184,200 bytes received 436 bytes 369,272.00 bytes/sec
35
+ total size is 182,411 speedup is 0.99
@@ -111,7 +111,7 @@
111
111
  'min-h-12 w-full resize-none overflow-hidden border text-lightInputText dark:text-darkInputText rounded-md bg-transparent text-sm bg-gray-50 dark:bg-gray-700 dark:border-gray-600 focus:outline-none',
112
112
  agentStore.availableModes.length > 1 ? 'p-4 pr-12 pb-12' : 'p-4 pr-12',
113
113
  ]"
114
- placeholder="Type a message..."
114
+ :placeholder="agentStore.userMessagePlaceholder"
115
115
  @keydown.enter.exact.prevent="sendMessage"
116
116
  />
117
117
  <div
@@ -74,7 +74,7 @@
74
74
  <script setup lang="ts">
75
75
  import Message from './Message.vue';
76
76
  import type { IMessage, IPart } from './types';
77
- import { useTemplateRef, ref, defineAsyncComponent, onMounted, watch, computed } from 'vue';
77
+ import { useTemplateRef, ref, defineAsyncComponent, onMounted, onUnmounted, watch, computed } from 'vue';
78
78
  import { IconArrowDownOutline } from '@iconify-prerendered/vue-flowbite';
79
79
  import SessionsHistory from './SessionsHistory.vue';
80
80
  import { useAgentStore } from './composables/useAgentStore';
@@ -98,6 +98,11 @@ function recalculateScroll() {
98
98
 
99
99
  onMounted(async () => {
100
100
  await import('@incremark/theme/styles.css')
101
+ await agentStore.fetchPlaceholderMessages()
102
+ });
103
+
104
+ onUnmounted(() => {
105
+ agentStore.stopPlaceholderAnimation();
101
106
  });
102
107
 
103
108
  watch(scrollContainer, () => {
@@ -12,6 +12,11 @@ type AgentMode = {
12
12
  name: string;
13
13
  };
14
14
 
15
+ const DEFAULT_TEXTAREA_PLACEHOLDER = 'Type a message...';
16
+ const PLACEHOLDER_TYPING_DELAY_MS = 60;
17
+ const PLACEHOLDER_DELETING_DELAY_MS = 35;
18
+ const PLACEHOLDER_HOLD_DELAY_MS = 3000;
19
+
15
20
  export const useAgentStore = defineStore('agent', () => {
16
21
  const DEFAULT_CHAT_WIDTH = 600;
17
22
  const MAX_WIDTH = 800;
@@ -25,8 +30,10 @@ export const useAgentStore = defineStore('agent', () => {
25
30
  const adminforth = useAdminforth();
26
31
  const isChatOpen = ref(false);
27
32
  const isSessionHistoryOpen = ref(false);
28
- const textInput = ref<HTMLInputElement | null>(null);
33
+ const textInput = ref<HTMLTextAreaElement | null>(null);
29
34
  const userMessageInput = ref();
35
+ const userMessagePlaceholder = ref(DEFAULT_TEXTAREA_PLACEHOLDER);
36
+ const placeholderMessages = ref<string[]>([]);
30
37
  const trimmedUserMessage = computed(() => userMessageInput.value ? userMessageInput.value.trim() : '');
31
38
  const lastMessage = ref('');
32
39
  const isTeleportedToBody = ref(false);
@@ -40,6 +47,9 @@ export const useAgentStore = defineStore('agent', () => {
40
47
  const chatWidth = ref(DEFAULT_CHAT_WIDTH);
41
48
  const availableModes = ref<AgentMode[]>([]);
42
49
  const activeModeName = ref<string | null>(null);
50
+ const hasTypedMessageInPageSession = ref(false);
51
+ let placeholderAnimationTimer: ReturnType<typeof setTimeout> | null = null;
52
+
43
53
  function setLocalStorageItem(key: string, value: string) {
44
54
  window.localStorage.setItem(`${coreStore.config.brandName || 'adminforth'}-${key}`, value);
45
55
  }
@@ -60,6 +70,16 @@ export const useAgentStore = defineStore('agent', () => {
60
70
  setLocalStorageItem('lastSessionId', newVal);
61
71
  }
62
72
  })
73
+ watch(userMessageInput, (newVal: unknown) => {
74
+ if (hasTypedMessageInPageSession.value) {
75
+ return;
76
+ }
77
+
78
+ if (typeof newVal === 'string' && newVal.trim() !== '') {
79
+ hasTypedMessageInPageSession.value = true;
80
+ stopPlaceholderAnimation();
81
+ }
82
+ })
63
83
  onMounted(() => {
64
84
  const chatWidthBeforeFullScreen = parseInt(getLocalStorageItem('chatWidthBeforeFullScreen') || '0', 10);
65
85
  if (chatWidthBeforeFullScreen) {
@@ -130,14 +150,14 @@ export const useAgentStore = defineStore('agent', () => {
130
150
  function setAvailableModes(modes: AgentMode[], defaultModeName?: string | null) {
131
151
  availableModes.value = modes;
132
152
  activeModeName.value =
133
- modes.find((mode) => mode.name === activeModeName.value)?.name
153
+ modes.find((mode: AgentMode) => mode.name === activeModeName.value)?.name
134
154
  ?? defaultModeName
135
155
  ?? modes[0]?.name
136
156
  ?? null;
137
157
  }
138
158
 
139
159
  function setActiveMode(modeName: string) {
140
- if (!availableModes.value.some((mode) => mode.name === modeName)) {
160
+ if (!availableModes.value.some((mode: AgentMode) => mode.name === modeName)) {
141
161
  return;
142
162
  }
143
163
 
@@ -179,6 +199,73 @@ export const useAgentStore = defineStore('agent', () => {
179
199
  }
180
200
 
181
201
  }
202
+
203
+ function clearPlaceholderAnimationTimer() {
204
+ if (placeholderAnimationTimer !== null) {
205
+ clearTimeout(placeholderAnimationTimer);
206
+ placeholderAnimationTimer = null;
207
+ }
208
+ }
209
+
210
+ function resetPlaceholder() {
211
+ clearPlaceholderAnimationTimer();
212
+ userMessagePlaceholder.value = DEFAULT_TEXTAREA_PLACEHOLDER;
213
+ }
214
+
215
+ function stopPlaceholderAnimation() {
216
+ resetPlaceholder();
217
+ }
218
+
219
+ function startPlaceholderAnimation(messages: string[]) {
220
+ clearPlaceholderAnimationTimer();
221
+
222
+ if (!messages.length) {
223
+ userMessagePlaceholder.value = DEFAULT_TEXTAREA_PLACEHOLDER;
224
+ return;
225
+ }
226
+
227
+ let messageIndex = 0;
228
+ let visibleLength = 0;
229
+ let isDeleting = false;
230
+
231
+ const animate = () => {
232
+ const currentMessage = messages[messageIndex];
233
+
234
+ if (!currentMessage) {
235
+ resetPlaceholder();
236
+ return;
237
+ }
238
+
239
+ if (!isDeleting) {
240
+ visibleLength += 1;
241
+ userMessagePlaceholder.value = currentMessage.slice(0, visibleLength);
242
+
243
+ if (visibleLength >= currentMessage.length) {
244
+ isDeleting = true;
245
+ placeholderAnimationTimer = setTimeout(animate, PLACEHOLDER_HOLD_DELAY_MS);
246
+ return;
247
+ }
248
+
249
+ placeholderAnimationTimer = setTimeout(animate, PLACEHOLDER_TYPING_DELAY_MS);
250
+ return;
251
+ }
252
+
253
+ visibleLength -= 1;
254
+ userMessagePlaceholder.value = currentMessage.slice(0, Math.max(visibleLength, 0));
255
+
256
+ if (visibleLength <= 0) {
257
+ isDeleting = false;
258
+ messageIndex = (messageIndex + 1) % messages.length;
259
+ placeholderAnimationTimer = setTimeout(animate, PLACEHOLDER_TYPING_DELAY_MS);
260
+ return;
261
+ }
262
+
263
+ placeholderAnimationTimer = setTimeout(animate, PLACEHOLDER_DELETING_DELAY_MS);
264
+ };
265
+
266
+ animate();
267
+ }
268
+
182
269
  const isResponseInProgress = computed( () => {
183
270
  return currentChat.value?.status === 'streaming';
184
271
  });
@@ -234,10 +321,46 @@ export const useAgentStore = defineStore('agent', () => {
234
321
  function setSessionHistoryOpen(isOpen: boolean) {
235
322
  isSessionHistoryOpen.value = isOpen;
236
323
  }
237
- function regisrerTextInput(el: HTMLInputElement | null) {
324
+ function regisrerTextInput(el: HTMLTextAreaElement | null) {
238
325
  textInput.value = el;
239
326
  }
240
327
 
328
+ async function fetchPlaceholderMessages() {
329
+ if (hasTypedMessageInPageSession.value) {
330
+ stopPlaceholderAnimation();
331
+ return;
332
+ }
333
+
334
+ try {
335
+ const res = await callAdminForthApi({
336
+ method: 'POST',
337
+ path: '/agent/get-placeholder-messages',
338
+ });
339
+
340
+ if (res.error) {
341
+ console.error('Error fetching placeholder messages:', res.error);
342
+ placeholderMessages.value = [];
343
+ resetPlaceholder();
344
+ return;
345
+ }
346
+
347
+ placeholderMessages.value = Array.isArray(res.messages)
348
+ ? res.messages.filter((message: unknown): message is string => typeof message === 'string' && message.length > 0)
349
+ : [];
350
+
351
+ if (!placeholderMessages.value.length) {
352
+ resetPlaceholder();
353
+ return;
354
+ }
355
+
356
+ startPlaceholderAnimation(placeholderMessages.value);
357
+ } catch (error) {
358
+ console.error('Error fetching placeholder messages', error);
359
+ placeholderMessages.value = [];
360
+ resetPlaceholder();
361
+ }
362
+ }
363
+
241
364
 
242
365
  //create a pre-session, until user will type something, so we can save session
243
366
  async function createPreSession() {
@@ -409,12 +532,15 @@ export const useAgentStore = defineStore('agent', () => {
409
532
  createPreSession,
410
533
  //____________________________________________
411
534
  regisrerTextInput,
535
+ fetchPlaceholderMessages,
536
+ stopPlaceholderAnimation,
412
537
  isChatOpen,
413
538
  setIsChatOpen,
414
539
  isSessionHistoryOpen,
415
540
  setSessionHistoryOpen,
416
541
  sendMessage,
417
542
  userMessageInput,
543
+ userMessagePlaceholder,
418
544
  chatMessages: computed(() => currentChat.value?.messages || []),
419
545
  trimmedUserMessage,
420
546
  isResponseInProgress,
@@ -111,7 +111,7 @@
111
111
  'min-h-12 w-full resize-none overflow-hidden border text-lightInputText dark:text-darkInputText rounded-md bg-transparent text-sm bg-gray-50 dark:bg-gray-700 dark:border-gray-600 focus:outline-none',
112
112
  agentStore.availableModes.length > 1 ? 'p-4 pr-12 pb-12' : 'p-4 pr-12',
113
113
  ]"
114
- placeholder="Type a message..."
114
+ :placeholder="agentStore.userMessagePlaceholder"
115
115
  @keydown.enter.exact.prevent="sendMessage"
116
116
  />
117
117
  <div
@@ -74,7 +74,7 @@
74
74
  <script setup lang="ts">
75
75
  import Message from './Message.vue';
76
76
  import type { IMessage, IPart } from './types';
77
- import { useTemplateRef, ref, defineAsyncComponent, onMounted, watch, computed } from 'vue';
77
+ import { useTemplateRef, ref, defineAsyncComponent, onMounted, onUnmounted, watch, computed } from 'vue';
78
78
  import { IconArrowDownOutline } from '@iconify-prerendered/vue-flowbite';
79
79
  import SessionsHistory from './SessionsHistory.vue';
80
80
  import { useAgentStore } from './composables/useAgentStore';
@@ -98,6 +98,11 @@ function recalculateScroll() {
98
98
 
99
99
  onMounted(async () => {
100
100
  await import('@incremark/theme/styles.css')
101
+ await agentStore.fetchPlaceholderMessages()
102
+ });
103
+
104
+ onUnmounted(() => {
105
+ agentStore.stopPlaceholderAnimation();
101
106
  });
102
107
 
103
108
  watch(scrollContainer, () => {
@@ -12,6 +12,11 @@ type AgentMode = {
12
12
  name: string;
13
13
  };
14
14
 
15
+ const DEFAULT_TEXTAREA_PLACEHOLDER = 'Type a message...';
16
+ const PLACEHOLDER_TYPING_DELAY_MS = 60;
17
+ const PLACEHOLDER_DELETING_DELAY_MS = 35;
18
+ const PLACEHOLDER_HOLD_DELAY_MS = 3000;
19
+
15
20
  export const useAgentStore = defineStore('agent', () => {
16
21
  const DEFAULT_CHAT_WIDTH = 600;
17
22
  const MAX_WIDTH = 800;
@@ -25,8 +30,10 @@ export const useAgentStore = defineStore('agent', () => {
25
30
  const adminforth = useAdminforth();
26
31
  const isChatOpen = ref(false);
27
32
  const isSessionHistoryOpen = ref(false);
28
- const textInput = ref<HTMLInputElement | null>(null);
33
+ const textInput = ref<HTMLTextAreaElement | null>(null);
29
34
  const userMessageInput = ref();
35
+ const userMessagePlaceholder = ref(DEFAULT_TEXTAREA_PLACEHOLDER);
36
+ const placeholderMessages = ref<string[]>([]);
30
37
  const trimmedUserMessage = computed(() => userMessageInput.value ? userMessageInput.value.trim() : '');
31
38
  const lastMessage = ref('');
32
39
  const isTeleportedToBody = ref(false);
@@ -40,6 +47,9 @@ export const useAgentStore = defineStore('agent', () => {
40
47
  const chatWidth = ref(DEFAULT_CHAT_WIDTH);
41
48
  const availableModes = ref<AgentMode[]>([]);
42
49
  const activeModeName = ref<string | null>(null);
50
+ const hasTypedMessageInPageSession = ref(false);
51
+ let placeholderAnimationTimer: ReturnType<typeof setTimeout> | null = null;
52
+
43
53
  function setLocalStorageItem(key: string, value: string) {
44
54
  window.localStorage.setItem(`${coreStore.config.brandName || 'adminforth'}-${key}`, value);
45
55
  }
@@ -60,6 +70,16 @@ export const useAgentStore = defineStore('agent', () => {
60
70
  setLocalStorageItem('lastSessionId', newVal);
61
71
  }
62
72
  })
73
+ watch(userMessageInput, (newVal: unknown) => {
74
+ if (hasTypedMessageInPageSession.value) {
75
+ return;
76
+ }
77
+
78
+ if (typeof newVal === 'string' && newVal.trim() !== '') {
79
+ hasTypedMessageInPageSession.value = true;
80
+ stopPlaceholderAnimation();
81
+ }
82
+ })
63
83
  onMounted(() => {
64
84
  const chatWidthBeforeFullScreen = parseInt(getLocalStorageItem('chatWidthBeforeFullScreen') || '0', 10);
65
85
  if (chatWidthBeforeFullScreen) {
@@ -130,14 +150,14 @@ export const useAgentStore = defineStore('agent', () => {
130
150
  function setAvailableModes(modes: AgentMode[], defaultModeName?: string | null) {
131
151
  availableModes.value = modes;
132
152
  activeModeName.value =
133
- modes.find((mode) => mode.name === activeModeName.value)?.name
153
+ modes.find((mode: AgentMode) => mode.name === activeModeName.value)?.name
134
154
  ?? defaultModeName
135
155
  ?? modes[0]?.name
136
156
  ?? null;
137
157
  }
138
158
 
139
159
  function setActiveMode(modeName: string) {
140
- if (!availableModes.value.some((mode) => mode.name === modeName)) {
160
+ if (!availableModes.value.some((mode: AgentMode) => mode.name === modeName)) {
141
161
  return;
142
162
  }
143
163
 
@@ -179,6 +199,73 @@ export const useAgentStore = defineStore('agent', () => {
179
199
  }
180
200
 
181
201
  }
202
+
203
+ function clearPlaceholderAnimationTimer() {
204
+ if (placeholderAnimationTimer !== null) {
205
+ clearTimeout(placeholderAnimationTimer);
206
+ placeholderAnimationTimer = null;
207
+ }
208
+ }
209
+
210
+ function resetPlaceholder() {
211
+ clearPlaceholderAnimationTimer();
212
+ userMessagePlaceholder.value = DEFAULT_TEXTAREA_PLACEHOLDER;
213
+ }
214
+
215
+ function stopPlaceholderAnimation() {
216
+ resetPlaceholder();
217
+ }
218
+
219
+ function startPlaceholderAnimation(messages: string[]) {
220
+ clearPlaceholderAnimationTimer();
221
+
222
+ if (!messages.length) {
223
+ userMessagePlaceholder.value = DEFAULT_TEXTAREA_PLACEHOLDER;
224
+ return;
225
+ }
226
+
227
+ let messageIndex = 0;
228
+ let visibleLength = 0;
229
+ let isDeleting = false;
230
+
231
+ const animate = () => {
232
+ const currentMessage = messages[messageIndex];
233
+
234
+ if (!currentMessage) {
235
+ resetPlaceholder();
236
+ return;
237
+ }
238
+
239
+ if (!isDeleting) {
240
+ visibleLength += 1;
241
+ userMessagePlaceholder.value = currentMessage.slice(0, visibleLength);
242
+
243
+ if (visibleLength >= currentMessage.length) {
244
+ isDeleting = true;
245
+ placeholderAnimationTimer = setTimeout(animate, PLACEHOLDER_HOLD_DELAY_MS);
246
+ return;
247
+ }
248
+
249
+ placeholderAnimationTimer = setTimeout(animate, PLACEHOLDER_TYPING_DELAY_MS);
250
+ return;
251
+ }
252
+
253
+ visibleLength -= 1;
254
+ userMessagePlaceholder.value = currentMessage.slice(0, Math.max(visibleLength, 0));
255
+
256
+ if (visibleLength <= 0) {
257
+ isDeleting = false;
258
+ messageIndex = (messageIndex + 1) % messages.length;
259
+ placeholderAnimationTimer = setTimeout(animate, PLACEHOLDER_TYPING_DELAY_MS);
260
+ return;
261
+ }
262
+
263
+ placeholderAnimationTimer = setTimeout(animate, PLACEHOLDER_DELETING_DELAY_MS);
264
+ };
265
+
266
+ animate();
267
+ }
268
+
182
269
  const isResponseInProgress = computed( () => {
183
270
  return currentChat.value?.status === 'streaming';
184
271
  });
@@ -234,10 +321,46 @@ export const useAgentStore = defineStore('agent', () => {
234
321
  function setSessionHistoryOpen(isOpen: boolean) {
235
322
  isSessionHistoryOpen.value = isOpen;
236
323
  }
237
- function regisrerTextInput(el: HTMLInputElement | null) {
324
+ function regisrerTextInput(el: HTMLTextAreaElement | null) {
238
325
  textInput.value = el;
239
326
  }
240
327
 
328
+ async function fetchPlaceholderMessages() {
329
+ if (hasTypedMessageInPageSession.value) {
330
+ stopPlaceholderAnimation();
331
+ return;
332
+ }
333
+
334
+ try {
335
+ const res = await callAdminForthApi({
336
+ method: 'POST',
337
+ path: '/agent/get-placeholder-messages',
338
+ });
339
+
340
+ if (res.error) {
341
+ console.error('Error fetching placeholder messages:', res.error);
342
+ placeholderMessages.value = [];
343
+ resetPlaceholder();
344
+ return;
345
+ }
346
+
347
+ placeholderMessages.value = Array.isArray(res.messages)
348
+ ? res.messages.filter((message: unknown): message is string => typeof message === 'string' && message.length > 0)
349
+ : [];
350
+
351
+ if (!placeholderMessages.value.length) {
352
+ resetPlaceholder();
353
+ return;
354
+ }
355
+
356
+ startPlaceholderAnimation(placeholderMessages.value);
357
+ } catch (error) {
358
+ console.error('Error fetching placeholder messages', error);
359
+ placeholderMessages.value = [];
360
+ resetPlaceholder();
361
+ }
362
+ }
363
+
241
364
 
242
365
  //create a pre-session, until user will type something, so we can save session
243
366
  async function createPreSession() {
@@ -409,12 +532,15 @@ export const useAgentStore = defineStore('agent', () => {
409
532
  createPreSession,
410
533
  //____________________________________________
411
534
  regisrerTextInput,
535
+ fetchPlaceholderMessages,
536
+ stopPlaceholderAnimation,
412
537
  isChatOpen,
413
538
  setIsChatOpen,
414
539
  isSessionHistoryOpen,
415
540
  setSessionHistoryOpen,
416
541
  sendMessage,
417
542
  userMessageInput,
543
+ userMessagePlaceholder,
418
544
  chatMessages: computed(() => currentChat.value?.messages || []),
419
545
  trimmedUserMessage,
420
546
  isResponseInProgress,
package/dist/index.js CHANGED
@@ -133,6 +133,31 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
133
133
  return `single`;
134
134
  }
135
135
  setupEndpoints(server) {
136
+ server.endpoint({
137
+ method: 'POST',
138
+ path: `/agent/get-placeholder-messages`,
139
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, query, headers, cookies, adminUser, response, requestUrl }) {
140
+ if (!this.options.placeholderMessages) {
141
+ return {
142
+ messages: [],
143
+ };
144
+ }
145
+ const messages = yield this.options.placeholderMessages({
146
+ adminUser,
147
+ httpExtra: {
148
+ body,
149
+ query,
150
+ headers,
151
+ cookies,
152
+ requestUrl,
153
+ response,
154
+ },
155
+ });
156
+ return {
157
+ messages,
158
+ };
159
+ })
160
+ });
136
161
  server.endpoint({
137
162
  method: 'POST',
138
163
  path: `/agent/response`,
package/index.ts CHANGED
@@ -147,6 +147,33 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
147
147
  }
148
148
 
149
149
  setupEndpoints(server: IHttpServer) {
150
+ server.endpoint({
151
+ method: 'POST',
152
+ path: `/agent/get-placeholder-messages`,
153
+ handler: async ({ body, query, headers, cookies, adminUser, response, requestUrl }) => {
154
+ if (!this.options.placeholderMessages) {
155
+ return {
156
+ messages: [],
157
+ };
158
+ }
159
+
160
+ const messages = await this.options.placeholderMessages({
161
+ adminUser,
162
+ httpExtra: {
163
+ body,
164
+ query,
165
+ headers,
166
+ cookies,
167
+ requestUrl,
168
+ response,
169
+ },
170
+ });
171
+
172
+ return {
173
+ messages,
174
+ };
175
+ }
176
+ });
150
177
  server.endpoint({
151
178
  method: 'POST',
152
179
  path: `/agent/response`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "1.14.1",
3
+ "version": "1.15.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
package/types.ts CHANGED
@@ -1,4 +1,9 @@
1
- import { type PluginsCommonOptions, type CompletionAdapter } from "adminforth";
1
+ import {
2
+ type PluginsCommonOptions,
3
+ type CompletionAdapter,
4
+ type AdminUser,
5
+ type HttpExtra,
6
+ } from "adminforth";
2
7
 
3
8
  interface ISessionResource {
4
9
  resourceId: string;
@@ -20,6 +25,15 @@ interface ITurnResource {
20
25
  }
21
26
 
22
27
  export interface PluginOptions extends PluginsCommonOptions {
28
+ /**
29
+ * Optional placeholder examples to preload for the chat textarea.
30
+ * They are resolved once when the chat frontend loads.
31
+ */
32
+ placeholderMessages?: ((input: {
33
+ adminUser: AdminUser;
34
+ httpExtra: HttpExtra;
35
+ }) => string[] | Promise<string[]>);
36
+
23
37
  /**
24
38
  * Modes for the plugin.
25
39
  * Each mode can have its own configuration.