@adminforth/agent 1.0.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/.woodpecker/buildRelease.sh +13 -0
- package/.woodpecker/buildSlackNotify.sh +46 -0
- package/.woodpecker/release.yml +57 -0
- package/agent/middleware/apiBasedTools.ts +109 -0
- package/agent/middleware/sequenceDebug.ts +302 -0
- package/agent/simpleAgent.ts +291 -0
- package/agent/skills/registry.ts +135 -0
- package/agent/systemPrompt.ts +69 -0
- package/agent/toolCallEvents.ts +17 -0
- package/agent/tools/apiTool.ts +99 -0
- package/agent/tools/fetchSkill.ts +58 -0
- package/agent/tools/fetchToolSchema.ts +50 -0
- package/agent/tools/index.ts +26 -0
- package/apiBasedTools.ts +625 -0
- package/build.log +30 -0
- package/custom/ChatSurface.vue +184 -0
- package/custom/ConversationArea.vue +175 -0
- package/custom/Message.vue +206 -0
- package/custom/SessionsHistory.vue +93 -0
- package/custom/ToolRenderer.vue +131 -0
- package/custom/ToolsGroup.vue +67 -0
- package/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +301 -0
- package/custom/incremark_code_renderers/incremarkCodeHighlight.ts +285 -0
- package/custom/incremark_code_renderers/incremarkRenderer.ts +653 -0
- package/custom/incremark_code_renderers/renderIncremarkMarkdown.ts +118 -0
- package/custom/package.json +26 -0
- package/custom/pnpm-lock.yaml +1467 -0
- package/custom/skills/fetch_data/SKILL.md +15 -0
- package/custom/skills/mutate_data/SKILL.md +108 -0
- package/custom/tsconfig.json +16 -0
- package/custom/types.ts +34 -0
- package/custom/useAgentStore.ts +349 -0
- package/dist/agent/middleware/apiBasedTools.js +91 -0
- package/dist/agent/middleware/sequenceDebug.js +210 -0
- package/dist/agent/simpleAgent.js +173 -0
- package/dist/agent/skills/registry.js +108 -0
- package/dist/agent/systemPrompt.js +64 -0
- package/dist/agent/toolCallEvents.js +1 -0
- package/dist/agent/tools/apiTool.js +93 -0
- package/dist/agent/tools/fetchSkill.js +51 -0
- package/dist/agent/tools/fetchToolSchema.js +36 -0
- package/dist/agent/tools/index.js +28 -0
- package/dist/apiBasedTools.js +412 -0
- package/dist/custom/ChatSurface.vue +184 -0
- package/dist/custom/ConversationArea.vue +175 -0
- package/dist/custom/Message.vue +206 -0
- package/dist/custom/SessionsHistory.vue +93 -0
- package/dist/custom/ToolRenderer.vue +131 -0
- package/dist/custom/ToolsGroup.vue +67 -0
- package/dist/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +301 -0
- package/dist/custom/incremark_code_renderers/incremarkCodeHighlight.ts +285 -0
- package/dist/custom/incremark_code_renderers/incremarkRenderer.ts +653 -0
- package/dist/custom/incremark_code_renderers/renderIncremarkMarkdown.ts +118 -0
- package/dist/custom/package.json +26 -0
- package/dist/custom/pnpm-lock.yaml +1467 -0
- package/dist/custom/skills/fetch_data/SKILL.md +15 -0
- package/dist/custom/skills/mutate_data/SKILL.md +108 -0
- package/dist/custom/tsconfig.json +16 -0
- package/dist/custom/types.ts +34 -0
- package/dist/custom/useAgentStore.ts +349 -0
- package/dist/index.js +415 -0
- package/dist/types.js +1 -0
- package/index.ts +457 -0
- package/package.json +58 -0
- package/tsconfig.json +13 -0
- package/types.ts +45 -0
package/build.log
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
> @adminforth/agent@1.0.1 build
|
|
3
|
+
> tsc && rsync -av --exclude 'node_modules' custom dist/
|
|
4
|
+
|
|
5
|
+
sending incremental file list
|
|
6
|
+
custom/
|
|
7
|
+
custom/ChatSurface.vue
|
|
8
|
+
custom/ConversationArea.vue
|
|
9
|
+
custom/Message.vue
|
|
10
|
+
custom/SessionsHistory.vue
|
|
11
|
+
custom/ToolRenderer.vue
|
|
12
|
+
custom/ToolsGroup.vue
|
|
13
|
+
custom/package.json
|
|
14
|
+
custom/pnpm-lock.yaml
|
|
15
|
+
custom/tsconfig.json
|
|
16
|
+
custom/types.ts
|
|
17
|
+
custom/useAgentStore.ts
|
|
18
|
+
custom/incremark_code_renderers/
|
|
19
|
+
custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue
|
|
20
|
+
custom/incremark_code_renderers/incremarkCodeHighlight.ts
|
|
21
|
+
custom/incremark_code_renderers/incremarkRenderer.ts
|
|
22
|
+
custom/incremark_code_renderers/renderIncremarkMarkdown.ts
|
|
23
|
+
custom/skills/
|
|
24
|
+
custom/skills/fetch_data/
|
|
25
|
+
custom/skills/fetch_data/SKILL.md
|
|
26
|
+
custom/skills/mutate_data/
|
|
27
|
+
custom/skills/mutate_data/SKILL.md
|
|
28
|
+
|
|
29
|
+
sent 136,251 bytes received 367 bytes 273,236.00 bytes/sec
|
|
30
|
+
total size is 134,767 speedup is 0.99
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="relative w-6 h-6 cursor-pointer mr-6 mt-1
|
|
4
|
+
text-lightNavbarIcons hover:text-lightNavbarIcons/80
|
|
5
|
+
dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80
|
|
6
|
+
hover:scale-110 transition-colors duration-200"
|
|
7
|
+
@click="agentStore.setIsChatOpen(!agentStore.isChatOpen)"
|
|
8
|
+
>
|
|
9
|
+
<IconChatBubbleLeft20Solid
|
|
10
|
+
class="w-6 h-6"
|
|
11
|
+
/>
|
|
12
|
+
<div class="absolute w-4 h-4 bg-lightNavbar dark:bg-darkNavbar rounded-full -top-1 -right-2">
|
|
13
|
+
<IconSparklesSolid
|
|
14
|
+
class="w-4 h-4"
|
|
15
|
+
/>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div
|
|
20
|
+
ref="chatSurface"
|
|
21
|
+
class="fixed bg-lightNavbar dark:bg-darkNavbar h-screen top-0 right-0 border-x border-b border-gray-200 dark:border-gray-700
|
|
22
|
+
transition-transform duration-200 ease-in-out
|
|
23
|
+
flex flex z-10"
|
|
24
|
+
:class="[agentStore.isChatOpen ? 'translate-x-0' : 'translate-x-full', !agentStore.isTeleportedToBody ? 'shadow-2xl' : '']"
|
|
25
|
+
:style="{ width: agentStore.chatWidth + 'px' }"
|
|
26
|
+
>
|
|
27
|
+
<div
|
|
28
|
+
v-if="!coreStore.isMobile"
|
|
29
|
+
class="w-2 cursor-ew-resize absolute left-0 top-0 h-full z-30"
|
|
30
|
+
@mousedown="startResize"
|
|
31
|
+
></div>
|
|
32
|
+
|
|
33
|
+
<div class="w-full h-full flex flex-col">
|
|
34
|
+
<div class="flex items-center justify-between">
|
|
35
|
+
<div class="flex items-center h-[55px]">
|
|
36
|
+
<IconBarsOutline
|
|
37
|
+
class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
|
|
38
|
+
text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
|
|
39
|
+
dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
|
|
40
|
+
:class="agentStore.isSessionHistoryOpen ?
|
|
41
|
+
'bg-lightNavbarIcons/20 text-lightNavbarIcons/80 dark:bg-darkNavbarIcons/20 dark:text-darkNavbarIcons/80' :
|
|
42
|
+
''"
|
|
43
|
+
@click="agentStore.setSessionHistoryOpen(!agentStore.isSessionHistoryOpen)"
|
|
44
|
+
/>
|
|
45
|
+
<IconOpenSidebarSolid
|
|
46
|
+
v-if="!agentStore.isTeleportedToBody && !coreStore.isMobile"
|
|
47
|
+
class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
|
|
48
|
+
text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
|
|
49
|
+
dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
|
|
50
|
+
@click="agentStore.setIsTeleportedToBody(true)"
|
|
51
|
+
/>
|
|
52
|
+
<IconCloseSidebarSolid
|
|
53
|
+
v-else-if="!coreStore.isMobile"
|
|
54
|
+
class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
|
|
55
|
+
text-lightNavbarIcons hover:text-lightNavbarIcons/80 bg-lightNavbarIcons/20
|
|
56
|
+
dark:bg-darkNavbarIcons/20 dark:text-darkNavbarIcons/80 hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
|
|
57
|
+
@click="agentStore.setIsTeleportedToBody(false)"
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<IconCloseOutline
|
|
62
|
+
class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
|
|
63
|
+
text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
|
|
64
|
+
dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
|
|
65
|
+
@click="agentStore.setIsChatOpen(false)"
|
|
66
|
+
/>
|
|
67
|
+
|
|
68
|
+
</div>
|
|
69
|
+
<div class="relative flex-1 flex flex-col overflow-hidden">
|
|
70
|
+
<ConversationArea
|
|
71
|
+
v-if="agentStore.isChatOpen"
|
|
72
|
+
class="flex-1 overflow-auto"
|
|
73
|
+
:messages="agentStore.chatMessages"
|
|
74
|
+
/>
|
|
75
|
+
|
|
76
|
+
<div class="w-full mb-2 flex items-center justify-center px-2 bg-transparent relative">
|
|
77
|
+
<textarea
|
|
78
|
+
v-model="agentStore.userMessageInput"
|
|
79
|
+
ref="textInput"
|
|
80
|
+
@input="autoResize"
|
|
81
|
+
class="min-h-12 p-4 pr-12 w-full resize-none overflow-hidden border text-lightInputText dark:text-darkInputText rounded-md bg-transparent text-sm bg-gray-50 dark:bg-gray-700 dark:border-gray-600 focus:outline-none"
|
|
82
|
+
placeholder="Type a message..."
|
|
83
|
+
@keydown.enter.exact.prevent="async () => {await agentStore.sendMessage(); autoResize();}"
|
|
84
|
+
/>
|
|
85
|
+
<Button
|
|
86
|
+
class="absolute right-4 bottom-2 !p-0 h-[34px] w-[34px]"
|
|
87
|
+
@click="async () => {await agentStore.sendMessage(); autoResize();}"
|
|
88
|
+
:disabled="!agentStore.trimmedUserMessage || agentStore.isResponseInProgress"
|
|
89
|
+
>
|
|
90
|
+
<IconArrowUpOutline
|
|
91
|
+
class="w-8 h-8 p-1
|
|
92
|
+
text-white"
|
|
93
|
+
/>
|
|
94
|
+
</Button>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
</template>
|
|
101
|
+
|
|
102
|
+
<script setup lang="ts">
|
|
103
|
+
import { IconChatBubbleLeft20Solid, IconSparklesSolid } from '@iconify-prerendered/vue-heroicons';
|
|
104
|
+
import { IconCloseOutline, IconBarsOutline, IconArrowUpOutline, IconCloseSidebarSolid, IconOpenSidebarSolid } from '@iconify-prerendered/vue-flowbite';
|
|
105
|
+
import { useTemplateRef, onMounted, ref, computed } from 'vue';
|
|
106
|
+
import { onClickOutside } from '@vueuse/core'
|
|
107
|
+
import ConversationArea from './ConversationArea.vue';
|
|
108
|
+
import { useAgentStore } from './useAgentStore';
|
|
109
|
+
import { Button } from '@/afcl';
|
|
110
|
+
import { useCoreStore } from '@/stores/core';
|
|
111
|
+
|
|
112
|
+
const props = defineProps<{
|
|
113
|
+
meta: {
|
|
114
|
+
pluginInstanceId: string;
|
|
115
|
+
}
|
|
116
|
+
}>();
|
|
117
|
+
|
|
118
|
+
const chatSurface = useTemplateRef('chatSurface');
|
|
119
|
+
const textInput = useTemplateRef('textInput');
|
|
120
|
+
const agentStore = useAgentStore();
|
|
121
|
+
const coreStore = useCoreStore();
|
|
122
|
+
|
|
123
|
+
const MAX_WIDTH = 800;
|
|
124
|
+
const MIN_WIDTH = 382; //w-96
|
|
125
|
+
|
|
126
|
+
let startX = 0
|
|
127
|
+
let startWidth = 0
|
|
128
|
+
|
|
129
|
+
const startResize = (e: MouseEvent) => {
|
|
130
|
+
startX = e.clientX
|
|
131
|
+
startWidth = agentStore.chatWidth
|
|
132
|
+
|
|
133
|
+
document.body.style.userSelect = 'none'
|
|
134
|
+
document.body.style.cursor = 'ew-resize'
|
|
135
|
+
|
|
136
|
+
document.addEventListener('mousemove', onResize)
|
|
137
|
+
document.addEventListener('mouseup', stopResize)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const onResize = (e: MouseEvent) => {
|
|
141
|
+
const dx = startX - e.clientX
|
|
142
|
+
agentStore.setChatWidth(Math.min(Math.max(startWidth + dx, MIN_WIDTH), MAX_WIDTH))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const stopResize = () => {
|
|
146
|
+
document.body.style.userSelect = ''
|
|
147
|
+
document.body.style.cursor = ''
|
|
148
|
+
|
|
149
|
+
document.removeEventListener('mousemove', onResize)
|
|
150
|
+
document.removeEventListener('mouseup', stopResize)
|
|
151
|
+
|
|
152
|
+
const appRoot = document.getElementById('app');
|
|
153
|
+
const header = document.getElementById('af-header-nav');
|
|
154
|
+
if (appRoot && header) {
|
|
155
|
+
appRoot.style.transition = 'padding-right 200ms ease-in-out';
|
|
156
|
+
header.style.transition = 'padding-right 200ms ease-in-out';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
onClickOutside(chatSurface, () => {if (!agentStore.isTeleportedToBody) agentStore.setIsChatOpen(false);});
|
|
161
|
+
|
|
162
|
+
onMounted(async () => {
|
|
163
|
+
agentStore.regisrerTextInput(textInput.value);
|
|
164
|
+
textInput.value?.focus();
|
|
165
|
+
await agentStore.fetchSessionsList();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
function autoResize() {
|
|
169
|
+
const el = textInput.value
|
|
170
|
+
if (!el) return
|
|
171
|
+
|
|
172
|
+
el.style.height = 'auto'
|
|
173
|
+
//max-w-48
|
|
174
|
+
const maxHeight = 192
|
|
175
|
+
if (el.scrollHeight > maxHeight) {
|
|
176
|
+
el.style.height = maxHeight + 'px'
|
|
177
|
+
el.style.overflowY = 'auto'
|
|
178
|
+
} else {
|
|
179
|
+
el.style.height = el.scrollHeight + 'px'
|
|
180
|
+
el.style.overflowY = 'hidden'
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
</script>
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button @click="scrollContainer.scrollToBottom()">
|
|
3
|
+
<IconArrowDownOutline
|
|
4
|
+
class="absolute bottom-32 right-4 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
|
+
<SessionsHistory
|
|
10
|
+
:class="agentStore.isSessionHistoryOpen ? 'translate-x-0' : '-translate-x-full'"
|
|
11
|
+
/>
|
|
12
|
+
<div
|
|
13
|
+
v-if="agentStore.isSessionHistoryOpen"
|
|
14
|
+
@click="agentStore.setSessionHistoryOpen(false)"
|
|
15
|
+
class="absolute bg-black/10 backdrop-blur-md z-10 h-full w-full"
|
|
16
|
+
>
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
<AutoScrollContainer
|
|
20
|
+
enabled
|
|
21
|
+
class="flex flex-col overflow-y-auto border-t border-gray-200 dark:border-gray-700"
|
|
22
|
+
ref="scrollContainer"
|
|
23
|
+
behavior="smooth"
|
|
24
|
+
>
|
|
25
|
+
|
|
26
|
+
<div
|
|
27
|
+
v-for="message in props.messages" :key="message.id"
|
|
28
|
+
class="flex flex-col w-full"
|
|
29
|
+
:class="message.role === 'user' ? 'self-end' : 'self-start'"
|
|
30
|
+
>
|
|
31
|
+
<ToolsGroup :toolGroup="groupToolCallParts(message)" />
|
|
32
|
+
<template
|
|
33
|
+
v-for="part in getParts(message)"
|
|
34
|
+
:key="part.type"
|
|
35
|
+
>
|
|
36
|
+
<Message
|
|
37
|
+
v-if="part.type !== 'data-tool-call'"
|
|
38
|
+
:message="part.text"
|
|
39
|
+
:role="message.role"
|
|
40
|
+
:type="part.type"
|
|
41
|
+
:state="part.state"
|
|
42
|
+
:data="part.data"
|
|
43
|
+
@toggle-thoughts="() => clicks++"
|
|
44
|
+
>
|
|
45
|
+
</Message>
|
|
46
|
+
</template>
|
|
47
|
+
</div>
|
|
48
|
+
<!-- Show a placeholder message if the last message is not of type 'text' or 'reasoning' -->
|
|
49
|
+
<Message
|
|
50
|
+
v-if="props.messages.length > 0 && showFakeThinkingMessage"
|
|
51
|
+
:message="''"
|
|
52
|
+
:role="props.messages[props.messages.length - 1].role"
|
|
53
|
+
type="reasoning"
|
|
54
|
+
state="streaming"
|
|
55
|
+
/>
|
|
56
|
+
<div
|
|
57
|
+
v-if="props.messages.length === 0"
|
|
58
|
+
class="flex-1 flex flex-col items-center justify-center text-gray-400 tracking-widest text-xl font-medium"
|
|
59
|
+
>
|
|
60
|
+
<p>Start the conversation</p>
|
|
61
|
+
<p class="tracking-normal text-base text">Give any input to begin</p>
|
|
62
|
+
</div>
|
|
63
|
+
</AutoScrollContainer>
|
|
64
|
+
</template>
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
<script setup lang="ts">
|
|
68
|
+
import Message from './Message.vue';
|
|
69
|
+
import type { IMessage, IPart } from './types';
|
|
70
|
+
import { useTemplateRef, ref, defineAsyncComponent, onMounted, watch, computed } from 'vue';
|
|
71
|
+
import { IconArrowDownOutline } from '@iconify-prerendered/vue-flowbite';
|
|
72
|
+
import SessionsHistory from './SessionsHistory.vue';
|
|
73
|
+
import { useAgentStore } from './useAgentStore';
|
|
74
|
+
import ToolRenderer from './ToolRenderer.vue';
|
|
75
|
+
import ToolsGroup from './ToolsGroup.vue';
|
|
76
|
+
|
|
77
|
+
const scrollContainer = useTemplateRef('scrollContainer');
|
|
78
|
+
const showScrollToBottomButton = ref(false);
|
|
79
|
+
const innerScrollContainerRef = ref(null);
|
|
80
|
+
const AutoScrollContainer = defineAsyncComponent(() => import('@incremark/vue').then(module => module.AutoScrollContainer))
|
|
81
|
+
const agentStore = useAgentStore();
|
|
82
|
+
const clicks = ref(0);
|
|
83
|
+
|
|
84
|
+
function recalculateScroll() {
|
|
85
|
+
if (scrollContainer.value) {
|
|
86
|
+
const isScrolledUp = scrollContainer.value.isUserScrolledUp();
|
|
87
|
+
showScrollToBottomButton.value = !!isScrolledUp;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
onMounted(async () => {
|
|
92
|
+
await import('@incremark/theme/styles.css')
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
watch(scrollContainer, () => {
|
|
96
|
+
if (scrollContainer.value) {
|
|
97
|
+
innerScrollContainerRef.value = scrollContainer.value.container;
|
|
98
|
+
|
|
99
|
+
innerScrollContainerRef.value.addEventListener('scroll', () => {
|
|
100
|
+
recalculateScroll();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
watch(clicks, () => {
|
|
106
|
+
recalculateScroll();
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const showFakeThinkingMessage = computed(() => {
|
|
110
|
+
const lastMessage = props.messages[props.messages.length - 1];
|
|
111
|
+
if (!lastMessage) return false;
|
|
112
|
+
const lastPart = getParts(lastMessage)[getParts(lastMessage).length - 1];
|
|
113
|
+
return lastPart?.type !== 'text' && lastPart?.type !== 'reasoning';
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const getParts = (message: IMessage) => {
|
|
117
|
+
return message.parts?.length
|
|
118
|
+
? message.parts
|
|
119
|
+
: [{ text: '', type: 'reasoning', state: 'streaming' }];
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const formatToolCallTextPart = ((part: IPart, currentMessage: IMessage) => {
|
|
123
|
+
if (part.type === 'data-tool-call') {
|
|
124
|
+
if (part.data?.phase === 'start') {
|
|
125
|
+
const finishedPart = currentMessage.parts?.find(p => p.type === 'data-tool-call' && p.data?.toolCallId === part.data?.toolCallId && p.data?.phase === 'end');
|
|
126
|
+
return {
|
|
127
|
+
type: 'text',
|
|
128
|
+
toolInfo: {
|
|
129
|
+
toolCallId: part.data!.toolCallId,
|
|
130
|
+
toolName: part.data!.toolName,
|
|
131
|
+
phase: finishedPart ? 'end' : 'start',
|
|
132
|
+
durationMs: finishedPart ? finishedPart.data?.durationMs : undefined,
|
|
133
|
+
input: part.data!.input,
|
|
134
|
+
output: finishedPart ? finishedPart.data!.output : undefined,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const groupToolCallParts = (message: IMessage) => {
|
|
143
|
+
const groupedParts = [];
|
|
144
|
+
let currentToolName = null;
|
|
145
|
+
const parts = getParts(message);
|
|
146
|
+
if (!parts) return [];
|
|
147
|
+
const formatedToolParts = parts.map(part => {
|
|
148
|
+
return formatToolCallTextPart(part as IPart, message)
|
|
149
|
+
});
|
|
150
|
+
for( const[index, part] of formatedToolParts.entries()){
|
|
151
|
+
if(!part?.toolInfo) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
console.log('part', part);
|
|
155
|
+
if (part.toolInfo.toolName === currentToolName) {
|
|
156
|
+
console.log('grouping part with tool name', currentToolName);
|
|
157
|
+
groupedParts[groupedParts.length - 1].groupedTools.push(part);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
currentToolName = part.toolInfo.toolName;
|
|
161
|
+
console.log('starting new group with tool name', currentToolName);
|
|
162
|
+
groupedParts.push({
|
|
163
|
+
title: currentToolName,
|
|
164
|
+
groupedTools: [part]
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
console.log('groupedParts', groupedParts);
|
|
168
|
+
return groupedParts;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const props = defineProps<{
|
|
172
|
+
messages: IMessage[]
|
|
173
|
+
}>();
|
|
174
|
+
|
|
175
|
+
</script>
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="max-w-[80%] flex px-4 py-2 m-2 rounded-xl border border-gray-200 dark:border-gray-700"
|
|
4
|
+
@click="handleMarkdownLinkClick"
|
|
5
|
+
:class="props.role === 'user' ? 'bg-lightListTableHeading dark:bg-darkListTableHeading self-end'
|
|
6
|
+
: isTypeReasoning || isTypeToolCall ? 'bg-transparent border-none self-start'
|
|
7
|
+
: 'bg-blue-100 dark:bg-blue-700/10 self-start'"
|
|
8
|
+
>
|
|
9
|
+
<IncremarkContent
|
|
10
|
+
class="text-wrap break-words max-w-full"
|
|
11
|
+
v-if="content && props.type === 'text'"
|
|
12
|
+
:content="content"
|
|
13
|
+
:is-finished="isFinished"
|
|
14
|
+
:components="incremarkComponents"
|
|
15
|
+
:incremark-options="incremarkOptions"
|
|
16
|
+
/>
|
|
17
|
+
<!-- reasoning/thinking -->
|
|
18
|
+
<div
|
|
19
|
+
v-else-if="isTypeReasoning || isStateStreaming"
|
|
20
|
+
class="flex flex-col items-start gap-1 text-gray-500 py-2 "
|
|
21
|
+
>
|
|
22
|
+
<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">
|
|
23
|
+
<IconAngleDownOutline
|
|
24
|
+
v-if="content"
|
|
25
|
+
:class="isThoughtsExpanded ? 'rotate-180' : 'rotate-0'"
|
|
26
|
+
class="transition-transform duration-200"
|
|
27
|
+
/>
|
|
28
|
+
{{ isStateStreaming ? 'Thinking' : 'Thoughts' }}
|
|
29
|
+
<template v-if="isStateStreaming">
|
|
30
|
+
<span class="bounce-dot1 rounded-full w-2 h-2 bg-lightPrimary"></span>
|
|
31
|
+
<span class="bounce-dot2 rounded-full w-2 h-2 bg-lightPrimary"></span>
|
|
32
|
+
<span class="bounce-dot3 rounded-full w-2 h-2 bg-lightPrimary"></span>
|
|
33
|
+
</template>
|
|
34
|
+
</div>
|
|
35
|
+
<transition name="expand" class="max-h-36 overflow-y-auto">
|
|
36
|
+
<p v-show="isThoughtsExpanded" class="overflow-hidden">
|
|
37
|
+
{{ content }}
|
|
38
|
+
</p>
|
|
39
|
+
</transition>
|
|
40
|
+
</div>
|
|
41
|
+
<div v-else-if="isTypeToolCall && isToolCallStart">
|
|
42
|
+
{{ props.data?.toolName }} start
|
|
43
|
+
</div>
|
|
44
|
+
<div v-else-if="isTypeToolCall && isToolCallEnd">
|
|
45
|
+
{{ props.data?.toolName }} end
|
|
46
|
+
</div>
|
|
47
|
+
<p v-else class="text-red-500 py-2">
|
|
48
|
+
Error occured
|
|
49
|
+
</p>
|
|
50
|
+
</div>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<script setup lang="ts">
|
|
54
|
+
import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue';
|
|
55
|
+
import { useRouter } from 'vue-router';
|
|
56
|
+
import { IconAngleDownOutline } from '@iconify-prerendered/vue-flowbite';
|
|
57
|
+
|
|
58
|
+
const IncremarkContent = defineAsyncComponent(() => import('@incremark/vue').then(module => module.IncremarkContent))
|
|
59
|
+
const ShikiCodeBlock = defineAsyncComponent(() => import('./incremark_code_renderers/IncremarkShikiCodeBlock.vue'))
|
|
60
|
+
|
|
61
|
+
const incremarkComponents = {
|
|
62
|
+
code: ShikiCodeBlock,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const incremarkOptions = {
|
|
66
|
+
gfm: true,
|
|
67
|
+
math: { tex: true },
|
|
68
|
+
containers: true,
|
|
69
|
+
htmlTree: true,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const router = useRouter();
|
|
73
|
+
|
|
74
|
+
onMounted(async () => {
|
|
75
|
+
void import('katex/dist/katex.min.css')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const props = defineProps<{
|
|
79
|
+
type: string,
|
|
80
|
+
message: string
|
|
81
|
+
state: string
|
|
82
|
+
data?: any
|
|
83
|
+
role: 'user' | 'assistant'
|
|
84
|
+
}>();
|
|
85
|
+
|
|
86
|
+
const emit = defineEmits(['toggle-thoughts']);
|
|
87
|
+
|
|
88
|
+
const content = computed(() => props.message)
|
|
89
|
+
const isFinished = computed(() => props.state === 'done')
|
|
90
|
+
const isThoughtsExpanded = ref(false)
|
|
91
|
+
|
|
92
|
+
const isTypeReasoning = computed(() => props.type === 'reasoning')
|
|
93
|
+
const isTypeToolCall = computed(() => props.type === 'data-tool-call')
|
|
94
|
+
const isToolCallStart = computed(() => {
|
|
95
|
+
if (props.type !== 'data-tool-call') return false;
|
|
96
|
+
return props.data?.phase === 'start';
|
|
97
|
+
})
|
|
98
|
+
const isToolCallEnd = computed(() => {
|
|
99
|
+
if (props.type !== 'data-tool-call') return false;
|
|
100
|
+
return props.data?.phase === 'end';
|
|
101
|
+
})
|
|
102
|
+
const isStateStreaming = computed(() => props.state === 'streaming')
|
|
103
|
+
|
|
104
|
+
watch(isThoughtsExpanded, (newValue: boolean) => {
|
|
105
|
+
emit('toggle-thoughts', newValue);
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
function handleMarkdownLinkClick(event: MouseEvent) {
|
|
109
|
+
if (props.type !== 'text' || event.defaultPrevented || event.button !== 0) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const target = event.target;
|
|
116
|
+
if (!(target instanceof Element)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const anchor = target.closest('a');
|
|
120
|
+
if (!(anchor instanceof HTMLAnchorElement)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const href = anchor.getAttribute('href');
|
|
124
|
+
if (!href || href.startsWith('mailto:') || href.startsWith('tel:')) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
|
|
129
|
+
const internalRoute = resolveInternalRoute(href);
|
|
130
|
+
if (internalRoute !== null) {
|
|
131
|
+
void router.push(internalRoute);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
window.location.assign(new URL(href, window.location.href).toString());
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function resolveInternalRoute(href: string): string | null {
|
|
139
|
+
if (href.startsWith('#')) {
|
|
140
|
+
return `${window.location.pathname}${window.location.search}${href}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const resolvedUrl = new URL(href, window.location.href);
|
|
144
|
+
if (resolvedUrl.origin !== window.location.origin) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return `${resolvedUrl.pathname}${resolvedUrl.search}${resolvedUrl.hash}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
</script>
|
|
152
|
+
|
|
153
|
+
<style lang="scss">
|
|
154
|
+
.incremark-paragraph {
|
|
155
|
+
margin: 8px 0;
|
|
156
|
+
}
|
|
157
|
+
</style>
|
|
158
|
+
|
|
159
|
+
<style scoped>
|
|
160
|
+
|
|
161
|
+
.bounce-dot1 {
|
|
162
|
+
animation: bounce 1.5s infinite;
|
|
163
|
+
animation-delay: 0s;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.bounce-dot2 {
|
|
167
|
+
animation: bounce 1.5s infinite;
|
|
168
|
+
animation-delay: 0.1s;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.bounce-dot3 {
|
|
172
|
+
animation: bounce 1.5s infinite;
|
|
173
|
+
animation-delay: 0.2s;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@keyframes bounce {
|
|
177
|
+
0%, 100% {
|
|
178
|
+
transform: translateY(20%);
|
|
179
|
+
opacity: 0.3;
|
|
180
|
+
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
|
|
181
|
+
}
|
|
182
|
+
50% {
|
|
183
|
+
transform: none;
|
|
184
|
+
opacity: 1;
|
|
185
|
+
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.expand-enter-active,
|
|
190
|
+
.expand-leave-active {
|
|
191
|
+
transition: all 0.3s ease;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.expand-enter-from,
|
|
195
|
+
.expand-leave-to {
|
|
196
|
+
opacity: 0;
|
|
197
|
+
max-height: 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.expand-enter-to,
|
|
201
|
+
.expand-leave-from {
|
|
202
|
+
opacity: 1;
|
|
203
|
+
max-height: 144px;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
</style>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="absolute top-0 left-0 transition-transform
|
|
3
|
+
duration-200 ease-in-out border-y border-r dark:border-gray-600 z-20
|
|
4
|
+
bg-lightNavbar dark:bg-darkNavbar w-96 h-full flex flex-col items-center
|
|
5
|
+
overflow-y-auto overflow-x-hidden
|
|
6
|
+
"
|
|
7
|
+
>
|
|
8
|
+
<h3 :class="h3Style">{{ $t('Chat history') }}</h3>
|
|
9
|
+
<Button @click="agentStore.createPreSession()" :disabled="agentStore.isResponseInProgress" class="w-[360px] mx-4 my-2 mb-4 rounded-3xl text-gray-800 dark:text-gray-200">
|
|
10
|
+
<IconPlusOutline class="w-5 h-5" />
|
|
11
|
+
{{ $t('New chat') }}
|
|
12
|
+
</Button>
|
|
13
|
+
<div class="w-full border-b border-gray-200 dark:border-gray-700"/>
|
|
14
|
+
<div class="absolute w-full h-full flex flex-col items-center justify-center bg-gray-100/50 dark:bg-gray-700/50 z-10" v-if="agentStore.isResponseInProgress">
|
|
15
|
+
<Spinner class="w-8 h-8" v-if="agentStore.isResponseInProgress" />
|
|
16
|
+
<p class="mt-2 text-gray-800 dark:text-gray-200">generation in progress...</p>
|
|
17
|
+
</div>
|
|
18
|
+
<div v-for="group in groupedSessions" :key="group.dayKey" class="w-full py-2">
|
|
19
|
+
<div class="px-4 pb-2 text-xs font-semibold uppercase tracking-[0.2em] text-gray-500 dark:text-gray-400">
|
|
20
|
+
{{ group.label }}
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<button
|
|
24
|
+
v-for="session in group.sessions"
|
|
25
|
+
:key="session.sessionId"
|
|
26
|
+
class="flex items-center justify-between w-full px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-200 ease-in-out text-gray-800 dark:text-gray-200 overflow-hidden text-nowrap"
|
|
27
|
+
:class="{'bg-lightPrimary/20 hover:bg-lightPrimary/20 dark:bg-darkPrimary/20 dark:hover:bg-darkPrimary/20': agentStore.activeSessionId === session.sessionId, 'cursor-default opacity-50 pointer-events-none': agentStore.isResponseInProgress}"
|
|
28
|
+
@click="agentStore.setActiveSession(session.sessionId)"
|
|
29
|
+
:disabled="agentStore.isResponseInProgress"
|
|
30
|
+
>
|
|
31
|
+
{{ session.title || session.sessionId }}
|
|
32
|
+
<div @click.stop="agentStore.deleteSession(session.sessionId)" class="w-7 h-7 p-1 hover:scale-110 hover:bg-gray-200 dark:hover:bg-gray-500 flex items-center justify-center rounded">
|
|
33
|
+
<IconPlusOutline class="rotate-45 w-6 h-6"/>
|
|
34
|
+
</div>
|
|
35
|
+
</button>
|
|
36
|
+
</div>
|
|
37
|
+
<p
|
|
38
|
+
v-if="!groupedSessions || groupedSessions.length === 0"
|
|
39
|
+
class="w-full h-full flex items-center justify-center text-gray-800 dark:text-gray-200"
|
|
40
|
+
>
|
|
41
|
+
There is no previous chat sessions
|
|
42
|
+
</p>
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
<script setup lang="ts">
|
|
48
|
+
import { Button, Spinner } from '@/afcl'
|
|
49
|
+
import { computed } from 'vue';
|
|
50
|
+
import { IconPlusOutline } from '@iconify-prerendered/vue-flowbite';
|
|
51
|
+
import type { ISessionsListItem } from './types';
|
|
52
|
+
import { useAgentStore } from './useAgentStore';
|
|
53
|
+
|
|
54
|
+
const agentStore = useAgentStore();
|
|
55
|
+
|
|
56
|
+
const h3Style = "text-gray-800 dark:text-gray-200 font-medium text-xl tracking-widest mt-4"
|
|
57
|
+
|
|
58
|
+
const dayLabelFormatter = new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric' });
|
|
59
|
+
const dayLabelWithYearFormatter = new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
|
|
60
|
+
|
|
61
|
+
function getLocalDayKey(date: Date) {
|
|
62
|
+
const year = date.getFullYear();
|
|
63
|
+
const month = `${date.getMonth() + 1}`.padStart(2, '0');
|
|
64
|
+
const day = `${date.getDate()}`.padStart(2, '0');
|
|
65
|
+
|
|
66
|
+
return `${year}-${month}-${day}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const groupedSessions = computed(() => {
|
|
70
|
+
const groups = new Map<string, { dayKey: string; label: string; sessions: ISessionsListItem[] }>();
|
|
71
|
+
|
|
72
|
+
for (const session of agentStore.sessionList) {
|
|
73
|
+
const date = new Date(session.timestamp);
|
|
74
|
+
const dayKey = getLocalDayKey(date);
|
|
75
|
+
const label = date.getFullYear() === new Date().getFullYear()
|
|
76
|
+
? dayLabelFormatter.format(date)
|
|
77
|
+
: dayLabelWithYearFormatter.format(date);
|
|
78
|
+
|
|
79
|
+
if (!groups.has(dayKey)) {
|
|
80
|
+
groups.set(dayKey, {
|
|
81
|
+
dayKey,
|
|
82
|
+
label,
|
|
83
|
+
sessions: [],
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
groups.get(dayKey)!.sessions.push(session);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return Array.from(groups.values());
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
</script>
|