@adminforth/agent 1.27.2 → 1.27.3

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
@@ -38,5 +38,5 @@ custom/skills/fetch_data/SKILL.md
38
38
  custom/skills/mutate_data/
39
39
  custom/skills/mutate_data/SKILL.md
40
40
 
41
- sent 208,293 bytes received 562 bytes 417,710.00 bytes/sec
42
- total size is 206,013 speedup is 0.99
41
+ sent 207,560 bytes received 562 bytes 416,244.00 bytes/sec
42
+ total size is 205,246 speedup is 0.99
@@ -55,18 +55,9 @@ import { useAgentStore } from './composables/useAgentStore';
55
55
  const agentStore = useAgentStore();
56
56
 
57
57
  const h3Style = "text-gray-800 dark:text-gray-200 font-medium text-xl tracking-widest my-2"
58
-
59
58
  const dayLabelFormatter = new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric' });
60
59
  const dayLabelWithYearFormatter = new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
61
60
 
62
- function getLocalDayKey(date: Date) {
63
- const year = date.getFullYear();
64
- const month = `${date.getMonth() + 1}`.padStart(2, '0');
65
- const day = `${date.getDate()}`.padStart(2, '0');
66
-
67
- return `${year}-${month}-${day}`;
68
- }
69
-
70
61
  const groupedSessions = computed(() => {
71
62
  const groups = new Map<string, { dayKey: string; label: string; sessions: ISessionsListItem[] }>();
72
63
 
@@ -91,4 +82,13 @@ const groupedSessions = computed(() => {
91
82
  return Array.from(groups.values());
92
83
  });
93
84
 
85
+
86
+ function getLocalDayKey(date: Date) {
87
+ const year = date.getFullYear();
88
+ const month = `${date.getMonth() + 1}`.padStart(2, '0');
89
+ const day = `${date.getDate()}`.padStart(2, '0');
90
+
91
+ return `${year}-${month}-${day}`;
92
+ }
93
+
94
94
  </script>
@@ -89,9 +89,8 @@ const agentStore = useAgentStore();
89
89
  const agentTransitions = useAgentTransitions();
90
90
  const showScrollContainer = ref(true);
91
91
  const chatContainerRef = ref<HTMLElement | null>(null);
92
-
93
92
  const messagesRefs = ref<Array<HTMLElement | null>>([]);
94
-
93
+ const useWaitingForHeight = ref(false);
95
94
 
96
95
  /*
97
96
  * Whenever user sends a message, it adds a bottom spacer, that takes the remaining height
@@ -116,15 +115,60 @@ let messageResizeObserver: ResizeObserver | null = null;
116
115
  let observedLastUserMessageElement: HTMLElement | null = null;
117
116
  let observedLastAgentMessageElement: HTMLElement | null = null;
118
117
 
119
- function resetSpacer() {
120
- showBottomSpacer.value = false;
121
- spacerHeight.value = 0;
122
- }
118
+ onMounted(async () => {
119
+ messageResizeObserver = new ResizeObserver(() => {
120
+ updateSpacerHeight();
121
+ });
122
+
123
+ await import('@incremark/theme/styles.css')
124
+ await agentStore.fetchPlaceholderMessages()
125
+ await refreshSpacerTracking();
126
+ });
127
+
128
+ onUnmounted(() => {
129
+ if (innerScrollContainerRef.value) {
130
+ innerScrollContainerRef.value.removeEventListener('scroll', recalculateScroll);
131
+ }
132
+
133
+ stopObservingLastMessages();
134
+ messageResizeObserver?.disconnect();
135
+ agentStore.stopPlaceholderAnimation();
136
+ });
123
137
 
124
138
  watch(() => agentStore.activeSessionId, () => {
125
139
  resetSpacer();
126
140
  });
127
141
 
142
+ watch(() => agentStore.activeSessionId, async () => {
143
+ showScrollContainer.value = false;
144
+ await nextTick();
145
+ showScrollContainer.value = true;
146
+ await refreshSpacerTracking();
147
+ recalculateScroll();
148
+ });
149
+
150
+ watch(() => props.messages.length, async () => {
151
+ await refreshSpacerTracking();
152
+ });
153
+
154
+ watch(scrollContainer, async () => {
155
+ if (innerScrollContainerRef.value) {
156
+ innerScrollContainerRef.value.removeEventListener('scroll', recalculateScroll);
157
+ }
158
+
159
+ if (scrollContainer.value) {
160
+ innerScrollContainerRef.value = scrollContainer.value.container.scrollEl;
161
+
162
+ innerScrollContainerRef.value.addEventListener('scroll', recalculateScroll);
163
+ await refreshSpacerTracking();
164
+ }
165
+ })
166
+
167
+ function resetSpacer() {
168
+ showBottomSpacer.value = false;
169
+ spacerHeight.value = 0;
170
+ }
171
+
128
172
  function getLastMessageElement(role: 'user' | 'assistant') {
129
173
  const lastMessageIndex = props.messages.findLastIndex((message: IMessage) => message.role === role);
130
174
  return messagesRefs.value[lastMessageIndex] ?? null;
@@ -156,7 +200,6 @@ async function waitForRealHeight(role: 'user' | 'assistant'): Promise<number> {
156
200
  });
157
201
  }
158
202
 
159
- const useWaitingForHeight = ref(false);
160
203
  async function updateSpacerHeight() {
161
204
  if (!showBottomSpacer.value) {
162
205
  return;
@@ -238,51 +281,4 @@ function recalculateScroll() {
238
281
  }
239
282
  }
240
283
 
241
- watch(() => agentStore.activeSessionId, async () => {
242
- showScrollContainer.value = false;
243
- await nextTick();
244
- showScrollContainer.value = true;
245
- await refreshSpacerTracking();
246
- recalculateScroll();
247
- });
248
-
249
- watch(() => props.messages.length, async () => {
250
- await refreshSpacerTracking();
251
- });
252
-
253
- onMounted(async () => {
254
- messageResizeObserver = new ResizeObserver(() => {
255
- updateSpacerHeight();
256
- });
257
-
258
- await import('@incremark/theme/styles.css')
259
- await agentStore.fetchPlaceholderMessages()
260
- await refreshSpacerTracking();
261
- });
262
-
263
- onUnmounted(() => {
264
- if (innerScrollContainerRef.value) {
265
- innerScrollContainerRef.value.removeEventListener('scroll', recalculateScroll);
266
- }
267
-
268
- stopObservingLastMessages();
269
- messageResizeObserver?.disconnect();
270
- agentStore.stopPlaceholderAnimation();
271
- });
272
-
273
- watch(scrollContainer, async () => {
274
- if (innerScrollContainerRef.value) {
275
- innerScrollContainerRef.value.removeEventListener('scroll', recalculateScroll);
276
- }
277
-
278
- if (scrollContainer.value) {
279
- innerScrollContainerRef.value = scrollContainer.value.container.scrollEl;
280
-
281
- innerScrollContainerRef.value.addEventListener('scroll', recalculateScroll);
282
- await refreshSpacerTracking();
283
- }
284
- })
285
-
286
-
287
-
288
284
  </script>
@@ -4,7 +4,7 @@
4
4
  class="ml-2 px-4 flex items-center gap-1 cursor-pointer select-none hover:opacity-80 tracking-wide font-medium text-sm text-listTableHeadingText dark:text-darkListTableHeadingText"
5
5
  @click="isExpanded = !isExpanded"
6
6
  >
7
- Thoughts
7
+ {{ $t('Thoughts') }}
8
8
  <span v-if="thinkingDuration > 0">({{ (thinkingDuration/1000).toFixed(2) }} s)</span>
9
9
  <ThreeDotsAnimation v-if="isResponseInProgress || showFakeThinkingMessage" />
10
10
  <IconAngleDownOutline
@@ -59,11 +59,27 @@
59
59
  const thinkingDuration = ref(0);
60
60
  const scrollContainerRef = ref<InstanceType<typeof CustomAutoScrollContainer> | null>(null);
61
61
  const innerScrollContainerRef = ref<HTMLElement | null>(null);
62
+ const isExpanded = ref(true);
63
+ const ToolOrReasoningParts = computed(() => {
64
+ return props.message.parts.filter((part: IPart) => part.type === 'data-tool-call' || part.type === 'reasoning');
65
+ });
66
+ const isResponseInProgress = computed(() =>{
67
+ return props.isLastMessageInChat && agentStore.isResponseInProgress;
68
+ });
69
+
70
+ const showFakeThinkingMessage = computed(() => {
71
+ if (props.message.parts.length === 0) return true;
72
+ return false;
73
+ })
62
74
 
63
75
  onMounted(() => {
64
76
  thinkingStartTime.value = Date.now();
65
77
  })
66
78
 
79
+ onUnmounted(() => {
80
+ scrollContainerRef.value?.container.scrollEl?.removeEventListener('scroll', handleScroll);
81
+ })
82
+
67
83
  watch(scrollContainerRef, async () => {
68
84
  if (innerScrollContainerRef.value) {
69
85
  innerScrollContainerRef.value.removeEventListener('scroll', handleScroll);
@@ -75,34 +91,12 @@
75
91
  }
76
92
  })
77
93
 
78
- onUnmounted(() => {
79
- scrollContainerRef.value?.container.scrollEl?.removeEventListener('scroll', handleScroll);
80
- })
81
-
82
- function handleScroll() {
83
- scrollContainerRef.value?.handleScroll();
84
- }
85
-
86
- const ToolOrReasoningParts = computed(() => {
87
- return props.message.parts.filter((part: IPart) => part.type === 'data-tool-call' || part.type === 'reasoning');
88
- });
89
- const isExpanded = ref(true);
90
-
91
- const isResponseInProgress = computed(() =>{
92
- return props.isLastMessageInChat && agentStore.isResponseInProgress;
93
- });
94
-
95
94
  watch(isResponseInProgress, (newValue: boolean) => {
96
95
  if (!newValue) {
97
96
  isExpanded.value = false;
98
97
  thinkingDuration.value = Date.now() - (thinkingStartTime.value ?? Date.now());
99
98
  }
100
99
  });
101
-
102
- const showFakeThinkingMessage = computed(() => {
103
- if (props.message.parts.length === 0) return true;
104
- return false;
105
- })
106
100
 
107
101
  const formatToolCallPart = (part: IPart, currentMessage: IMessage): IFormattedToolCallPart | null => {
108
102
  if (part.type !== 'data-tool-call' || part.data?.phase !== 'start') {
@@ -182,7 +176,9 @@
182
176
  return groupedParts;
183
177
  };
184
178
 
185
-
179
+ function handleScroll() {
180
+ scrollContainerRef.value?.handleScroll();
181
+ }
186
182
  </script>
187
183
 
188
184
  <style scoped>
@@ -35,17 +35,16 @@ import type { IPart } from '../types';
35
35
  import { ref, computed, watch, defineAsyncComponent } from 'vue';
36
36
  import ThreeDotsAnimation from './ThreeDotsAnimation.vue';
37
37
  import { extractTitleAndTextFromReasoning } from '../utils';
38
- import CustomAutoScrollContainer from '../CustomAutoScrollContainer.vue';
39
-
40
- const IncremarkContent = defineAsyncComponent(() => import('@incremark/vue').then(module => module.IncremarkContent))
41
38
 
42
39
  const props = defineProps<{
43
40
  state?: IPart['state']
44
41
  text?: string
45
42
  }>();
46
43
 
47
- const isStreaming = computed(() => props.state === 'streaming');
44
+ const IncremarkContent = defineAsyncComponent(() => import('@incremark/vue').then(module => module.IncremarkContent))
45
+
48
46
  const isExpanded = ref(true);
47
+ const isStreaming = computed(() => props.state === 'streaming');
49
48
  const parsedReasoning = computed(() => extractTitleAndTextFromReasoning(props.text ?? ''));
50
49
  const reasoningTitle = computed(() => parsedReasoning.value.title ?? '');
51
50
  const reasoningText = computed(() => parsedReasoning.value.body);
@@ -56,8 +55,6 @@ watch(() => props.state, (newValue: IPart['state']) => {
56
55
  }
57
56
  });
58
57
 
59
-
60
-
61
58
  </script>
62
59
 
63
60
 
@@ -17,27 +17,43 @@
17
17
  :incremark-options="incremarkOptions"
18
18
  />
19
19
  <p v-else class="text-red-500 py-2">
20
- Error occurred
20
+ {{ $t('Error occurred') }}
21
21
  </p>
22
22
  </div>
23
23
  </template>
24
24
 
25
- <script setup lang="ts">
25
+ <script setup lang="ts">const isExpanded = ref(true);
26
+
26
27
  import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue';
27
28
  import { useRouter } from 'vue-router';
28
29
  import { useAgentStore } from '../composables/useAgentStore';
29
30
  import { useCoreStore } from '@/stores/core';
30
31
 
32
+ const props = defineProps<{
33
+ message: string | undefined,
34
+ state: string | undefined,
35
+ role: 'user' | 'assistant'
36
+ }>();
37
+
38
+ const emit = defineEmits(['toggle-thoughts']);
39
+
31
40
  const IncremarkContent = defineAsyncComponent(() => import('@incremark/vue').then(module => module.IncremarkContent))
32
41
  const ShikiCodeBlock = defineAsyncComponent(() => import('../incremark_code_renderers/IncremarkShikiCodeBlock.vue'))
33
42
 
34
43
  const agentStore = useAgentStore();
35
44
  const coreStore = useCoreStore();
45
+ const router = useRouter();
46
+
47
+ const isThoughtsExpanded = ref(true);
48
+
49
+ const content = computed(() => props.message)
50
+ const isFinished = computed(() => props.state === 'done')
51
+ const hasVegaLite = computed(() => props.message?.includes('```vega-lite'))
52
+ const isStateStreaming = computed(() => props.state === 'streaming')
36
53
 
37
54
  const incremarkComponents = {
38
55
  code: ShikiCodeBlock,
39
56
  };
40
-
41
57
  const incremarkOptions = {
42
58
  gfm: true,
43
59
  math: { tex: true },
@@ -45,27 +61,10 @@
45
61
  htmlTree: true,
46
62
  };
47
63
 
48
- const router = useRouter();
49
-
50
64
  onMounted(async () => {
51
65
  void import('katex/dist/katex.min.css')
52
66
  })
53
67
 
54
- const props = defineProps<{
55
- message: string | undefined,
56
- state: string | undefined,
57
- role: 'user' | 'assistant'
58
- }>();
59
-
60
- const emit = defineEmits(['toggle-thoughts']);
61
-
62
- const content = computed(() => props.message)
63
- const isFinished = computed(() => props.state === 'done')
64
- const isThoughtsExpanded = ref(true);
65
- const hasVegaLite = computed(() => props.message?.includes('```vega-lite'))
66
-
67
- const isStateStreaming = computed(() => props.state === 'streaming')
68
-
69
68
  watch(isStateStreaming, (newValue: boolean) => {
70
69
  if (!newValue) {
71
70
  isThoughtsExpanded.value = false;
@@ -125,12 +124,6 @@
125
124
 
126
125
  </script>
127
126
 
128
- <style lang="scss">
129
- .incremark-paragraph {
130
- margin: 8px 0;
131
- }
132
- </style>
133
-
134
127
  <style lang="scss">
135
128
  .incremark a.incremark-link,
136
129
  .incremark a.incremark-link:visited {
@@ -173,4 +166,8 @@ a.incremark-link::after {
173
166
  .incremark-list {
174
167
  list-style: disc;
175
168
  }
169
+
170
+ .incremark-paragraph {
171
+ margin: 8px 0;
172
+ }
176
173
  </style>
@@ -24,10 +24,6 @@
24
24
  </div>
25
25
 
26
26
  <div class="min-w-0">
27
- <!-- <p class="text-xs text-gray-500 dark:text-gray-400 font-bold">
28
- {{ statusLabel }}
29
- <span v-if="props.data?.toolInfo?.durationMs" class="text-xs">({{ (props.data.toolInfo.durationMs / 1000).toFixed(2) }}s)</span>
30
- </p> -->
31
27
  <p class="break-all font-mono text-sm leading-5 text-nowrap">
32
28
  {{ props.data?.toolInfo?.toolInfo ? props.data.toolInfo.toolInfo : props.data?.toolInfo?.toolName}}
33
29
  </p>
@@ -67,27 +63,57 @@
67
63
  import { Spinner } from '@/afcl';
68
64
  import { IconAngleDownOutline, IconCheckOutline } from '@iconify-prerendered/vue-flowbite';
69
65
 
66
+ const props = defineProps<{
67
+ data: IFormattedToolCallPart
68
+ }>();
69
+
70
+ interface IToolSection {
71
+ label: string;
72
+ lines: Array<{
73
+ number: number;
74
+ content: string;
75
+ }>;
76
+ }
77
+
70
78
  const isInputOutputExpanded = ref(false);
71
79
  const activateShrinkedStyle = ref(true);
72
- const isAnimatingShrinkFinal = ref(false);
73
80
  const toolRendererInitialWidth = ref<number | null>(null);
74
81
  const toolRendererRef = ref<HTMLElement | null>(null);
75
82
  const activateFullWidth = ref(false);
76
83
  const blockClicksDuringAnimation = ref(false);
77
84
  const ANIMATION_DURATION = 300;
78
85
 
86
+ const isRunning = computed(() => props.data?.toolInfo?.phase === 'start');
87
+ const toolSections = computed<IToolSection[]>(() => {
88
+ const sections = [
89
+ {
90
+ label: 'Input',
91
+ content: normalizeToolPayload(props.data.toolInfo.input),
92
+ },
93
+ {
94
+ label: 'Output',
95
+ content: normalizeToolPayload(props.data.toolInfo.output),
96
+ },
97
+ ];
98
+
99
+ return sections
100
+ .filter((section): section is { label: string; content: string } => Boolean(section.content))
101
+ .map(section => ({
102
+ label: section.label,
103
+ lines: section.content.split('\n').map((content, index) => ({
104
+ number: index + 1,
105
+ content,
106
+ })),
107
+ }));
108
+ });
109
+ const hasToolSections = computed(() => toolSections.value.length > 0);
110
+
79
111
  onMounted(() => {
80
112
  if (toolRendererRef.value) {
81
113
  toolRendererInitialWidth.value = toolRendererRef.value.offsetWidth;
82
114
  }
83
115
  });
84
116
 
85
- function finishTransition() {
86
- if (!isInputOutputExpanded.value) {
87
- activateFullWidth.value = false;
88
- activateShrinkedStyle.value = true;
89
- }
90
- }
91
117
  watch(isInputOutputExpanded, (newValue) => {
92
118
  if (newValue) {
93
119
  activateShrinkedStyle.value = false;
@@ -95,6 +121,13 @@
95
121
  }
96
122
  });
97
123
 
124
+ function finishTransition() {
125
+ if (!isInputOutputExpanded.value) {
126
+ activateFullWidth.value = false;
127
+ activateShrinkedStyle.value = true;
128
+ }
129
+ }
130
+
98
131
  function toggleInputOutput() {
99
132
  if (blockClicksDuringAnimation.value) return;
100
133
  isInputOutputExpanded.value = !isInputOutputExpanded.value;
@@ -104,21 +137,6 @@
104
137
  }, ANIMATION_DURATION);
105
138
  }
106
139
 
107
- interface IToolSection {
108
- label: string;
109
- lines: Array<{
110
- number: number;
111
- content: string;
112
- }>;
113
- }
114
-
115
- const props = defineProps<{
116
- data: IFormattedToolCallPart
117
- }>();
118
-
119
- const isRunning = computed(() => props.data?.toolInfo?.phase === 'start');
120
- const statusLabel = computed(() => isRunning.value ? 'Running tool' : 'Tool finished');
121
-
122
140
  const normalizeToolPayload = (value: unknown): string | null => {
123
141
  if (value === null || value === undefined || value === '') {
124
142
  return null;
@@ -131,30 +149,6 @@
131
149
  return JSON.stringify(value, null, 2);
132
150
  };
133
151
 
134
- const toolSections = computed<IToolSection[]>(() => {
135
- const sections = [
136
- {
137
- label: 'Input',
138
- content: normalizeToolPayload(props.data.toolInfo.input),
139
- },
140
- {
141
- label: 'Output',
142
- content: normalizeToolPayload(props.data.toolInfo.output),
143
- },
144
- ];
145
-
146
- return sections
147
- .filter((section): section is { label: string; content: string } => Boolean(section.content))
148
- .map(section => ({
149
- label: section.label,
150
- lines: section.content.split('\n').map((content, index) => ({
151
- number: index + 1,
152
- content,
153
- })),
154
- }));
155
- });
156
-
157
- const hasToolSections = computed(() => toolSections.value.length > 0);
158
152
  </script>
159
153
 
160
154
 
@@ -9,7 +9,7 @@
9
9
  <h3
10
10
  class="flex items-center mb-1 text-sm my-2 ml-3 gap-1 text-listTableHeadingText dark:text-darkListTableHeadingText"
11
11
  >
12
- <span class="font-semibold select-none ">Call tools</span>
12
+ <span class="font-semibold select-none ">{{ $t('Call tools') }}</span>
13
13
  </h3>
14
14
  <div class="flex flex-wrap">
15
15
  <template v-for="group in props.toolGroup" :key="group.title">
@@ -23,24 +23,12 @@
23
23
  <script setup lang="ts">
24
24
  import ToolRenderer from './ToolRenderer.vue';
25
25
  import type { IToolGroup } from '../types';
26
- import { ref } from 'vue';
27
26
  import { IconWrenchSolid } from '@iconify-prerendered/vue-heroicons';
28
27
 
29
-
30
28
  const props = defineProps<{
31
29
  toolGroup: IToolGroup[]
32
30
  }>();
33
31
 
34
- const expandedGroups = ref<string[]>([]);
35
-
36
- function toggleGroup(groupTitle: string) {
37
- if (expandedGroups.value.includes(groupTitle)) {
38
- expandedGroups.value = expandedGroups.value.filter((title: string) => title !== groupTitle);
39
- } else {
40
- expandedGroups.value.push(groupTitle);
41
- }
42
- }
43
-
44
32
  </script>
45
33
 
46
34
  <style scoped>
@@ -12,7 +12,7 @@
12
12
  :disabled="!sourceCode"
13
13
  @click="copyCode"
14
14
  >
15
- {{ copied ? 'Copied' : 'Copy' }}
15
+ {{ copied ? $t('Copied') : $t('Copy') }}
16
16
  </button>
17
17
  </div>
18
18
 
@@ -55,18 +55,9 @@ import { useAgentStore } from './composables/useAgentStore';
55
55
  const agentStore = useAgentStore();
56
56
 
57
57
  const h3Style = "text-gray-800 dark:text-gray-200 font-medium text-xl tracking-widest my-2"
58
-
59
58
  const dayLabelFormatter = new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric' });
60
59
  const dayLabelWithYearFormatter = new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
61
60
 
62
- function getLocalDayKey(date: Date) {
63
- const year = date.getFullYear();
64
- const month = `${date.getMonth() + 1}`.padStart(2, '0');
65
- const day = `${date.getDate()}`.padStart(2, '0');
66
-
67
- return `${year}-${month}-${day}`;
68
- }
69
-
70
61
  const groupedSessions = computed(() => {
71
62
  const groups = new Map<string, { dayKey: string; label: string; sessions: ISessionsListItem[] }>();
72
63
 
@@ -91,4 +82,13 @@ const groupedSessions = computed(() => {
91
82
  return Array.from(groups.values());
92
83
  });
93
84
 
85
+
86
+ function getLocalDayKey(date: Date) {
87
+ const year = date.getFullYear();
88
+ const month = `${date.getMonth() + 1}`.padStart(2, '0');
89
+ const day = `${date.getDate()}`.padStart(2, '0');
90
+
91
+ return `${year}-${month}-${day}`;
92
+ }
93
+
94
94
  </script>
@@ -89,9 +89,8 @@ const agentStore = useAgentStore();
89
89
  const agentTransitions = useAgentTransitions();
90
90
  const showScrollContainer = ref(true);
91
91
  const chatContainerRef = ref<HTMLElement | null>(null);
92
-
93
92
  const messagesRefs = ref<Array<HTMLElement | null>>([]);
94
-
93
+ const useWaitingForHeight = ref(false);
95
94
 
96
95
  /*
97
96
  * Whenever user sends a message, it adds a bottom spacer, that takes the remaining height
@@ -116,15 +115,60 @@ let messageResizeObserver: ResizeObserver | null = null;
116
115
  let observedLastUserMessageElement: HTMLElement | null = null;
117
116
  let observedLastAgentMessageElement: HTMLElement | null = null;
118
117
 
119
- function resetSpacer() {
120
- showBottomSpacer.value = false;
121
- spacerHeight.value = 0;
122
- }
118
+ onMounted(async () => {
119
+ messageResizeObserver = new ResizeObserver(() => {
120
+ updateSpacerHeight();
121
+ });
122
+
123
+ await import('@incremark/theme/styles.css')
124
+ await agentStore.fetchPlaceholderMessages()
125
+ await refreshSpacerTracking();
126
+ });
127
+
128
+ onUnmounted(() => {
129
+ if (innerScrollContainerRef.value) {
130
+ innerScrollContainerRef.value.removeEventListener('scroll', recalculateScroll);
131
+ }
132
+
133
+ stopObservingLastMessages();
134
+ messageResizeObserver?.disconnect();
135
+ agentStore.stopPlaceholderAnimation();
136
+ });
123
137
 
124
138
  watch(() => agentStore.activeSessionId, () => {
125
139
  resetSpacer();
126
140
  });
127
141
 
142
+ watch(() => agentStore.activeSessionId, async () => {
143
+ showScrollContainer.value = false;
144
+ await nextTick();
145
+ showScrollContainer.value = true;
146
+ await refreshSpacerTracking();
147
+ recalculateScroll();
148
+ });
149
+
150
+ watch(() => props.messages.length, async () => {
151
+ await refreshSpacerTracking();
152
+ });
153
+
154
+ watch(scrollContainer, async () => {
155
+ if (innerScrollContainerRef.value) {
156
+ innerScrollContainerRef.value.removeEventListener('scroll', recalculateScroll);
157
+ }
158
+
159
+ if (scrollContainer.value) {
160
+ innerScrollContainerRef.value = scrollContainer.value.container.scrollEl;
161
+
162
+ innerScrollContainerRef.value.addEventListener('scroll', recalculateScroll);
163
+ await refreshSpacerTracking();
164
+ }
165
+ })
166
+
167
+ function resetSpacer() {
168
+ showBottomSpacer.value = false;
169
+ spacerHeight.value = 0;
170
+ }
171
+
128
172
  function getLastMessageElement(role: 'user' | 'assistant') {
129
173
  const lastMessageIndex = props.messages.findLastIndex((message: IMessage) => message.role === role);
130
174
  return messagesRefs.value[lastMessageIndex] ?? null;
@@ -156,7 +200,6 @@ async function waitForRealHeight(role: 'user' | 'assistant'): Promise<number> {
156
200
  });
157
201
  }
158
202
 
159
- const useWaitingForHeight = ref(false);
160
203
  async function updateSpacerHeight() {
161
204
  if (!showBottomSpacer.value) {
162
205
  return;
@@ -238,51 +281,4 @@ function recalculateScroll() {
238
281
  }
239
282
  }
240
283
 
241
- watch(() => agentStore.activeSessionId, async () => {
242
- showScrollContainer.value = false;
243
- await nextTick();
244
- showScrollContainer.value = true;
245
- await refreshSpacerTracking();
246
- recalculateScroll();
247
- });
248
-
249
- watch(() => props.messages.length, async () => {
250
- await refreshSpacerTracking();
251
- });
252
-
253
- onMounted(async () => {
254
- messageResizeObserver = new ResizeObserver(() => {
255
- updateSpacerHeight();
256
- });
257
-
258
- await import('@incremark/theme/styles.css')
259
- await agentStore.fetchPlaceholderMessages()
260
- await refreshSpacerTracking();
261
- });
262
-
263
- onUnmounted(() => {
264
- if (innerScrollContainerRef.value) {
265
- innerScrollContainerRef.value.removeEventListener('scroll', recalculateScroll);
266
- }
267
-
268
- stopObservingLastMessages();
269
- messageResizeObserver?.disconnect();
270
- agentStore.stopPlaceholderAnimation();
271
- });
272
-
273
- watch(scrollContainer, async () => {
274
- if (innerScrollContainerRef.value) {
275
- innerScrollContainerRef.value.removeEventListener('scroll', recalculateScroll);
276
- }
277
-
278
- if (scrollContainer.value) {
279
- innerScrollContainerRef.value = scrollContainer.value.container.scrollEl;
280
-
281
- innerScrollContainerRef.value.addEventListener('scroll', recalculateScroll);
282
- await refreshSpacerTracking();
283
- }
284
- })
285
-
286
-
287
-
288
284
  </script>
@@ -4,7 +4,7 @@
4
4
  class="ml-2 px-4 flex items-center gap-1 cursor-pointer select-none hover:opacity-80 tracking-wide font-medium text-sm text-listTableHeadingText dark:text-darkListTableHeadingText"
5
5
  @click="isExpanded = !isExpanded"
6
6
  >
7
- Thoughts
7
+ {{ $t('Thoughts') }}
8
8
  <span v-if="thinkingDuration > 0">({{ (thinkingDuration/1000).toFixed(2) }} s)</span>
9
9
  <ThreeDotsAnimation v-if="isResponseInProgress || showFakeThinkingMessage" />
10
10
  <IconAngleDownOutline
@@ -59,11 +59,27 @@
59
59
  const thinkingDuration = ref(0);
60
60
  const scrollContainerRef = ref<InstanceType<typeof CustomAutoScrollContainer> | null>(null);
61
61
  const innerScrollContainerRef = ref<HTMLElement | null>(null);
62
+ const isExpanded = ref(true);
63
+ const ToolOrReasoningParts = computed(() => {
64
+ return props.message.parts.filter((part: IPart) => part.type === 'data-tool-call' || part.type === 'reasoning');
65
+ });
66
+ const isResponseInProgress = computed(() =>{
67
+ return props.isLastMessageInChat && agentStore.isResponseInProgress;
68
+ });
69
+
70
+ const showFakeThinkingMessage = computed(() => {
71
+ if (props.message.parts.length === 0) return true;
72
+ return false;
73
+ })
62
74
 
63
75
  onMounted(() => {
64
76
  thinkingStartTime.value = Date.now();
65
77
  })
66
78
 
79
+ onUnmounted(() => {
80
+ scrollContainerRef.value?.container.scrollEl?.removeEventListener('scroll', handleScroll);
81
+ })
82
+
67
83
  watch(scrollContainerRef, async () => {
68
84
  if (innerScrollContainerRef.value) {
69
85
  innerScrollContainerRef.value.removeEventListener('scroll', handleScroll);
@@ -75,34 +91,12 @@
75
91
  }
76
92
  })
77
93
 
78
- onUnmounted(() => {
79
- scrollContainerRef.value?.container.scrollEl?.removeEventListener('scroll', handleScroll);
80
- })
81
-
82
- function handleScroll() {
83
- scrollContainerRef.value?.handleScroll();
84
- }
85
-
86
- const ToolOrReasoningParts = computed(() => {
87
- return props.message.parts.filter((part: IPart) => part.type === 'data-tool-call' || part.type === 'reasoning');
88
- });
89
- const isExpanded = ref(true);
90
-
91
- const isResponseInProgress = computed(() =>{
92
- return props.isLastMessageInChat && agentStore.isResponseInProgress;
93
- });
94
-
95
94
  watch(isResponseInProgress, (newValue: boolean) => {
96
95
  if (!newValue) {
97
96
  isExpanded.value = false;
98
97
  thinkingDuration.value = Date.now() - (thinkingStartTime.value ?? Date.now());
99
98
  }
100
99
  });
101
-
102
- const showFakeThinkingMessage = computed(() => {
103
- if (props.message.parts.length === 0) return true;
104
- return false;
105
- })
106
100
 
107
101
  const formatToolCallPart = (part: IPart, currentMessage: IMessage): IFormattedToolCallPart | null => {
108
102
  if (part.type !== 'data-tool-call' || part.data?.phase !== 'start') {
@@ -182,7 +176,9 @@
182
176
  return groupedParts;
183
177
  };
184
178
 
185
-
179
+ function handleScroll() {
180
+ scrollContainerRef.value?.handleScroll();
181
+ }
186
182
  </script>
187
183
 
188
184
  <style scoped>
@@ -35,17 +35,16 @@ import type { IPart } from '../types';
35
35
  import { ref, computed, watch, defineAsyncComponent } from 'vue';
36
36
  import ThreeDotsAnimation from './ThreeDotsAnimation.vue';
37
37
  import { extractTitleAndTextFromReasoning } from '../utils';
38
- import CustomAutoScrollContainer from '../CustomAutoScrollContainer.vue';
39
-
40
- const IncremarkContent = defineAsyncComponent(() => import('@incremark/vue').then(module => module.IncremarkContent))
41
38
 
42
39
  const props = defineProps<{
43
40
  state?: IPart['state']
44
41
  text?: string
45
42
  }>();
46
43
 
47
- const isStreaming = computed(() => props.state === 'streaming');
44
+ const IncremarkContent = defineAsyncComponent(() => import('@incremark/vue').then(module => module.IncremarkContent))
45
+
48
46
  const isExpanded = ref(true);
47
+ const isStreaming = computed(() => props.state === 'streaming');
49
48
  const parsedReasoning = computed(() => extractTitleAndTextFromReasoning(props.text ?? ''));
50
49
  const reasoningTitle = computed(() => parsedReasoning.value.title ?? '');
51
50
  const reasoningText = computed(() => parsedReasoning.value.body);
@@ -56,8 +55,6 @@ watch(() => props.state, (newValue: IPart['state']) => {
56
55
  }
57
56
  });
58
57
 
59
-
60
-
61
58
  </script>
62
59
 
63
60
 
@@ -17,27 +17,43 @@
17
17
  :incremark-options="incremarkOptions"
18
18
  />
19
19
  <p v-else class="text-red-500 py-2">
20
- Error occurred
20
+ {{ $t('Error occurred') }}
21
21
  </p>
22
22
  </div>
23
23
  </template>
24
24
 
25
- <script setup lang="ts">
25
+ <script setup lang="ts">const isExpanded = ref(true);
26
+
26
27
  import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue';
27
28
  import { useRouter } from 'vue-router';
28
29
  import { useAgentStore } from '../composables/useAgentStore';
29
30
  import { useCoreStore } from '@/stores/core';
30
31
 
32
+ const props = defineProps<{
33
+ message: string | undefined,
34
+ state: string | undefined,
35
+ role: 'user' | 'assistant'
36
+ }>();
37
+
38
+ const emit = defineEmits(['toggle-thoughts']);
39
+
31
40
  const IncremarkContent = defineAsyncComponent(() => import('@incremark/vue').then(module => module.IncremarkContent))
32
41
  const ShikiCodeBlock = defineAsyncComponent(() => import('../incremark_code_renderers/IncremarkShikiCodeBlock.vue'))
33
42
 
34
43
  const agentStore = useAgentStore();
35
44
  const coreStore = useCoreStore();
45
+ const router = useRouter();
46
+
47
+ const isThoughtsExpanded = ref(true);
48
+
49
+ const content = computed(() => props.message)
50
+ const isFinished = computed(() => props.state === 'done')
51
+ const hasVegaLite = computed(() => props.message?.includes('```vega-lite'))
52
+ const isStateStreaming = computed(() => props.state === 'streaming')
36
53
 
37
54
  const incremarkComponents = {
38
55
  code: ShikiCodeBlock,
39
56
  };
40
-
41
57
  const incremarkOptions = {
42
58
  gfm: true,
43
59
  math: { tex: true },
@@ -45,27 +61,10 @@
45
61
  htmlTree: true,
46
62
  };
47
63
 
48
- const router = useRouter();
49
-
50
64
  onMounted(async () => {
51
65
  void import('katex/dist/katex.min.css')
52
66
  })
53
67
 
54
- const props = defineProps<{
55
- message: string | undefined,
56
- state: string | undefined,
57
- role: 'user' | 'assistant'
58
- }>();
59
-
60
- const emit = defineEmits(['toggle-thoughts']);
61
-
62
- const content = computed(() => props.message)
63
- const isFinished = computed(() => props.state === 'done')
64
- const isThoughtsExpanded = ref(true);
65
- const hasVegaLite = computed(() => props.message?.includes('```vega-lite'))
66
-
67
- const isStateStreaming = computed(() => props.state === 'streaming')
68
-
69
68
  watch(isStateStreaming, (newValue: boolean) => {
70
69
  if (!newValue) {
71
70
  isThoughtsExpanded.value = false;
@@ -125,12 +124,6 @@
125
124
 
126
125
  </script>
127
126
 
128
- <style lang="scss">
129
- .incremark-paragraph {
130
- margin: 8px 0;
131
- }
132
- </style>
133
-
134
127
  <style lang="scss">
135
128
  .incremark a.incremark-link,
136
129
  .incremark a.incremark-link:visited {
@@ -173,4 +166,8 @@ a.incremark-link::after {
173
166
  .incremark-list {
174
167
  list-style: disc;
175
168
  }
169
+
170
+ .incremark-paragraph {
171
+ margin: 8px 0;
172
+ }
176
173
  </style>
@@ -24,10 +24,6 @@
24
24
  </div>
25
25
 
26
26
  <div class="min-w-0">
27
- <!-- <p class="text-xs text-gray-500 dark:text-gray-400 font-bold">
28
- {{ statusLabel }}
29
- <span v-if="props.data?.toolInfo?.durationMs" class="text-xs">({{ (props.data.toolInfo.durationMs / 1000).toFixed(2) }}s)</span>
30
- </p> -->
31
27
  <p class="break-all font-mono text-sm leading-5 text-nowrap">
32
28
  {{ props.data?.toolInfo?.toolInfo ? props.data.toolInfo.toolInfo : props.data?.toolInfo?.toolName}}
33
29
  </p>
@@ -67,27 +63,57 @@
67
63
  import { Spinner } from '@/afcl';
68
64
  import { IconAngleDownOutline, IconCheckOutline } from '@iconify-prerendered/vue-flowbite';
69
65
 
66
+ const props = defineProps<{
67
+ data: IFormattedToolCallPart
68
+ }>();
69
+
70
+ interface IToolSection {
71
+ label: string;
72
+ lines: Array<{
73
+ number: number;
74
+ content: string;
75
+ }>;
76
+ }
77
+
70
78
  const isInputOutputExpanded = ref(false);
71
79
  const activateShrinkedStyle = ref(true);
72
- const isAnimatingShrinkFinal = ref(false);
73
80
  const toolRendererInitialWidth = ref<number | null>(null);
74
81
  const toolRendererRef = ref<HTMLElement | null>(null);
75
82
  const activateFullWidth = ref(false);
76
83
  const blockClicksDuringAnimation = ref(false);
77
84
  const ANIMATION_DURATION = 300;
78
85
 
86
+ const isRunning = computed(() => props.data?.toolInfo?.phase === 'start');
87
+ const toolSections = computed<IToolSection[]>(() => {
88
+ const sections = [
89
+ {
90
+ label: 'Input',
91
+ content: normalizeToolPayload(props.data.toolInfo.input),
92
+ },
93
+ {
94
+ label: 'Output',
95
+ content: normalizeToolPayload(props.data.toolInfo.output),
96
+ },
97
+ ];
98
+
99
+ return sections
100
+ .filter((section): section is { label: string; content: string } => Boolean(section.content))
101
+ .map(section => ({
102
+ label: section.label,
103
+ lines: section.content.split('\n').map((content, index) => ({
104
+ number: index + 1,
105
+ content,
106
+ })),
107
+ }));
108
+ });
109
+ const hasToolSections = computed(() => toolSections.value.length > 0);
110
+
79
111
  onMounted(() => {
80
112
  if (toolRendererRef.value) {
81
113
  toolRendererInitialWidth.value = toolRendererRef.value.offsetWidth;
82
114
  }
83
115
  });
84
116
 
85
- function finishTransition() {
86
- if (!isInputOutputExpanded.value) {
87
- activateFullWidth.value = false;
88
- activateShrinkedStyle.value = true;
89
- }
90
- }
91
117
  watch(isInputOutputExpanded, (newValue) => {
92
118
  if (newValue) {
93
119
  activateShrinkedStyle.value = false;
@@ -95,6 +121,13 @@
95
121
  }
96
122
  });
97
123
 
124
+ function finishTransition() {
125
+ if (!isInputOutputExpanded.value) {
126
+ activateFullWidth.value = false;
127
+ activateShrinkedStyle.value = true;
128
+ }
129
+ }
130
+
98
131
  function toggleInputOutput() {
99
132
  if (blockClicksDuringAnimation.value) return;
100
133
  isInputOutputExpanded.value = !isInputOutputExpanded.value;
@@ -104,21 +137,6 @@
104
137
  }, ANIMATION_DURATION);
105
138
  }
106
139
 
107
- interface IToolSection {
108
- label: string;
109
- lines: Array<{
110
- number: number;
111
- content: string;
112
- }>;
113
- }
114
-
115
- const props = defineProps<{
116
- data: IFormattedToolCallPart
117
- }>();
118
-
119
- const isRunning = computed(() => props.data?.toolInfo?.phase === 'start');
120
- const statusLabel = computed(() => isRunning.value ? 'Running tool' : 'Tool finished');
121
-
122
140
  const normalizeToolPayload = (value: unknown): string | null => {
123
141
  if (value === null || value === undefined || value === '') {
124
142
  return null;
@@ -131,30 +149,6 @@
131
149
  return JSON.stringify(value, null, 2);
132
150
  };
133
151
 
134
- const toolSections = computed<IToolSection[]>(() => {
135
- const sections = [
136
- {
137
- label: 'Input',
138
- content: normalizeToolPayload(props.data.toolInfo.input),
139
- },
140
- {
141
- label: 'Output',
142
- content: normalizeToolPayload(props.data.toolInfo.output),
143
- },
144
- ];
145
-
146
- return sections
147
- .filter((section): section is { label: string; content: string } => Boolean(section.content))
148
- .map(section => ({
149
- label: section.label,
150
- lines: section.content.split('\n').map((content, index) => ({
151
- number: index + 1,
152
- content,
153
- })),
154
- }));
155
- });
156
-
157
- const hasToolSections = computed(() => toolSections.value.length > 0);
158
152
  </script>
159
153
 
160
154
 
@@ -9,7 +9,7 @@
9
9
  <h3
10
10
  class="flex items-center mb-1 text-sm my-2 ml-3 gap-1 text-listTableHeadingText dark:text-darkListTableHeadingText"
11
11
  >
12
- <span class="font-semibold select-none ">Call tools</span>
12
+ <span class="font-semibold select-none ">{{ $t('Call tools') }}</span>
13
13
  </h3>
14
14
  <div class="flex flex-wrap">
15
15
  <template v-for="group in props.toolGroup" :key="group.title">
@@ -23,24 +23,12 @@
23
23
  <script setup lang="ts">
24
24
  import ToolRenderer from './ToolRenderer.vue';
25
25
  import type { IToolGroup } from '../types';
26
- import { ref } from 'vue';
27
26
  import { IconWrenchSolid } from '@iconify-prerendered/vue-heroicons';
28
27
 
29
-
30
28
  const props = defineProps<{
31
29
  toolGroup: IToolGroup[]
32
30
  }>();
33
31
 
34
- const expandedGroups = ref<string[]>([]);
35
-
36
- function toggleGroup(groupTitle: string) {
37
- if (expandedGroups.value.includes(groupTitle)) {
38
- expandedGroups.value = expandedGroups.value.filter((title: string) => title !== groupTitle);
39
- } else {
40
- expandedGroups.value.push(groupTitle);
41
- }
42
- }
43
-
44
32
  </script>
45
33
 
46
34
  <style scoped>
@@ -12,7 +12,7 @@
12
12
  :disabled="!sourceCode"
13
13
  @click="copyCode"
14
14
  >
15
- {{ copied ? 'Copied' : 'Copy' }}
15
+ {{ copied ? $t('Copied') : $t('Copy') }}
16
16
  </button>
17
17
  </div>
18
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "1.27.2",
3
+ "version": "1.27.3",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",