@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
package/build.log
CHANGED
|
@@ -5,11 +5,8 @@
|
|
|
5
5
|
sending incremental file list
|
|
6
6
|
custom/
|
|
7
7
|
custom/ChatSurface.vue
|
|
8
|
-
custom/
|
|
9
|
-
custom/Message.vue
|
|
8
|
+
custom/CustomAutoScrollContainer.vue
|
|
10
9
|
custom/SessionsHistory.vue
|
|
11
|
-
custom/ToolRenderer.vue
|
|
12
|
-
custom/ToolsGroup.vue
|
|
13
10
|
custom/chat.ts
|
|
14
11
|
custom/package.json
|
|
15
12
|
custom/pnpm-lock.yaml
|
|
@@ -19,6 +16,15 @@ custom/utils.ts
|
|
|
19
16
|
custom/composables/
|
|
20
17
|
custom/composables/useAgentStore.ts
|
|
21
18
|
custom/composables/useAgentTransitions.ts
|
|
19
|
+
custom/conversation_area/
|
|
20
|
+
custom/conversation_area/ConversationArea.vue
|
|
21
|
+
custom/conversation_area/MessageRenderer.vue
|
|
22
|
+
custom/conversation_area/ProcessingTimeline.vue
|
|
23
|
+
custom/conversation_area/ReasoningRenderer.vue
|
|
24
|
+
custom/conversation_area/TextRenderer.vue
|
|
25
|
+
custom/conversation_area/ThreeDotsAnimation.vue
|
|
26
|
+
custom/conversation_area/ToolRenderer.vue
|
|
27
|
+
custom/conversation_area/ToolsGroup.vue
|
|
22
28
|
custom/incremark_code_renderers/
|
|
23
29
|
custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue
|
|
24
30
|
custom/incremark_code_renderers/incremarkCodeHighlight.ts
|
|
@@ -32,5 +38,5 @@ custom/skills/fetch_data/SKILL.md
|
|
|
32
38
|
custom/skills/mutate_data/
|
|
33
39
|
custom/skills/mutate_data/SKILL.md
|
|
34
40
|
|
|
35
|
-
sent
|
|
36
|
-
total size is
|
|
41
|
+
sent 199,171 bytes received 562 bytes 399,466.00 bytes/sec
|
|
42
|
+
total size is 196,867 speedup is 0.99
|
package/custom/ChatSurface.vue
CHANGED
|
@@ -179,7 +179,7 @@ import { IconChatBubbleLeft20Solid, IconSparklesSolid, IconArrowsPointingOut, Ic
|
|
|
179
179
|
import { IconCloseOutline, IconBarsOutline, IconArrowUpOutline, IconCloseSidebarSolid, IconOpenSidebarSolid, IconAngleDownOutline } from '@iconify-prerendered/vue-flowbite';
|
|
180
180
|
import { useTemplateRef, onMounted, ref,computed } from 'vue';
|
|
181
181
|
import { onClickOutside } from '@vueuse/core'
|
|
182
|
-
import ConversationArea from './ConversationArea.vue';
|
|
182
|
+
import ConversationArea from './conversation_area/ConversationArea.vue';
|
|
183
183
|
import { useAgentStore } from './composables/useAgentStore';
|
|
184
184
|
import { useAgentTransitions } from './composables/useAgentTransitions';
|
|
185
185
|
import { Button } from '@/afcl';
|
|
@@ -246,7 +246,7 @@ onMounted(async () => {
|
|
|
246
246
|
agentStore.setAvailableModes(props.meta.modes, props.meta.defaultModeName);
|
|
247
247
|
agentStore.regisrerTextInput(textInput.value);
|
|
248
248
|
textInput.value?.focus();
|
|
249
|
-
const isTeleportedToBodyFromLocalStorage = agentStore.getLocalStorageItem('isTeleportedToBody') === 'true';
|
|
249
|
+
const isTeleportedToBodyFromLocalStorage = agentStore.getLocalStorageItem('isTeleportedToBody') === 'true' || agentStore.getLocalStorageItem('isTeleportedToBodyBeforeFullScreen') === 'true';
|
|
250
250
|
if( coreStore.isMobile ) {
|
|
251
251
|
agentStore.setIsTeleportedToBody(false);
|
|
252
252
|
} else {
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
|
3
|
+
import vueCustomScrollbar from 'vue-custom-scrollbar'
|
|
4
|
+
|
|
5
|
+
const props = withDefaults(defineProps<{
|
|
6
|
+
enabled?: boolean
|
|
7
|
+
threshold?: number
|
|
8
|
+
behavior?: ScrollBehavior
|
|
9
|
+
}>(), {
|
|
10
|
+
enabled: true,
|
|
11
|
+
threshold: 50,
|
|
12
|
+
behavior: 'instant'
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const containerRef = ref<HTMLDivElement | null>(null)
|
|
16
|
+
const isUserScrolledUp = ref(false)
|
|
17
|
+
|
|
18
|
+
let lastScrollTop = 0
|
|
19
|
+
let lastScrollHeight = 0
|
|
20
|
+
|
|
21
|
+
function isNearBottom(): boolean {
|
|
22
|
+
const container = containerRef.value
|
|
23
|
+
if (!container) return true
|
|
24
|
+
|
|
25
|
+
const { scrollTop, scrollHeight, clientHeight } = container
|
|
26
|
+
return scrollHeight - scrollTop - clientHeight <= props.threshold
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function scrollToBottom(force = false): void {
|
|
30
|
+
const container = containerRef.value
|
|
31
|
+
if (!container) return
|
|
32
|
+
|
|
33
|
+
if (isUserScrolledUp.value && !force) return
|
|
34
|
+
|
|
35
|
+
container.scrollTo({
|
|
36
|
+
top: container.scrollHeight,
|
|
37
|
+
behavior: props.behavior
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
function hasScrollbar(): boolean {
|
|
43
|
+
const container = containerRef.value
|
|
44
|
+
if (!container) return false
|
|
45
|
+
return container.scrollHeight > container.clientHeight
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
function handleScroll(): void {
|
|
50
|
+
const container = containerRef.value
|
|
51
|
+
if (!container) return
|
|
52
|
+
|
|
53
|
+
const { scrollTop, scrollHeight, clientHeight } = container
|
|
54
|
+
|
|
55
|
+
if (scrollHeight <= clientHeight) {
|
|
56
|
+
isUserScrolledUp.value = false
|
|
57
|
+
lastScrollTop = 0
|
|
58
|
+
lastScrollHeight = scrollHeight
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (isNearBottom()) {
|
|
63
|
+
isUserScrolledUp.value = false
|
|
64
|
+
} else {
|
|
65
|
+
const isScrollingUp = scrollTop < lastScrollTop
|
|
66
|
+
const isContentUnchanged = scrollHeight === lastScrollHeight
|
|
67
|
+
|
|
68
|
+
if (isScrollingUp && isContentUnchanged) {
|
|
69
|
+
isUserScrolledUp.value = true
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
lastScrollTop = scrollTop
|
|
74
|
+
lastScrollHeight = scrollHeight
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let observer: MutationObserver | null = null
|
|
78
|
+
|
|
79
|
+
onMounted(() => {
|
|
80
|
+
if (!containerRef.value) return
|
|
81
|
+
|
|
82
|
+
lastScrollTop = containerRef.value.scrollTop
|
|
83
|
+
lastScrollHeight = containerRef.value.scrollHeight
|
|
84
|
+
|
|
85
|
+
observer = new MutationObserver(() => {
|
|
86
|
+
nextTick(() => {
|
|
87
|
+
if (!containerRef.value) return
|
|
88
|
+
|
|
89
|
+
if (!hasScrollbar()) {
|
|
90
|
+
isUserScrolledUp.value = false
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
lastScrollHeight = containerRef.value.scrollHeight
|
|
94
|
+
|
|
95
|
+
if (props.enabled && !isUserScrolledUp.value) {
|
|
96
|
+
scrollToBottom()
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
observer.observe(containerRef.value, {
|
|
102
|
+
childList: true,
|
|
103
|
+
subtree: true,
|
|
104
|
+
characterData: true
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
onUnmounted(() => {
|
|
109
|
+
observer?.disconnect()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
defineExpose({
|
|
113
|
+
scrollToBottom: () => scrollToBottom(true),
|
|
114
|
+
isUserScrolledUp: () => isUserScrolledUp.value,
|
|
115
|
+
container: containerRef
|
|
116
|
+
})
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<template>
|
|
120
|
+
<div
|
|
121
|
+
ref="containerRef"
|
|
122
|
+
class="auto-scroll-container h-full"
|
|
123
|
+
@scroll="handleScroll"
|
|
124
|
+
>
|
|
125
|
+
<slot />
|
|
126
|
+
</div>
|
|
127
|
+
</template>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineStore } from 'pinia';
|
|
2
|
-
import { IAgentSession, ISessionsListItem, IMessage } from '../types';
|
|
2
|
+
import { IAgentSession, ISessionsListItem, IMessage, IPart } from '../types';
|
|
3
3
|
import { ref, nextTick, computed, watch, onMounted, shallowRef } from 'vue';
|
|
4
4
|
import { callAdminForthApi } from '@/utils';
|
|
5
5
|
import { useAdminforth } from '@/adminforth';
|
|
@@ -93,7 +93,9 @@ export const useAgentStore = defineStore('agent', () => {
|
|
|
93
93
|
})
|
|
94
94
|
onMounted(() => {
|
|
95
95
|
const chatWidthBeforeFullScreen = parseInt(getLocalStorageItem('chatWidthBeforeFullScreen') || '0', 10);
|
|
96
|
-
if (chatWidthBeforeFullScreen) {
|
|
96
|
+
if (chatWidthBeforeFullScreen && (chatWidthBeforeFullScreen > MAX_WIDTH || chatWidthBeforeFullScreen < MIN_WIDTH)) {
|
|
97
|
+
setChatWidth(remToPx(DEFAULT_CHAT_WIDTH));
|
|
98
|
+
} else if (chatWidthBeforeFullScreen) {
|
|
97
99
|
setChatWidth(remToPx(chatWidthBeforeFullScreen));
|
|
98
100
|
} else {
|
|
99
101
|
const savedChatWidth = parseInt(getLocalStorageItem('chatWidth') || '0', 10);
|
|
@@ -105,7 +107,7 @@ export const useAgentStore = defineStore('agent', () => {
|
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
109
|
}
|
|
108
|
-
isTeleportedToBody
|
|
110
|
+
setIsTeleportedToBody(getLocalStorageItem('isTeleportedToBody') === 'true' || getLocalStorageItem('isTeleportedToBodyBeforeFullScreen') === 'true');
|
|
109
111
|
lastSessionId.value = getLocalStorageItem('lastSessionId');
|
|
110
112
|
if (lastSessionId.value && lastSessionId.value !== 'pre-session') {
|
|
111
113
|
setActiveSession(lastSessionId.value);
|
|
@@ -510,7 +512,7 @@ export const useAgentStore = defineStore('agent', () => {
|
|
|
510
512
|
if (currentSession.value) {
|
|
511
513
|
currentSession.value.messages = currentChat.value?.messages.map((m: any) => ({
|
|
512
514
|
role: m.role,
|
|
513
|
-
text: m.parts.map((p:
|
|
515
|
+
text: m.parts.map((p: IPart) => p.type === 'text' ? p.text : '').join(''),
|
|
514
516
|
})) || [];
|
|
515
517
|
sessions.value[currentSession.value.sessionId] = currentSession.value;
|
|
516
518
|
}
|
|
@@ -522,8 +524,10 @@ export const useAgentStore = defineStore('agent', () => {
|
|
|
522
524
|
if (!sessions.value[sessionId]) {
|
|
523
525
|
await fetchSession(sessionId);
|
|
524
526
|
}
|
|
527
|
+
console.log('Set active session from sessions', sessionId, sessions.value[sessionId]);
|
|
525
528
|
currentSession.value = sessions.value[sessionId];
|
|
526
529
|
setCurrentChat(sessionId);
|
|
530
|
+
console.log('Set active session chat', sessionId, currentSession.value);
|
|
527
531
|
currentChat.value.messages = currentSession.value?.messages.map((m: any) => ({
|
|
528
532
|
role: m.role,
|
|
529
533
|
parts:[{
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button @click="scrollContainer.scrollToBottom(); recalculateScroll();">
|
|
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
|
+
<CustomAutoScrollContainer
|
|
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, index) in props.messages" :key="message.id"
|
|
37
|
+
class="flex flex-col w-full"
|
|
38
|
+
:class="message.role === 'user' ? 'self-end' : 'self-start'"
|
|
39
|
+
>
|
|
40
|
+
<MessageRenderer :message="message" :isLastMessageInChat="index === props.messages.length - 1"/>
|
|
41
|
+
</div>
|
|
42
|
+
<div
|
|
43
|
+
v-if="props.messages.length === 0"
|
|
44
|
+
class="flex-1 flex flex-col items-center justify-center text-gray-400 tracking-widest text-xl font-medium"
|
|
45
|
+
>
|
|
46
|
+
<p>{{ $t('Start the conversation') }}</p>
|
|
47
|
+
<p class="tracking-normal text-base text">{{ $t('Give any input to begin') }}</p>
|
|
48
|
+
</div>
|
|
49
|
+
</CustomAutoScrollContainer>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
<script setup lang="ts">
|
|
54
|
+
import Message from './Message.vue';
|
|
55
|
+
import type { IMessage, IPart } from '../types';
|
|
56
|
+
import { useTemplateRef, ref, defineAsyncComponent, onMounted, onUnmounted, watch, computed } from 'vue';
|
|
57
|
+
import { IconArrowDownOutline } from '@iconify-prerendered/vue-flowbite';
|
|
58
|
+
import SessionsHistory from '../SessionsHistory.vue';
|
|
59
|
+
import { useAgentStore } from '../composables/useAgentStore';
|
|
60
|
+
import ToolsGroup from './ToolsGroup.vue';
|
|
61
|
+
import { useAgentTransitions } from '../composables/useAgentTransitions';
|
|
62
|
+
import { getMessageParts } from '../utils';
|
|
63
|
+
import MessageRenderer from './MessageRenderer.vue';
|
|
64
|
+
import CustomAutoScrollContainer from '../CustomAutoScrollContainer.vue';
|
|
65
|
+
|
|
66
|
+
const scrollContainer = useTemplateRef('scrollContainer');
|
|
67
|
+
const showScrollToBottomButton = ref(false);
|
|
68
|
+
const innerScrollContainerRef = ref(null);
|
|
69
|
+
const agentStore = useAgentStore();
|
|
70
|
+
const agentTransitions = useAgentTransitions();
|
|
71
|
+
const clicks = ref(0);
|
|
72
|
+
|
|
73
|
+
function recalculateScroll() {
|
|
74
|
+
if (scrollContainer.value) {
|
|
75
|
+
const isScrolledUp = scrollContainer.value.isUserScrolledUp();
|
|
76
|
+
showScrollToBottomButton.value = !!isScrolledUp;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
onMounted(async () => {
|
|
81
|
+
await import('@incremark/theme/styles.css')
|
|
82
|
+
await agentStore.fetchPlaceholderMessages()
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
onUnmounted(() => {
|
|
86
|
+
agentStore.stopPlaceholderAnimation();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
watch(scrollContainer, () => {
|
|
90
|
+
if (scrollContainer.value) {
|
|
91
|
+
innerScrollContainerRef.value = scrollContainer.value.container;
|
|
92
|
+
|
|
93
|
+
innerScrollContainerRef.value.addEventListener('scroll', () => {
|
|
94
|
+
recalculateScroll();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
watch(clicks, () => {
|
|
100
|
+
recalculateScroll();
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
const props = defineProps<{
|
|
106
|
+
messages: IMessage[]
|
|
107
|
+
}>();
|
|
108
|
+
|
|
109
|
+
</script>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ProcessingTimeline
|
|
3
|
+
:message="message"
|
|
4
|
+
:isLastMessageInChat="isLastMessageInChat"
|
|
5
|
+
/>
|
|
6
|
+
<template
|
|
7
|
+
v-for="(part, index) in getMessageParts(message)"
|
|
8
|
+
:key="part.type"
|
|
9
|
+
>
|
|
10
|
+
|
|
11
|
+
<TextRenderer
|
|
12
|
+
v-if="part.type === 'text'"
|
|
13
|
+
:message="part.text"
|
|
14
|
+
:role="props.message.role"
|
|
15
|
+
/>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
import TextRenderer from './TextRenderer.vue';
|
|
25
|
+
import type { IMessage } from '../types';
|
|
26
|
+
import { getMessageParts } from '../utils';
|
|
27
|
+
import ProcessingTimeline from './ProcessingTimeline.vue';
|
|
28
|
+
|
|
29
|
+
const props = defineProps<{
|
|
30
|
+
message: IMessage
|
|
31
|
+
isLastMessageInChat: boolean
|
|
32
|
+
}>();
|
|
33
|
+
</script>
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<template v-if="ToolOrReasoningParts.length > 0 || isResponseInProgress || showFakeThinkingMessage">
|
|
3
|
+
<div
|
|
4
|
+
class="ml-2 px-4 flex items-center gap-1 cursor-pointer select-none hover:opacity-80 tracking-wide font-medium text-sm"
|
|
5
|
+
@click="isExpanded = !isExpanded"
|
|
6
|
+
>
|
|
7
|
+
Thoughts
|
|
8
|
+
<span v-if="thinkingDuration > 0">({{ (thinkingDuration/1000).toFixed(2) }} s)</span>
|
|
9
|
+
<ThreeDotsAnimation v-if="isResponseInProgress || showFakeThinkingMessage" />
|
|
10
|
+
<IconAngleDownOutline
|
|
11
|
+
:class="isExpanded ? 'rotate-180' : 'rotate-0'"
|
|
12
|
+
class="transition-transform duration-200"
|
|
13
|
+
/>
|
|
14
|
+
</div>
|
|
15
|
+
<transition name="expand" class="max-h-96 overflow-y-auto mb-4 pt-1">
|
|
16
|
+
<CustomAutoScrollContainer
|
|
17
|
+
:enabled="true"
|
|
18
|
+
behavior="smooth"
|
|
19
|
+
v-if="ToolOrReasoningParts.length > 0"
|
|
20
|
+
v-show="isExpanded"
|
|
21
|
+
class="mask-y"
|
|
22
|
+
>
|
|
23
|
+
<ol class="ml-8 relative border-l border-l-2 border-black border-default">
|
|
24
|
+
<li class="mb-6 ms-2 z-50" v-for="(part, index) in ToolOrReasoningParts" :key="index">
|
|
25
|
+
<ReasoningRenderer v-if="part.type === 'reasoning'" :state="part.state" :text="part.text" />
|
|
26
|
+
<ToolsGroup v-else :toolGroup="groupToolCallParts(message, part)" />
|
|
27
|
+
</li>
|
|
28
|
+
</ol>
|
|
29
|
+
</CustomAutoScrollContainer>
|
|
30
|
+
</transition>
|
|
31
|
+
</template>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
<script setup lang="ts">
|
|
37
|
+
import type { IFormattedToolCallPart, IMessage, IPart, IToolGroup } from '../types';
|
|
38
|
+
import { ref, computed, watch, defineAsyncComponent, onMounted } from 'vue';
|
|
39
|
+
import ReasoningRenderer from './ReasoningRenderer.vue';
|
|
40
|
+
import { IconAngleDownOutline } from '@iconify-prerendered/vue-flowbite';
|
|
41
|
+
import ThreeDotsAnimation from './ThreeDotsAnimation.vue';
|
|
42
|
+
import { useAgentStore } from '../composables/useAgentStore';
|
|
43
|
+
import { getMessageParts } from '../utils';
|
|
44
|
+
import ToolsGroup from './ToolsGroup.vue';
|
|
45
|
+
import CustomAutoScrollContainer from '../CustomAutoScrollContainer.vue';
|
|
46
|
+
|
|
47
|
+
const props = defineProps<{
|
|
48
|
+
message: IMessage
|
|
49
|
+
isLastMessageInChat: boolean
|
|
50
|
+
}>()
|
|
51
|
+
|
|
52
|
+
// const AutoScrollContainer = defineAsyncComponent(() => import('@incremark/vue').then(module => module.AutoScrollContainer))
|
|
53
|
+
const agentStore = useAgentStore();
|
|
54
|
+
const thinkingStartTime = ref<number | null>(null);
|
|
55
|
+
const thinkingDuration = ref(0);
|
|
56
|
+
|
|
57
|
+
onMounted(() => {
|
|
58
|
+
thinkingStartTime.value = Date.now();
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const ToolOrReasoningParts = computed(() => {
|
|
62
|
+
return props.message.parts.filter((part: IPart) => part.type === 'data-tool-call' || part.type === 'reasoning');
|
|
63
|
+
});
|
|
64
|
+
const isExpanded = ref(true);
|
|
65
|
+
|
|
66
|
+
const isResponseInProgress = computed(() =>{
|
|
67
|
+
return props.isLastMessageInChat && agentStore.isResponseInProgress;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
watch(isResponseInProgress, (newValue: boolean) => {
|
|
71
|
+
if (!newValue) {
|
|
72
|
+
isExpanded.value = false;
|
|
73
|
+
thinkingDuration.value = Date.now() - (thinkingStartTime.value ?? Date.now());
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const showFakeThinkingMessage = computed(() => {
|
|
78
|
+
if (props.message.parts.length === 0) return true;
|
|
79
|
+
return false;
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const formatToolCallPart = (part: IPart, currentMessage: IMessage): IFormattedToolCallPart | null => {
|
|
83
|
+
if (part.type !== 'data-tool-call' || part.data?.phase !== 'start') {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const finishedPart = currentMessage.parts.find(candidate => {
|
|
88
|
+
return candidate.type === 'data-tool-call'
|
|
89
|
+
&& candidate.data?.toolCallId === part.data?.toolCallId
|
|
90
|
+
&& candidate.data?.phase === 'end';
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
type: 'data-tool-call',
|
|
95
|
+
toolInfo: {
|
|
96
|
+
toolCallId: part.data.toolCallId,
|
|
97
|
+
toolName: part.data.toolName,
|
|
98
|
+
phase: finishedPart ? 'end' : 'start',
|
|
99
|
+
durationMs: finishedPart?.data?.durationMs,
|
|
100
|
+
input: part.data.input,
|
|
101
|
+
output: finishedPart?.data?.output,
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const getVisibleTimelineParts = (message: IMessage) => {
|
|
107
|
+
return getMessageParts(message).filter(part => {
|
|
108
|
+
return part.type === 'reasoning' || (part.type === 'data-tool-call' && part.data?.phase === 'start');
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const groupToolCallParts = (message: IMessage, currentPart: IPart): IToolGroup[] => {
|
|
113
|
+
if (currentPart.type !== 'data-tool-call') {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const visibleParts = getVisibleTimelineParts(message);
|
|
118
|
+
const currentPartIndex = visibleParts.findIndex(part => part === currentPart);
|
|
119
|
+
|
|
120
|
+
if (currentPartIndex === -1) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (currentPartIndex > 0 && visibleParts[currentPartIndex - 1]?.type === 'data-tool-call') {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const groupedParts: IToolGroup[] = [];
|
|
129
|
+
|
|
130
|
+
for (let index = currentPartIndex; index < visibleParts.length; index += 1) {
|
|
131
|
+
const part = visibleParts[index];
|
|
132
|
+
|
|
133
|
+
if (part.type === 'reasoning') {
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const formattedPart = formatToolCallPart(part, message);
|
|
138
|
+
|
|
139
|
+
if (!formattedPart) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const lastGroup = groupedParts[groupedParts.length - 1];
|
|
144
|
+
|
|
145
|
+
if (lastGroup?.title === formattedPart.toolInfo.toolName) {
|
|
146
|
+
lastGroup.groupedTools.push(formattedPart);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
groupedParts.push({
|
|
151
|
+
title: formattedPart.toolInfo.toolName,
|
|
152
|
+
groupedTools: [formattedPart],
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return groupedParts;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
<style scoped>
|
|
163
|
+
.expand-enter-active,
|
|
164
|
+
.expand-leave-active {
|
|
165
|
+
transition: all 0.3s ease;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.expand-enter-from,
|
|
169
|
+
.expand-leave-to {
|
|
170
|
+
opacity: 0;
|
|
171
|
+
max-height: 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.expand-enter-to,
|
|
175
|
+
.expand-leave-from {
|
|
176
|
+
opacity: 1;
|
|
177
|
+
max-height: 384px;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.mask-y {
|
|
181
|
+
mask-image: linear-gradient(
|
|
182
|
+
to bottom,
|
|
183
|
+
transparent,
|
|
184
|
+
black 20px,
|
|
185
|
+
black calc(100% - 20px),
|
|
186
|
+
transparent
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
</style>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<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">
|
|
3
|
+
<div class="w-5 h-5 rounded-full flex items-center justify-center">
|
|
4
|
+
<IconBrainOutline class="w-4 h-4" />
|
|
5
|
+
</div>
|
|
6
|
+
</span>
|
|
7
|
+
<h3
|
|
8
|
+
class="flex items-center mb-1 text-sm my-2 ml-3 gap-1 cursor-pointer select-none hover:opacity-80"
|
|
9
|
+
@click="isExpanded = !isExpanded"
|
|
10
|
+
>
|
|
11
|
+
<span class="font-semibold">{{ reasoningTitle }}</span>
|
|
12
|
+
<ThreeDotsAnimation v-if="isStreaming"/>
|
|
13
|
+
<IconAngleDownOutline
|
|
14
|
+
:class="isExpanded ? 'rotate-180' : 'rotate-0'"
|
|
15
|
+
class="transition-transform duration-200"
|
|
16
|
+
/>
|
|
17
|
+
</h3>
|
|
18
|
+
<transition name="expand">
|
|
19
|
+
<div v-show="isExpanded" class="overflow-hidden mb-4 text-sm mr-48 max-h-64 pl-4 ">
|
|
20
|
+
<AutoScrollContainer
|
|
21
|
+
:enabled="true"
|
|
22
|
+
>
|
|
23
|
+
<IncremarkContent
|
|
24
|
+
:content="reasoningText"
|
|
25
|
+
/>
|
|
26
|
+
</AutoScrollContainer>
|
|
27
|
+
</div>
|
|
28
|
+
</transition>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
<script setup lang="ts">
|
|
34
|
+
import { IconBrainOutline, IconAngleDownOutline } from '@iconify-prerendered/vue-flowbite';
|
|
35
|
+
import type { IPart } from '../types';
|
|
36
|
+
import { ref, computed, watch, defineAsyncComponent } from 'vue';
|
|
37
|
+
import ThreeDotsAnimation from './ThreeDotsAnimation.vue';
|
|
38
|
+
import { extractTitleAndTextFromReasoning } from '../utils';
|
|
39
|
+
import { useAgentStore } from '../composables/useAgentStore';
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
const IncremarkContent = defineAsyncComponent(() => import('@incremark/vue').then(module => module.IncremarkContent))
|
|
43
|
+
const AutoScrollContainer = defineAsyncComponent(() => import('@incremark/vue').then(module => module.AutoScrollContainer))
|
|
44
|
+
|
|
45
|
+
const props = defineProps<{
|
|
46
|
+
state?: IPart['state']
|
|
47
|
+
text?: string
|
|
48
|
+
}>();
|
|
49
|
+
|
|
50
|
+
const agentStore = useAgentStore();
|
|
51
|
+
|
|
52
|
+
const isStreaming = computed(() => props.state === 'streaming');
|
|
53
|
+
const isExpanded = ref(true);
|
|
54
|
+
const parsedReasoning = computed(() => extractTitleAndTextFromReasoning(props.text ?? ''));
|
|
55
|
+
const reasoningTitle = computed(() => parsedReasoning.value.title ?? '');
|
|
56
|
+
const reasoningText = computed(() => parsedReasoning.value.body);
|
|
57
|
+
|
|
58
|
+
watch(() => props.state, (newValue: IPart['state']) => {
|
|
59
|
+
if ( newValue !== 'streaming') {
|
|
60
|
+
isExpanded.value = false;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
<style scoped>
|
|
70
|
+
.expand-enter-active,
|
|
71
|
+
.expand-leave-active {
|
|
72
|
+
transition: all 0.3s ease;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.expand-enter-from,
|
|
76
|
+
.expand-leave-to {
|
|
77
|
+
opacity: 0;
|
|
78
|
+
max-height: 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.expand-enter-to,
|
|
82
|
+
.expand-leave-from {
|
|
83
|
+
opacity: 1;
|
|
84
|
+
max-height: 256px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
</style>
|