@adminforth/agent 2.9.1 → 2.10.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/README.md +4 -0
- package/dist/application/runTurnUseCase.d.ts +2 -1
- package/dist/application/runTurnUseCase.js +3 -3
- package/dist/custom/ChatFooter.vue +103 -81
- package/dist/custom/ChatHeader.vue +10 -3
- package/dist/custom/composables/agentStore/constants.ts +1 -1
- package/dist/custom/composables/agentStore/useAgentChat.ts +4 -3
- package/dist/custom/composables/agentStore/useAgentPlaceholder.ts +1 -0
- package/dist/custom/composables/agentStore/useAgentSessions.ts +2 -2
- package/dist/custom/composables/useAgentStore.ts +6 -29
- package/dist/custom/conversation_area/ConversationArea.vue +35 -3
- package/dist/custom/conversation_area/MessageRenderer.vue +9 -5
- package/dist/custom/conversation_area/ProcessingTimeline.vue +83 -123
- package/dist/custom/conversation_area/ReasoningRenderer.vue +14 -16
- package/dist/custom/conversation_area/TextRenderer.vue +4 -3
- package/dist/custom/conversation_area/ToolApprovalRenderer.vue +95 -54
- package/dist/custom/conversation_area/ToolRenderer.vue +44 -82
- package/dist/custom/speech_recognition_frontend/AudioLines.vue +1 -2
- package/dist/custom/speech_recognition_frontend/MicrophoneButon.vue +34 -32
- package/dist/custom/types.ts +16 -8
- package/dist/domain/agentEvents.d.ts +3 -2
- package/dist/tools/approvalFormatter.d.ts +19 -5
- package/dist/tools/approvalFormatter.js +88 -40
- package/dist/transport/http/context.d.ts +2 -1
- package/package.json +1 -1
- package/dist/custom/conversation_area/ToolsGroup.vue +0 -59
|
@@ -1,58 +1,69 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<template v-if="
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
<template v-if="timelineParts.length > 0 || inProgress">
|
|
3
|
+
<button
|
|
4
|
+
type="button"
|
|
5
|
+
class="z-20 self-start flex items-center gap-1.5 mx-4 my-1 select-none text-[0.9375rem] font-medium transition-colors duration-150
|
|
6
|
+
text-gray-600 dark:text-gray-400"
|
|
7
|
+
:class="timelineParts.length > 0 ? 'hover:text-gray-900 dark:hover:text-gray-200' : 'cursor-default'"
|
|
6
8
|
@click="isExpanded = !isExpanded"
|
|
7
9
|
>
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
bg-[linear-gradient(-65deg,currentColor,_rgb(120,120,120)_50%,currentColor)]
|
|
13
|
-
dark:bg-[linear-gradient(-65deg,currentColor,_rgb(250,250,250,0.8)_70%,currentColor)]
|
|
14
|
-
`
|
|
15
|
-
: '']"
|
|
16
|
-
>
|
|
17
|
-
{{ inProgress ? $t('Thinking') : $t('Thoughts') }}
|
|
18
|
-
</p>
|
|
19
|
-
<ThreeDotsAnimation v-if="inProgress" />
|
|
20
|
-
<span v-if="thinkingDuration > 0">({{ (thinkingDuration/1000).toFixed(2) }} s)</span>
|
|
21
|
-
<IconAngleDownOutline
|
|
22
|
-
v-if="ToolOrReasoningParts.length > 0"
|
|
23
|
-
:class="isExpanded ? 'rotate-180' : 'rotate-0'"
|
|
24
|
-
class="transition-transform duration-200"
|
|
10
|
+
<IconChevronRight
|
|
11
|
+
v-if="timelineParts.length > 0"
|
|
12
|
+
class="w-4 h-4 transition-transform duration-200"
|
|
13
|
+
:class="isExpanded ? 'rotate-90' : 'rotate-0'"
|
|
25
14
|
/>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
15
|
+
<template v-if="inProgress">
|
|
16
|
+
<span
|
|
17
|
+
class="shine-text text-listTableHeadingText dark:text-darkListTableHeadingText
|
|
18
|
+
bg-[linear-gradient(-65deg,currentColor,_rgb(120,120,120)_50%,currentColor)]
|
|
19
|
+
dark:bg-[linear-gradient(-65deg,currentColor,_rgb(250,250,250,0.8)_70%,currentColor)]"
|
|
20
|
+
>
|
|
21
|
+
{{ $t('Thinking') }}
|
|
22
|
+
</span>
|
|
23
|
+
<ThreeDotsAnimation />
|
|
24
|
+
</template>
|
|
25
|
+
<template v-else>
|
|
26
|
+
<span>{{ thinkingSeconds ? $t('Thought for {seconds}s', { seconds: thinkingSeconds }) : $t('Thoughts') }}</span>
|
|
27
|
+
<span v-if="toolCallsCount">
|
|
28
|
+
· {{ toolCallsCount === 1 ? $t('1 tool') : $t('{count} tools', { count: toolCallsCount }) }}
|
|
29
|
+
</span>
|
|
30
|
+
</template>
|
|
31
|
+
</button>
|
|
32
|
+
<transition name="expand">
|
|
33
|
+
<!-- the box is inset from the message edge, so its own scrollbar never sits on top of the conversation scrollbar -->
|
|
34
|
+
<div
|
|
35
|
+
v-if="timelineParts.length > 0"
|
|
33
36
|
v-show="isExpanded"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
marginRight: '1rem',
|
|
37
|
-
}"
|
|
38
|
-
:scrollBarAutoHide="false"
|
|
37
|
+
class="z-20 mx-4 mt-1 mb-3 overflow-hidden rounded-xl border
|
|
38
|
+
border-gray-100 bg-gray-50/70 dark:border-gray-700 dark:bg-gray-800/50"
|
|
39
39
|
>
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
<CustomAutoScrollContainer
|
|
41
|
+
ref="scrollContainerRef"
|
|
42
|
+
class="max-h-48 sm:max-h-96 overflow-y-auto"
|
|
43
|
+
:enabled="isResponseInProgress"
|
|
44
|
+
behavior="smooth"
|
|
45
|
+
:threshold="5"
|
|
46
|
+
:scrollBarAutoHide="false"
|
|
47
|
+
>
|
|
48
|
+
<!-- wider right padding keeps the row chevrons clear of the box's scrollbar -->
|
|
49
|
+
<ul class="flex flex-col gap-1 pl-2.5 pr-5 py-2">
|
|
50
|
+
<template v-for="(part, index) in timelineParts" :key="index">
|
|
51
|
+
<ReasoningRenderer v-if="part.type === 'reasoning'" :state="part.state" :text="part.text" />
|
|
52
|
+
<ToolRenderer v-else-if="part.type === 'data-tool-call'" :data="formatToolCallPart(part)" />
|
|
53
|
+
<li
|
|
54
|
+
v-else-if="part.type === 'data-rendering'"
|
|
55
|
+
class="flex items-center gap-2 py-0.5 text-[0.8125rem] text-gray-700 dark:text-gray-300"
|
|
56
|
+
>
|
|
57
|
+
<span class="flex items-center justify-center w-4 h-4 shrink-0">
|
|
58
|
+
<span class="w-2 h-2 rounded-full bg-current animate-pulse" />
|
|
59
|
+
</span>
|
|
60
|
+
{{ part.data?.label ?? 'Rendering...' }}
|
|
50
61
|
<ThreeDotsAnimation />
|
|
51
|
-
</
|
|
52
|
-
</
|
|
53
|
-
</
|
|
54
|
-
</
|
|
55
|
-
</
|
|
62
|
+
</li>
|
|
63
|
+
</template>
|
|
64
|
+
</ul>
|
|
65
|
+
</CustomAutoScrollContainer>
|
|
66
|
+
</div>
|
|
56
67
|
</transition>
|
|
57
68
|
</template>
|
|
58
69
|
</template>
|
|
@@ -60,14 +71,13 @@
|
|
|
60
71
|
|
|
61
72
|
|
|
62
73
|
<script setup lang="ts">
|
|
63
|
-
import type { IFormattedToolCallPart, IMessage, IPart
|
|
74
|
+
import type { IFormattedToolCallPart, IMessage, IPart } from '../types';
|
|
64
75
|
import { ref, computed, watch, onUnmounted, onMounted, defineAsyncComponent } from 'vue';
|
|
76
|
+
import { IconChevronRight } from '@iconify-prerendered/vue-heroicons';
|
|
65
77
|
import ReasoningRenderer from './ReasoningRenderer.vue';
|
|
66
|
-
import
|
|
78
|
+
import ToolRenderer from './ToolRenderer.vue';
|
|
67
79
|
import ThreeDotsAnimation from './ThreeDotsAnimation.vue';
|
|
68
80
|
import { useAgentStore } from '../composables/useAgentStore';
|
|
69
|
-
import { getMessageParts } from '../utils';
|
|
70
|
-
import ToolsGroup from './ToolsGroup.vue';
|
|
71
81
|
|
|
72
82
|
const CustomAutoScrollContainer = defineAsyncComponent(() => import('../CustomAutoScrollContainer.vue'));
|
|
73
83
|
|
|
@@ -83,17 +93,23 @@
|
|
|
83
93
|
const innerScrollContainerRef = ref<HTMLElement | null>(null);
|
|
84
94
|
const isExpanded = ref(true);
|
|
85
95
|
let isUserScrolled = false;
|
|
86
|
-
|
|
96
|
+
// One step per reasoning block and per tool call (its 'start' part; the matching 'end'
|
|
97
|
+
// part is merged in by formatToolCallPart), plus a rendering step while it is running.
|
|
98
|
+
const timelineParts = computed(() => {
|
|
87
99
|
return props.message.parts.filter((part: IPart) => {
|
|
88
|
-
return part.type === '
|
|
89
|
-
|| part.type === '
|
|
100
|
+
return part.type === 'reasoning'
|
|
101
|
+
|| (part.type === 'data-tool-call' && part.data?.phase === 'start')
|
|
90
102
|
|| isActiveRenderingPart(part);
|
|
91
103
|
});
|
|
92
104
|
});
|
|
105
|
+
const toolCallsCount = computed(() => timelineParts.value.filter(part => part.type === 'data-tool-call').length);
|
|
106
|
+
const thinkingSeconds = computed(() => (
|
|
107
|
+
thinkingDuration.value > 0 ? Math.max(1, Math.round(thinkingDuration.value / 1000)) : 0
|
|
108
|
+
));
|
|
93
109
|
const isResponseInProgress = computed(() =>{
|
|
94
|
-
return props.isLastMessageInChat && agentStore.isResponseInProgress;
|
|
110
|
+
return props.isLastMessageInChat && agentStore.isResponseInProgress;
|
|
95
111
|
});
|
|
96
|
-
|
|
112
|
+
|
|
97
113
|
const showFakeThinkingMessage = computed(() => {
|
|
98
114
|
if (props.message.parts.length === 0 && props.isLastMessageInChat) return true;
|
|
99
115
|
return false;
|
|
@@ -141,37 +157,28 @@
|
|
|
141
157
|
}
|
|
142
158
|
});
|
|
143
159
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const finishedPart = currentMessage.parts.find(candidate => {
|
|
160
|
+
function formatToolCallPart(part: IPart): IFormattedToolCallPart {
|
|
161
|
+
const data = part.data!;
|
|
162
|
+
const finishedPart = props.message.parts.find(candidate => {
|
|
150
163
|
return candidate.type === 'data-tool-call'
|
|
151
|
-
&& candidate.data?.toolCallId ===
|
|
164
|
+
&& candidate.data?.toolCallId === data.toolCallId
|
|
152
165
|
&& candidate.data?.phase === 'end';
|
|
153
166
|
});
|
|
154
167
|
|
|
155
168
|
return {
|
|
156
169
|
type: 'data-tool-call',
|
|
157
170
|
toolInfo: {
|
|
158
|
-
toolCallId:
|
|
159
|
-
toolName:
|
|
171
|
+
toolCallId: data.toolCallId,
|
|
172
|
+
toolName: data.toolName,
|
|
160
173
|
phase: finishedPart ? 'end' : 'start',
|
|
161
174
|
durationMs: finishedPart?.data?.durationMs,
|
|
162
|
-
input:
|
|
175
|
+
input: data.input,
|
|
163
176
|
output: finishedPart?.data?.output,
|
|
164
|
-
toolInfo:
|
|
177
|
+
toolInfo: data.toolInfo,
|
|
165
178
|
error: finishedPart?.data?.error,
|
|
166
179
|
}
|
|
167
180
|
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const getVisibleTimelineParts = (message: IMessage) => {
|
|
171
|
-
return getMessageParts(message).filter(part => {
|
|
172
|
-
return part.type === 'reasoning' || (part.type === 'data-tool-call' && part.data?.phase === 'start');
|
|
173
|
-
});
|
|
174
|
-
};
|
|
181
|
+
}
|
|
175
182
|
|
|
176
183
|
function isActiveRenderingPart(part: IPart) {
|
|
177
184
|
return part.type === 'data-rendering'
|
|
@@ -181,55 +188,8 @@
|
|
|
181
188
|
});
|
|
182
189
|
}
|
|
183
190
|
|
|
184
|
-
const groupToolCallParts = (message: IMessage, currentPart: IPart): IToolGroup[] => {
|
|
185
|
-
if (currentPart.type !== 'data-tool-call') {
|
|
186
|
-
return [];
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const visibleParts = getVisibleTimelineParts(message);
|
|
190
|
-
const currentPartIndex = visibleParts.findIndex(part => part === currentPart);
|
|
191
|
-
|
|
192
|
-
if (currentPartIndex === -1) {
|
|
193
|
-
return [];
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (currentPartIndex > 0 && visibleParts[currentPartIndex - 1]?.type === 'data-tool-call') {
|
|
197
|
-
return [];
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const groupedParts: IToolGroup[] = [];
|
|
201
|
-
|
|
202
|
-
for (let index = currentPartIndex; index < visibleParts.length; index += 1) {
|
|
203
|
-
const part = visibleParts[index];
|
|
204
|
-
|
|
205
|
-
if (part.type === 'reasoning') {
|
|
206
|
-
break;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const formattedPart = formatToolCallPart(part, message);
|
|
210
|
-
|
|
211
|
-
if (!formattedPart) {
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const lastGroup = groupedParts[groupedParts.length - 1];
|
|
216
|
-
|
|
217
|
-
if (lastGroup?.title === formattedPart.toolInfo.toolName) {
|
|
218
|
-
lastGroup.groupedTools.push(formattedPart);
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
groupedParts.push({
|
|
223
|
-
title: formattedPart.toolInfo.toolName,
|
|
224
|
-
groupedTools: [formattedPart],
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return groupedParts;
|
|
229
|
-
};
|
|
230
|
-
|
|
231
191
|
function handleScroll() {
|
|
232
|
-
if (isUserScrolled) {
|
|
192
|
+
if (isUserScrolled) {
|
|
233
193
|
scrollContainerRef.value?.handleScroll();
|
|
234
194
|
}
|
|
235
195
|
}
|
|
@@ -1,36 +1,34 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<li class="
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
</div>
|
|
7
|
-
</span>
|
|
8
|
-
<h3
|
|
9
|
-
class=" flex items-center mb-1 text-sm ml-3 gap-1 cursor-pointer select-none hover:opacity-80 text-listTableHeadingText dark:text-darkListTableHeadingText"
|
|
2
|
+
<li class="overflow-hidden">
|
|
3
|
+
<button
|
|
4
|
+
type="button"
|
|
5
|
+
class="flex items-center gap-2 w-full py-0.5 text-left text-[0.8125rem] leading-5 select-none"
|
|
10
6
|
@click="isExpanded = !isExpanded"
|
|
11
7
|
>
|
|
12
|
-
<
|
|
8
|
+
<IconBrainOutline class="w-4 h-4 shrink-0 text-gray-500 dark:text-gray-400" />
|
|
9
|
+
<span class="min-w-0 truncate font-medium text-gray-700 dark:text-gray-300">{{ reasoningTitle || $t('Reasoning') }}</span>
|
|
13
10
|
<ThreeDotsAnimation v-if="isStreaming"/>
|
|
14
|
-
<
|
|
11
|
+
<IconChevronDown
|
|
15
12
|
:class="isExpanded ? 'rotate-180' : 'rotate-0'"
|
|
16
|
-
class="transition-transform duration-200"
|
|
13
|
+
class="ml-auto w-3.5 h-3.5 shrink-0 text-gray-400 transition-transform duration-200"
|
|
17
14
|
/>
|
|
18
|
-
</
|
|
15
|
+
</button>
|
|
19
16
|
<transition name="expand">
|
|
20
17
|
<IncremarkContent
|
|
21
|
-
v-if="isExpanded"
|
|
18
|
+
v-if="isExpanded"
|
|
22
19
|
v-show="isExpanded"
|
|
23
|
-
class="
|
|
20
|
+
class="ml-6 mb-1 text-sm text-gray-600 dark:text-gray-400"
|
|
24
21
|
:content="reasoningText"
|
|
25
22
|
/>
|
|
26
|
-
</transition>
|
|
23
|
+
</transition>
|
|
27
24
|
</li>
|
|
28
25
|
</template>
|
|
29
26
|
|
|
30
27
|
|
|
31
28
|
|
|
32
29
|
<script setup lang="ts">
|
|
33
|
-
import { IconBrainOutline
|
|
30
|
+
import { IconBrainOutline } from '@iconify-prerendered/vue-flowbite';
|
|
31
|
+
import { IconChevronDown } from '@iconify-prerendered/vue-heroicons';
|
|
34
32
|
import type { IPart } from '../types';
|
|
35
33
|
import { ref, computed, watch, defineAsyncComponent } from 'vue';
|
|
36
34
|
import ThreeDotsAnimation from './ThreeDotsAnimation.vue';
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
class="flex items-center justify-center
|
|
3
|
+
class="flex items-center justify-center min-w-0 transition-shadow duration-150 z-20"
|
|
4
4
|
@click="handleMarkdownLinkClick"
|
|
5
5
|
:class="[
|
|
6
6
|
hasVegaLite ? 'w-full my-2' : 'my-2',
|
|
7
|
-
props.role === 'user'
|
|
8
|
-
|
|
7
|
+
props.role === 'user'
|
|
8
|
+
? 'self-end max-w-[82%] mr-4 px-3.5 py-0.5 rounded-2xl rounded-br-md bg-lightPrimary/10 dark:bg-darkPrimary/20'
|
|
9
|
+
: 'self-start max-w-full px-4',
|
|
9
10
|
props.editMode ? 'ring-2 ring-lightPrimary dark:ring-darkPrimary w-full max-w-[calc(100%-2rem)] flex-col items-stretch py-3' : ''
|
|
10
11
|
]"
|
|
11
12
|
>
|
|
@@ -1,72 +1,111 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
<div
|
|
3
|
+
class="mx-4 my-2 max-w-[min(34rem,calc(100%-2rem))] z-20 rounded-xl border p-3.5"
|
|
4
|
+
:class="isAwaitingDecision
|
|
5
|
+
? 'border-amber-200 bg-amber-50 dark:border-amber-500/30 dark:bg-amber-500/10'
|
|
6
|
+
: 'border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-800/50'"
|
|
7
|
+
>
|
|
8
|
+
<div class="flex items-start gap-2.5">
|
|
9
|
+
<span
|
|
10
|
+
class="flex h-7 w-7 shrink-0 items-center justify-center rounded-full"
|
|
11
|
+
:class="isAwaitingDecision
|
|
12
|
+
? 'bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300'
|
|
13
|
+
: 'bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-300'"
|
|
14
|
+
>
|
|
15
|
+
<IconShieldExclamation class="h-4 w-4" />
|
|
16
|
+
</span>
|
|
7
17
|
<div class="min-w-0 flex-1">
|
|
8
|
-
<h3 class="text-sm font-semibold leading-5">{{ $t('Approval
|
|
9
|
-
<p class="
|
|
18
|
+
<h3 class="text-sm font-semibold leading-5 text-gray-900 dark:text-gray-100">{{ $t('Approval needed') }}</h3>
|
|
19
|
+
<p class="text-[0.8125rem] leading-5 text-gray-600 dark:text-gray-300">
|
|
10
20
|
{{ $t('Review what the agent is about to do before continuing.') }}
|
|
11
21
|
</p>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<ul
|
|
25
|
+
class="mt-3 divide-y rounded-lg border bg-white text-[0.8125rem] leading-5 text-gray-800
|
|
26
|
+
divide-gray-100 border-gray-200 dark:divide-gray-700 dark:border-gray-700 dark:bg-gray-900/40 dark:text-gray-200"
|
|
27
|
+
>
|
|
28
|
+
<li
|
|
29
|
+
v-for="(approval, approvalIndex) in data.approvals"
|
|
30
|
+
:key="approvalIndex"
|
|
31
|
+
class="px-3 py-2.5"
|
|
32
|
+
>
|
|
33
|
+
<p class="font-medium [overflow-wrap:anywhere]">{{ approval.summary }}</p>
|
|
34
|
+
<dl
|
|
35
|
+
v-if="approval.fields.length"
|
|
36
|
+
class="mt-2 grid grid-cols-[minmax(0,auto)_minmax(0,1fr)] gap-x-4 gap-y-1.5"
|
|
15
37
|
>
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
type="button"
|
|
37
|
-
class="inline-flex h-9 items-center gap-2 rounded-md border border-lightListTableText/20 bg-transparent px-3 text-sm font-medium text-lightListTableHeadingText transition hover:bg-lightListTableText/10 dark:border-darkListTableText/20 dark:text-darkListTableHeadingText dark:hover:bg-darkListTableText/10"
|
|
38
|
-
@click="submit('reject')"
|
|
39
|
-
>
|
|
40
|
-
<IconCloseOutline class="h-4 w-4" />
|
|
41
|
-
{{ $t('Reject') }}
|
|
42
|
-
</button>
|
|
38
|
+
<template v-for="(field, fieldIndex) in approval.fields" :key="fieldIndex">
|
|
39
|
+
<dt class="text-gray-500 dark:text-gray-400 [overflow-wrap:anywhere]">{{ field.label }}</dt>
|
|
40
|
+
<dd class="flex min-w-0 flex-wrap items-center gap-1.5">
|
|
41
|
+
<template v-if="field.from !== undefined && field.from !== field.to">
|
|
42
|
+
<span
|
|
43
|
+
class="min-w-0 rounded px-1.5 line-through [overflow-wrap:anywhere]
|
|
44
|
+
bg-red-50 text-red-700 decoration-red-700/50 dark:bg-red-500/15 dark:text-red-300 dark:decoration-red-300/50"
|
|
45
|
+
>
|
|
46
|
+
{{ field.from }}
|
|
47
|
+
</span>
|
|
48
|
+
<IconArrowRight class="h-3.5 w-3.5 shrink-0 text-gray-400" />
|
|
49
|
+
<span
|
|
50
|
+
class="min-w-0 rounded px-1.5 font-medium [overflow-wrap:anywhere]
|
|
51
|
+
bg-green-50 text-green-700 dark:bg-green-500/15 dark:text-green-300"
|
|
52
|
+
>
|
|
53
|
+
{{ field.to }}
|
|
54
|
+
</span>
|
|
55
|
+
</template>
|
|
56
|
+
<span v-else class="min-w-0 font-medium [overflow-wrap:anywhere]">{{ field.to }}</span>
|
|
57
|
+
</dd>
|
|
43
58
|
</template>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<p
|
|
55
|
-
v-if="data.error"
|
|
56
|
-
class="mt-3 text-sm leading-5 text-red-600 dark:text-red-400"
|
|
59
|
+
</dl>
|
|
60
|
+
</li>
|
|
61
|
+
</ul>
|
|
62
|
+
<div class="mt-3 flex flex-wrap gap-2">
|
|
63
|
+
<template v-if="data.status === 'pending'">
|
|
64
|
+
<button
|
|
65
|
+
type="button"
|
|
66
|
+
class="inline-flex h-8 items-center gap-1.5 rounded-lg px-3 text-[0.8125rem] font-medium transition-opacity duration-150
|
|
67
|
+
bg-lightPrimary text-lightPrimaryContrast hover:opacity-90 dark:bg-darkPrimary dark:text-darkPrimaryContrast"
|
|
68
|
+
@click="submit('approve')"
|
|
57
69
|
>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
70
|
+
<IconCheck class="h-4 w-4" />
|
|
71
|
+
{{ $t('Approve') }}
|
|
72
|
+
</button>
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
class="inline-flex h-8 items-center rounded-lg border px-3 text-[0.8125rem] font-medium transition-colors duration-150
|
|
76
|
+
border-gray-300 bg-white text-gray-700 hover:bg-gray-50
|
|
77
|
+
dark:border-gray-600 dark:bg-transparent dark:text-gray-200 dark:hover:bg-gray-700/50"
|
|
78
|
+
@click="submit('reject')"
|
|
79
|
+
>
|
|
80
|
+
{{ $t('Reject') }}
|
|
81
|
+
</button>
|
|
82
|
+
</template>
|
|
83
|
+
<span
|
|
84
|
+
v-else
|
|
85
|
+
class="inline-flex h-7 items-center gap-1.5 rounded-lg px-2.5 text-[0.8125rem] font-medium
|
|
86
|
+
bg-white text-gray-700 dark:bg-gray-900/40 dark:text-gray-200"
|
|
87
|
+
>
|
|
88
|
+
<Spinner v-if="data.status === 'processing'" class="h-4 w-4" />
|
|
89
|
+
<IconCheck v-else-if="data.status === 'approved'" class="h-4 w-4" />
|
|
90
|
+
<IconXMark v-else class="h-4 w-4" />
|
|
91
|
+
{{ data.status === 'processing' ? $t('Processing') : data.status === 'approved' ? $t('Approved') : $t('Rejected') }}
|
|
92
|
+
</span>
|
|
61
93
|
</div>
|
|
94
|
+
<p
|
|
95
|
+
v-if="data.error"
|
|
96
|
+
class="mt-3 text-sm leading-5 text-red-600 dark:text-red-400"
|
|
97
|
+
>
|
|
98
|
+
{{ data.error }}
|
|
99
|
+
</p>
|
|
62
100
|
</div>
|
|
63
101
|
</template>
|
|
64
102
|
|
|
65
103
|
<script setup lang="ts">
|
|
104
|
+
import { computed } from 'vue';
|
|
66
105
|
import type { IPartData } from '../types';
|
|
67
106
|
import { useAgentStore } from '../composables/useAgentStore';
|
|
68
107
|
import { Spinner } from '@/afcl';
|
|
69
|
-
import {
|
|
108
|
+
import { IconArrowRight, IconCheck, IconShieldExclamation, IconXMark } from '@iconify-prerendered/vue-heroicons';
|
|
70
109
|
|
|
71
110
|
const props = defineProps<{
|
|
72
111
|
data: IPartData;
|
|
@@ -74,6 +113,8 @@ const props = defineProps<{
|
|
|
74
113
|
|
|
75
114
|
const agentStore = useAgentStore();
|
|
76
115
|
|
|
116
|
+
const isAwaitingDecision = computed(() => props.data.status === 'pending' || props.data.status === 'processing');
|
|
117
|
+
|
|
77
118
|
function submit(decision: 'approve' | 'reject') {
|
|
78
119
|
if (!props.data.sessionId || props.data.status !== 'pending') {
|
|
79
120
|
return;
|