@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.
Files changed (36) hide show
  1. package/build.log +12 -6
  2. package/custom/ChatSurface.vue +2 -2
  3. package/custom/CustomAutoScrollContainer.vue +127 -0
  4. package/custom/composables/useAgentStore.ts +8 -4
  5. package/custom/conversation_area/ConversationArea.vue +109 -0
  6. package/custom/conversation_area/MessageRenderer.vue +33 -0
  7. package/custom/conversation_area/ProcessingTimeline.vue +190 -0
  8. package/custom/conversation_area/ReasoningRenderer.vue +87 -0
  9. package/{dist/custom/Message.vue → custom/conversation_area/TextRenderer.vue} +14 -102
  10. package/custom/conversation_area/ThreeDotsAnimation.vue +35 -0
  11. package/custom/{ToolRenderer.vue → conversation_area/ToolRenderer.vue} +65 -13
  12. package/custom/conversation_area/ToolsGroup.vue +63 -0
  13. package/custom/package.json +2 -1
  14. package/custom/pnpm-lock.yaml +18 -0
  15. package/custom/types.ts +11 -1
  16. package/custom/utils.ts +29 -0
  17. package/dist/custom/ChatSurface.vue +2 -2
  18. package/dist/custom/CustomAutoScrollContainer.vue +127 -0
  19. package/dist/custom/composables/useAgentStore.ts +8 -4
  20. package/dist/custom/conversation_area/ConversationArea.vue +109 -0
  21. package/dist/custom/conversation_area/MessageRenderer.vue +33 -0
  22. package/dist/custom/conversation_area/ProcessingTimeline.vue +190 -0
  23. package/dist/custom/conversation_area/ReasoningRenderer.vue +87 -0
  24. package/{custom/Message.vue → dist/custom/conversation_area/TextRenderer.vue} +14 -102
  25. package/dist/custom/conversation_area/ThreeDotsAnimation.vue +35 -0
  26. package/dist/custom/{ToolRenderer.vue → conversation_area/ToolRenderer.vue} +65 -13
  27. package/dist/custom/conversation_area/ToolsGroup.vue +63 -0
  28. package/dist/custom/package.json +2 -1
  29. package/dist/custom/pnpm-lock.yaml +18 -0
  30. package/dist/custom/types.ts +11 -1
  31. package/dist/custom/utils.ts +29 -0
  32. package/package.json +1 -1
  33. package/custom/ConversationArea.vue +0 -198
  34. package/custom/ToolsGroup.vue +0 -67
  35. package/dist/custom/ConversationArea.vue +0 -198
  36. package/dist/custom/ToolsGroup.vue +0 -67
@@ -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.value = getLocalStorageItem('isTeleportedToBody') === 'true';
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: any) => p.text).join(' '),
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>
@@ -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
- : isTypeReasoning || isTypeToolCall ? 'bg-transparent border-none self-start'
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 && props.type === 'text'"
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 { IconAngleDownOutline } from '@iconify-prerendered/vue-flowbite';
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('./incremark_code_renderers/IncremarkShikiCodeBlock.vue'))
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(false)
99
- const hasVegaLite = computed(() => props.type === 'text' && props.message.includes('```vega-lite'))
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 (props.type !== 'text' || event.defaultPrevented || event.button !== 0) {
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 {
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <span class="bounce-dot1 rounded-full w-2 h-2 bg-lightPrimary"></span>
3
+ <span class="bounce-dot2 rounded-full w-2 h-2 bg-lightPrimary"></span>
4
+ <span class="bounce-dot3 rounded-full w-2 h-2 bg-lightPrimary"></span>
5
+ </template>
6
+
7
+ <style scoped>
8
+ .bounce-dot1 {
9
+ animation: bounce 1.5s infinite;
10
+ animation-delay: 0s;
11
+ }
12
+
13
+ .bounce-dot2 {
14
+ animation: bounce 1.5s infinite;
15
+ animation-delay: 0.1s;
16
+ }
17
+
18
+ .bounce-dot3 {
19
+ animation: bounce 1.5s infinite;
20
+ animation-delay: 0.2s;
21
+ }
22
+
23
+ @keyframes bounce {
24
+ 0%, 100% {
25
+ transform: translateY(20%);
26
+ opacity: 0.3;
27
+ animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
28
+ }
29
+ 50% {
30
+ transform: none;
31
+ opacity: 1;
32
+ animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
33
+ }
34
+ }
35
+ </style>