1
1
<script setup lang="ts">
2
- import { ref , reactive , onBeforeMount , watch } from ' vue' ;
2
+ import { ref , reactive , onBeforeMount , watch , computed } from ' vue' ;
3
3
import { useI18n } from ' vue-i18n' ;
4
4
import ChatInput from ' ./ChatInput.vue' ;
5
- import ChatMessageList from ' ./ChatMessageList.vue' ;
6
5
import useRequest from ' @/services/request' ;
7
6
import { getRequestBaseUrl } from ' @/utils' ;
8
7
import { debounce } from ' lodash' ;
9
8
10
9
const { t } = useI18n ();
11
10
const { get } = useRequest ();
12
11
12
+ // Add current conversation ref
13
+ const currentConversation = ref <ChatConversation | null >(null );
14
+
15
+ // Add computed property for current conversation title
16
+ const currentConversationTitle = computed (() => {
17
+ if (! currentConversationId .value ) return t (' components.ai.chatbot.newChat' );
18
+ return currentConversation .value ?.title || t (' components.ai.chatbot.newChat' );
19
+ });
20
+
13
21
defineProps <{
14
22
visible: boolean ;
15
23
}>();
16
24
17
25
const emit = defineEmits <{
18
- (e : ' toggle ' ): void ;
26
+ (e : ' close ' ): void ;
19
27
}>();
20
28
21
29
const chatbotConfig = ref <ChatbotConfig >({
@@ -43,7 +51,9 @@ const isLoadingMessages = ref(false);
43
51
const historyDialogVisible = ref (false );
44
52
45
53
// Add ref for message list component
46
- const messageListRef = ref <{ scrollToBottom: () => Promise <void > } | null >(null );
54
+ const messageListRef = ref <{ scrollToBottom: () => Promise <void > } | null >(
55
+ null
56
+ );
47
57
48
58
// Load conversations
49
59
const loadConversations = async () => {
@@ -116,23 +126,38 @@ const createNewConversation = () => {
116
126
focusChatInput ();
117
127
};
118
128
119
- // Watch for conversation updates
120
- watch (
121
- () => currentConversationId .value ,
122
- async newId => {
123
- // Save current conversation ID to localStorage
124
- if (newId ) {
125
- localStorage .setItem (' currentConversationId' , newId );
126
- } else {
127
- localStorage .removeItem (' currentConversationId' );
128
- }
129
+ // Load current conversation details
130
+ const loadCurrentConversation = async (conversationId : string ) => {
131
+ if (! conversationId ) {
132
+ currentConversation .value = null ;
133
+ return ;
134
+ }
135
+ try {
136
+ const res = await get (` /ai/chat/conversations/${conversationId } ` );
137
+ currentConversation .value = res .data ;
138
+ } catch (error ) {
139
+ console .error (' Failed to load conversation details:' , error );
140
+ currentConversation .value = null ;
141
+ }
142
+ };
129
143
130
- if (newId && ! selectedConversationId .value ) {
131
- await loadConversations ();
132
- selectedConversationId .value = newId ;
133
- }
144
+ // Watch for conversation ID changes to load details
145
+ watch (currentConversationId , async newId => {
146
+ // Save current conversation ID to localStorage
147
+ if (newId ) {
148
+ localStorage .setItem (' currentConversationId' , newId );
149
+ await loadCurrentConversation (newId );
150
+ } else {
151
+ localStorage .removeItem (' currentConversationId' );
152
+ currentConversation .value = null ;
134
153
}
135
- );
154
+
155
+ // Update selected conversation ID if needed
156
+ if (newId && ! selectedConversationId .value ) {
157
+ await loadConversations ();
158
+ selectedConversationId .value = newId ;
159
+ }
160
+ });
136
161
137
162
// Initialize
138
163
onBeforeMount (async () => {
@@ -144,6 +169,7 @@ onBeforeMount(async () => {
144
169
const savedConversationId = localStorage .getItem (' currentConversationId' );
145
170
if (savedConversationId ) {
146
171
await loadConversationMessages (savedConversationId );
172
+ await loadCurrentConversation (savedConversationId );
147
173
selectedConversationId .value = savedConversationId ;
148
174
}
149
175
});
@@ -157,7 +183,10 @@ const extractErrorMessage = (errorData: string): string => {
157
183
// Try to parse the error data as JSON
158
184
const parsed = JSON .parse (errorData );
159
185
if (parsed .error_detail ?.message ) return parsed .error_detail .message ;
160
- if (parsed .error ) return typeof parsed .error === ' string' ? parsed .error : JSON .stringify (parsed .error );
186
+ if (parsed .error )
187
+ return typeof parsed .error === ' string'
188
+ ? parsed .error
189
+ : JSON .stringify (parsed .error );
161
190
if (parsed .text ?.startsWith (' Error:' )) return parsed .text ;
162
191
if (typeof parsed === ' object' ) return JSON .stringify (parsed , null , 2 );
163
192
return errorData ;
@@ -176,7 +205,10 @@ const loadLLMProviders = debounce(async () => {
176
205
if (availableProviders .value .length > 0 ) {
177
206
chatbotConfig .value .provider = availableProviders .value [0 ].key ! ;
178
207
chatbotConfig .value .model = availableProviders .value [0 ].models ! [0 ];
179
- localStorage .setItem (' chatbotConfig' , JSON .stringify (chatbotConfig .value ));
208
+ localStorage .setItem (
209
+ ' chatbotConfig' ,
210
+ JSON .stringify (chatbotConfig .value )
211
+ );
180
212
}
181
213
}
182
214
} catch (error ) {
@@ -235,9 +267,10 @@ const sendMessage = async (message: string) => {
235
267
if (error instanceof DOMException && error .name === ' AbortError' ) {
236
268
chatHistory .splice (responseIndex , 1 );
237
269
} else {
238
- const errorMessage = error instanceof Error
239
- ? extractErrorMessage (error .message )
240
- : ' An error occurred while sending your message' ;
270
+ const errorMessage =
271
+ error instanceof Error
272
+ ? extractErrorMessage (error .message )
273
+ : ' An error occurred while sending your message' ;
241
274
242
275
streamError .value = errorMessage ;
243
276
@@ -274,10 +307,13 @@ const sendStreamingRequest = async (
274
307
responseIndex : number
275
308
): Promise <void > => {
276
309
return new Promise <void >((resolve , reject ) => {
277
- const { provider, model, systemPrompt, temperature, maxTokens } = chatbotConfig .value ;
310
+ const { provider, model, systemPrompt, temperature, maxTokens } =
311
+ chatbotConfig .value ;
278
312
279
313
if (! provider || ! model ) {
280
- reject (new Error (' Please select a provider and model before sending a message' ));
314
+ reject (
315
+ new Error (' Please select a provider and model before sending a message' )
316
+ );
281
317
return ;
282
318
}
283
319
@@ -352,7 +388,8 @@ const sendStreamingRequest = async (
352
388
fullResponse += chunk .content ;
353
389
if (chatHistory [responseIndex ]) {
354
390
chatHistory [responseIndex ].content = fullResponse ;
355
- chatHistory [responseIndex ].conversationId = chunk .conversation_id ;
391
+ chatHistory [responseIndex ].conversationId =
392
+ chunk .conversation_id ;
356
393
// Scroll to bottom during streaming
357
394
messageListRef .value ?.scrollToBottom ();
358
395
}
@@ -431,61 +468,50 @@ defineOptions({ name: 'ClChatConsole' });
431
468
<template >
432
469
<div class =" chat-console" >
433
470
<div class =" console-header" >
434
- <div class =" left-content" >
435
- <el-button
436
- v-if =" visible"
437
- type =" primary"
438
- @click =" emit('toggle')"
439
- class =" chat-toggle-btn is-active"
440
- >
441
- <cl-icon :icon =" ['fa', 'comment-dots']" />
442
- <span class =" button-text" >{{
443
- t('components.ai.chatbot.button')
444
- }}</span >
445
- <cl-icon :icon =" ['fa', 'angles-right']" class =" toggle-indicator" />
446
- </el-button >
447
- <h3 v-else >{{ t('components.ai.chatbot.title') }}</h3 >
448
- </div >
449
- <div class =" right-content" >
450
- <el-tooltip :content =" t('components.ai.chatbot.new')" >
451
- <el-button type =" text" @click =" createNewConversation" class =" new-btn" >
452
- <cl-icon :icon =" ['fas', 'plus']" />
453
- </el-button >
454
- </el-tooltip >
455
- <el-popover
456
- v-model:visible =" historyDialogVisible"
457
- trigger =" click"
458
- :show-arrow =" false"
459
- placement =" bottom-end"
460
- width =" 320"
461
- >
462
- <template #reference >
463
- <div class =" history-btn-wrapper" >
464
- <el-tooltip :content =" t('components.ai.chatbot.history')" >
465
- <el-button
466
- type =" text"
467
- @click.prevent =" openHistory"
468
- class =" history-btn"
469
- >
470
- <cl-icon :icon =" ['fas', 'history']" />
471
- </el-button >
472
- </el-tooltip >
473
- </div >
474
- </template >
475
- <cl-chat-history
476
- :conversations =" conversations"
477
- :selected-conversation-id =" selectedConversationId"
478
- :is-loading =" isLoadingConversations"
479
- @select =" selectConversation"
480
- @close =" historyDialogVisible = false"
481
- />
482
- </el-popover >
483
- <el-tooltip :content =" t('components.ai.chatbot.config.title')" >
484
- <el-button type =" text" @click =" openConfig" class =" config-btn" >
485
- <cl-icon :icon =" ['fas', 'cog']" />
486
- </el-button >
487
- </el-tooltip >
471
+ <span v-if =" visible" class =" chat-toggle-btn" @click =" emit('close')" >
472
+ <cl-icon :icon =" ['fa', 'angles-right']" class =" toggle-indicator" />
473
+ </span >
474
+ <div class =" chat-conversation-title" :title =" currentConversationTitle" >
475
+ {{ currentConversationTitle }}
488
476
</div >
477
+ <el-tooltip :content =" t('components.ai.chatbot.new')" >
478
+ <el-button type =" text" @click =" createNewConversation" class =" new-btn" >
479
+ <cl-icon :icon =" ['fas', 'plus']" />
480
+ </el-button >
481
+ </el-tooltip >
482
+ <el-popover
483
+ v-model:visible =" historyDialogVisible"
484
+ trigger =" click"
485
+ :show-arrow =" false"
486
+ placement =" bottom-end"
487
+ width =" 320"
488
+ >
489
+ <template #reference >
490
+ <div class =" history-btn-wrapper" >
491
+ <el-tooltip :content =" t('components.ai.chatbot.history')" >
492
+ <el-button
493
+ type =" text"
494
+ @click.prevent =" openHistory"
495
+ class =" history-btn"
496
+ >
497
+ <cl-icon :icon =" ['fas', 'history']" />
498
+ </el-button >
499
+ </el-tooltip >
500
+ </div >
501
+ </template >
502
+ <cl-chat-history
503
+ :conversations =" conversations"
504
+ :selected-conversation-id =" selectedConversationId"
505
+ :is-loading =" isLoadingConversations"
506
+ @select =" selectConversation"
507
+ @close =" historyDialogVisible = false"
508
+ />
509
+ </el-popover >
510
+ <el-tooltip :content =" t('components.ai.chatbot.config.title')" >
511
+ <el-button type =" text" @click =" openConfig" class =" config-btn" >
512
+ <cl-icon :icon =" ['fas', 'cog']" />
513
+ </el-button >
514
+ </el-tooltip >
489
515
</div >
490
516
491
517
<div class =" chat-container" >
@@ -532,25 +558,11 @@ defineOptions({ name: 'ClChatConsole' });
532
558
justify-content : space-between ;
533
559
align-items : center ;
534
560
padding : 16px ;
561
+ gap : 8px ;
535
562
border-bottom : 1px solid var (--el-border-color-light );
536
563
background-color : var (--el-color-white );
537
564
}
538
565
539
- .left-content {
540
- display : flex ;
541
- align-items : center ;
542
- }
543
-
544
- .console-header h3 {
545
- margin : 0 ;
546
- }
547
-
548
- .right-content {
549
- display : flex ;
550
- align-items : center ;
551
- gap : 8px ;
552
- }
553
-
554
566
.chat-container {
555
567
display : flex ;
556
568
flex : 1 ;
@@ -567,9 +579,17 @@ defineOptions({ name: 'ClChatConsole' });
567
579
.chat-toggle-btn {
568
580
display : flex ;
569
581
align-items : center ;
570
- border-radius : 20px ;
571
- padding : 8px 16px ;
582
+ padding : 8px ;
572
583
animation : fadeIn 0.3s ease-in-out ;
584
+ color : var (--el-color-primary-dark-2 );
585
+ cursor : pointer ;
586
+ border-radius : 20px ;
587
+ transition : all 0.3s ;
588
+
589
+ &:hover {
590
+ color : white ;
591
+ background-color : var (--el-color-primary-dark-2 );
592
+ }
573
593
}
574
594
575
595
.chat-toggle-btn .button-text {
@@ -590,6 +610,15 @@ defineOptions({ name: 'ClChatConsole' });
590
610
transform : rotate (180deg );
591
611
}
592
612
613
+ .chat-conversation-title {
614
+ font-size : 14px ;
615
+ color : var (--el-text-color-regular );
616
+ text-overflow : ellipsis ;
617
+ overflow : hidden ;
618
+ white-space : nowrap ;
619
+ width : 100% ;
620
+ }
621
+
593
622
@keyframes fadeIn {
594
623
from {
595
624
opacity : 0 ;
0 commit comments