@adminforth/agent 1.21.0 → 1.22.1
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 +3 -2
- package/custom/CustomAutoScrollContainer.vue +127 -0
- package/custom/SessionsHistory.vue +11 -2
- package/custom/composables/useAgentStore.ts +6 -4
- package/custom/conversation_area/ConversationArea.vue +106 -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 +3 -2
- package/dist/custom/CustomAutoScrollContainer.vue +127 -0
- package/dist/custom/SessionsHistory.vue +11 -2
- package/dist/custom/composables/useAgentStore.ts +6 -4
- package/dist/custom/conversation_area/ConversationArea.vue +106 -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,157 bytes received 562 bytes 399,438.00 bytes/sec
|
|
42
|
+
total size is 196,887 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';
|
|
@@ -195,6 +195,7 @@ const props = defineProps<{
|
|
|
195
195
|
defaultModeName: string | null;
|
|
196
196
|
stickByDefault: boolean;
|
|
197
197
|
}
|
|
198
|
+
adminUser: any
|
|
198
199
|
}>();
|
|
199
200
|
|
|
200
201
|
const chatSurface = useTemplateRef('chatSurface');
|
|
@@ -246,7 +247,7 @@ onMounted(async () => {
|
|
|
246
247
|
agentStore.setAvailableModes(props.meta.modes, props.meta.defaultModeName);
|
|
247
248
|
agentStore.regisrerTextInput(textInput.value);
|
|
248
249
|
textInput.value?.focus();
|
|
249
|
-
const isTeleportedToBodyFromLocalStorage = agentStore.getLocalStorageItem('isTeleportedToBody') === 'true';
|
|
250
|
+
const isTeleportedToBodyFromLocalStorage = agentStore.getLocalStorageItem('isTeleportedToBody') === 'true' || agentStore.getLocalStorageItem('isTeleportedToBodyBeforeFullScreen') === 'true';
|
|
250
251
|
if( coreStore.isMobile ) {
|
|
251
252
|
agentStore.setIsTeleportedToBody(false);
|
|
252
253
|
} 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>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<h3 :class="h3Style">{{ $t('Chat history') }}</h3>
|
|
9
9
|
<div class="w-full flex items-center justify-center">
|
|
10
10
|
<Button
|
|
11
|
-
@click="agentStore.createPreSession(); agentStore.setSessionHistoryOpen(false); agentStore.focusTextInput();"
|
|
11
|
+
@click="agentStore.createPreSession(); agentStore.setSessionHistoryOpen(false); agentStore.focusTextInput(); recalculateScroll();"
|
|
12
12
|
:disabled="agentStore.isResponseInProgress"
|
|
13
13
|
class="w-[90%] my-2 mb-4 rounded-3xl text-gray-800 dark:text-gray-200"
|
|
14
14
|
>
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
'bg-lightPrimary/20 hover:bg-lightPrimary/20 dark:bg-darkPrimary/20 dark:hover:bg-darkPrimary/20': agentStore.activeSessionId === session.sessionId,
|
|
35
35
|
'cursor-default opacity-50 pointer-events-none': agentStore.isResponseInProgress,
|
|
36
36
|
}"
|
|
37
|
-
@click="agentStore.setActiveSession(session.sessionId); agentStore.setSessionHistoryOpen(false);"
|
|
37
|
+
@click="agentStore.setActiveSession(session.sessionId); agentStore.setSessionHistoryOpen(false); recalculateScroll();"
|
|
38
38
|
:disabled="agentStore.isResponseInProgress"
|
|
39
39
|
>
|
|
40
40
|
<p class="truncate">{{ session.title || session.sessionId }}</p>
|
|
@@ -99,4 +99,13 @@ const groupedSessions = computed(() => {
|
|
|
99
99
|
return Array.from(groups.values());
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
+
const emit = defineEmits<{
|
|
103
|
+
(e: 'recalculateScroll'): void
|
|
104
|
+
}>()
|
|
105
|
+
|
|
106
|
+
function recalculateScroll() {
|
|
107
|
+
// Emit an event to notify the parent component to recalculate scroll
|
|
108
|
+
emit('recalculateScroll');
|
|
109
|
+
}
|
|
110
|
+
|
|
102
111
|
</script>
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
@recalculateScroll="recalculateScroll"
|
|
13
|
+
/>
|
|
14
|
+
<div
|
|
15
|
+
v-if="agentStore.isSessionHistoryOpen"
|
|
16
|
+
@click="agentStore.setSessionHistoryOpen(false)"
|
|
17
|
+
class="absolute bg-black/10 backdrop-blur-md z-10 h-full w-full"
|
|
18
|
+
>
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
<CustomAutoScrollContainer
|
|
22
|
+
:enabled="!showScrollToBottomButton"
|
|
23
|
+
class="relative flex flex-col overflow-y-auto translate-x-[-50%] left-1/2"
|
|
24
|
+
ref="scrollContainer"
|
|
25
|
+
:threshold="10"
|
|
26
|
+
behavior="smooth"
|
|
27
|
+
:style="{
|
|
28
|
+
maxWidth: agentStore.isFullScreen ? agentStore.MAX_WIDTH+'rem' : '100%',
|
|
29
|
+
transition: `
|
|
30
|
+
max-width ${agentTransitions.TRANSITION_DURATION}ms ease-in-out,
|
|
31
|
+
transform ${agentTransitions.TRANSITION_DURATION}ms ease-in-out
|
|
32
|
+
`
|
|
33
|
+
}"
|
|
34
|
+
>
|
|
35
|
+
|
|
36
|
+
<div
|
|
37
|
+
v-for="(message, index) in props.messages" :key="message.id"
|
|
38
|
+
class="flex flex-col w-full"
|
|
39
|
+
:class="message.role === 'user' ? 'self-end' : 'self-start'"
|
|
40
|
+
>
|
|
41
|
+
<MessageRenderer :message="message" :isLastMessageInChat="index === props.messages.length - 1"/>
|
|
42
|
+
</div>
|
|
43
|
+
<div
|
|
44
|
+
v-if="props.messages.length === 0"
|
|
45
|
+
class="flex-1 flex flex-col items-center justify-center text-gray-400 tracking-widest text-xl font-medium"
|
|
46
|
+
>
|
|
47
|
+
<p>{{ $t('Start the conversation') }}</p>
|
|
48
|
+
<p class="tracking-normal text-base text">{{ $t('Give any input to begin') }}</p>
|
|
49
|
+
</div>
|
|
50
|
+
</CustomAutoScrollContainer>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
<script setup lang="ts">
|
|
55
|
+
import type { IMessage, IPart } from '../types';
|
|
56
|
+
import { useTemplateRef, ref, defineAsyncComponent, onMounted, onUnmounted, watch, computed, nextTick } from 'vue';
|
|
57
|
+
import { IconArrowDownOutline } from '@iconify-prerendered/vue-flowbite';
|
|
58
|
+
import SessionsHistory from '../SessionsHistory.vue';
|
|
59
|
+
import { useAgentStore } from '../composables/useAgentStore';
|
|
60
|
+
import { useAgentTransitions } from '../composables/useAgentTransitions';
|
|
61
|
+
import MessageRenderer from './MessageRenderer.vue';
|
|
62
|
+
import CustomAutoScrollContainer from '../CustomAutoScrollContainer.vue';
|
|
63
|
+
|
|
64
|
+
const props = defineProps<{
|
|
65
|
+
messages: IMessage[]
|
|
66
|
+
}>();
|
|
67
|
+
|
|
68
|
+
const scrollContainer = useTemplateRef('scrollContainer');
|
|
69
|
+
const showScrollToBottomButton = ref(false);
|
|
70
|
+
const innerScrollContainerRef = ref(null);
|
|
71
|
+
const agentStore = useAgentStore();
|
|
72
|
+
const agentTransitions = useAgentTransitions();
|
|
73
|
+
const clicks = ref(0);
|
|
74
|
+
|
|
75
|
+
function recalculateScroll() {
|
|
76
|
+
if (scrollContainer.value) {
|
|
77
|
+
const isScrolledUp = scrollContainer.value.isUserScrolledUp();
|
|
78
|
+
showScrollToBottomButton.value = !!isScrolledUp;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
onMounted(async () => {
|
|
83
|
+
await import('@incremark/theme/styles.css')
|
|
84
|
+
await agentStore.fetchPlaceholderMessages()
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
onUnmounted(() => {
|
|
88
|
+
agentStore.stopPlaceholderAnimation();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
watch(scrollContainer, () => {
|
|
92
|
+
if (scrollContainer.value) {
|
|
93
|
+
innerScrollContainerRef.value = scrollContainer.value.container;
|
|
94
|
+
|
|
95
|
+
innerScrollContainerRef.value.addEventListener('scroll', () => {
|
|
96
|
+
recalculateScroll();
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
watch(clicks, () => {
|
|
102
|
+
recalculateScroll();
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
</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>
|