Skip to content

Commit e584fd5

Browse files
committed
feat: group executed tools, if 2 or more tools with the same name were executed
1 parent da353e5 commit e584fd5

3 files changed

Lines changed: 98 additions & 14 deletions

File tree

custom/ConversationArea.vue

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
class="flex flex-col w-full"
2929
:class="message.role === 'user' ? 'self-end' : 'self-start'"
3030
>
31+
<ToolsGroup :toolGroup="groupToolCallParts(message)" />
3132
<template
3233
v-for="part in getParts(message)"
3334
:key="part.type"
@@ -42,7 +43,6 @@
4243
@toggle-thoughts="() => clicks++"
4344
>
4445
</Message>
45-
<ToolRenderer v-else :data="formatToolCallTextPart(part, message)" />
4646
</template>
4747
</div>
4848
<!-- Show a placeholder message if the last message is not of type 'text' or 'reasoning' -->
@@ -72,6 +72,7 @@ import { IconArrowDownOutline } from '@iconify-prerendered/vue-flowbite';
7272
import SessionsHistory from './SessionsHistory.vue';
7373
import { useAgentStore } from './useAgentStore';
7474
import ToolRenderer from './ToolRenderer.vue';
75+
import ToolsGroup from './ToolsGroup.vue';
7576
7677
const scrollContainer = useTemplateRef('scrollContainer');
7778
const showScrollToBottomButton = ref(false);
@@ -138,18 +139,34 @@ const formatToolCallTextPart = ((part: IPart, currentMessage: IMessage) => {
138139
return null;
139140
});
140141
141-
// const groupToolCallParts = (parts: IPart[], message: IMessage) => {
142-
// const groupedParts = [];
143-
// const formatedToolParts = parts.map(part => {
144-
// return formatToolCallTextPart(part, message)
145-
// });
146-
// for( const[index, part] of formatedToolParts.entries()){
147-
// if(!part?.toolInfo) {
148-
// continue;
149-
// }
150-
151-
// }
152-
// }
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+
}
153170
154171
const props = defineProps<{
155172
messages: IMessage[]

custom/SessionsHistory.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<button
2424
v-for="session in group.sessions"
2525
: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"
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"
2727
: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}"
2828
@click="agentStore.setActiveSession(session.sessionId)"
2929
:disabled="agentStore.isResponseInProgress"

custom/ToolsGroup.vue

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<template>
2+
<template v-for="group in props.toolGroup" :key="group.title">
3+
<div v-if="group.groupedTools.length > 1" class="mb-4 flex flex-col">
4+
<div class="flex items-center gap-2 p-2 m-2 cursor-pointer hover:opacity-75 break-all font-mono text-sm leading-5" @click="toggleGroup(group.title)">
5+
- {{ group.title }} {{ 'x' + group.groupedTools.length }}
6+
<IconAngleDownOutline
7+
class="transition-transform duration-200 hover:scale-105 hover:opacity-75"
8+
:class="expandedGroups.includes(group.title) ? 'rotate-180' : 'rotate-0'"
9+
/>
10+
</div>
11+
<transition name="expand">
12+
<div v-show="expandedGroups.includes(group.title)" class="flex flex-col">
13+
<ToolRenderer v-for="part in group.groupedTools" :key="part.text + part.type" :data="part" />
14+
</div>
15+
</transition>
16+
</div>
17+
<ToolRenderer v-else :data="group.groupedTools[0]" />
18+
</template>
19+
20+
</template>
21+
22+
<script setup lang="ts">
23+
import { Tool } from 'langchain';
24+
import ToolRenderer from './ToolRenderer.vue';
25+
import type { IPart } from './types';
26+
import { ref } from 'vue';
27+
import { IconAngleDownOutline } from '@iconify-prerendered/vue-flowbite';
28+
29+
const props = defineProps<{
30+
toolGroup: {
31+
title: string;
32+
groupedTools: IPart[];
33+
}[]
34+
}>();
35+
36+
const expandedGroups = ref<string[]>([]);
37+
38+
function toggleGroup(groupTitle: string) {
39+
if (expandedGroups.value.includes(groupTitle)) {
40+
expandedGroups.value = expandedGroups.value.filter((title: string) => title !== groupTitle);
41+
} else {
42+
expandedGroups.value.push(groupTitle);
43+
}
44+
}
45+
46+
</script>
47+
48+
<style scoped>
49+
50+
.expand-enter-active,
51+
.expand-leave-active {
52+
transition: all 0.3s ease;
53+
}
54+
55+
.expand-enter-from,
56+
.expand-leave-to {
57+
opacity: 0;
58+
max-height: 0;
59+
}
60+
61+
.expand-enter-to,
62+
.expand-leave-from {
63+
opacity: 1;
64+
max-height: 288px;
65+
}
66+
67+
</style>

0 commit comments

Comments
 (0)