@adminforth/agent 2.9.1 → 3.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/README.md +4 -0
- package/dist/application/runTurnUseCase.d.ts +2 -1
- package/dist/application/runTurnUseCase.js +3 -3
- package/dist/custom/ChatFooter.vue +91 -104
- package/dist/custom/ChatHeader.vue +63 -62
- package/dist/custom/ChatSurface.vue +11 -18
- package/dist/custom/EditActionButtons.vue +12 -8
- package/dist/custom/SessionsHistory.vue +90 -28
- package/dist/custom/SteerQueue.vue +15 -9
- 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 +23 -2
- package/dist/custom/composables/useAgentStore.ts +8 -29
- package/dist/custom/conversation_area/ConversationArea.vue +48 -16
- package/dist/custom/conversation_area/MessageRenderer.vue +18 -13
- package/dist/custom/conversation_area/ProcessingTimeline.vue +83 -123
- package/dist/custom/conversation_area/ReasoningRenderer.vue +13 -16
- package/dist/custom/conversation_area/SystemMessageRenderer.vue +4 -4
- package/dist/custom/conversation_area/TextRenderer.vue +67 -28
- package/dist/custom/conversation_area/ToolApprovalRenderer.vue +89 -56
- package/dist/custom/conversation_area/ToolRenderer.vue +44 -82
- package/dist/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +22 -61
- package/dist/custom/speech_recognition_frontend/AudioLines.vue +1 -2
- package/dist/custom/speech_recognition_frontend/MicrophoneButon.vue +36 -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/dist/transport/http/sessionEndpoints.js +34 -0
- package/package.json +3 -3
- package/dist/custom/SteerDebugPanel.vue +0 -45
- package/dist/custom/conversation_area/ToolsGroup.vue +0 -59
package/README.md
CHANGED
|
@@ -424,6 +424,10 @@ approval interrupt: generation pauses and the UI shows an approve/reject prompt.
|
|
|
424
424
|
resolves it via `POST /agent/approval`, and the run resumes (or, on reject, the model is
|
|
425
425
|
told the action was declined).
|
|
426
426
|
|
|
427
|
+
The prompt names the resource and columns by their labels and lists every value the tool
|
|
428
|
+
would write; for `update_record` each field shows its current value next to the new one.
|
|
429
|
+
Values of `masked` and `backendOnly` columns are never shown.
|
|
430
|
+
|
|
427
431
|
Where the pending approval lives depends on your setup: with `checkpointResource` configured
|
|
428
432
|
the LangGraph checkpoint is authoritative, so a resume survives a restart and works across
|
|
429
433
|
instances; with the in-memory fallback the state is held per process instance.
|
|
@@ -3,6 +3,7 @@ import type { AgentSessionStore } from "../persistence/sessionStore.js";
|
|
|
3
3
|
import type { SteerBuffer } from "../domain/steerBuffer.js";
|
|
4
4
|
import type { PluginOptions } from "../types.js";
|
|
5
5
|
import type { LlmPort } from "./ports.js";
|
|
6
|
+
import { type ApprovalDescription } from "../tools/approvalFormatter.js";
|
|
6
7
|
import type { BaseAgentTurnInput, DebugSink, HandleTurnInput, HandleEditTurnInput, PendingInterrupt, RunAndPersistAgentResponseInput, RunAndPersistAgentResponseResult } from "../domain/turnTypes.js";
|
|
7
8
|
/**
|
|
8
9
|
* Build the provider-agnostic resume payload for a human-in-the-loop turn.
|
|
@@ -78,7 +79,7 @@ export declare class RunTurnUseCase {
|
|
|
78
79
|
* page reload would otherwise leave the user with no way to answer it. Empty when
|
|
79
80
|
* nothing is pending.
|
|
80
81
|
*/
|
|
81
|
-
getPendingApprovals(sessionId: string, modeName?: string | null): Promise<
|
|
82
|
+
getPendingApprovals(sessionId: string, modeName?: string | null): Promise<ApprovalDescription[]>;
|
|
82
83
|
private requiresSessionOwnership;
|
|
83
84
|
/** Verify the session exists and belongs to the requesting admin user. */
|
|
84
85
|
assertSessionOwnership(sessionId: string, adminUser: BaseAgentTurnInput["adminUser"]): Promise<void>;
|
|
@@ -125,9 +125,9 @@ export class RunTurnUseCase {
|
|
|
125
125
|
/** Describe pending tool calls in plain language an admin (not a developer) can act on. */
|
|
126
126
|
describeApprovals(interrupts) {
|
|
127
127
|
const adminforth = this.deps.getAdminforth();
|
|
128
|
-
return interrupts
|
|
128
|
+
return Promise.all(interrupts
|
|
129
129
|
.flatMap((interrupt) => interrupt.requests)
|
|
130
|
-
.map((request) => formatApprovalRequest(adminforth, request));
|
|
130
|
+
.map((request) => formatApprovalRequest(adminforth, request)));
|
|
131
131
|
}
|
|
132
132
|
/**
|
|
133
133
|
* Describe what a session is still waiting for approval on, so a client that (re)loads
|
|
@@ -322,7 +322,7 @@ export class RunTurnUseCase {
|
|
|
322
322
|
type: "interrupt",
|
|
323
323
|
sessionId: prepared.sessionId,
|
|
324
324
|
interrupt,
|
|
325
|
-
approvals: this.describeApprovals(descriptors),
|
|
325
|
+
approvals: yield this.describeApprovals(descriptors),
|
|
326
326
|
}));
|
|
327
327
|
});
|
|
328
328
|
}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
ref="promptInput"
|
|
4
|
-
class="w-full mb-
|
|
5
|
-
:style="{
|
|
3
|
+
ref="promptInput"
|
|
4
|
+
class="w-full mb-3 px-3 relative translate-x-[-50%] left-1/2"
|
|
5
|
+
:style="{
|
|
6
6
|
maxWidth: agentStore.isFullScreen ? remToPx(agentStore.MAX_WIDTH)+'px' : '100%',
|
|
7
7
|
transition: `transform ${agentTransitions.TRANSITION_DURATION}ms ease-in-out`
|
|
8
|
-
}"
|
|
8
|
+
}"
|
|
9
9
|
>
|
|
10
10
|
<SteerQueue />
|
|
11
|
-
<!-- <SteerDebugPanel /> -->
|
|
12
11
|
<div
|
|
13
|
-
class="w-full
|
|
14
|
-
|
|
12
|
+
class="af-agent-composer w-full flex flex-col rounded-xl border transition-colors duration-150
|
|
13
|
+
border-lightInputBorder bg-lightInputBackground hover:border-lightInputBorderHover
|
|
14
|
+
focus-within:border-lightInputFocusBorder focus-within:ring-[3px] focus-within:ring-lightInputFocusRing
|
|
15
|
+
dark:border-darkInputBorder dark:bg-darkInputBackground dark:hover:border-darkInputBorderHover
|
|
16
|
+
dark:focus-within:border-darkInputFocusBorder dark:focus-within:ring-darkInputFocusRing"
|
|
15
17
|
>
|
|
16
18
|
<textarea
|
|
17
19
|
v-if="!agentStore.isAudioChatMode"
|
|
@@ -20,112 +22,106 @@
|
|
|
20
22
|
rows="1"
|
|
21
23
|
@input="autoResize"
|
|
22
24
|
:class="[
|
|
23
|
-
|
|
25
|
+
`block w-full px-3 pt-2.5 pb-1 resize-none overflow-hidden bg-transparent border-none text-sm leading-[22px]
|
|
26
|
+
focus:outline-none focus:ring-0
|
|
27
|
+
text-lightInputText placeholder:text-lightInputPlaceholderText dark:text-darkInputText dark:placeholder:text-darkInputPlaceholderText`,
|
|
24
28
|
{ '!text-base': coreStore.isIos }
|
|
25
29
|
]"
|
|
26
30
|
:placeholder="placeholderText"
|
|
27
31
|
@keydown.enter.exact.prevent="sendMessage"
|
|
28
32
|
@keydown.esc="onEscape"
|
|
29
33
|
/>
|
|
30
|
-
|
|
34
|
+
<!-- in audio chat mode the voice button is centered between the mode picker and an empty column -->
|
|
31
35
|
<div
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class="relative"
|
|
36
|
+
class="items-center gap-1 px-2 pb-2"
|
|
37
|
+
:class="agentStore.isAudioChatMode ? 'grid grid-cols-[1fr_auto_1fr] pt-2' : 'flex pt-1'"
|
|
35
38
|
>
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
dark:text-darkListTableHeadingText dark:bg-gray-700 dark:hover:bg-gray-800"
|
|
42
|
-
:class="isModeMenuOpen ? 'bg-gray-100 dark:bg-gray-700' : ''"
|
|
43
|
-
title="Select mode"
|
|
44
|
-
type="button"
|
|
45
|
-
@click="toggleModeMenu"
|
|
39
|
+
<DropdownMenu
|
|
40
|
+
v-if="agentStore.availableModes.length > 1"
|
|
41
|
+
v-model:open="isModeMenuOpen"
|
|
42
|
+
class="af-agent-mode-menu justify-self-start"
|
|
43
|
+
teleportToBody
|
|
46
44
|
>
|
|
47
|
-
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
/>
|
|
52
|
-
</button>
|
|
53
|
-
<Transition
|
|
54
|
-
enter-active-class="transition ease-out duration-200"
|
|
55
|
-
enter-from-class="opacity-0 scale-95"
|
|
56
|
-
enter-to-class="opacity-100 scale-100"
|
|
57
|
-
leave-active-class="transition ease-in duration-150"
|
|
58
|
-
leave-from-class="opacity-100 scale-100"
|
|
59
|
-
leave-to-class="opacity-0 scale-95"
|
|
60
|
-
>
|
|
61
|
-
<div
|
|
62
|
-
v-if="isModeMenuOpen"
|
|
63
|
-
class="absolute z-20 bottom-full left-0 mb-2 min-w-40 overflow-hidden rounded-md border border-gray-200 bg-white shadow-lg dark:border-gray-600 dark:bg-gray-800"
|
|
64
|
-
>
|
|
65
|
-
<button
|
|
66
|
-
v-for="mode in agentStore.availableModes"
|
|
67
|
-
:key="mode.name"
|
|
68
|
-
class="block w-full px-3 py-2 text-left text-sm text-lightInputText transition-colors duration-150 hover:bg-gray-100 dark:text-darkInputText dark:hover:bg-gray-700"
|
|
69
|
-
:class="mode.name === agentStore.activeModeName ? 'bg-gray-100 dark:bg-gray-700' : ''"
|
|
45
|
+
<template #trigger="{ open }">
|
|
46
|
+
<Button
|
|
47
|
+
variant="ghost"
|
|
48
|
+
size="sm"
|
|
70
49
|
type="button"
|
|
71
|
-
|
|
50
|
+
:class="open && 'bg-lightSecondaryDarken text-lightInputText dark:bg-darkSecondaryLighten dark:text-darkInputText'"
|
|
51
|
+
:aria-label="$t('Select mode')"
|
|
52
|
+
:aria-expanded="open"
|
|
53
|
+
:title="$t('Select mode')"
|
|
72
54
|
>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<template v-if="agentStore.isEditingMessage">
|
|
83
|
-
<EditActionButtons
|
|
84
|
-
:confirm-disabled="!agentStore.trimmedUserMessage"
|
|
85
|
-
@cancel="cancelEdit"
|
|
86
|
-
@confirm="sendMessage"
|
|
87
|
-
/>
|
|
88
|
-
</template>
|
|
89
|
-
<template v-else>
|
|
90
|
-
<Button
|
|
91
|
-
v-if="!agentStore.isResponseInProgress"
|
|
92
|
-
class=" !p-0 h-7 w-7 transition-opacity duration-200"
|
|
93
|
-
@click="sendMessage"
|
|
94
|
-
:disabled="!agentStore.trimmedUserMessage"
|
|
55
|
+
<IconSparkles class="w-4 h-4" />
|
|
56
|
+
{{ agentStore.activeModeName }}
|
|
57
|
+
<IconChevronDown class="w-3.5 h-3.5 transition-transform duration-200" :class="open && 'rotate-180'" />
|
|
58
|
+
</Button>
|
|
59
|
+
</template>
|
|
60
|
+
<DropdownMenuItem
|
|
61
|
+
v-for="mode in agentStore.availableModes"
|
|
62
|
+
:key="mode.name"
|
|
63
|
+
@click="agentStore.setActiveMode(mode.name)"
|
|
95
64
|
>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
</
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
65
|
+
{{ mode.name }}
|
|
66
|
+
<template v-if="mode.name === agentStore.activeModeName" #suffix>
|
|
67
|
+
<IconCheck class="w-4 h-4 text-lightPrimary dark:text-darkPrimary" />
|
|
68
|
+
</template>
|
|
69
|
+
</DropdownMenuItem>
|
|
70
|
+
</DropdownMenu>
|
|
71
|
+
<div
|
|
72
|
+
class="ml-auto flex items-center gap-1"
|
|
73
|
+
:class="agentStore.isAudioChatMode ? 'col-start-2' : ''"
|
|
74
|
+
>
|
|
75
|
+
<div v-if="props.meta.hasAudioAdapter" v-show="!agentStore.isEditingMessage" class="contents">
|
|
76
|
+
<MicrophoneButton />
|
|
77
|
+
</div>
|
|
78
|
+
<template v-if="!agentStore.isAudioChatMode">
|
|
79
|
+
<EditActionButtons
|
|
80
|
+
v-if="agentStore.isEditingMessage"
|
|
81
|
+
:confirm-disabled="!agentStore.trimmedUserMessage"
|
|
82
|
+
@cancel="cancelEdit"
|
|
83
|
+
@confirm="sendMessage"
|
|
108
84
|
/>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
85
|
+
<Button
|
|
86
|
+
v-else-if="!agentStore.isResponseInProgress"
|
|
87
|
+
type="button"
|
|
88
|
+
iconOnly
|
|
89
|
+
class="rounded-full"
|
|
90
|
+
:disabled="!agentStore.trimmedUserMessage"
|
|
91
|
+
:title="$t('Send message')"
|
|
92
|
+
:aria-label="$t('Send message')"
|
|
93
|
+
@click="sendMessage"
|
|
94
|
+
>
|
|
95
|
+
<IconArrowUp />
|
|
96
|
+
</Button>
|
|
97
|
+
<Button
|
|
98
|
+
v-else
|
|
99
|
+
type="button"
|
|
100
|
+
iconOnly
|
|
101
|
+
class="rounded-full"
|
|
102
|
+
:title="$t('Stop')"
|
|
103
|
+
:aria-label="$t('Stop')"
|
|
104
|
+
@click="stopCurrentRequest"
|
|
105
|
+
>
|
|
106
|
+
<span class="w-2.5 h-2.5 rounded-sm bg-current" />
|
|
107
|
+
</Button>
|
|
108
|
+
</template>
|
|
109
|
+
</div>
|
|
112
110
|
</div>
|
|
113
111
|
</div>
|
|
114
112
|
</div>
|
|
115
113
|
</template>
|
|
116
114
|
|
|
117
115
|
<script setup lang="ts">
|
|
118
|
-
import {
|
|
119
|
-
import { useTemplateRef, onMounted, ref,
|
|
120
|
-
import {
|
|
116
|
+
import { IconArrowUp, IconCheck, IconChevronDown, IconSparkles } from '@iconify-prerendered/vue-lucide';
|
|
117
|
+
import { useTemplateRef, onMounted, ref, watch, computed } from 'vue';
|
|
118
|
+
import { Button, DropdownMenu, DropdownMenuItem } from '@/afcl';
|
|
121
119
|
import { useAgentStore } from './composables/useAgentStore';
|
|
122
120
|
import { useAgentTransitions } from './composables/useAgentTransitions';
|
|
123
|
-
import { Button } from '@/afcl';
|
|
124
121
|
import { useCoreStore } from '@/stores/core';
|
|
125
122
|
import { remToPx } from './utils';
|
|
126
123
|
import MicrophoneButton from './speech_recognition_frontend/MicrophoneButon.vue';
|
|
127
124
|
import SteerQueue from './SteerQueue.vue';
|
|
128
|
-
import SteerDebugPanel from './SteerDebugPanel.vue';
|
|
129
125
|
import EditActionButtons from './EditActionButtons.vue';
|
|
130
126
|
|
|
131
127
|
const props = defineProps<{
|
|
@@ -144,7 +140,6 @@ const props = defineProps<{
|
|
|
144
140
|
}>();
|
|
145
141
|
|
|
146
142
|
const textInput = useTemplateRef('textInput');
|
|
147
|
-
const modeMenu = useTemplateRef('modeMenu');
|
|
148
143
|
const agentStore = useAgentStore();
|
|
149
144
|
const agentTransitions = useAgentTransitions();
|
|
150
145
|
const coreStore = useCoreStore();
|
|
@@ -157,20 +152,21 @@ const placeholderText = computed(() => {
|
|
|
157
152
|
return agentStore.userMessagePlaceholder;
|
|
158
153
|
});
|
|
159
154
|
|
|
160
|
-
onClickOutside(modeMenu as any, () => { isModeMenuOpen.value = false; });
|
|
161
|
-
|
|
162
155
|
onMounted(async () => {
|
|
163
156
|
agentStore.setAvailableModes(props.meta.modes, props.meta.defaultModeName);
|
|
164
157
|
agentStore.setCurrentGenerationModeFromLocalStorage();
|
|
165
158
|
agentStore.setEditingEnabled(props.meta.editingEnabled);
|
|
166
|
-
agentStore.regisrerTextInput(textInput.value);
|
|
167
159
|
textInput.value?.focus();
|
|
168
160
|
autoResize();
|
|
169
161
|
});
|
|
170
162
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
});
|
|
163
|
+
// The textarea is re-created after leaving audio chat mode; keep the store pointing at the
|
|
164
|
+
// live element, otherwise focusTextInput() targets a detached one and does nothing.
|
|
165
|
+
watch(textInput, (el) => agentStore.regisrerTextInput(el), { immediate: true, flush: 'post' });
|
|
166
|
+
|
|
167
|
+
// Re-measure whenever the text changes programmatically (suggestion picked, edit restored)
|
|
168
|
+
// or the textarea is re-mounted after leaving audio chat mode.
|
|
169
|
+
watch([() => agentStore.userMessageInput, textInput], autoResize, { flush: 'post' });
|
|
174
170
|
|
|
175
171
|
function autoResize() {
|
|
176
172
|
const el = textInput.value
|
|
@@ -188,15 +184,6 @@ function autoResize() {
|
|
|
188
184
|
}
|
|
189
185
|
}
|
|
190
186
|
|
|
191
|
-
function toggleModeMenu() {
|
|
192
|
-
isModeMenuOpen.value = !isModeMenuOpen.value;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function selectMode(modeName: string) {
|
|
196
|
-
agentStore.setActiveMode(modeName);
|
|
197
|
-
isModeMenuOpen.value = false;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
187
|
async function sendMessage() {
|
|
201
188
|
if (agentStore.isAudioChatMode) return;
|
|
202
189
|
isModeMenuOpen.value = false;
|
|
@@ -1,78 +1,79 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
3
|
ref="headerRef"
|
|
4
|
-
class="flex items-center
|
|
4
|
+
class="af-agent-header flex items-center gap-0.5 h-12 px-2 border-b border-lightNavbarBorder dark:border-darkNavbarBorder"
|
|
5
5
|
>
|
|
6
|
-
<
|
|
7
|
-
|
|
6
|
+
<Button
|
|
7
|
+
variant="ghost"
|
|
8
|
+
iconOnly
|
|
9
|
+
:class="agentStore.isSessionHistoryOpen && PRESSED_CLASSES"
|
|
10
|
+
:aria-pressed="agentStore.isSessionHistoryOpen"
|
|
11
|
+
:aria-label="$t('Chat history')"
|
|
12
|
+
:title="$t('Chat history')"
|
|
13
|
+
@click="agentStore.setSessionHistoryOpen(!agentStore.isSessionHistoryOpen)"
|
|
8
14
|
>
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
/>
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
>
|
|
55
|
-
<IconPlusOutline class="w-5 h-5" />
|
|
56
|
-
{{ $t('New chat') }}
|
|
57
|
-
</Button>
|
|
58
|
-
<IconCloseOutline
|
|
59
|
-
class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
|
|
60
|
-
text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
|
|
61
|
-
dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
|
|
62
|
-
@click="agentStore.setIsChatOpen(false)"
|
|
63
|
-
/>
|
|
64
|
-
</div>
|
|
15
|
+
<IconHistory />
|
|
16
|
+
</Button>
|
|
17
|
+
<Button
|
|
18
|
+
v-if="!coreStore.isMobile && !agentStore.isFullScreen"
|
|
19
|
+
variant="ghost"
|
|
20
|
+
iconOnly
|
|
21
|
+
:class="agentStore.isTeleportedToBody && PRESSED_CLASSES"
|
|
22
|
+
:aria-pressed="agentStore.isTeleportedToBody"
|
|
23
|
+
:aria-label="$t('Dock to the side')"
|
|
24
|
+
:title="$t('Dock to the side')"
|
|
25
|
+
@click="agentStore.setIsTeleportedToBody(!agentStore.isTeleportedToBody)"
|
|
26
|
+
>
|
|
27
|
+
<IconPanelRight />
|
|
28
|
+
</Button>
|
|
29
|
+
<Button
|
|
30
|
+
v-if="!coreStore.isMobile"
|
|
31
|
+
variant="ghost"
|
|
32
|
+
iconOnly
|
|
33
|
+
:aria-label="agentStore.isFullScreen ? $t('Exit full screen') : $t('Full screen')"
|
|
34
|
+
:title="agentStore.isFullScreen ? $t('Exit full screen') : $t('Full screen')"
|
|
35
|
+
@click="agentStore.setFullScreen(!agentStore.isFullScreen)"
|
|
36
|
+
>
|
|
37
|
+
<IconMinimize2 v-if="agentStore.isFullScreen" />
|
|
38
|
+
<IconMaximize2 v-else />
|
|
39
|
+
</Button>
|
|
40
|
+
<p class="flex-1 min-w-0 truncate px-1 text-[13px] font-semibold text-lightNavbarText dark:text-darkNavbarText">
|
|
41
|
+
{{ isNewChat ? $t('New chat') : agentStore.currentSession.title }}
|
|
42
|
+
</p>
|
|
43
|
+
<Button
|
|
44
|
+
variant="secondary"
|
|
45
|
+
class="shrink-0"
|
|
46
|
+
@click="agentStore.createPreSession(); agentStore.setSessionHistoryOpen(false); agentStore.focusTextInput();"
|
|
47
|
+
>
|
|
48
|
+
<IconPlus class="w-4 h-4" />
|
|
49
|
+
{{ $t('New chat') }}
|
|
50
|
+
</Button>
|
|
51
|
+
<Button
|
|
52
|
+
variant="ghost"
|
|
53
|
+
iconOnly
|
|
54
|
+
:aria-label="$t('Close')"
|
|
55
|
+
:title="$t('Close')"
|
|
56
|
+
@click="agentStore.setIsChatOpen(false)"
|
|
57
|
+
>
|
|
58
|
+
<IconX />
|
|
59
|
+
</Button>
|
|
65
60
|
</div>
|
|
66
61
|
</template>
|
|
67
62
|
|
|
68
63
|
|
|
69
64
|
<script setup>
|
|
70
|
-
import {
|
|
71
|
-
import {
|
|
65
|
+
import { computed } from 'vue';
|
|
66
|
+
import { IconHistory, IconMaximize2, IconMinimize2, IconPanelRight, IconPlus, IconX } from '@iconify-prerendered/vue-lucide';
|
|
72
67
|
import { useAgentStore } from './composables/useAgentStore';
|
|
73
68
|
import { useCoreStore } from '@/stores/core';
|
|
74
69
|
import { Button } from '@/afcl';
|
|
70
|
+
import { PRE_SESSION_ID } from './composables/agentStore/constants';
|
|
71
|
+
|
|
72
|
+
// a toggle in the on state, same fill as the hover of a ghost button
|
|
73
|
+
const PRESSED_CLASSES = 'bg-lightSecondaryDarken text-lightInputText dark:bg-darkSecondaryLighten dark:text-darkInputText';
|
|
75
74
|
|
|
76
75
|
const agentStore = useAgentStore();
|
|
77
76
|
const coreStore = useCoreStore();
|
|
77
|
+
|
|
78
|
+
const isNewChat = computed(() => !agentStore.currentSession || agentStore.currentSession.sessionId === PRE_SESSION_ID);
|
|
78
79
|
</script>
|
|
@@ -1,28 +1,20 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
hover:scale-110 transition-colors duration-200"
|
|
2
|
+
<Button
|
|
3
|
+
variant="secondary"
|
|
4
|
+
:class="{ 'bg-lightButtonGroupActiveBackground hover:bg-lightButtonGroupActiveBackground dark:bg-darkButtonGroupActiveBackground dark:hover:bg-darkButtonGroupActiveBackground': agentStore.isChatOpen }"
|
|
5
|
+
:aria-expanded="agentStore.isChatOpen"
|
|
7
6
|
@click="agentStore.setIsChatOpen(!agentStore.isChatOpen)"
|
|
8
7
|
>
|
|
9
|
-
<
|
|
10
|
-
|
|
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>
|
|
8
|
+
<IconBot class="w-4 h-4" />
|
|
9
|
+
{{ $t('Agent') }}
|
|
10
|
+
</Button>
|
|
18
11
|
|
|
19
12
|
<Teleport to="body">
|
|
20
13
|
<div
|
|
21
14
|
ref="chatSurface"
|
|
22
15
|
id="adminforth-agent-chat-surface"
|
|
23
|
-
class="fixed
|
|
24
|
-
|
|
25
|
-
:class="[agentStore.isChatOpen ? 'translate-x-0' : 'translate-x-full', !agentStore.isTeleportedToBody ? 'shadow-2xl' : '']"
|
|
16
|
+
class="fixed top-0 right-0 z-40 flex h-screen border-l border-lightNavbarBorder bg-lightNavbar dark:border-darkNavbarBorder dark:bg-darkNavbar"
|
|
17
|
+
:class="[agentStore.isChatOpen ? 'translate-x-0' : 'translate-x-full', !agentStore.isTeleportedToBody ? 'shadow-popover dark:shadow-darkPopover' : '']"
|
|
26
18
|
:style="{ width: agentStore.chatWidth + 'rem' }"
|
|
27
19
|
>
|
|
28
20
|
<div
|
|
@@ -58,7 +50,8 @@
|
|
|
58
50
|
</template>
|
|
59
51
|
|
|
60
52
|
<script setup lang="ts">
|
|
61
|
-
import {
|
|
53
|
+
import { IconBot } from '@iconify-prerendered/vue-lucide';
|
|
54
|
+
import { Button } from '@/afcl';
|
|
62
55
|
import { useTemplateRef, onMounted, ref, onUnmounted } from 'vue';
|
|
63
56
|
import { onClickOutside } from '@vueuse/core'
|
|
64
57
|
import ConversationArea from './conversation_area/ConversationArea.vue';
|
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="flex items-center gap-
|
|
2
|
+
<div class="flex items-center gap-2">
|
|
3
3
|
<Button
|
|
4
4
|
variant="secondary"
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
size="sm"
|
|
6
|
+
iconOnly
|
|
7
|
+
:title="$t('Cancel edit')"
|
|
8
|
+
:aria-label="$t('Cancel edit')"
|
|
7
9
|
@click="emit('cancel')"
|
|
8
10
|
>
|
|
9
|
-
<
|
|
11
|
+
<IconX />
|
|
10
12
|
</Button>
|
|
11
13
|
<Button
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
size="sm"
|
|
15
|
+
iconOnly
|
|
16
|
+
:title="$t('Send edited message')"
|
|
17
|
+
:aria-label="$t('Send edited message')"
|
|
14
18
|
:disabled="confirmDisabled"
|
|
15
19
|
@click="emit('confirm')"
|
|
16
20
|
>
|
|
17
|
-
<
|
|
21
|
+
<IconCheck />
|
|
18
22
|
</Button>
|
|
19
23
|
</div>
|
|
20
24
|
</template>
|
|
21
25
|
|
|
22
26
|
<script setup lang="ts">
|
|
23
|
-
import {
|
|
27
|
+
import { IconCheck, IconX } from '@iconify-prerendered/vue-lucide';
|
|
24
28
|
import { Button } from '@/afcl';
|
|
25
29
|
|
|
26
30
|
defineProps<{
|