@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
|
@@ -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>
|
|
@@ -1,52 +1,21 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
3
|
class="max-w-[80%] flex px-4 m-2 rounded-xl border border-gray-200 dark:border-gray-700"
|
|
4
4
|
@click="handleMarkdownLinkClick"
|
|
5
5
|
:class="[
|
|
6
6
|
hasVegaLite ? 'w-full' : '',
|
|
7
7
|
props.role === 'user' ? 'bg-lightListTableHeading dark:bg-darkListTableHeading self-end'
|
|
8
|
-
|
|
9
|
-
: 'bg-blue-100 dark:bg-blue-700/10 self-start'
|
|
8
|
+
: 'border-none self-start'
|
|
10
9
|
]"
|
|
11
10
|
>
|
|
12
11
|
<IncremarkContent
|
|
13
12
|
class="text-wrap break-words w-full max-w-full"
|
|
14
|
-
v-if="content
|
|
13
|
+
v-if="content"
|
|
15
14
|
:content="content"
|
|
16
15
|
:is-finished="isFinished"
|
|
17
16
|
:components="incremarkComponents"
|
|
18
17
|
:incremark-options="incremarkOptions"
|
|
19
18
|
/>
|
|
20
|
-
<!-- reasoning/thinking -->
|
|
21
|
-
<div
|
|
22
|
-
v-else-if="isTypeReasoning || isStateStreaming"
|
|
23
|
-
class="flex flex-col items-start gap-1 text-gray-500 py-2 "
|
|
24
|
-
>
|
|
25
|
-
<div class="flex items-center gap-1 hover:underline cursor-pointer text-lightListTableHeadingText hover:text-lightListTableHeadingText dark:text-darkListTableHeadingText dark:hover:text-darkListTableHeadingText" @click="isThoughtsExpanded = !isThoughtsExpanded">
|
|
26
|
-
<IconAngleDownOutline
|
|
27
|
-
v-if="content"
|
|
28
|
-
:class="isThoughtsExpanded ? 'rotate-180' : 'rotate-0'"
|
|
29
|
-
class="transition-transform duration-200"
|
|
30
|
-
/>
|
|
31
|
-
{{ isStateStreaming ? 'Thinking' : 'Thoughts' }}
|
|
32
|
-
<template v-if="isStateStreaming">
|
|
33
|
-
<span class="bounce-dot1 rounded-full w-2 h-2 bg-lightPrimary"></span>
|
|
34
|
-
<span class="bounce-dot2 rounded-full w-2 h-2 bg-lightPrimary"></span>
|
|
35
|
-
<span class="bounce-dot3 rounded-full w-2 h-2 bg-lightPrimary"></span>
|
|
36
|
-
</template>
|
|
37
|
-
</div>
|
|
38
|
-
<transition name="expand" class="max-h-36 overflow-y-auto">
|
|
39
|
-
<p v-show="isThoughtsExpanded" class="overflow-hidden">
|
|
40
|
-
{{ content }}
|
|
41
|
-
</p>
|
|
42
|
-
</transition>
|
|
43
|
-
</div>
|
|
44
|
-
<div v-else-if="isTypeToolCall && isToolCallStart">
|
|
45
|
-
{{ props.data?.toolName }} start
|
|
46
|
-
</div>
|
|
47
|
-
<div v-else-if="isTypeToolCall && isToolCallEnd">
|
|
48
|
-
{{ props.data?.toolName }} end
|
|
49
|
-
</div>
|
|
50
19
|
<p v-else class="text-red-500 py-2">
|
|
51
20
|
Error occured
|
|
52
21
|
</p>
|
|
@@ -56,12 +25,11 @@
|
|
|
56
25
|
<script setup lang="ts">
|
|
57
26
|
import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue';
|
|
58
27
|
import { useRouter } from 'vue-router';
|
|
59
|
-
import {
|
|
60
|
-
import { useAgentStore } from './composables/useAgentStore';
|
|
28
|
+
import { useAgentStore } from '../composables/useAgentStore';
|
|
61
29
|
import { useCoreStore } from '@/stores/core';
|
|
62
30
|
|
|
63
31
|
const IncremarkContent = defineAsyncComponent(() => import('@incremark/vue').then(module => module.IncremarkContent))
|
|
64
|
-
const ShikiCodeBlock = defineAsyncComponent(() => import('
|
|
32
|
+
const ShikiCodeBlock = defineAsyncComponent(() => import('../incremark_code_renderers/IncremarkShikiCodeBlock.vue'))
|
|
65
33
|
|
|
66
34
|
const agentStore = useAgentStore();
|
|
67
35
|
const coreStore = useCoreStore();
|
|
@@ -84,10 +52,8 @@
|
|
|
84
52
|
})
|
|
85
53
|
|
|
86
54
|
const props = defineProps<{
|
|
87
|
-
type: string,
|
|
88
55
|
message: string | undefined,
|
|
89
56
|
state: string | undefined,
|
|
90
|
-
data?: any
|
|
91
57
|
role: 'user' | 'assistant'
|
|
92
58
|
}>();
|
|
93
59
|
|
|
@@ -95,27 +61,23 @@
|
|
|
95
61
|
|
|
96
62
|
const content = computed(() => props.message)
|
|
97
63
|
const isFinished = computed(() => props.state === 'done')
|
|
98
|
-
const isThoughtsExpanded = ref(
|
|
99
|
-
const hasVegaLite = computed(() => props.
|
|
64
|
+
const isThoughtsExpanded = ref(true);
|
|
65
|
+
const hasVegaLite = computed(() => props.message?.includes('```vega-lite'))
|
|
100
66
|
|
|
101
|
-
const isTypeReasoning = computed(() => props.type === 'reasoning')
|
|
102
|
-
const isTypeToolCall = computed(() => props.type === 'data-tool-call')
|
|
103
|
-
const isToolCallStart = computed(() => {
|
|
104
|
-
if (props.type !== 'data-tool-call') return false;
|
|
105
|
-
return props.data?.phase === 'start';
|
|
106
|
-
})
|
|
107
|
-
const isToolCallEnd = computed(() => {
|
|
108
|
-
if (props.type !== 'data-tool-call') return false;
|
|
109
|
-
return props.data?.phase === 'end';
|
|
110
|
-
})
|
|
111
67
|
const isStateStreaming = computed(() => props.state === 'streaming')
|
|
112
68
|
|
|
69
|
+
watch(isStateStreaming, (newValue: boolean) => {
|
|
70
|
+
if (!newValue) {
|
|
71
|
+
isThoughtsExpanded.value = false;
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
113
75
|
watch(isThoughtsExpanded, (newValue: boolean) => {
|
|
114
76
|
emit('toggle-thoughts', newValue);
|
|
115
77
|
})
|
|
116
78
|
|
|
117
79
|
function handleMarkdownLinkClick(event: MouseEvent) {
|
|
118
|
-
if (
|
|
80
|
+
if (event.defaultPrevented || event.button !== 0) {
|
|
119
81
|
return;
|
|
120
82
|
}
|
|
121
83
|
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
@@ -134,7 +96,6 @@
|
|
|
134
96
|
return;
|
|
135
97
|
}
|
|
136
98
|
event.preventDefault();
|
|
137
|
-
|
|
138
99
|
const internalRoute = resolveInternalRoute(href);
|
|
139
100
|
if (internalRoute !== null) {
|
|
140
101
|
if (agentStore.isFullScreen && !coreStore.isMobile) {
|
|
@@ -170,55 +131,6 @@
|
|
|
170
131
|
}
|
|
171
132
|
</style>
|
|
172
133
|
|
|
173
|
-
<style scoped>
|
|
174
|
-
|
|
175
|
-
.bounce-dot1 {
|
|
176
|
-
animation: bounce 1.5s infinite;
|
|
177
|
-
animation-delay: 0s;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.bounce-dot2 {
|
|
181
|
-
animation: bounce 1.5s infinite;
|
|
182
|
-
animation-delay: 0.1s;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
.bounce-dot3 {
|
|
186
|
-
animation: bounce 1.5s infinite;
|
|
187
|
-
animation-delay: 0.2s;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
@keyframes bounce {
|
|
191
|
-
0%, 100% {
|
|
192
|
-
transform: translateY(20%);
|
|
193
|
-
opacity: 0.3;
|
|
194
|
-
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
|
|
195
|
-
}
|
|
196
|
-
50% {
|
|
197
|
-
transform: none;
|
|
198
|
-
opacity: 1;
|
|
199
|
-
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
.expand-enter-active,
|
|
204
|
-
.expand-leave-active {
|
|
205
|
-
transition: all 0.3s ease;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.expand-enter-from,
|
|
209
|
-
.expand-leave-to {
|
|
210
|
-
opacity: 0;
|
|
211
|
-
max-height: 0;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
.expand-enter-to,
|
|
215
|
-
.expand-leave-from {
|
|
216
|
-
opacity: 1;
|
|
217
|
-
max-height: 144px;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
</style>
|
|
221
|
-
|
|
222
134
|
<style lang="scss">
|
|
223
135
|
.incremark a.incremark-link,
|
|
224
136
|
.incremark a.incremark-link:visited {
|