@adminforth/agent 1.21.0 → 1.22.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 +12 -6
- package/custom/ChatSurface.vue +2 -2
- package/custom/CustomAutoScrollContainer.vue +127 -0
- package/custom/composables/useAgentStore.ts +8 -4
- package/custom/conversation_area/ConversationArea.vue +109 -0
- package/custom/conversation_area/MessageRenderer.vue +33 -0
- package/custom/conversation_area/ProcessingTimeline.vue +190 -0
- package/custom/conversation_area/ReasoningRenderer.vue +87 -0
- package/{dist/custom/Message.vue → custom/conversation_area/TextRenderer.vue} +14 -102
- package/custom/conversation_area/ThreeDotsAnimation.vue +35 -0
- package/custom/{ToolRenderer.vue → conversation_area/ToolRenderer.vue} +65 -13
- package/custom/conversation_area/ToolsGroup.vue +63 -0
- package/custom/package.json +2 -1
- package/custom/pnpm-lock.yaml +18 -0
- package/custom/types.ts +11 -1
- package/custom/utils.ts +29 -0
- package/dist/custom/ChatSurface.vue +2 -2
- package/dist/custom/CustomAutoScrollContainer.vue +127 -0
- package/dist/custom/composables/useAgentStore.ts +8 -4
- package/dist/custom/conversation_area/ConversationArea.vue +109 -0
- package/dist/custom/conversation_area/MessageRenderer.vue +33 -0
- package/dist/custom/conversation_area/ProcessingTimeline.vue +190 -0
- package/dist/custom/conversation_area/ReasoningRenderer.vue +87 -0
- package/{custom/Message.vue → dist/custom/conversation_area/TextRenderer.vue} +14 -102
- package/dist/custom/conversation_area/ThreeDotsAnimation.vue +35 -0
- package/dist/custom/{ToolRenderer.vue → conversation_area/ToolRenderer.vue} +65 -13
- package/dist/custom/conversation_area/ToolsGroup.vue +63 -0
- package/dist/custom/package.json +2 -1
- package/dist/custom/pnpm-lock.yaml +18 -0
- package/dist/custom/types.ts +11 -1
- package/dist/custom/utils.ts +29 -0
- package/package.json +1 -1
- package/custom/ConversationArea.vue +0 -198
- package/custom/ToolsGroup.vue +0 -67
- package/dist/custom/ConversationArea.vue +0 -198
- package/dist/custom/ToolsGroup.vue +0 -67
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
<div
|
|
3
|
+
ref="toolRendererRef"
|
|
4
|
+
class="py-1 inline-flex justify-center m-2
|
|
5
|
+
flex-col gap-3 rounded-xl px-2 text-lightListTableHeadingText
|
|
6
|
+
dark:text-darkListTableHeadingText select-none
|
|
7
|
+
"
|
|
8
|
+
:class="[
|
|
9
|
+
isInputOutputExpanded ? 'items-start border-none' : '',
|
|
10
|
+
activateShrinkedStyle ? 'border items-center' : '',
|
|
11
|
+
activateFullWidth ? 'w-full' : '',
|
|
12
|
+
]"
|
|
13
|
+
:style="{
|
|
14
|
+
maxWidth: isAnimatingShrinkFinal ? toolRendererInitialWidth + 'px' : '',
|
|
15
|
+
transition: 'max-width 0.3s ease',
|
|
16
|
+
}"
|
|
5
17
|
>
|
|
6
|
-
<div
|
|
18
|
+
<div
|
|
19
|
+
class="flex items-center gap-1 cursor-pointer"
|
|
20
|
+
@click="toggleInputOutput()"
|
|
21
|
+
>
|
|
7
22
|
<div class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-white/70 dark:bg-blue-700/20">
|
|
8
23
|
<Spinner v-if="isRunning" class="h-4 w-4" />
|
|
9
24
|
<IconCheckOutline v-else class="h-4 w-4 text-lightPrimary dark:text-darkPrimary" />
|
|
10
25
|
</div>
|
|
11
26
|
|
|
12
27
|
<div class="min-w-0">
|
|
13
|
-
<p class="text-xs text-gray-500 dark:text-gray-400 font-bold">
|
|
28
|
+
<!-- <p class="text-xs text-gray-500 dark:text-gray-400 font-bold">
|
|
14
29
|
{{ statusLabel }}
|
|
15
30
|
<span v-if="props.data?.toolInfo?.durationMs" class="text-xs">({{ (props.data.toolInfo.durationMs / 1000).toFixed(2) }}s)</span>
|
|
16
|
-
</p>
|
|
31
|
+
</p> -->
|
|
17
32
|
<p class="break-all font-mono text-sm leading-5">
|
|
18
33
|
{{ props.data?.toolInfo?.toolName }}
|
|
19
34
|
</p>
|
|
@@ -47,12 +62,52 @@
|
|
|
47
62
|
</template>
|
|
48
63
|
|
|
49
64
|
<script setup lang="ts">
|
|
50
|
-
import { computed, ref } from 'vue';
|
|
51
|
-
import { type
|
|
65
|
+
import { computed, ref, watch, onMounted } from 'vue';
|
|
66
|
+
import { type IFormattedToolCallPart } from '../types';
|
|
52
67
|
import { Spinner } from '@/afcl';
|
|
53
68
|
import { IconAngleDownOutline, IconCheckOutline } from '@iconify-prerendered/vue-flowbite';
|
|
54
69
|
|
|
55
70
|
const isInputOutputExpanded = ref(false);
|
|
71
|
+
const activateShrinkedStyle = ref(true);
|
|
72
|
+
const isAnimatingShrinkFinal = ref(false);
|
|
73
|
+
const toolRendererInitialWidth = ref<number | null>(null);
|
|
74
|
+
const toolRendererRef = ref<HTMLElement | null>(null);
|
|
75
|
+
const activateFullWidth = ref(false);
|
|
76
|
+
const blockClicksDuringAnimation = ref(false);
|
|
77
|
+
const ANIMATION_DURATION = 300;
|
|
78
|
+
|
|
79
|
+
onMounted(() => {
|
|
80
|
+
if (toolRendererRef.value) {
|
|
81
|
+
toolRendererInitialWidth.value = toolRendererRef.value.offsetWidth;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
watch(isInputOutputExpanded, (newValue) => {
|
|
86
|
+
if (!newValue) {
|
|
87
|
+
setTimeout(() => {
|
|
88
|
+
activateFullWidth.value = false;
|
|
89
|
+
isAnimatingShrinkFinal.value = true;
|
|
90
|
+
}, ANIMATION_DURATION - 10)
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
isAnimatingShrinkFinal.value = false;
|
|
93
|
+
}, ANIMATION_DURATION);
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
activateShrinkedStyle.value = true;
|
|
96
|
+
}, ANIMATION_DURATION + 10);
|
|
97
|
+
} else {
|
|
98
|
+
activateShrinkedStyle.value = false;
|
|
99
|
+
activateFullWidth.value = true;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
function toggleInputOutput() {
|
|
104
|
+
if (blockClicksDuringAnimation.value) return;
|
|
105
|
+
isInputOutputExpanded.value = !isInputOutputExpanded.value;
|
|
106
|
+
blockClicksDuringAnimation.value = true;
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
blockClicksDuringAnimation.value = false;
|
|
109
|
+
}, ANIMATION_DURATION);
|
|
110
|
+
}
|
|
56
111
|
|
|
57
112
|
interface IToolSection {
|
|
58
113
|
label: string;
|
|
@@ -63,10 +118,7 @@
|
|
|
63
118
|
}
|
|
64
119
|
|
|
65
120
|
const props = defineProps<{
|
|
66
|
-
data:
|
|
67
|
-
type: string;
|
|
68
|
-
toolInfo: IPartData;
|
|
69
|
-
}
|
|
121
|
+
data: IFormattedToolCallPart
|
|
70
122
|
}>();
|
|
71
123
|
|
|
72
124
|
const isRunning = computed(() => props.data?.toolInfo?.phase === 'start');
|
|
@@ -115,7 +167,7 @@
|
|
|
115
167
|
|
|
116
168
|
.expand-enter-active,
|
|
117
169
|
.expand-leave-active {
|
|
118
|
-
transition: all
|
|
170
|
+
transition: all 300ms ease;
|
|
119
171
|
}
|
|
120
172
|
|
|
121
173
|
.expand-enter-from,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<template >
|
|
2
|
+
<template v-if="toolGroup.length > 0">
|
|
3
|
+
<span class="bg-lightNavbar absolute flex items-center justify-center w-5 h-5 bg-brand-softer rounded-full -start-[0.68rem] ring-4 ring-lightNavbar ring-default">
|
|
4
|
+
<div class="w-5 h-5 rounded-full flex items-center justify-center">
|
|
5
|
+
<IconWrenchSolid class="w-4 h-4" />
|
|
6
|
+
</div>
|
|
7
|
+
</span>
|
|
8
|
+
<h3
|
|
9
|
+
class="flex items-center mb-1 text-sm my-2 ml-3 gap-1"
|
|
10
|
+
>
|
|
11
|
+
<span class="font-semibold select-none ">Call tools</span>
|
|
12
|
+
</h3>
|
|
13
|
+
<div class="flex flex-wrap">
|
|
14
|
+
<template v-for="group in props.toolGroup" :key="group.title">
|
|
15
|
+
<ToolRenderer v-for="part in group.groupedTools" :key="part.toolInfo.toolCallId" :data="part"/>
|
|
16
|
+
</template>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import ToolRenderer from './ToolRenderer.vue';
|
|
23
|
+
import type { IToolGroup } from '../types';
|
|
24
|
+
import { ref } from 'vue';
|
|
25
|
+
import { IconWrenchSolid } from '@iconify-prerendered/vue-heroicons';
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
const props = defineProps<{
|
|
29
|
+
toolGroup: IToolGroup[]
|
|
30
|
+
}>();
|
|
31
|
+
|
|
32
|
+
const expandedGroups = ref<string[]>([]);
|
|
33
|
+
|
|
34
|
+
function toggleGroup(groupTitle: string) {
|
|
35
|
+
if (expandedGroups.value.includes(groupTitle)) {
|
|
36
|
+
expandedGroups.value = expandedGroups.value.filter((title: string) => title !== groupTitle);
|
|
37
|
+
} else {
|
|
38
|
+
expandedGroups.value.push(groupTitle);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<style scoped>
|
|
45
|
+
|
|
46
|
+
.expand-enter-active,
|
|
47
|
+
.expand-leave-active {
|
|
48
|
+
transition: all 0.3s ease;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.expand-enter-from,
|
|
52
|
+
.expand-leave-to {
|
|
53
|
+
opacity: 0;
|
|
54
|
+
max-height: 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.expand-enter-to,
|
|
58
|
+
.expand-leave-from {
|
|
59
|
+
opacity: 1;
|
|
60
|
+
max-height: 288px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
</style>
|
package/dist/custom/package.json
CHANGED
|
@@ -41,6 +41,9 @@ importers:
|
|
|
41
41
|
vega-embed:
|
|
42
42
|
specifier: ^7.1.0
|
|
43
43
|
version: 7.1.0(vega-lite@6.4.2(vega@6.2.0))(vega@6.2.0)
|
|
44
|
+
vue-custom-scrollbar:
|
|
45
|
+
specifier: ^2.0.2
|
|
46
|
+
version: 2.0.2(vue@3.5.32)
|
|
44
47
|
|
|
45
48
|
packages:
|
|
46
49
|
|
|
@@ -653,6 +656,9 @@ packages:
|
|
|
653
656
|
parse-entities@4.0.2:
|
|
654
657
|
resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
|
|
655
658
|
|
|
659
|
+
perfect-scrollbar@1.5.6:
|
|
660
|
+
resolution: {integrity: sha512-rixgxw3SxyJbCaSpo1n35A/fwI1r2rdwMKOTCg/AcG+xOEyZcE8UHVjpZMFCVImzsFoCZeJTT+M/rdEIQYO2nw==}
|
|
661
|
+
|
|
656
662
|
picocolors@1.1.1:
|
|
657
663
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
|
658
664
|
|
|
@@ -873,6 +879,11 @@ packages:
|
|
|
873
879
|
vfile@6.0.3:
|
|
874
880
|
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
|
|
875
881
|
|
|
882
|
+
vue-custom-scrollbar@2.0.2:
|
|
883
|
+
resolution: {integrity: sha512-eRyxGb7UFLLH8P0B8FDux2uPrzNBH0X6IN+A/RB5sfmLq1ym7shbCPVKua1pC7LPqPB7dc86evFXyWO/svrfKA==}
|
|
884
|
+
peerDependencies:
|
|
885
|
+
vue: ^3.3.0
|
|
886
|
+
|
|
876
887
|
vue@3.5.32:
|
|
877
888
|
resolution: {integrity: sha512-vM4z4Q9tTafVfMAK7IVzmxg34rSzTFMyIe0UUEijUCkn9+23lj0WRfA83dg7eQZIUlgOSGrkViIaCfqSAUXsMw==}
|
|
878
889
|
peerDependencies:
|
|
@@ -1755,6 +1766,8 @@ snapshots:
|
|
|
1755
1766
|
is-decimal: 2.0.1
|
|
1756
1767
|
is-hexadecimal: 2.0.1
|
|
1757
1768
|
|
|
1769
|
+
perfect-scrollbar@1.5.6: {}
|
|
1770
|
+
|
|
1758
1771
|
picocolors@1.1.1: {}
|
|
1759
1772
|
|
|
1760
1773
|
postcss@8.5.10:
|
|
@@ -2129,6 +2142,11 @@ snapshots:
|
|
|
2129
2142
|
'@types/unist': 3.0.3
|
|
2130
2143
|
vfile-message: 4.0.3
|
|
2131
2144
|
|
|
2145
|
+
vue-custom-scrollbar@2.0.2(vue@3.5.32):
|
|
2146
|
+
dependencies:
|
|
2147
|
+
perfect-scrollbar: 1.5.6
|
|
2148
|
+
vue: 3.5.32
|
|
2149
|
+
|
|
2132
2150
|
vue@3.5.32:
|
|
2133
2151
|
dependencies:
|
|
2134
2152
|
'@vue/compiler-dom': 3.5.32
|
package/dist/custom/types.ts
CHANGED
|
@@ -7,12 +7,22 @@ export interface IPartData {
|
|
|
7
7
|
durationMs?: number;
|
|
8
8
|
}
|
|
9
9
|
export interface IPart {
|
|
10
|
-
type:
|
|
10
|
+
type: 'reasoning' | 'data-tool-call' | 'text';
|
|
11
11
|
text?: string;
|
|
12
12
|
state?: 'started' | 'thinking' | 'processing' | 'streaming' | 'done';
|
|
13
13
|
data?: IPartData;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export interface IFormattedToolCallPart {
|
|
17
|
+
type: 'data-tool-call';
|
|
18
|
+
toolInfo: IPartData;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface IToolGroup {
|
|
22
|
+
title: string;
|
|
23
|
+
groupedTools: IFormattedToolCallPart[];
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
export interface IMessage {
|
|
17
27
|
id: string;
|
|
18
28
|
role: 'user' | 'assistant';
|
package/dist/custom/utils.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { IMessage, IPart } from "./types";
|
|
2
|
+
|
|
1
3
|
export function remToPx(rem: number): number {
|
|
2
4
|
const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
3
5
|
return rem * rootFontSize;
|
|
@@ -7,3 +9,30 @@ export function pxToRem(px: number): number {
|
|
|
7
9
|
const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
8
10
|
return px / rootFontSize;
|
|
9
11
|
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export function getMessageParts(message: IMessage): IPart[] {
|
|
15
|
+
return message.parts?.length
|
|
16
|
+
? message.parts
|
|
17
|
+
: [{ text: '', type: 'reasoning', state: 'streaming' }];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function addNewLineBeforeTitles(text: string): string {
|
|
21
|
+
return text.replace(/(\*\*[^*]+\*\*)/g, '\n\n$1');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function extractTitleAndTextFromReasoning(reasoningText: string): { title: string | null; body: string } {
|
|
25
|
+
const match = reasoningText.match(/^\*\*(.*?)\*\*(.*)$/s);
|
|
26
|
+
|
|
27
|
+
if (!match) {
|
|
28
|
+
return {
|
|
29
|
+
title: null,
|
|
30
|
+
body: addNewLineBeforeTitles(reasoningText)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
title: match[1].trim(),
|
|
36
|
+
body: addNewLineBeforeTitles(match[2].trim())
|
|
37
|
+
};
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<button @click="scrollContainer.scrollToBottom()">
|
|
3
|
-
<IconArrowDownOutline
|
|
4
|
-
class="absolute z-10 bottom-32 left-1/2 bg-lightPrimary dark:bg-darkPrimary text-white p-2 w-10 h-10 rounded-full transition-opacity duration-100 ease-in"
|
|
5
|
-
:class="showScrollToBottomButton ? 'opacity-100' : 'opacity-0 pointer-events-none'"
|
|
6
|
-
:disabled="!showScrollToBottomButton"
|
|
7
|
-
/>
|
|
8
|
-
</button>
|
|
9
|
-
|
|
10
|
-
<SessionsHistory
|
|
11
|
-
:class="agentStore.isSessionHistoryOpen ? 'translate-x-0' : '-translate-x-full'"
|
|
12
|
-
/>
|
|
13
|
-
<div
|
|
14
|
-
v-if="agentStore.isSessionHistoryOpen"
|
|
15
|
-
@click="agentStore.setSessionHistoryOpen(false)"
|
|
16
|
-
class="absolute bg-black/10 backdrop-blur-md z-10 h-full w-full"
|
|
17
|
-
>
|
|
18
|
-
|
|
19
|
-
</div>
|
|
20
|
-
<AutoScrollContainer
|
|
21
|
-
:enabled="!showScrollToBottomButton"
|
|
22
|
-
class="relative flex flex-col overflow-y-auto translate-x-[-50%] left-1/2"
|
|
23
|
-
ref="scrollContainer"
|
|
24
|
-
:threshold="10"
|
|
25
|
-
behavior="smooth"
|
|
26
|
-
:style="{
|
|
27
|
-
maxWidth: agentStore.isFullScreen ? agentStore.MAX_WIDTH+'rem' : '100%',
|
|
28
|
-
transition: `
|
|
29
|
-
max-width ${agentTransitions.TRANSITION_DURATION}ms ease-in-out,
|
|
30
|
-
transform ${agentTransitions.TRANSITION_DURATION}ms ease-in-out
|
|
31
|
-
`
|
|
32
|
-
}"
|
|
33
|
-
>
|
|
34
|
-
|
|
35
|
-
<div
|
|
36
|
-
v-for="message in props.messages" :key="message.id"
|
|
37
|
-
class="flex flex-col w-full"
|
|
38
|
-
:class="message.role === 'user' ? 'self-end' : 'self-start'"
|
|
39
|
-
>
|
|
40
|
-
<template
|
|
41
|
-
v-for="(part, index) in getParts(message)"
|
|
42
|
-
:key="part.type"
|
|
43
|
-
>
|
|
44
|
-
<Message
|
|
45
|
-
v-if="part.type !== 'data-tool-call'"
|
|
46
|
-
:message="part.text"
|
|
47
|
-
:role="message.role"
|
|
48
|
-
:type="part.type"
|
|
49
|
-
:state="part.state"
|
|
50
|
-
:data="part.data"
|
|
51
|
-
@toggle-thoughts="() => clicks++"
|
|
52
|
-
>
|
|
53
|
-
</Message>
|
|
54
|
-
<ToolsGroup v-else :toolGroup="groupToolCallParts(message, index, part)" />
|
|
55
|
-
</template>
|
|
56
|
-
</div>
|
|
57
|
-
<!-- Show a placeholder message if the last message is not of type 'text' or 'reasoning' -->
|
|
58
|
-
<Message
|
|
59
|
-
v-if="props.messages.length > 0 && showFakeThinkingMessage"
|
|
60
|
-
:message="''"
|
|
61
|
-
:role="props.messages[props.messages.length - 1].role"
|
|
62
|
-
type="reasoning"
|
|
63
|
-
state="streaming"
|
|
64
|
-
/>
|
|
65
|
-
<div
|
|
66
|
-
v-if="props.messages.length === 0"
|
|
67
|
-
class="flex-1 flex flex-col items-center justify-center text-gray-400 tracking-widest text-xl font-medium"
|
|
68
|
-
>
|
|
69
|
-
<p>{{ $t('Start the conversation') }}</p>
|
|
70
|
-
<p class="tracking-normal text-base text">{{ $t('Give any input to begin') }}</p>
|
|
71
|
-
</div>
|
|
72
|
-
</AutoScrollContainer>
|
|
73
|
-
</template>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
<script setup lang="ts">
|
|
77
|
-
import Message from './Message.vue';
|
|
78
|
-
import type { IMessage, IPart } from './types';
|
|
79
|
-
import { useTemplateRef, ref, defineAsyncComponent, onMounted, onUnmounted, watch, computed } from 'vue';
|
|
80
|
-
import { IconArrowDownOutline } from '@iconify-prerendered/vue-flowbite';
|
|
81
|
-
import SessionsHistory from './SessionsHistory.vue';
|
|
82
|
-
import { useAgentStore } from './composables/useAgentStore';
|
|
83
|
-
import ToolsGroup from './ToolsGroup.vue';
|
|
84
|
-
import { useAgentTransitions } from './composables/useAgentTransitions';
|
|
85
|
-
|
|
86
|
-
const scrollContainer = useTemplateRef('scrollContainer');
|
|
87
|
-
const showScrollToBottomButton = ref(false);
|
|
88
|
-
const innerScrollContainerRef = ref(null);
|
|
89
|
-
const AutoScrollContainer = defineAsyncComponent(() => import('@incremark/vue').then(module => module.AutoScrollContainer))
|
|
90
|
-
const agentStore = useAgentStore();
|
|
91
|
-
const agentTransitions = useAgentTransitions();
|
|
92
|
-
const clicks = ref(0);
|
|
93
|
-
|
|
94
|
-
function recalculateScroll() {
|
|
95
|
-
if (scrollContainer.value) {
|
|
96
|
-
const isScrolledUp = scrollContainer.value.isUserScrolledUp();
|
|
97
|
-
showScrollToBottomButton.value = !!isScrolledUp;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
onMounted(async () => {
|
|
102
|
-
await import('@incremark/theme/styles.css')
|
|
103
|
-
await agentStore.fetchPlaceholderMessages()
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
onUnmounted(() => {
|
|
107
|
-
agentStore.stopPlaceholderAnimation();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
watch(scrollContainer, () => {
|
|
111
|
-
if (scrollContainer.value) {
|
|
112
|
-
innerScrollContainerRef.value = scrollContainer.value.container;
|
|
113
|
-
|
|
114
|
-
innerScrollContainerRef.value.addEventListener('scroll', () => {
|
|
115
|
-
recalculateScroll();
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
watch(clicks, () => {
|
|
121
|
-
recalculateScroll();
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
const showFakeThinkingMessage = computed(() => {
|
|
125
|
-
const lastMessage = props.messages[props.messages.length - 1];
|
|
126
|
-
if (!lastMessage) return false;
|
|
127
|
-
const lastPart = getParts(lastMessage)[getParts(lastMessage).length - 1];
|
|
128
|
-
return lastPart?.type !== 'text' && lastPart?.type !== 'reasoning';
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
const getParts = (message: IMessage) => {
|
|
132
|
-
return message.parts?.length
|
|
133
|
-
? message.parts
|
|
134
|
-
: [{ text: '', type: 'reasoning', state: 'streaming' }];
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const formatToolCallTextPart = ((part: IPart, currentMessage: IMessage) => {
|
|
138
|
-
if (part.type === 'data-tool-call') {
|
|
139
|
-
if (part.data?.phase === 'start') {
|
|
140
|
-
const finishedPart = currentMessage.parts?.find(p => p.type === 'data-tool-call' && p.data?.toolCallId === part.data?.toolCallId && p.data?.phase === 'end');
|
|
141
|
-
return {
|
|
142
|
-
type: 'text',
|
|
143
|
-
toolInfo: {
|
|
144
|
-
toolCallId: part.data!.toolCallId,
|
|
145
|
-
toolName: part.data!.toolName,
|
|
146
|
-
phase: finishedPart ? 'end' : 'start',
|
|
147
|
-
durationMs: finishedPart ? finishedPart.data?.durationMs : undefined,
|
|
148
|
-
input: part.data!.input,
|
|
149
|
-
output: finishedPart ? finishedPart.data!.output : undefined,
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return null;
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
const groupToolCallParts = (message: IMessage, currentPartIndex: number, currentPart: IPart) => {
|
|
158
|
-
const groupedParts: { title: string; groupedTools: IPart[] }[] = [];
|
|
159
|
-
let currentToolName: string | null = null;
|
|
160
|
-
const parts = getParts(message);
|
|
161
|
-
if (!parts) return [];
|
|
162
|
-
const formatedToolParts = parts.map(part => {
|
|
163
|
-
return formatToolCallTextPart(part as IPart, message)
|
|
164
|
-
});
|
|
165
|
-
const currentPartIndexInFormatedParts = formatedToolParts.findIndex(part => part?.toolInfo?.toolCallId === currentPart.data?.toolCallId);
|
|
166
|
-
if (currentPartIndexInFormatedParts === -1) {
|
|
167
|
-
return [];
|
|
168
|
-
}
|
|
169
|
-
for( const[index, part] of formatedToolParts.entries()){
|
|
170
|
-
if ( index < currentPartIndexInFormatedParts - 1 ) {
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
if(!part || !part.toolInfo) {
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
currentToolName = part.toolInfo.toolName;
|
|
177
|
-
if (!groupedParts.find(group => group.title === currentToolName)) {
|
|
178
|
-
groupedParts.push({
|
|
179
|
-
title: currentToolName,
|
|
180
|
-
groupedTools: []
|
|
181
|
-
})
|
|
182
|
-
}
|
|
183
|
-
if( formatedToolParts[currentPartIndexInFormatedParts - 1]?.toolInfo.toolName === part.toolInfo.toolName) {
|
|
184
|
-
continue;
|
|
185
|
-
} else if ( formatedToolParts[currentPartIndexInFormatedParts + 1]?.toolInfo.toolName === part.toolInfo.toolName) {
|
|
186
|
-
groupedParts[groupedParts.length - 1].groupedTools.push(formatedToolParts[currentPartIndexInFormatedParts + 1] as IPart);
|
|
187
|
-
} else {
|
|
188
|
-
groupedParts[groupedParts.length - 1].groupedTools.push(part);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return groupedParts;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const props = defineProps<{
|
|
195
|
-
messages: IMessage[]
|
|
196
|
-
}>();
|
|
197
|
-
|
|
198
|
-
</script>
|
package/custom/ToolsGroup.vue
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<template v-for="group in props.toolGroup" :key="group.title">
|
|
3
|
-
<div v-if="group.groupedTools.length > 1" class="flex flex-col">
|
|
4
|
-
<div class="flex items-center gap-2 px-2 m-2 cursor-pointer hover:opacity-75 break-all font-mono text-sm leading-5 text-lightListTableHeadingText dark:text-darkListTableHeadingText" @click="toggleGroup(group.title)">
|
|
5
|
-
<IconCheckOutline class="w-6 h-6 p-1"/>
|
|
6
|
-
{{ group.title }} {{ 'x' + group.groupedTools.length }}
|
|
7
|
-
<IconAngleDownOutline
|
|
8
|
-
class="transition-transform duration-200 hover:scale-105 hover:opacity-75"
|
|
9
|
-
:class="expandedGroups.includes(group.title) ? 'rotate-180' : 'rotate-0'"
|
|
10
|
-
/>
|
|
11
|
-
</div>
|
|
12
|
-
<transition name="expand">
|
|
13
|
-
<div v-show="expandedGroups.includes(group.title)" class="flex flex-col">
|
|
14
|
-
<ToolRenderer v-for="part in group.groupedTools" :key="part.text + part.type" :data="part" class="ml-8"/>
|
|
15
|
-
</div>
|
|
16
|
-
</transition>
|
|
17
|
-
</div>
|
|
18
|
-
<ToolRenderer v-else-if="group.groupedTools.length > 0" :data="group.groupedTools[0]" />
|
|
19
|
-
</template>
|
|
20
|
-
|
|
21
|
-
</template>
|
|
22
|
-
|
|
23
|
-
<script setup lang="ts">
|
|
24
|
-
import ToolRenderer from './ToolRenderer.vue';
|
|
25
|
-
import type { IPart } from './types';
|
|
26
|
-
import { ref } from 'vue';
|
|
27
|
-
import { IconAngleDownOutline, IconCheckOutline } from '@iconify-prerendered/vue-flowbite';
|
|
28
|
-
|
|
29
|
-
const props = defineProps<{
|
|
30
|
-
toolGroup: {
|
|
31
|
-
title: string;
|
|
32
|
-
groupedTools: IPart[];
|
|
33
|
-
}[]
|
|
34
|
-
}>();
|
|
35
|
-
|
|
36
|
-
const expandedGroups = ref<string[]>([]);
|
|
37
|
-
|
|
38
|
-
function toggleGroup(groupTitle: string) {
|
|
39
|
-
if (expandedGroups.value.includes(groupTitle)) {
|
|
40
|
-
expandedGroups.value = expandedGroups.value.filter((title: string) => title !== groupTitle);
|
|
41
|
-
} else {
|
|
42
|
-
expandedGroups.value.push(groupTitle);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
</script>
|
|
47
|
-
|
|
48
|
-
<style scoped>
|
|
49
|
-
|
|
50
|
-
.expand-enter-active,
|
|
51
|
-
.expand-leave-active {
|
|
52
|
-
transition: all 0.3s ease;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.expand-enter-from,
|
|
56
|
-
.expand-leave-to {
|
|
57
|
-
opacity: 0;
|
|
58
|
-
max-height: 0;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.expand-enter-to,
|
|
62
|
-
.expand-leave-from {
|
|
63
|
-
opacity: 1;
|
|
64
|
-
max-height: 288px;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
</style>
|