@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.
Files changed (34) hide show
  1. package/README.md +4 -0
  2. package/dist/application/runTurnUseCase.d.ts +2 -1
  3. package/dist/application/runTurnUseCase.js +3 -3
  4. package/dist/custom/ChatFooter.vue +91 -104
  5. package/dist/custom/ChatHeader.vue +63 -62
  6. package/dist/custom/ChatSurface.vue +11 -18
  7. package/dist/custom/EditActionButtons.vue +12 -8
  8. package/dist/custom/SessionsHistory.vue +90 -28
  9. package/dist/custom/SteerQueue.vue +15 -9
  10. package/dist/custom/composables/agentStore/constants.ts +1 -1
  11. package/dist/custom/composables/agentStore/useAgentChat.ts +4 -3
  12. package/dist/custom/composables/agentStore/useAgentPlaceholder.ts +1 -0
  13. package/dist/custom/composables/agentStore/useAgentSessions.ts +23 -2
  14. package/dist/custom/composables/useAgentStore.ts +8 -29
  15. package/dist/custom/conversation_area/ConversationArea.vue +48 -16
  16. package/dist/custom/conversation_area/MessageRenderer.vue +18 -13
  17. package/dist/custom/conversation_area/ProcessingTimeline.vue +83 -123
  18. package/dist/custom/conversation_area/ReasoningRenderer.vue +13 -16
  19. package/dist/custom/conversation_area/SystemMessageRenderer.vue +4 -4
  20. package/dist/custom/conversation_area/TextRenderer.vue +67 -28
  21. package/dist/custom/conversation_area/ToolApprovalRenderer.vue +89 -56
  22. package/dist/custom/conversation_area/ToolRenderer.vue +44 -82
  23. package/dist/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +22 -61
  24. package/dist/custom/speech_recognition_frontend/AudioLines.vue +1 -2
  25. package/dist/custom/speech_recognition_frontend/MicrophoneButon.vue +36 -32
  26. package/dist/custom/types.ts +16 -8
  27. package/dist/domain/agentEvents.d.ts +3 -2
  28. package/dist/tools/approvalFormatter.d.ts +19 -5
  29. package/dist/tools/approvalFormatter.js +88 -40
  30. package/dist/transport/http/context.d.ts +2 -1
  31. package/dist/transport/http/sessionEndpoints.js +34 -0
  32. package/package.json +3 -3
  33. package/dist/custom/SteerDebugPanel.vue +0 -45
  34. 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<string[]>;
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-2 flex items-center justify-center px-2 bg-transparent relative translate-x-[-50%] left-1/2"
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 border rounded-lg flex flex-col"
14
- :class="agentStore.isAudioChatMode ? 'border-none mt-8' : 'border dark:bg-gray-700'"
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
- 'h-8 px-4 py-3 rounded-xl rounded-b-none w-full resize-none overflow-hidden 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',
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
- <div class="flex items-center justify-between px-2 py-1">
34
+ <!-- in audio chat mode the voice button is centered between the mode picker and an empty column -->
31
35
  <div
32
- v-if="agentStore.availableModes.length > 1"
33
- ref="modeMenu"
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
- <button
37
- aria-label="Select mode"
38
- class="flex px-2 py-1 items-center text-sm justify-center
39
- rounded-md bg-white text-lightListTableHeadingText
40
- transition-colors duration-200 hover:bg-gray-100
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
- {{ agentStore.activeModeName }}
48
- <IconAngleDownOutline
49
- class="w-4 h-4 ml-1 transition-transform duration-200"
50
- :class="isModeMenuOpen ? '!rotate-180' : '!rotate-0'"
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
- @click="selectMode(mode.name)"
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
- {{ mode.name }}
74
- </button>
75
- </div>
76
- </Transition>
77
- </div>
78
- <div v-if="props.meta.hasAudioAdapter" v-show="!agentStore.isEditingMessage" class="contents">
79
- <MicrophoneButton />
80
- </div>
81
- <template v-if="!agentStore.isAudioChatMode">
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
- <IconArrowUpOutline
97
- class="w-6 h-6
98
- text-white"
99
- />
100
- </Button>
101
- <Button
102
- v-else
103
- class="right-4 bottom-2 !p-0 h-7 w-7"
104
- @click="stopCurrentRequest"
105
- >
106
- <div
107
- class="w-2.5 h-2.5 bg-white rounded-sm"
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
- </Button>
110
- </template>
111
- </template>
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 { IconArrowUpOutline, IconAngleDownOutline } from '@iconify-prerendered/vue-flowbite';
119
- import { useTemplateRef, onMounted, ref, onUnmounted, watch, computed } from 'vue';
120
- import { onClickOutside } from '@vueuse/core'
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
- watch(() => textInput.value?.value, () => {
172
- autoResize();
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 justify-between h-14 border-b border-gray-200 dark:border-gray-700"
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
- <div
7
- class="flex items-center"
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
- <IconBarsOutline
10
- class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
11
- text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
12
- dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
13
- :class="agentStore.isSessionHistoryOpen ?
14
- 'bg-lightNavbarIcons/20 text-lightNavbarIcons/80 dark:bg-darkNavbarIcons/20 dark:text-darkNavbarIcons/80' :
15
- ''"
16
- @click="agentStore.setSessionHistoryOpen(!agentStore.isSessionHistoryOpen)"
17
- />
18
- <IconOpenSidebarSolid
19
- v-if="!agentStore.isTeleportedToBody && !coreStore.isMobile && !agentStore.isFullScreen"
20
- class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
21
- text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
22
- dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
23
- @click="agentStore.setIsTeleportedToBody(true)"
24
- />
25
- <IconCloseSidebarSolid
26
- v-else-if="!coreStore.isMobile && !agentStore.isFullScreen"
27
- class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
28
- text-lightNavbarIcons hover:text-lightNavbarIcons/80 bg-lightNavbarIcons/20
29
- dark:bg-darkNavbarIcons/20 dark:text-darkNavbarIcons/80 hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
30
- @click="agentStore.setIsTeleportedToBody(false)"
31
- />
32
- <IconArrowsPointingOut
33
- v-if="!agentStore.isFullScreen && !coreStore.isMobile"
34
- class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
35
- text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
36
- dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
37
- @click="agentStore.setFullScreen(true)"
38
- />
39
- <IconArrowsPointingIn
40
- v-else-if="!coreStore.isMobile"
41
- class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
42
- text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
43
- dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
44
- :class="agentStore.isFullScreen ?
45
- 'bg-lightNavbarIcons/20 text-lightNavbarIcons/80 dark:bg-darkNavbarIcons/20 dark:text-darkNavbarIcons/80' :
46
- ''"
47
- @click="agentStore.setFullScreen(false)"
48
- />
49
- </div>
50
- <div class="flex items-center justify-center">
51
- <Button
52
- @click="agentStore.createPreSession(); agentStore.setSessionHistoryOpen(false); agentStore.focusTextInput();"
53
- class="!py-1 !px-2 max-w-64 mr-2"
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 { IconArrowsPointingOut, IconArrowsPointingIn } from '@iconify-prerendered/vue-heroicons';
71
- import { IconCloseOutline, IconBarsOutline, IconCloseSidebarSolid, IconOpenSidebarSolid, IconPlusOutline } from '@iconify-prerendered/vue-flowbite';
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
- <div
3
- class="relative w-6 h-6 cursor-pointer mr-1 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"
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
- <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>
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 bg-lightNavbar dark:bg-darkNavbar top-0 right-0 border-x border-b border-gray-200 dark:border-gray-700
24
- flex flex z-40 h-screen"
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 { IconChatBubbleLeft20Solid, IconSparklesSolid } from '@iconify-prerendered/vue-heroicons';
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-3">
2
+ <div class="flex items-center gap-2">
3
3
  <Button
4
4
  variant="secondary"
5
- class="!p-0 h-7 w-7 mr-1 bg-red-600 text-white hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-800"
6
- title="Cancel edit"
5
+ size="sm"
6
+ iconOnly
7
+ :title="$t('Cancel edit')"
8
+ :aria-label="$t('Cancel edit')"
7
9
  @click="emit('cancel')"
8
10
  >
9
- <IconCloseOutline class="w-5 h-5" />
11
+ <IconX />
10
12
  </Button>
11
13
  <Button
12
- class="!p-0 h-7 w-7"
13
- title="Send edited message"
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
- <IconCheckOutline class="w-6 h-6 text-white" />
21
+ <IconCheck />
18
22
  </Button>
19
23
  </div>
20
24
  </template>
21
25
 
22
26
  <script setup lang="ts">
23
- import { IconCloseOutline, IconCheckOutline } from '@iconify-prerendered/vue-flowbite';
27
+ import { IconCheck, IconX } from '@iconify-prerendered/vue-lucide';
24
28
  import { Button } from '@/afcl';
25
29
 
26
30
  defineProps<{