@adminforth/agent 1.11.0 → 1.12.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/build.log CHANGED
@@ -15,7 +15,9 @@ custom/package.json
15
15
  custom/pnpm-lock.yaml
16
16
  custom/tsconfig.json
17
17
  custom/types.ts
18
- custom/useAgentStore.ts
18
+ custom/composables/
19
+ custom/composables/useAgentStore.ts
20
+ custom/composables/useAgentTransitions.ts
19
21
  custom/incremark_code_renderers/
20
22
  custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue
21
23
  custom/incremark_code_renderers/incremarkCodeHighlight.ts
@@ -29,5 +31,5 @@ custom/skills/fetch_data/SKILL.md
29
31
  custom/skills/mutate_data/
30
32
  custom/skills/mutate_data/SKILL.md
31
33
 
32
- sent 175,146 bytes received 413 bytes 351,118.00 bytes/sec
33
- total size is 173,468 speedup is 0.99
34
+ sent 180,279 bytes received 444 bytes 361,446.00 bytes/sec
35
+ total size is 178,505 speedup is 0.99
@@ -16,132 +16,163 @@
16
16
  </div>
17
17
  </div>
18
18
 
19
- <div
20
- ref="chatSurface"
21
- class="fixed bg-lightNavbar dark:bg-darkNavbar h-screen top-0 right-0 border-x border-b border-gray-200 dark:border-gray-700
22
- transition-transform duration-200 ease-in-out
23
- flex flex z-10"
24
- :class="[agentStore.isChatOpen ? 'translate-x-0' : 'translate-x-full', !agentStore.isTeleportedToBody ? 'shadow-2xl' : '']"
25
- :style="{ width: agentStore.chatWidth + 'px' }"
26
- >
19
+ <Teleport to="body">
27
20
  <div
28
- v-if="!coreStore.isMobile"
29
- class="w-2 cursor-ew-resize absolute left-0 top-0 h-full z-30"
30
- @mousedown="startResize"
31
- ></div>
32
-
33
- <div class="w-full h-full flex flex-col">
34
- <div class="flex items-center justify-between">
35
- <div class="flex items-center h-[55px]">
36
- <IconBarsOutline
37
- class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
38
- text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
39
- dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
40
- :class="agentStore.isSessionHistoryOpen ?
41
- 'bg-lightNavbarIcons/20 text-lightNavbarIcons/80 dark:bg-darkNavbarIcons/20 dark:text-darkNavbarIcons/80' :
42
- ''"
43
- @click="agentStore.setSessionHistoryOpen(!agentStore.isSessionHistoryOpen)"
44
- />
45
- <IconOpenSidebarSolid
46
- v-if="!agentStore.isTeleportedToBody && !coreStore.isMobile"
21
+ ref="chatSurface"
22
+ id="adminforth-agent-chat-surface"
23
+ class="fixed bg-lightNavbar dark:bg-darkNavbar h-screen top-0 right-0 border-x border-b border-gray-200 dark:border-gray-700
24
+ flex flex z-40"
25
+ :class="[agentStore.isChatOpen ? 'translate-x-0' : 'translate-x-full', !agentStore.isTeleportedToBody ? 'shadow-2xl' : '']"
26
+ :style="{ width: agentStore.chatWidth + 'px' }"
27
+ >
28
+ <div
29
+ v-if="!coreStore.isMobile"
30
+ class="w-2 cursor-ew-resize absolute left-0 top-0 h-full z-30"
31
+ @mousedown="startResize"
32
+ ></div>
33
+
34
+ <div
35
+ class="w-full h-full flex flex-col"
36
+ :class="{ 'ml-4': agentStore.isFullScreen }"
37
+ >
38
+ <div class="flex items-center justify-between border-b border-gray-200 dark:border-gray-700">
39
+ <div
40
+ class="flex items-center h-[55px]"
41
+ >
42
+ <IconBarsOutline
43
+ class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
44
+ text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
45
+ dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
46
+ :class="agentStore.isSessionHistoryOpen ?
47
+ 'bg-lightNavbarIcons/20 text-lightNavbarIcons/80 dark:bg-darkNavbarIcons/20 dark:text-darkNavbarIcons/80' :
48
+ ''"
49
+ @click="agentStore.setSessionHistoryOpen(!agentStore.isSessionHistoryOpen)"
50
+ />
51
+ <IconOpenSidebarSolid
52
+ v-if="!agentStore.isTeleportedToBody && !coreStore.isMobile && !agentStore.isFullScreen"
53
+ class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
54
+ text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
55
+ dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
56
+ @click="agentStore.setIsTeleportedToBody(true)"
57
+ />
58
+ <IconCloseSidebarSolid
59
+ v-else-if="!coreStore.isMobile && !agentStore.isFullScreen"
60
+ class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
61
+ text-lightNavbarIcons hover:text-lightNavbarIcons/80 bg-lightNavbarIcons/20
62
+ dark:bg-darkNavbarIcons/20 dark:text-darkNavbarIcons/80 hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
63
+ @click="agentStore.setIsTeleportedToBody(false)"
64
+ />
65
+ <IconArrowsPointingOut
66
+ v-if="!agentStore.isFullScreen && !coreStore.isMobile"
67
+ class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
68
+ text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
69
+ dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
70
+ @click="agentStore.setFullScreen(true)"
71
+ />
72
+ <IconArrowsPointingIn
73
+ v-else-if="!coreStore.isMobile"
74
+ class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
75
+ text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
76
+ dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
77
+ :class="agentStore.isFullScreen ?
78
+ 'bg-lightNavbarIcons/20 text-lightNavbarIcons/80 dark:bg-darkNavbarIcons/20 dark:text-darkNavbarIcons/80' :
79
+ ''"
80
+ @click="agentStore.setFullScreen(false)"
81
+ />
82
+ </div>
83
+
84
+ <IconCloseOutline
47
85
  class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
48
86
  text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
49
87
  dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
50
- @click="agentStore.setIsTeleportedToBody(true)"
51
- />
52
- <IconCloseSidebarSolid
53
- v-else-if="!coreStore.isMobile"
54
- class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
55
- text-lightNavbarIcons hover:text-lightNavbarIcons/80 bg-lightNavbarIcons/20
56
- dark:bg-darkNavbarIcons/20 dark:text-darkNavbarIcons/80 hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
57
- @click="agentStore.setIsTeleportedToBody(false)"
88
+ @click="agentStore.setIsChatOpen(false)"
58
89
  />
59
- </div>
60
-
61
- <IconCloseOutline
62
- class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
63
- text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
64
- dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
65
- @click="agentStore.setIsChatOpen(false)"
66
- />
67
90
 
68
- </div>
69
- <div class="relative flex-1 flex flex-col overflow-hidden">
70
- <ConversationArea
71
- v-if="agentStore.isChatOpen"
72
- class="flex-1 overflow-auto"
73
- :messages="agentStore.chatMessages"
74
- />
75
-
76
- <div class="w-full mb-2 flex items-center justify-center px-2 bg-transparent relative">
77
- <textarea
78
- v-model="agentStore.userMessageInput"
79
- ref="textInput"
80
- @input="autoResize"
81
- :class="[
82
- 'min-h-12 w-full resize-none overflow-hidden border 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',
83
- agentStore.availableModes.length > 1 ? 'p-4 pr-12 pb-12' : 'p-4 pr-12',
84
- ]"
85
- placeholder="Type a message..."
86
- @keydown.enter.exact.prevent="sendMessage"
91
+ </div>
92
+ <div
93
+ class="relative flex-1 flex flex-col overflow-hidden"
94
+ >
95
+ <ConversationArea
96
+ v-if="agentStore.isChatOpen"
97
+ class="flex-1 overflow-auto"
98
+ :messages="agentStore.chatMessages"
87
99
  />
88
- <div
89
- v-if="agentStore.availableModes.length > 1"
90
- ref="modeMenu"
91
- class="absolute bottom-2 left-4"
92
- >
93
- <button
94
- aria-label="Select mode"
95
- class="flex h-8 w-8 items-center justify-center rounded-md border border-gray-200 bg-white text-lightNavbarIcons transition-colors duration-200 hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-darkNavbarIcons dark:hover:bg-gray-700"
96
- :class="isModeMenuOpen ? 'bg-gray-100 dark:bg-gray-700' : ''"
97
- :disabled="agentStore.isResponseInProgress"
98
- title="Select mode"
99
- type="button"
100
- @click="toggleModeMenu"
101
- >
102
- <IconBrainOutline class="h-4 w-4" />
103
- </button>
104
100
 
101
+ <div
102
+ class="w-full mb-2 flex items-center justify-center px-2 bg-transparent relative"
103
+ :class="agentStore.isFullScreen ? 'mx-auto' : ''"
104
+ :style="{ maxWidth: agentStore.isFullScreen ? agentStore.MAX_WIDTH+'px' : '100%' }"
105
+ >
106
+ <textarea
107
+ v-model="agentStore.userMessageInput"
108
+ ref="textInput"
109
+ @input="autoResize"
110
+ :class="[
111
+ 'min-h-12 w-full resize-none overflow-hidden border 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',
112
+ agentStore.availableModes.length > 1 ? 'p-4 pr-12 pb-12' : 'p-4 pr-12',
113
+ ]"
114
+ placeholder="Type a message..."
115
+ @keydown.enter.exact.prevent="sendMessage"
116
+ />
105
117
  <div
106
- v-if="isModeMenuOpen"
107
- class="absolute 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"
118
+ v-if="agentStore.availableModes.length > 1"
119
+ ref="modeMenu"
120
+ class="absolute bottom-2 left-4"
108
121
  >
109
122
  <button
110
- v-for="mode in agentStore.availableModes"
111
- :key="mode.name"
112
- 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"
113
- :class="mode.name === agentStore.activeModeName ? 'bg-gray-100 dark:bg-gray-700' : ''"
123
+ aria-label="Select mode"
124
+ class="flex h-8 w-8 items-center justify-center rounded-md border border-gray-200 bg-white text-lightNavbarIcons transition-colors duration-200 hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-darkNavbarIcons dark:hover:bg-gray-700"
125
+ :class="isModeMenuOpen ? 'bg-gray-100 dark:bg-gray-700' : ''"
126
+ :disabled="agentStore.isResponseInProgress"
127
+ title="Select mode"
114
128
  type="button"
115
- @click="selectMode(mode.name)"
129
+ @click="toggleModeMenu"
116
130
  >
117
- {{ mode.name }}
131
+ <IconBrainOutline class="h-4 w-4" />
118
132
  </button>
133
+
134
+ <div
135
+ v-if="isModeMenuOpen"
136
+ class="absolute 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"
137
+ >
138
+ <button
139
+ v-for="mode in agentStore.availableModes"
140
+ :key="mode.name"
141
+ 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"
142
+ :class="mode.name === agentStore.activeModeName ? 'bg-gray-100 dark:bg-gray-700' : ''"
143
+ type="button"
144
+ @click="selectMode(mode.name)"
145
+ >
146
+ {{ mode.name }}
147
+ </button>
148
+ </div>
119
149
  </div>
150
+ <Button
151
+ class="absolute right-4 bottom-2 !p-0 h-[34px] w-[34px]"
152
+ @click="sendMessage"
153
+ :disabled="!agentStore.trimmedUserMessage || agentStore.isResponseInProgress"
154
+ >
155
+ <IconArrowUpOutline
156
+ class="w-8 h-8 p-1
157
+ text-white"
158
+ />
159
+ </Button>
120
160
  </div>
121
- <Button
122
- class="absolute right-4 bottom-2 !p-0 h-[34px] w-[34px]"
123
- @click="sendMessage"
124
- :disabled="!agentStore.trimmedUserMessage || agentStore.isResponseInProgress"
125
- >
126
- <IconArrowUpOutline
127
- class="w-8 h-8 p-1
128
- text-white"
129
- />
130
- </Button>
131
161
  </div>
132
162
  </div>
133
163
  </div>
134
- </div>
164
+ </Teleport>
135
165
 
136
166
  </template>
137
167
 
138
168
  <script setup lang="ts">
139
- import { IconChatBubbleLeft20Solid, IconSparklesSolid } from '@iconify-prerendered/vue-heroicons';
169
+ import { IconChatBubbleLeft20Solid, IconSparklesSolid, IconArrowsPointingOut, IconArrowsPointingIn } from '@iconify-prerendered/vue-heroicons';
140
170
  import { IconCloseOutline, IconBarsOutline, IconArrowUpOutline, IconCloseSidebarSolid, IconOpenSidebarSolid, IconBrainOutline } from '@iconify-prerendered/vue-flowbite';
141
- import { useTemplateRef, onMounted, ref } from 'vue';
171
+ import { useTemplateRef, onMounted, ref,computed } from 'vue';
142
172
  import { onClickOutside } from '@vueuse/core'
143
173
  import ConversationArea from './ConversationArea.vue';
144
- import { useAgentStore } from './useAgentStore';
174
+ import { useAgentStore } from './composables/useAgentStore';
175
+ import { useAgentTransitions } from './composables/useAgentTransitions';
145
176
  import { Button } from '@/afcl';
146
177
  import { useCoreStore } from '@/stores/core';
147
178
 
@@ -159,12 +190,10 @@ const chatSurface = useTemplateRef('chatSurface');
159
190
  const textInput = useTemplateRef('textInput');
160
191
  const modeMenu = useTemplateRef('modeMenu');
161
192
  const agentStore = useAgentStore();
193
+ const agentTransitions = useAgentTransitions();
162
194
  const coreStore = useCoreStore();
163
195
  const isModeMenuOpen = ref(false);
164
196
 
165
- const MAX_WIDTH = 800;
166
- const MIN_WIDTH = 382; //w-96
167
-
168
197
  let startX = 0
169
198
  let startWidth = 0
170
199
 
@@ -181,7 +210,8 @@ const startResize = (e: MouseEvent) => {
181
210
 
182
211
  const onResize = (e: MouseEvent) => {
183
212
  const dx = startX - e.clientX
184
- agentStore.setChatWidth(Math.min(Math.max(startWidth + dx, MIN_WIDTH), MAX_WIDTH))
213
+ agentStore.setChatWidth(Math.min(Math.max(startWidth + dx, agentStore.MIN_WIDTH), agentStore.MAX_WIDTH))
214
+ agentTransitions.setChatSurfaceTransition(true);
185
215
  }
186
216
 
187
217
  const stopResize = () => {
@@ -194,8 +224,8 @@ const stopResize = () => {
194
224
  const appRoot = document.getElementById('app');
195
225
  const header = document.getElementById('af-header-nav');
196
226
  if (appRoot && header) {
197
- appRoot.style.transition = 'padding-right 200ms ease-in-out';
198
- header.style.transition = 'padding-right 200ms ease-in-out';
227
+ agentTransitions.setAppRootTransition(false);
228
+ agentTransitions.setChatSurfaceTransition(false);
199
229
  }
200
230
  }
201
231
 
@@ -1,11 +1,5 @@
1
1
  <template>
2
- <button @click="scrollContainer.scrollToBottom()">
3
- <IconArrowDownOutline
4
- class="absolute bottom-32 right-4 bg-lightPrimary dark:bg-darkPrimary text-white p-2 w-10 h-10 rounded-full transition-opacity duration-100 ease-in"
5
- :class="showScrollToBottomButton ? 'opacity-100' : 'opacity-0 pointer-events-none'"
6
- :disabled="!showScrollToBottomButton"
7
- />
8
- </button>
2
+
9
3
  <SessionsHistory
10
4
  :class="agentStore.isSessionHistoryOpen ? 'translate-x-0' : '-translate-x-full'"
11
5
  />
@@ -18,12 +12,24 @@
18
12
  </div>
19
13
  <AutoScrollContainer
20
14
  :enabled="!showScrollToBottomButton"
21
- class="flex flex-col overflow-y-auto border-t border-gray-200 dark:border-gray-700"
15
+ class="relative flex flex-col overflow-y-auto"
22
16
  ref="scrollContainer"
23
17
  :threshold="10"
24
18
  behavior="smooth"
19
+ :class="agentStore.isFullScreen ? 'mx-auto' : ''"
20
+ :style="{
21
+ maxWidth: agentStore.isFullScreen ? agentStore.MAX_WIDTH+'px' : '100%',
22
+ transition: `max-width ${agentTransitions.TRANSITION_DURATION}ms ease-in-out`
23
+ }"
25
24
  >
26
25
 
26
+ <button @click="scrollContainer.scrollToBottom()">
27
+ <IconArrowDownOutline
28
+ class="fixed bottom-32 left-1/2 bg-lightPrimary dark:bg-darkPrimary text-white p-2 w-10 h-10 rounded-full transition-opacity duration-100 ease-in"
29
+ :class="showScrollToBottomButton ? 'opacity-100' : 'opacity-0 pointer-events-none'"
30
+ :disabled="!showScrollToBottomButton"
31
+ />
32
+ </button>
27
33
  <div
28
34
  v-for="message in props.messages" :key="message.id"
29
35
  class="flex flex-col w-full"
@@ -71,15 +77,16 @@ import type { IMessage, IPart } from './types';
71
77
  import { useTemplateRef, ref, defineAsyncComponent, onMounted, watch, computed } from 'vue';
72
78
  import { IconArrowDownOutline } from '@iconify-prerendered/vue-flowbite';
73
79
  import SessionsHistory from './SessionsHistory.vue';
74
- import { useAgentStore } from './useAgentStore';
75
- import ToolRenderer from './ToolRenderer.vue';
80
+ import { useAgentStore } from './composables/useAgentStore';
76
81
  import ToolsGroup from './ToolsGroup.vue';
82
+ import { useAgentTransitions } from './composables/useAgentTransitions';
77
83
 
78
84
  const scrollContainer = useTemplateRef('scrollContainer');
79
85
  const showScrollToBottomButton = ref(false);
80
86
  const innerScrollContainerRef = ref(null);
81
87
  const AutoScrollContainer = defineAsyncComponent(() => import('@incremark/vue').then(module => module.AutoScrollContainer))
82
88
  const agentStore = useAgentStore();
89
+ const agentTransitions = useAgentTransitions();
83
90
  const clicks = ref(0);
84
91
 
85
92
  function recalculateScroll() {
@@ -49,7 +49,7 @@ import { Button, Spinner } from '@/afcl'
49
49
  import { computed } from 'vue';
50
50
  import { IconPlusOutline } from '@iconify-prerendered/vue-flowbite';
51
51
  import type { ISessionsListItem } from './types';
52
- import { useAgentStore } from './useAgentStore';
52
+ import { useAgentStore } from './composables/useAgentStore';
53
53
 
54
54
  const agentStore = useAgentStore();
55
55
 
@@ -1,17 +1,23 @@
1
1
  import { defineStore } from 'pinia';
2
- import { IAgentSession, ISessionsListItem, IMessage } from './types';
2
+ import { IAgentSession, ISessionsListItem, IMessage } from '../types';
3
3
  import { ref, nextTick, computed, watch, onMounted, shallowRef } from 'vue';
4
4
  import { callAdminForthApi } from '@/utils';
5
5
  import { useAdminforth } from '@/adminforth';
6
- import { Chat } from './chat';
7
- import { cosineSimilarity, DefaultChatTransport } from 'ai';
6
+ import { Chat } from '../chat';
7
+ import { DefaultChatTransport } from 'ai';
8
8
  import { useCoreStore } from '@/stores/core';
9
+ import { useAgentTransitions } from './useAgentTransitions';
9
10
 
10
11
  type AgentMode = {
11
12
  name: string;
12
13
  };
13
14
 
14
15
  export const useAgentStore = defineStore('agent', () => {
16
+ const DEFAULT_CHAT_WIDTH = 600;
17
+ const MAX_WIDTH = 800;
18
+ const MIN_WIDTH = 382; //w-96
19
+ const agentTransitions = useAgentTransitions();
20
+
15
21
  const activeSessionId = ref<string | null>(null);
16
22
  const currentSession = ref<IAgentSession | null>(null);
17
23
  const sessionList = ref<ISessionsListItem[]>([]);
@@ -31,7 +37,7 @@ export const useAgentStore = defineStore('agent', () => {
31
37
  const appRoot = ref<HTMLElement | null>(null);
32
38
  const header = ref<HTMLElement | null>(null);
33
39
  const lastSessionId = ref<string | null>(null);
34
- const chatWidth = ref(600);
40
+ const chatWidth = ref(DEFAULT_CHAT_WIDTH);
35
41
  const availableModes = ref<AgentMode[]>([]);
36
42
  const activeModeName = ref<string | null>(null);
37
43
  function setLocalStorageItem(key: string, value: string) {
@@ -55,7 +61,12 @@ export const useAgentStore = defineStore('agent', () => {
55
61
  }
56
62
  })
57
63
  onMounted(() => {
58
- chatWidth.value = parseInt(getLocalStorageItem('chatWidth') || '600', 10);
64
+ const chatWidthBeforeFullScreen = parseInt(getLocalStorageItem('chatWidthBeforeFullScreen') || '0', 10);
65
+ if (chatWidthBeforeFullScreen) {
66
+ chatWidth.value = chatWidthBeforeFullScreen;
67
+ } else {
68
+ chatWidth.value = parseInt(getLocalStorageItem('chatWidth') || DEFAULT_CHAT_WIDTH.toString(), 10);
69
+ }
59
70
  isTeleportedToBody.value = getLocalStorageItem('isTeleportedToBody') === 'true';
60
71
  lastSessionId.value = getLocalStorageItem('lastSessionId');
61
72
  if (lastSessionId.value && lastSessionId.value !== 'pre-session') {
@@ -71,29 +82,33 @@ export const useAgentStore = defineStore('agent', () => {
71
82
  header.value = document.getElementById('af-header-nav');
72
83
  if (appRoot.value && header.value) {
73
84
  nextTick(() => {
74
- appRoot.value.style.transition = 'padding-right 200ms ease-in-out';
75
- header.value.style.transition = 'padding-right 200ms ease-in-out';
85
+ agentTransitions.setAppRootTransition(false);
76
86
  });
77
87
  }
78
88
  })
89
+
79
90
  const isFullScreen = ref(false);
80
91
  function setFullScreen(fullScreen: boolean) {
81
92
  isFullScreen.value = fullScreen;
82
- console.log('setFullScreen', fullScreen);
83
93
  if (fullScreen) {
84
- setChatWidth(window.innerWidth, true);
94
+ setLocalStorageItem('chatWidthBeforeFullScreen', chatWidth.value.toString());
95
+ setLocalStorageItem('isTeleportedToBodyBeforeFullScreen', isTeleportedToBody.value ? 'true' : 'false');
96
+ setIsTeleportedToBody(false);
97
+ useAgentTransitions().setChatSurfaceTransition(false);
98
+ setChatWidth(window.innerWidth, false);
85
99
  } else {
86
- console.log('setChatWidth to default');
87
- chatWidth.value = 600;
100
+ const lastChatWidth = parseInt(getLocalStorageItem('chatWidthBeforeFullScreen') || DEFAULT_CHAT_WIDTH.toString(), 10);
101
+ const isTeleportedBeforeFullScreen = getLocalStorageItem('isTeleportedToBodyBeforeFullScreen') === 'true';
102
+ agentTransitions.setAppRootTransition(true);
103
+ setIsTeleportedToBody(isTeleportedBeforeFullScreen);
104
+ setChatWidth(lastChatWidth, false);
105
+ setTimeout(() => agentTransitions.setAppRootTransition(false), agentTransitions.TRANSITION_DURATION);
88
106
  }
89
107
  }
90
108
 
91
- function setChatWidth(width: number, blockTransition = false) {
92
- if (appRoot.value && header.value) {
93
- if (blockTransition) {
94
- appRoot.value.style.transition = '';
95
- header.value.style.transition = '';
96
- }
109
+ function setChatWidth(width: number, blockTransition = true) {
110
+ if (blockTransition) {
111
+ agentTransitions.setAppRootTransition(true);
97
112
  }
98
113
  chatWidth.value = width;
99
114
 
@@ -414,5 +429,8 @@ export const useAgentStore = defineStore('agent', () => {
414
429
  activeModeName,
415
430
  setAvailableModes,
416
431
  setActiveMode,
432
+ DEFAULT_CHAT_WIDTH,
433
+ MAX_WIDTH,
434
+ MIN_WIDTH,
417
435
  }
418
436
  })
@@ -0,0 +1,58 @@
1
+ import { defineStore } from 'pinia';
2
+ import { ref, nextTick,onMounted } from 'vue';
3
+ import { useAgentStore } from './useAgentStore';
4
+
5
+
6
+ export const useAgentTransitions = defineStore('agentTransitions', () => {
7
+ const TRANSITION_DURATION = 200;
8
+
9
+ const agentStore = useAgentStore();
10
+ const appRoot = ref<HTMLElement | null>(null);
11
+ const header = ref<HTMLElement | null>(null);
12
+
13
+ const chatRoot = ref<HTMLElement | null>(null);
14
+
15
+ onMounted(() => {
16
+ appRoot.value = document.getElementById('app');
17
+ header.value = document.getElementById('af-header-nav');
18
+ chatRoot.value = document.getElementById('adminforth-agent-chat-surface');
19
+ if (appRoot.value && header.value) {
20
+ nextTick(() => {
21
+ setAppRootTransition(false);
22
+ });
23
+ }
24
+ if (chatRoot.value) {
25
+ nextTick(() => {
26
+ setChatSurfaceTransition(false);
27
+ });
28
+ }
29
+ });
30
+ function setAppRootTransition(blockTransition = true) {
31
+ if (appRoot.value && header.value) {
32
+ if (blockTransition) {
33
+ appRoot.value.style.transition = '';
34
+ header.value.style.transition = '';
35
+ } else {
36
+ appRoot.value.style.transition = `padding-right ${TRANSITION_DURATION}ms ease-in-out`;
37
+ header.value.style.transition = `padding-right ${TRANSITION_DURATION}ms ease-in-out`;
38
+ }
39
+ }
40
+ }
41
+ function setChatSurfaceTransition(blockTransition = true) {
42
+ if (chatRoot.value) {
43
+ if (blockTransition) {
44
+ chatRoot.value.style.transition = '';
45
+ } else {
46
+ chatRoot.value.style.transition = `
47
+ transform ${TRANSITION_DURATION}ms ease-in-out,
48
+ width ${TRANSITION_DURATION}ms ease-in-out
49
+ `;
50
+ }
51
+ }
52
+ }
53
+ return {
54
+ TRANSITION_DURATION,
55
+ setAppRootTransition,
56
+ setChatSurfaceTransition
57
+ }
58
+ });
@@ -16,132 +16,163 @@
16
16
  </div>
17
17
  </div>
18
18
 
19
- <div
20
- ref="chatSurface"
21
- class="fixed bg-lightNavbar dark:bg-darkNavbar h-screen top-0 right-0 border-x border-b border-gray-200 dark:border-gray-700
22
- transition-transform duration-200 ease-in-out
23
- flex flex z-10"
24
- :class="[agentStore.isChatOpen ? 'translate-x-0' : 'translate-x-full', !agentStore.isTeleportedToBody ? 'shadow-2xl' : '']"
25
- :style="{ width: agentStore.chatWidth + 'px' }"
26
- >
19
+ <Teleport to="body">
27
20
  <div
28
- v-if="!coreStore.isMobile"
29
- class="w-2 cursor-ew-resize absolute left-0 top-0 h-full z-30"
30
- @mousedown="startResize"
31
- ></div>
32
-
33
- <div class="w-full h-full flex flex-col">
34
- <div class="flex items-center justify-between">
35
- <div class="flex items-center h-[55px]">
36
- <IconBarsOutline
37
- class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
38
- text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
39
- dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
40
- :class="agentStore.isSessionHistoryOpen ?
41
- 'bg-lightNavbarIcons/20 text-lightNavbarIcons/80 dark:bg-darkNavbarIcons/20 dark:text-darkNavbarIcons/80' :
42
- ''"
43
- @click="agentStore.setSessionHistoryOpen(!agentStore.isSessionHistoryOpen)"
44
- />
45
- <IconOpenSidebarSolid
46
- v-if="!agentStore.isTeleportedToBody && !coreStore.isMobile"
21
+ ref="chatSurface"
22
+ id="adminforth-agent-chat-surface"
23
+ class="fixed bg-lightNavbar dark:bg-darkNavbar h-screen top-0 right-0 border-x border-b border-gray-200 dark:border-gray-700
24
+ flex flex z-40"
25
+ :class="[agentStore.isChatOpen ? 'translate-x-0' : 'translate-x-full', !agentStore.isTeleportedToBody ? 'shadow-2xl' : '']"
26
+ :style="{ width: agentStore.chatWidth + 'px' }"
27
+ >
28
+ <div
29
+ v-if="!coreStore.isMobile"
30
+ class="w-2 cursor-ew-resize absolute left-0 top-0 h-full z-30"
31
+ @mousedown="startResize"
32
+ ></div>
33
+
34
+ <div
35
+ class="w-full h-full flex flex-col"
36
+ :class="{ 'ml-4': agentStore.isFullScreen }"
37
+ >
38
+ <div class="flex items-center justify-between border-b border-gray-200 dark:border-gray-700">
39
+ <div
40
+ class="flex items-center h-[55px]"
41
+ >
42
+ <IconBarsOutline
43
+ class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
44
+ text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
45
+ dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
46
+ :class="agentStore.isSessionHistoryOpen ?
47
+ 'bg-lightNavbarIcons/20 text-lightNavbarIcons/80 dark:bg-darkNavbarIcons/20 dark:text-darkNavbarIcons/80' :
48
+ ''"
49
+ @click="agentStore.setSessionHistoryOpen(!agentStore.isSessionHistoryOpen)"
50
+ />
51
+ <IconOpenSidebarSolid
52
+ v-if="!agentStore.isTeleportedToBody && !coreStore.isMobile && !agentStore.isFullScreen"
53
+ class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
54
+ text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
55
+ dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
56
+ @click="agentStore.setIsTeleportedToBody(true)"
57
+ />
58
+ <IconCloseSidebarSolid
59
+ v-else-if="!coreStore.isMobile && !agentStore.isFullScreen"
60
+ class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
61
+ text-lightNavbarIcons hover:text-lightNavbarIcons/80 bg-lightNavbarIcons/20
62
+ dark:bg-darkNavbarIcons/20 dark:text-darkNavbarIcons/80 hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
63
+ @click="agentStore.setIsTeleportedToBody(false)"
64
+ />
65
+ <IconArrowsPointingOut
66
+ v-if="!agentStore.isFullScreen && !coreStore.isMobile"
67
+ class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
68
+ text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
69
+ dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
70
+ @click="agentStore.setFullScreen(true)"
71
+ />
72
+ <IconArrowsPointingIn
73
+ v-else-if="!coreStore.isMobile"
74
+ class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
75
+ text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
76
+ dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
77
+ :class="agentStore.isFullScreen ?
78
+ 'bg-lightNavbarIcons/20 text-lightNavbarIcons/80 dark:bg-darkNavbarIcons/20 dark:text-darkNavbarIcons/80' :
79
+ ''"
80
+ @click="agentStore.setFullScreen(false)"
81
+ />
82
+ </div>
83
+
84
+ <IconCloseOutline
47
85
  class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
48
86
  text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
49
87
  dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
50
- @click="agentStore.setIsTeleportedToBody(true)"
51
- />
52
- <IconCloseSidebarSolid
53
- v-else-if="!coreStore.isMobile"
54
- class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
55
- text-lightNavbarIcons hover:text-lightNavbarIcons/80 bg-lightNavbarIcons/20
56
- dark:bg-darkNavbarIcons/20 dark:text-darkNavbarIcons/80 hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
57
- @click="agentStore.setIsTeleportedToBody(false)"
88
+ @click="agentStore.setIsChatOpen(false)"
58
89
  />
59
- </div>
60
-
61
- <IconCloseOutline
62
- class="m-2 w-8 h-8 p-1 cursor-pointer hover:scale-110 rounded transition-colors duration-200
63
- text-lightNavbarIcons hover:text-lightNavbarIcons/80 hover:bg-lightNavbarIcons/20
64
- dark:text-darkNavbarIcons hover:text-darkNavbarIcons/80 hover:bg-darkNavbarIcons/20 "
65
- @click="agentStore.setIsChatOpen(false)"
66
- />
67
90
 
68
- </div>
69
- <div class="relative flex-1 flex flex-col overflow-hidden">
70
- <ConversationArea
71
- v-if="agentStore.isChatOpen"
72
- class="flex-1 overflow-auto"
73
- :messages="agentStore.chatMessages"
74
- />
75
-
76
- <div class="w-full mb-2 flex items-center justify-center px-2 bg-transparent relative">
77
- <textarea
78
- v-model="agentStore.userMessageInput"
79
- ref="textInput"
80
- @input="autoResize"
81
- :class="[
82
- 'min-h-12 w-full resize-none overflow-hidden border 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',
83
- agentStore.availableModes.length > 1 ? 'p-4 pr-12 pb-12' : 'p-4 pr-12',
84
- ]"
85
- placeholder="Type a message..."
86
- @keydown.enter.exact.prevent="sendMessage"
91
+ </div>
92
+ <div
93
+ class="relative flex-1 flex flex-col overflow-hidden"
94
+ >
95
+ <ConversationArea
96
+ v-if="agentStore.isChatOpen"
97
+ class="flex-1 overflow-auto"
98
+ :messages="agentStore.chatMessages"
87
99
  />
88
- <div
89
- v-if="agentStore.availableModes.length > 1"
90
- ref="modeMenu"
91
- class="absolute bottom-2 left-4"
92
- >
93
- <button
94
- aria-label="Select mode"
95
- class="flex h-8 w-8 items-center justify-center rounded-md border border-gray-200 bg-white text-lightNavbarIcons transition-colors duration-200 hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-darkNavbarIcons dark:hover:bg-gray-700"
96
- :class="isModeMenuOpen ? 'bg-gray-100 dark:bg-gray-700' : ''"
97
- :disabled="agentStore.isResponseInProgress"
98
- title="Select mode"
99
- type="button"
100
- @click="toggleModeMenu"
101
- >
102
- <IconBrainOutline class="h-4 w-4" />
103
- </button>
104
100
 
101
+ <div
102
+ class="w-full mb-2 flex items-center justify-center px-2 bg-transparent relative"
103
+ :class="agentStore.isFullScreen ? 'mx-auto' : ''"
104
+ :style="{ maxWidth: agentStore.isFullScreen ? agentStore.MAX_WIDTH+'px' : '100%' }"
105
+ >
106
+ <textarea
107
+ v-model="agentStore.userMessageInput"
108
+ ref="textInput"
109
+ @input="autoResize"
110
+ :class="[
111
+ 'min-h-12 w-full resize-none overflow-hidden border 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',
112
+ agentStore.availableModes.length > 1 ? 'p-4 pr-12 pb-12' : 'p-4 pr-12',
113
+ ]"
114
+ placeholder="Type a message..."
115
+ @keydown.enter.exact.prevent="sendMessage"
116
+ />
105
117
  <div
106
- v-if="isModeMenuOpen"
107
- class="absolute 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"
118
+ v-if="agentStore.availableModes.length > 1"
119
+ ref="modeMenu"
120
+ class="absolute bottom-2 left-4"
108
121
  >
109
122
  <button
110
- v-for="mode in agentStore.availableModes"
111
- :key="mode.name"
112
- 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"
113
- :class="mode.name === agentStore.activeModeName ? 'bg-gray-100 dark:bg-gray-700' : ''"
123
+ aria-label="Select mode"
124
+ class="flex h-8 w-8 items-center justify-center rounded-md border border-gray-200 bg-white text-lightNavbarIcons transition-colors duration-200 hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-darkNavbarIcons dark:hover:bg-gray-700"
125
+ :class="isModeMenuOpen ? 'bg-gray-100 dark:bg-gray-700' : ''"
126
+ :disabled="agentStore.isResponseInProgress"
127
+ title="Select mode"
114
128
  type="button"
115
- @click="selectMode(mode.name)"
129
+ @click="toggleModeMenu"
116
130
  >
117
- {{ mode.name }}
131
+ <IconBrainOutline class="h-4 w-4" />
118
132
  </button>
133
+
134
+ <div
135
+ v-if="isModeMenuOpen"
136
+ class="absolute 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"
137
+ >
138
+ <button
139
+ v-for="mode in agentStore.availableModes"
140
+ :key="mode.name"
141
+ 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"
142
+ :class="mode.name === agentStore.activeModeName ? 'bg-gray-100 dark:bg-gray-700' : ''"
143
+ type="button"
144
+ @click="selectMode(mode.name)"
145
+ >
146
+ {{ mode.name }}
147
+ </button>
148
+ </div>
119
149
  </div>
150
+ <Button
151
+ class="absolute right-4 bottom-2 !p-0 h-[34px] w-[34px]"
152
+ @click="sendMessage"
153
+ :disabled="!agentStore.trimmedUserMessage || agentStore.isResponseInProgress"
154
+ >
155
+ <IconArrowUpOutline
156
+ class="w-8 h-8 p-1
157
+ text-white"
158
+ />
159
+ </Button>
120
160
  </div>
121
- <Button
122
- class="absolute right-4 bottom-2 !p-0 h-[34px] w-[34px]"
123
- @click="sendMessage"
124
- :disabled="!agentStore.trimmedUserMessage || agentStore.isResponseInProgress"
125
- >
126
- <IconArrowUpOutline
127
- class="w-8 h-8 p-1
128
- text-white"
129
- />
130
- </Button>
131
161
  </div>
132
162
  </div>
133
163
  </div>
134
- </div>
164
+ </Teleport>
135
165
 
136
166
  </template>
137
167
 
138
168
  <script setup lang="ts">
139
- import { IconChatBubbleLeft20Solid, IconSparklesSolid } from '@iconify-prerendered/vue-heroicons';
169
+ import { IconChatBubbleLeft20Solid, IconSparklesSolid, IconArrowsPointingOut, IconArrowsPointingIn } from '@iconify-prerendered/vue-heroicons';
140
170
  import { IconCloseOutline, IconBarsOutline, IconArrowUpOutline, IconCloseSidebarSolid, IconOpenSidebarSolid, IconBrainOutline } from '@iconify-prerendered/vue-flowbite';
141
- import { useTemplateRef, onMounted, ref } from 'vue';
171
+ import { useTemplateRef, onMounted, ref,computed } from 'vue';
142
172
  import { onClickOutside } from '@vueuse/core'
143
173
  import ConversationArea from './ConversationArea.vue';
144
- import { useAgentStore } from './useAgentStore';
174
+ import { useAgentStore } from './composables/useAgentStore';
175
+ import { useAgentTransitions } from './composables/useAgentTransitions';
145
176
  import { Button } from '@/afcl';
146
177
  import { useCoreStore } from '@/stores/core';
147
178
 
@@ -159,12 +190,10 @@ const chatSurface = useTemplateRef('chatSurface');
159
190
  const textInput = useTemplateRef('textInput');
160
191
  const modeMenu = useTemplateRef('modeMenu');
161
192
  const agentStore = useAgentStore();
193
+ const agentTransitions = useAgentTransitions();
162
194
  const coreStore = useCoreStore();
163
195
  const isModeMenuOpen = ref(false);
164
196
 
165
- const MAX_WIDTH = 800;
166
- const MIN_WIDTH = 382; //w-96
167
-
168
197
  let startX = 0
169
198
  let startWidth = 0
170
199
 
@@ -181,7 +210,8 @@ const startResize = (e: MouseEvent) => {
181
210
 
182
211
  const onResize = (e: MouseEvent) => {
183
212
  const dx = startX - e.clientX
184
- agentStore.setChatWidth(Math.min(Math.max(startWidth + dx, MIN_WIDTH), MAX_WIDTH))
213
+ agentStore.setChatWidth(Math.min(Math.max(startWidth + dx, agentStore.MIN_WIDTH), agentStore.MAX_WIDTH))
214
+ agentTransitions.setChatSurfaceTransition(true);
185
215
  }
186
216
 
187
217
  const stopResize = () => {
@@ -194,8 +224,8 @@ const stopResize = () => {
194
224
  const appRoot = document.getElementById('app');
195
225
  const header = document.getElementById('af-header-nav');
196
226
  if (appRoot && header) {
197
- appRoot.style.transition = 'padding-right 200ms ease-in-out';
198
- header.style.transition = 'padding-right 200ms ease-in-out';
227
+ agentTransitions.setAppRootTransition(false);
228
+ agentTransitions.setChatSurfaceTransition(false);
199
229
  }
200
230
  }
201
231
 
@@ -1,11 +1,5 @@
1
1
  <template>
2
- <button @click="scrollContainer.scrollToBottom()">
3
- <IconArrowDownOutline
4
- class="absolute bottom-32 right-4 bg-lightPrimary dark:bg-darkPrimary text-white p-2 w-10 h-10 rounded-full transition-opacity duration-100 ease-in"
5
- :class="showScrollToBottomButton ? 'opacity-100' : 'opacity-0 pointer-events-none'"
6
- :disabled="!showScrollToBottomButton"
7
- />
8
- </button>
2
+
9
3
  <SessionsHistory
10
4
  :class="agentStore.isSessionHistoryOpen ? 'translate-x-0' : '-translate-x-full'"
11
5
  />
@@ -18,12 +12,24 @@
18
12
  </div>
19
13
  <AutoScrollContainer
20
14
  :enabled="!showScrollToBottomButton"
21
- class="flex flex-col overflow-y-auto border-t border-gray-200 dark:border-gray-700"
15
+ class="relative flex flex-col overflow-y-auto"
22
16
  ref="scrollContainer"
23
17
  :threshold="10"
24
18
  behavior="smooth"
19
+ :class="agentStore.isFullScreen ? 'mx-auto' : ''"
20
+ :style="{
21
+ maxWidth: agentStore.isFullScreen ? agentStore.MAX_WIDTH+'px' : '100%',
22
+ transition: `max-width ${agentTransitions.TRANSITION_DURATION}ms ease-in-out`
23
+ }"
25
24
  >
26
25
 
26
+ <button @click="scrollContainer.scrollToBottom()">
27
+ <IconArrowDownOutline
28
+ class="fixed bottom-32 left-1/2 bg-lightPrimary dark:bg-darkPrimary text-white p-2 w-10 h-10 rounded-full transition-opacity duration-100 ease-in"
29
+ :class="showScrollToBottomButton ? 'opacity-100' : 'opacity-0 pointer-events-none'"
30
+ :disabled="!showScrollToBottomButton"
31
+ />
32
+ </button>
27
33
  <div
28
34
  v-for="message in props.messages" :key="message.id"
29
35
  class="flex flex-col w-full"
@@ -71,15 +77,16 @@ import type { IMessage, IPart } from './types';
71
77
  import { useTemplateRef, ref, defineAsyncComponent, onMounted, watch, computed } from 'vue';
72
78
  import { IconArrowDownOutline } from '@iconify-prerendered/vue-flowbite';
73
79
  import SessionsHistory from './SessionsHistory.vue';
74
- import { useAgentStore } from './useAgentStore';
75
- import ToolRenderer from './ToolRenderer.vue';
80
+ import { useAgentStore } from './composables/useAgentStore';
76
81
  import ToolsGroup from './ToolsGroup.vue';
82
+ import { useAgentTransitions } from './composables/useAgentTransitions';
77
83
 
78
84
  const scrollContainer = useTemplateRef('scrollContainer');
79
85
  const showScrollToBottomButton = ref(false);
80
86
  const innerScrollContainerRef = ref(null);
81
87
  const AutoScrollContainer = defineAsyncComponent(() => import('@incremark/vue').then(module => module.AutoScrollContainer))
82
88
  const agentStore = useAgentStore();
89
+ const agentTransitions = useAgentTransitions();
83
90
  const clicks = ref(0);
84
91
 
85
92
  function recalculateScroll() {
@@ -49,7 +49,7 @@ import { Button, Spinner } from '@/afcl'
49
49
  import { computed } from 'vue';
50
50
  import { IconPlusOutline } from '@iconify-prerendered/vue-flowbite';
51
51
  import type { ISessionsListItem } from './types';
52
- import { useAgentStore } from './useAgentStore';
52
+ import { useAgentStore } from './composables/useAgentStore';
53
53
 
54
54
  const agentStore = useAgentStore();
55
55
 
@@ -1,17 +1,23 @@
1
1
  import { defineStore } from 'pinia';
2
- import { IAgentSession, ISessionsListItem, IMessage } from './types';
2
+ import { IAgentSession, ISessionsListItem, IMessage } from '../types';
3
3
  import { ref, nextTick, computed, watch, onMounted, shallowRef } from 'vue';
4
4
  import { callAdminForthApi } from '@/utils';
5
5
  import { useAdminforth } from '@/adminforth';
6
- import { Chat } from './chat';
7
- import { cosineSimilarity, DefaultChatTransport } from 'ai';
6
+ import { Chat } from '../chat';
7
+ import { DefaultChatTransport } from 'ai';
8
8
  import { useCoreStore } from '@/stores/core';
9
+ import { useAgentTransitions } from './useAgentTransitions';
9
10
 
10
11
  type AgentMode = {
11
12
  name: string;
12
13
  };
13
14
 
14
15
  export const useAgentStore = defineStore('agent', () => {
16
+ const DEFAULT_CHAT_WIDTH = 600;
17
+ const MAX_WIDTH = 800;
18
+ const MIN_WIDTH = 382; //w-96
19
+ const agentTransitions = useAgentTransitions();
20
+
15
21
  const activeSessionId = ref<string | null>(null);
16
22
  const currentSession = ref<IAgentSession | null>(null);
17
23
  const sessionList = ref<ISessionsListItem[]>([]);
@@ -31,7 +37,7 @@ export const useAgentStore = defineStore('agent', () => {
31
37
  const appRoot = ref<HTMLElement | null>(null);
32
38
  const header = ref<HTMLElement | null>(null);
33
39
  const lastSessionId = ref<string | null>(null);
34
- const chatWidth = ref(600);
40
+ const chatWidth = ref(DEFAULT_CHAT_WIDTH);
35
41
  const availableModes = ref<AgentMode[]>([]);
36
42
  const activeModeName = ref<string | null>(null);
37
43
  function setLocalStorageItem(key: string, value: string) {
@@ -55,7 +61,12 @@ export const useAgentStore = defineStore('agent', () => {
55
61
  }
56
62
  })
57
63
  onMounted(() => {
58
- chatWidth.value = parseInt(getLocalStorageItem('chatWidth') || '600', 10);
64
+ const chatWidthBeforeFullScreen = parseInt(getLocalStorageItem('chatWidthBeforeFullScreen') || '0', 10);
65
+ if (chatWidthBeforeFullScreen) {
66
+ chatWidth.value = chatWidthBeforeFullScreen;
67
+ } else {
68
+ chatWidth.value = parseInt(getLocalStorageItem('chatWidth') || DEFAULT_CHAT_WIDTH.toString(), 10);
69
+ }
59
70
  isTeleportedToBody.value = getLocalStorageItem('isTeleportedToBody') === 'true';
60
71
  lastSessionId.value = getLocalStorageItem('lastSessionId');
61
72
  if (lastSessionId.value && lastSessionId.value !== 'pre-session') {
@@ -71,29 +82,33 @@ export const useAgentStore = defineStore('agent', () => {
71
82
  header.value = document.getElementById('af-header-nav');
72
83
  if (appRoot.value && header.value) {
73
84
  nextTick(() => {
74
- appRoot.value.style.transition = 'padding-right 200ms ease-in-out';
75
- header.value.style.transition = 'padding-right 200ms ease-in-out';
85
+ agentTransitions.setAppRootTransition(false);
76
86
  });
77
87
  }
78
88
  })
89
+
79
90
  const isFullScreen = ref(false);
80
91
  function setFullScreen(fullScreen: boolean) {
81
92
  isFullScreen.value = fullScreen;
82
- console.log('setFullScreen', fullScreen);
83
93
  if (fullScreen) {
84
- setChatWidth(window.innerWidth, true);
94
+ setLocalStorageItem('chatWidthBeforeFullScreen', chatWidth.value.toString());
95
+ setLocalStorageItem('isTeleportedToBodyBeforeFullScreen', isTeleportedToBody.value ? 'true' : 'false');
96
+ setIsTeleportedToBody(false);
97
+ useAgentTransitions().setChatSurfaceTransition(false);
98
+ setChatWidth(window.innerWidth, false);
85
99
  } else {
86
- console.log('setChatWidth to default');
87
- chatWidth.value = 600;
100
+ const lastChatWidth = parseInt(getLocalStorageItem('chatWidthBeforeFullScreen') || DEFAULT_CHAT_WIDTH.toString(), 10);
101
+ const isTeleportedBeforeFullScreen = getLocalStorageItem('isTeleportedToBodyBeforeFullScreen') === 'true';
102
+ agentTransitions.setAppRootTransition(true);
103
+ setIsTeleportedToBody(isTeleportedBeforeFullScreen);
104
+ setChatWidth(lastChatWidth, false);
105
+ setTimeout(() => agentTransitions.setAppRootTransition(false), agentTransitions.TRANSITION_DURATION);
88
106
  }
89
107
  }
90
108
 
91
- function setChatWidth(width: number, blockTransition = false) {
92
- if (appRoot.value && header.value) {
93
- if (blockTransition) {
94
- appRoot.value.style.transition = '';
95
- header.value.style.transition = '';
96
- }
109
+ function setChatWidth(width: number, blockTransition = true) {
110
+ if (blockTransition) {
111
+ agentTransitions.setAppRootTransition(true);
97
112
  }
98
113
  chatWidth.value = width;
99
114
 
@@ -414,5 +429,8 @@ export const useAgentStore = defineStore('agent', () => {
414
429
  activeModeName,
415
430
  setAvailableModes,
416
431
  setActiveMode,
432
+ DEFAULT_CHAT_WIDTH,
433
+ MAX_WIDTH,
434
+ MIN_WIDTH,
417
435
  }
418
436
  })
@@ -0,0 +1,58 @@
1
+ import { defineStore } from 'pinia';
2
+ import { ref, nextTick,onMounted } from 'vue';
3
+ import { useAgentStore } from './useAgentStore';
4
+
5
+
6
+ export const useAgentTransitions = defineStore('agentTransitions', () => {
7
+ const TRANSITION_DURATION = 200;
8
+
9
+ const agentStore = useAgentStore();
10
+ const appRoot = ref<HTMLElement | null>(null);
11
+ const header = ref<HTMLElement | null>(null);
12
+
13
+ const chatRoot = ref<HTMLElement | null>(null);
14
+
15
+ onMounted(() => {
16
+ appRoot.value = document.getElementById('app');
17
+ header.value = document.getElementById('af-header-nav');
18
+ chatRoot.value = document.getElementById('adminforth-agent-chat-surface');
19
+ if (appRoot.value && header.value) {
20
+ nextTick(() => {
21
+ setAppRootTransition(false);
22
+ });
23
+ }
24
+ if (chatRoot.value) {
25
+ nextTick(() => {
26
+ setChatSurfaceTransition(false);
27
+ });
28
+ }
29
+ });
30
+ function setAppRootTransition(blockTransition = true) {
31
+ if (appRoot.value && header.value) {
32
+ if (blockTransition) {
33
+ appRoot.value.style.transition = '';
34
+ header.value.style.transition = '';
35
+ } else {
36
+ appRoot.value.style.transition = `padding-right ${TRANSITION_DURATION}ms ease-in-out`;
37
+ header.value.style.transition = `padding-right ${TRANSITION_DURATION}ms ease-in-out`;
38
+ }
39
+ }
40
+ }
41
+ function setChatSurfaceTransition(blockTransition = true) {
42
+ if (chatRoot.value) {
43
+ if (blockTransition) {
44
+ chatRoot.value.style.transition = '';
45
+ } else {
46
+ chatRoot.value.style.transition = `
47
+ transform ${TRANSITION_DURATION}ms ease-in-out,
48
+ width ${TRANSITION_DURATION}ms ease-in-out
49
+ `;
50
+ }
51
+ }
52
+ }
53
+ return {
54
+ TRANSITION_DURATION,
55
+ setAppRootTransition,
56
+ setChatSurfaceTransition
57
+ }
58
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",