@adminforth/agent 2.10.0 → 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.
@@ -203,6 +203,26 @@ export function createAgentSessionManager({
203
203
  setCurrentChat(PRE_SESSION_ID);
204
204
  }
205
205
 
206
+ async function renameSession(sessionId: string, title: string) {
207
+ const res = await callAdminForthApi({
208
+ method: 'POST',
209
+ path: '/agent/rename-session',
210
+ body: { sessionId, title },
211
+ });
212
+ if (res.error) {
213
+ console.error('Error renaming session:', res.error);
214
+ return;
215
+ }
216
+ sessionList.value.find((s: ISessionsListItem) => s.sessionId === sessionId)!.title = res.title;
217
+ // a loaded session is cached apart from the list, and the open one is shown in the header
218
+ if (sessions.value[sessionId]) {
219
+ sessions.value[sessionId].title = res.title;
220
+ }
221
+ if (currentSession.value?.sessionId === sessionId) {
222
+ currentSession.value.title = res.title;
223
+ }
224
+ }
225
+
206
226
  async function deleteSession(sessionId: string) {
207
227
  if (sessionId === PRE_SESSION_ID) {
208
228
  deletePreSession();
@@ -370,6 +390,7 @@ export function createAgentSessionManager({
370
390
  createPreSession,
371
391
  setActiveSession,
372
392
  fetchSessionsList,
393
+ renameSession,
373
394
  deleteSession,
374
395
  addDebugMessage,
375
396
  addSystemMessage,
@@ -198,6 +198,7 @@ export const useAgentStore = defineStore('agent', () => {
198
198
  createPreSession,
199
199
  setActiveSession,
200
200
  fetchSessionsList,
201
+ renameSession,
201
202
  deleteSession,
202
203
  addDebugMessage,
203
204
  addSystemMessage,
@@ -590,6 +591,7 @@ export const useAgentStore = defineStore('agent', () => {
590
591
  sessionList,
591
592
  setActiveSession,
592
593
  fetchSessionsList,
594
+ renameSession,
593
595
  deleteSession,
594
596
  createPreSession,
595
597
  //____________________________________________
@@ -5,7 +5,7 @@
5
5
  <div
6
6
  v-if="agentStore.isSessionHistoryOpen"
7
7
  @click="agentStore.setSessionHistoryOpen(false)"
8
- class="absolute bg-black/10 backdrop-blur-md z-10 h-full w-full"
8
+ class="absolute z-10 h-full w-full bg-lightScrim dark:bg-darkScrim"
9
9
  >
10
10
  </div>
11
11
  <div class="relative flex-1 min-h-0 overflow-hidden [container-type:size]" @click="recalculateScroll()">
@@ -36,7 +36,7 @@
36
36
 
37
37
  <div
38
38
  v-for="(message, index) in props.messages" :key="index"
39
- class="flex flex-col w-full mt-2 pb-2"
39
+ class="flex flex-col w-full pt-5 first:pt-4"
40
40
  :class="message.role === 'user' ? 'self-end' : 'self-start'"
41
41
  ref="messagesRefs"
42
42
  >
@@ -50,25 +50,27 @@
50
50
  <div class="flex items-center justify-center w-11 h-11 rounded-xl bg-lightPrimary/10 text-lightPrimary dark:bg-darkPrimary/15 dark:text-darkPrimary">
51
51
  <IconSparkles class="w-6 h-6" />
52
52
  </div>
53
- <h2 class="mt-4 text-xl font-semibold text-lightNavbarText dark:text-darkNavbarText">
53
+ <h2 class="mt-4 text-lg font-semibold tracking-tight text-lightNavbarText dark:text-darkNavbarText">
54
54
  {{ $t('How can I help?') }}
55
55
  </h2>
56
- <p class="mt-1.5 max-w-xs text-sm leading-relaxed text-gray-600 dark:text-gray-400">
56
+ <p class="mt-1.5 max-w-xs text-sm leading-relaxed text-lightInputIconColor dark:text-darkInputIconColor">
57
57
  {{ $t('Ask about your data, build charts or update records.') }}
58
58
  </p>
59
59
  <div v-if="suggestions.length" class="mt-7 w-full max-w-sm flex flex-col gap-2 text-left">
60
- <p class="pl-0.5 text-xs font-medium text-gray-500 dark:text-gray-400">{{ $t('Try asking') }}</p>
60
+ <p class="pl-0.5 text-xs font-medium text-lightInputIconColor dark:text-darkInputIconColor">{{ $t('Try asking') }}</p>
61
61
  <button
62
62
  v-for="suggestion in suggestions"
63
63
  :key="suggestion"
64
64
  type="button"
65
- class="group flex items-center gap-2.5 w-full px-3 py-2.5 rounded-xl border text-sm text-left transition-colors duration-150
66
- border-gray-200 text-lightNavbarText hover:border-gray-300 hover:bg-gray-50
67
- dark:border-gray-700 dark:text-darkNavbarText dark:hover:border-gray-600 dark:hover:bg-gray-800"
65
+ class="group flex items-center gap-2.5 w-full px-3 py-2.5 rounded-lg border text-sm text-left transition-colors duration-150
66
+ border-lightCardBorder bg-lightCardBackground text-lightNavbarText hover:bg-lightCardBackgroundHover
67
+ focus:outline-none focus-visible:ring-[3px] focus-visible:ring-lightFocusRing
68
+ dark:border-darkCardBorder dark:bg-darkCardBackground dark:text-darkNavbarText dark:hover:bg-darkCardBackgroundHover
69
+ dark:focus-visible:ring-darkFocusRing"
68
70
  @click="applySuggestion(suggestion)"
69
71
  >
70
72
  <span class="flex-1 min-w-0">{{ suggestion }}</span>
71
- <IconArrowRight class="w-4 h-4 shrink-0 text-gray-400 transition-transform duration-150 group-hover:translate-x-0.5" />
73
+ <IconArrowRight class="w-4 h-4 shrink-0 text-lightInputIconColor transition-transform duration-150 group-hover:translate-x-0.5 dark:text-darkInputIconColor" />
72
74
  </button>
73
75
  </div>
74
76
  </div>
@@ -84,18 +86,17 @@
84
86
  <button
85
87
  v-if="showScrollToBottomButton"
86
88
  @click="scrollContainer.scrollToBottom();"
87
- class="absolute z-10 bottom-8 left-1/2 -translate-x-1/2
88
- bg-lightPrimary/20 dark:bg-darkPrimary/10
89
- border border-lightPrimary dark:border-darkPrimary
90
- text-lightPrimary dark:text-darkPrimary
91
- backdrop-blur-sm rounded-full
92
- w-10 h-10 flex items-center justify-center gap-0.5"
93
- :class="{ 'w-12 h-8': showAnimationInScrollToBottomButton }"
89
+ class="absolute z-10 bottom-8 left-1/2 -translate-x-1/2 flex items-center justify-center gap-0.5 w-[2.125rem] h-[2.125rem] rounded-full border
90
+ border-lightInputBorder bg-lightInputBackground text-lightInputIconColor shadow-popover hover:text-lightInputText
91
+ focus:outline-none focus-visible:ring-[3px] focus-visible:ring-lightFocusRing
92
+ dark:border-darkInputBorder dark:bg-darkInputBackground dark:text-darkInputIconColor dark:shadow-darkPopover dark:hover:text-darkInputText
93
+ dark:focus-visible:ring-darkFocusRing"
94
+ :class="{ 'w-12': showAnimationInScrollToBottomButton }"
95
+ :aria-label="$t('Scroll to the latest message')"
94
96
  >
95
- <IconArrowDownOutline
97
+ <IconArrowDown
96
98
  v-if="!showAnimationInScrollToBottomButton"
97
- class="p-2 w-10 h-10 transition-opacity duration-100 ease-in "
98
- :disabled="!showScrollToBottomButton"
99
+ class="w-4 h-4"
99
100
  />
100
101
  <ThreeDotsAnimation
101
102
  v-else
@@ -110,8 +111,7 @@
110
111
  <script setup lang="ts">
111
112
  import type { IMessage } from '../types';
112
113
  import { useTemplateRef, ref, onMounted, onUnmounted, watch, nextTick, defineAsyncComponent, computed } from 'vue';
113
- import { IconArrowDownOutline } from '@iconify-prerendered/vue-flowbite';
114
- import { IconArrowRight, IconSparkles } from '@iconify-prerendered/vue-heroicons';
114
+ import { IconArrowDown, IconArrowRight, IconSparkles } from '@iconify-prerendered/vue-lucide';
115
115
  import SessionsHistory from '../SessionsHistory.vue';
116
116
  import { useAgentStore } from '../composables/useAgentStore';
117
117
  import { useAgentTransitions } from '../composables/useAgentTransitions';
@@ -18,34 +18,38 @@
18
18
  >
19
19
  <div
20
20
  v-if="showActionsBar && showEditButton"
21
- class="absolute flex -bottom-6 p-1 mt-0.5 z-20"
21
+ class="absolute flex top-full -mt-1 p-1 z-20"
22
22
  :class="isUserMessage ? 'right-0 self-end mr-4' : 'left-0 self-start ml-4'"
23
23
  @mouseenter="showEditButtonOnHover"
24
24
  @mouseleave="hideEditButtonOnHover"
25
25
  >
26
- <button
26
+ <Button
27
27
  v-if="canCopy"
28
- type="button"
29
- :class="ACTION_BUTTON_CLASSES"
28
+ variant="ghost"
29
+ size="sm"
30
+ iconOnly
30
31
  :title="$t('Copy message')"
32
+ :aria-label="$t('Copy message')"
31
33
  @click="copyMessageToClipboard"
32
34
  >
33
- <IconFileCopyOutline class="w-4 h-4" />
34
- </button>
35
- <button
35
+ <IconCopy />
36
+ </Button>
37
+ <Button
36
38
  v-if="canEdit"
37
- type="button"
38
- :class="ACTION_BUTTON_CLASSES"
39
+ variant="ghost"
40
+ size="sm"
41
+ iconOnly
39
42
  :title="$t('Edit message')"
43
+ :aria-label="$t('Edit message')"
40
44
  @click="agentStore.startEditMessage(message)"
41
45
  >
42
- <IconPenOutline class="w-4 h-4" />
43
- </button>
46
+ <IconPencil />
47
+ </Button>
44
48
  </div>
45
49
  </Transition>
46
50
  <div
47
51
  v-if="message.role === 'user' && message.metadata?.steer && !isBeingEditedTurn"
48
- class="self-end mr-4 mb-0.5 text-xs italic text-gray-400 dark:text-gray-500"
52
+ class="self-end mr-4 mb-0.5 text-xs italic text-lightInputPlaceholderText dark:text-darkInputPlaceholderText"
49
53
  >
50
54
  {{ $t('Steering instruction') }}
51
55
  </div>
@@ -83,7 +87,8 @@
83
87
 
84
88
  <script setup lang="ts">
85
89
  import { computed, ref } from 'vue';
86
- import { IconPenOutline, IconFileCopyOutline } from '@iconify-prerendered/vue-flowbite';
90
+ import { IconCopy, IconPencil } from '@iconify-prerendered/vue-lucide';
91
+ import { Button } from '@/afcl';
87
92
  import TextRenderer from './TextRenderer.vue';
88
93
  import type { IMessage } from '../types';
89
94
  import { getMessageParts } from '../utils';
@@ -99,10 +104,6 @@
99
104
  isLastMessageInChat: boolean
100
105
  }>();
101
106
 
102
- const ACTION_BUTTON_CLASSES = `flex items-center justify-center w-7 h-7 rounded-md transition-colors duration-150
103
- text-gray-500 hover:bg-gray-100 hover:text-gray-800
104
- dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200`;
105
-
106
107
  const agentStore = useAgentStore();
107
108
  const { alert } = useAdminforth();
108
109
  const showEditButton = ref(false);
@@ -2,21 +2,21 @@
2
2
  <template v-if="timelineParts.length > 0 || inProgress">
3
3
  <button
4
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'"
5
+ class="z-20 self-start flex items-center gap-1 mx-4 mb-2 select-none text-[13px] font-medium transition-colors duration-150
6
+ text-lightInputIconColor dark:text-darkInputIconColor"
7
+ :class="timelineParts.length > 0 ? 'hover:text-lightInputText dark:hover:text-darkInputText' : 'cursor-default'"
8
8
  @click="isExpanded = !isExpanded"
9
9
  >
10
10
  <IconChevronRight
11
11
  v-if="timelineParts.length > 0"
12
- class="w-4 h-4 transition-transform duration-200"
12
+ class="w-3.5 h-3.5 transition-transform duration-200"
13
13
  :class="isExpanded ? 'rotate-90' : 'rotate-0'"
14
14
  />
15
15
  <template v-if="inProgress">
16
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)]"
17
+ class="shine-text
18
+ bg-[linear-gradient(-65deg,currentColor,theme(colors.lightInputPlaceholderText)_50%,currentColor)]
19
+ dark:bg-[linear-gradient(-65deg,currentColor,theme(colors.darkInputText)_70%,currentColor)]"
20
20
  >
21
21
  {{ $t('Thinking') }}
22
22
  </span>
@@ -34,8 +34,8 @@
34
34
  <div
35
35
  v-if="timelineParts.length > 0"
36
36
  v-show="isExpanded"
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"
37
+ class="z-20 mx-4 mb-3 overflow-hidden rounded-lg border
38
+ border-lightCardBorder bg-lightCardBackground dark:border-darkCardBorder dark:bg-darkCardBackground"
39
39
  >
40
40
  <CustomAutoScrollContainer
41
41
  ref="scrollContainerRef"
@@ -52,7 +52,7 @@
52
52
  <ToolRenderer v-else-if="part.type === 'data-tool-call'" :data="formatToolCallPart(part)" />
53
53
  <li
54
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"
55
+ class="flex items-center gap-2 py-0.5 text-[0.8125rem] text-lightTableText dark:text-darkTableText"
56
56
  >
57
57
  <span class="flex items-center justify-center w-4 h-4 shrink-0">
58
58
  <span class="w-2 h-2 rounded-full bg-current animate-pulse" />
@@ -73,7 +73,7 @@
73
73
  <script setup lang="ts">
74
74
  import type { IFormattedToolCallPart, IMessage, IPart } from '../types';
75
75
  import { ref, computed, watch, onUnmounted, onMounted, defineAsyncComponent } from 'vue';
76
- import { IconChevronRight } from '@iconify-prerendered/vue-heroicons';
76
+ import { IconChevronRight } from '@iconify-prerendered/vue-lucide';
77
77
  import ReasoningRenderer from './ReasoningRenderer.vue';
78
78
  import ToolRenderer from './ToolRenderer.vue';
79
79
  import ThreeDotsAnimation from './ThreeDotsAnimation.vue';
@@ -5,19 +5,19 @@
5
5
  class="flex items-center gap-2 w-full py-0.5 text-left text-[0.8125rem] leading-5 select-none"
6
6
  @click="isExpanded = !isExpanded"
7
7
  >
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>
8
+ <IconBrain class="w-4 h-4 shrink-0 text-lightInputIconColor dark:text-darkInputIconColor" />
9
+ <span class="min-w-0 truncate font-medium text-lightTableText dark:text-darkTableText">{{ reasoningTitle || $t('Reasoning') }}</span>
10
10
  <ThreeDotsAnimation v-if="isStreaming"/>
11
11
  <IconChevronDown
12
12
  :class="isExpanded ? 'rotate-180' : 'rotate-0'"
13
- class="ml-auto w-3.5 h-3.5 shrink-0 text-gray-400 transition-transform duration-200"
13
+ class="ml-auto w-3.5 h-3.5 shrink-0 text-lightInputIconColor transition-transform duration-200 dark:text-darkInputIconColor"
14
14
  />
15
15
  </button>
16
16
  <transition name="expand">
17
17
  <IncremarkContent
18
18
  v-if="isExpanded"
19
19
  v-show="isExpanded"
20
- class="ml-6 mb-1 text-sm text-gray-600 dark:text-gray-400"
20
+ class="ml-6 mb-1 text-sm text-lightInputIconColor dark:text-darkInputIconColor"
21
21
  :content="reasoningText"
22
22
  />
23
23
  </transition>
@@ -27,8 +27,7 @@
27
27
 
28
28
 
29
29
  <script setup lang="ts">
30
- import { IconBrainOutline } from '@iconify-prerendered/vue-flowbite';
31
- import { IconChevronDown } from '@iconify-prerendered/vue-heroicons';
30
+ import { IconBrain, IconChevronDown } from '@iconify-prerendered/vue-lucide';
32
31
  import type { IPart } from '../types';
33
32
  import { ref, computed, watch, defineAsyncComponent } from 'vue';
34
33
  import ThreeDotsAnimation from './ThreeDotsAnimation.vue';
@@ -1,10 +1,10 @@
1
1
  <template>
2
2
  <p :class="`${TEXT_CLASSES}`" v-if="message === RESERVED_SYSTEM_MESSAGE_CONTENT.START_AUDIO_CHAT">
3
- <IconPhoneSolid class="inline-block w-4 h-4 mr-1" />
3
+ <IconPhone class="inline-block w-4 h-4 mr-1" />
4
4
  {{$t('Audio chat started')}}
5
5
  </p>
6
6
  <p :class="`${TEXT_CLASSES}`" v-else-if="message === RESERVED_SYSTEM_MESSAGE_CONTENT.END_AUDIO_CHAT">
7
- <IconPhoneHangupSolid class="inline-block w-4 h-4 mr-1" />
7
+ <IconPhoneOff class="inline-block w-4 h-4 mr-1" />
8
8
  {{$t('Audio chat ended')}}
9
9
  </p>
10
10
  <p :class="`${TEXT_CLASSES}`" v-else-if="message === RESERVED_SYSTEM_MESSAGE_CONTENT.AGENT_RESPONSE_ABORTED">
@@ -17,9 +17,9 @@
17
17
 
18
18
  <script setup lang="ts">
19
19
  import { RESERVED_SYSTEM_MESSAGE_CONTENT } from '../composables/agentStore/constants';
20
- import { IconPhoneSolid, IconPhoneHangupSolid } from '@iconify-prerendered/vue-flowbite';
20
+ import { IconPhone, IconPhoneOff } from '@iconify-prerendered/vue-lucide';
21
21
 
22
- const TEXT_CLASSES = 'text-center italic text-lightListTableHeadingText dark:text-darkListTableHeadingText flex items-center justify-center';
22
+ const TEXT_CLASSES = 'flex items-center justify-center text-center text-sm italic text-lightInputIconColor dark:text-darkInputIconColor';
23
23
 
24
24
  const props = defineProps<{
25
25
  message: string
@@ -1,13 +1,14 @@
1
1
  <template>
2
2
  <div
3
- class="flex items-center justify-center min-w-0 transition-shadow duration-150 z-20"
3
+ class="flex items-center justify-center min-w-0 text-sm transition-shadow duration-150 z-20"
4
4
  @click="handleMarkdownLinkClick"
5
5
  :class="[
6
- hasVegaLite ? 'w-full my-2' : 'my-2',
6
+ hasVegaLite && 'w-full',
7
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'
8
+ ? 'self-end max-w-[82%] mr-4 px-3 py-2 rounded-xl rounded-br [&_.incremark-paragraph]:leading-5 bg-lightSecondaryDarken dark:bg-darkSecondaryLighten'
9
9
  : 'self-start max-w-full px-4',
10
- props.editMode ? 'ring-2 ring-lightPrimary dark:ring-darkPrimary w-full max-w-[calc(100%-2rem)] flex-col items-stretch py-3' : ''
10
+ props.editMode ? `w-full max-w-[calc(100%-2rem)] flex-col items-stretch py-3 border bg-lightInputBackground border-lightInputFocusBorder ring-[3px] ring-lightInputFocusRing
11
+ dark:bg-darkInputBackground dark:border-darkInputFocusBorder dark:ring-darkInputFocusRing` : ''
11
12
  ]"
12
13
  >
13
14
  <IncremarkContent
@@ -19,13 +20,10 @@
19
20
  :components="incremarkComponents"
20
21
  :incremark-options="incremarkOptions"
21
22
  />
22
- <!-- <p v-else class="text-red-500 py-2">
23
- {{ $t('No content to render') }}
24
- </p> -->
25
23
  <template v-if="props.editMode">
26
24
  <textarea
27
25
  v-model="agentStore.userMessageInput"
28
- class="w-full min-h-24 resize-y bg-transparent text-sm text-gray-900 dark:text-gray-100 border-none focus:outline-none focus:ring-0 p-0"
26
+ class="w-full min-h-24 resize-y bg-transparent text-sm text-lightInputText dark:text-darkInputText border-none focus:outline-none focus:ring-0 p-0"
29
27
  @keydown.esc.prevent="agentStore.cancelEditMessage"
30
28
  />
31
29
  <EditActionButtons
@@ -155,31 +153,55 @@
155
153
  </script>
156
154
 
157
155
  <style lang="scss">
156
+ // the incremark theme ships its own fonts and palette, point them to the design tokens
157
+ .incremark {
158
+ --incremark-typography-font-family-base: theme('fontFamily.sans');
159
+ --incremark-typography-font-family-mono: theme('fontFamily.mono');
160
+ --incremark-color-text-primary: theme('colors.lightListTableText');
161
+ --incremark-color-text-secondary: theme('colors.lightInputIconColor');
162
+ --incremark-color-text-tertiary: theme('colors.lightInputPlaceholderText');
163
+ --incremark-color-border-default: theme('colors.lightCardBorder');
164
+ --incremark-color-border-strong: theme('colors.lightCardBorder');
165
+ --incremark-color-border-subtle: theme('colors.lightCardBorder');
166
+ --incremark-color-background-base: theme('colors.lightCardBackground');
167
+ --incremark-color-background-elevated: theme('colors.lightCardBackgroundHover');
168
+ --incremark-color-code-inline-background: theme('colors.lightSecondaryDarken');
169
+ --incremark-color-code-inline-text: theme('colors.lightInputText');
170
+ --incremark-color-brand-primary: theme('colors.lightPrimary');
171
+ --incremark-color-interactive-link: theme('colors.lightPrimary');
172
+ --incremark-color-interactive-link-hover: theme('colors.lightPrimary');
173
+ --incremark-color-interactive-link-visited: theme('colors.lightPrimary');
174
+ }
175
+
176
+ .dark .incremark {
177
+ --incremark-color-text-primary: theme('colors.darkListTableText');
178
+ --incremark-color-text-secondary: theme('colors.darkInputIconColor');
179
+ --incremark-color-text-tertiary: theme('colors.darkInputPlaceholderText');
180
+ --incremark-color-border-default: theme('colors.darkCardBorder');
181
+ --incremark-color-border-strong: theme('colors.darkCardBorder');
182
+ --incremark-color-border-subtle: theme('colors.darkCardBorder');
183
+ --incremark-color-background-base: theme('colors.darkCardBackground');
184
+ --incremark-color-background-elevated: theme('colors.darkCardBackgroundHover');
185
+ --incremark-color-code-inline-background: theme('colors.darkSecondaryLighten');
186
+ --incremark-color-code-inline-text: theme('colors.darkInputText');
187
+ --incremark-color-brand-primary: theme('colors.darkPrimary');
188
+ --incremark-color-interactive-link: theme('colors.darkPrimary');
189
+ --incremark-color-interactive-link-hover: theme('colors.darkPrimary');
190
+ --incremark-color-interactive-link-visited: theme('colors.darkPrimary');
191
+ }
192
+
158
193
  .incremark a.incremark-link,
159
194
  .incremark a.incremark-link:visited {
195
+ @apply text-lightPrimary dark:text-darkPrimary;
160
196
  display: inline-block;
161
197
  text-decoration: underline;
162
198
  text-underline-offset: 4px;
163
- text-decoration-style:dotted;
164
- color: rgb(0, 0, 0);
165
- transition: color 0.2s ease;
199
+ text-decoration-style: dotted;
166
200
  word-break: break-all;
167
201
  }
168
202
 
169
203
  .incremark a.incremark-link:hover {
170
- color: rgb(74, 74, 255);
171
- text-decoration: underline;
172
- text-underline-offset: 4px;
173
- text-decoration-style:dotted;
174
- }
175
-
176
- html[data-theme="dark"] .incremark a.incremark-link,
177
- html[data-theme="dark"] .incremark a.incremark-link:visited {
178
- color: rgb(220, 220, 220);
179
- }
180
-
181
- html[data-theme="dark"] .incremark a.incremark-link:hover {
182
- color: rgb(147, 147, 255);
204
+ text-decoration-style: solid;
183
205
  }
184
206
 
185
207
  a.incremark-link::after {
@@ -198,11 +220,27 @@ a.incremark-link::after {
198
220
  word-break: break-word;
199
221
  }
200
222
 
201
- .incremark-list {
223
+ // scoped under .incremark: the incremark theme is imported lazily and would win an equal selector
224
+ .incremark .incremark-list {
202
225
  list-style: disc;
226
+ margin: 0 0 8px;
227
+ padding-left: 18px;
228
+ }
229
+
230
+ .incremark .incremark-list-item {
231
+ margin: 2px 0;
232
+ }
233
+
234
+ .incremark .incremark-paragraph {
235
+ margin: 0 0 8px;
236
+ }
237
+
238
+ .incremark .incremark-list-item > .incremark-paragraph {
239
+ margin: 0;
203
240
  }
204
241
 
205
- .incremark-paragraph {
206
- margin: 8px 0;
242
+ // the gap between messages comes from the conversation, not from the last block of a message
243
+ .incremark > .incremark-block:last-child > :last-child {
244
+ margin-bottom: 0;
207
245
  }
208
246
  </style>
@@ -1,29 +1,26 @@
1
1
  <template>
2
2
  <div
3
- class="mx-4 my-2 max-w-[min(34rem,calc(100%-2rem))] z-20 rounded-xl border p-3.5"
3
+ class="af-agent-approval mx-4 my-2 max-w-[min(34rem,calc(100%-2rem))] z-20 rounded-lg border p-3.5"
4
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'"
5
+ ? 'border-lightWarningBorder bg-lightWarningSoft dark:border-darkWarningBorder dark:bg-darkWarningSoft'
6
+ : 'border-lightCardBorder bg-lightCardBackground dark:border-darkCardBorder dark:bg-darkCardBackground'"
7
7
  >
8
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>
9
+ <IconShieldAlert
10
+ class="mt-0.5 h-4 w-4 shrink-0"
11
+ :class="isAwaitingDecision ? 'text-lightWarning dark:text-darkWarning' : 'text-lightInputIconColor dark:text-darkInputIconColor'"
12
+ />
17
13
  <div class="min-w-0 flex-1">
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">
14
+ <h3 class="text-sm font-semibold leading-5 text-lightCardTitle dark:text-darkCardTitle">{{ $t('Approval needed') }}</h3>
15
+ <p class="text-[0.8125rem] leading-5 text-lightCardDescription dark:text-darkCardDescription">
20
16
  {{ $t('Review what the agent is about to do before continuing.') }}
21
17
  </p>
22
18
  </div>
23
19
  </div>
24
20
  <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"
21
+ class="mt-3 divide-y rounded-default border text-[0.8125rem] leading-5
22
+ divide-lightCardBorder border-lightCardBorder bg-lightCardBackground text-lightTableText
23
+ dark:divide-darkCardBorder dark:border-darkCardBorder dark:bg-darkCardBackground dark:text-darkTableText"
27
24
  >
28
25
  <li
29
26
  v-for="(approval, approvalIndex) in data.approvals"
@@ -36,19 +33,19 @@
36
33
  class="mt-2 grid grid-cols-[minmax(0,auto)_minmax(0,1fr)] gap-x-4 gap-y-1.5"
37
34
  >
38
35
  <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>
36
+ <dt class="text-lightInputIconColor dark:text-darkInputIconColor [overflow-wrap:anywhere]">{{ field.label }}</dt>
40
37
  <dd class="flex min-w-0 flex-wrap items-center gap-1.5">
41
38
  <template v-if="field.from !== undefined && field.from !== field.to">
42
39
  <span
43
40
  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"
41
+ bg-lightDangerSoft text-lightDanger decoration-lightDanger/50 dark:bg-darkDangerSoft dark:text-darkDanger dark:decoration-darkDanger/50"
45
42
  >
46
43
  {{ field.from }}
47
44
  </span>
48
- <IconArrowRight class="h-3.5 w-3.5 shrink-0 text-gray-400" />
45
+ <IconArrowRight class="h-3.5 w-3.5 shrink-0 text-lightInputIconColor dark:text-darkInputIconColor" />
49
46
  <span
50
47
  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"
48
+ bg-lightSuccessSoft text-lightSuccess dark:bg-darkSuccessSoft dark:text-darkSuccess"
52
49
  >
53
50
  {{ field.to }}
54
51
  </span>
@@ -61,39 +58,28 @@
61
58
  </ul>
62
59
  <div class="mt-3 flex flex-wrap gap-2">
63
60
  <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')"
69
- >
61
+ <Button size="sm" type="button" @click="submit('approve')">
70
62
  <IconCheck class="h-4 w-4" />
71
63
  {{ $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
- >
64
+ </Button>
65
+ <Button variant="secondary" size="sm" type="button" @click="submit('reject')">
80
66
  {{ $t('Reject') }}
81
- </button>
67
+ </Button>
82
68
  </template>
83
69
  <span
84
70
  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"
71
+ class="inline-flex h-6 items-center gap-1.5 rounded-full border px-2 text-xs font-medium"
72
+ :class="STATUS_BADGE_CLASSES[data.status]"
87
73
  >
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" />
74
+ <Spinner v-if="data.status === 'processing'" class="h-3.5 w-3.5" />
75
+ <IconCheck v-else-if="data.status === 'approved'" class="h-3.5 w-3.5" />
76
+ <IconX v-else class="h-3.5 w-3.5" />
91
77
  {{ data.status === 'processing' ? $t('Processing') : data.status === 'approved' ? $t('Approved') : $t('Rejected') }}
92
78
  </span>
93
79
  </div>
94
80
  <p
95
81
  v-if="data.error"
96
- class="mt-3 text-sm leading-5 text-red-600 dark:text-red-400"
82
+ class="mt-3 text-sm leading-5 text-lightDanger dark:text-darkDanger"
97
83
  >
98
84
  {{ data.error }}
99
85
  </p>
@@ -104,13 +90,19 @@
104
90
  import { computed } from 'vue';
105
91
  import type { IPartData } from '../types';
106
92
  import { useAgentStore } from '../composables/useAgentStore';
107
- import { Spinner } from '@/afcl';
108
- import { IconArrowRight, IconCheck, IconShieldExclamation, IconXMark } from '@iconify-prerendered/vue-heroicons';
93
+ import { Button, Spinner } from '@/afcl';
94
+ import { IconArrowRight, IconCheck, IconShieldAlert, IconX } from '@iconify-prerendered/vue-lucide';
109
95
 
110
96
  const props = defineProps<{
111
97
  data: IPartData;
112
98
  }>();
113
99
 
100
+ const STATUS_BADGE_CLASSES: Record<string, string> = {
101
+ processing: 'border-transparent bg-lightSecondaryDarken text-lightInputIconColor dark:bg-darkSecondaryLighten dark:text-darkInputIconColor',
102
+ approved: 'border-lightSuccessBorder bg-lightSuccessSoft text-lightSuccess dark:border-darkSuccessBorder dark:bg-darkSuccessSoft dark:text-darkSuccess',
103
+ rejected: 'border-lightDangerBorder bg-lightDangerSoft text-lightDanger dark:border-darkDangerBorder dark:bg-darkDangerSoft dark:text-darkDanger',
104
+ };
105
+
114
106
  const agentStore = useAgentStore();
115
107
 
116
108
  const isAwaitingDecision = computed(() => props.data.status === 'pending' || props.data.status === 'processing');