Skip to content

Commit cdb9613

Browse files
author
Marvin Zhang
committed
feat: enhance chat console and sidebar functionality
- Added current conversation management with computed title display in ChatConsole - Refactored event emission from 'toggle' to 'close' for better clarity in ChatConsole and ChatSidebar - Improved conversation loading logic and localStorage handling for current conversation ID - Cleaned up HTML structure and CSS styles for better readability and user experience - Updated event handling in NormalLayout to remove unnecessary toggle functionality
1 parent d2b44d2 commit cdb9613

File tree

4 files changed

+135
-109
lines changed

4 files changed

+135
-109
lines changed

src/components/ui/chat/ChatConsole.vue

Lines changed: 127 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
11
<script setup lang="ts">
2-
import { ref, reactive, onBeforeMount, watch } from 'vue';
2+
import { ref, reactive, onBeforeMount, watch, computed } from 'vue';
33
import { useI18n } from 'vue-i18n';
44
import ChatInput from './ChatInput.vue';
5-
import ChatMessageList from './ChatMessageList.vue';
65
import useRequest from '@/services/request';
76
import { getRequestBaseUrl } from '@/utils';
87
import { debounce } from 'lodash';
98
109
const { t } = useI18n();
1110
const { get } = useRequest();
1211
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+
1321
defineProps<{
1422
visible: boolean;
1523
}>();
1624
1725
const emit = defineEmits<{
18-
(e: 'toggle'): void;
26+
(e: 'close'): void;
1927
}>();
2028
2129
const chatbotConfig = ref<ChatbotConfig>({
@@ -43,7 +51,9 @@ const isLoadingMessages = ref(false);
4351
const historyDialogVisible = ref(false);
4452
4553
// 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+
);
4757
4858
// Load conversations
4959
const loadConversations = async () => {
@@ -116,23 +126,38 @@ const createNewConversation = () => {
116126
focusChatInput();
117127
};
118128
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+
};
129143
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;
134153
}
135-
);
154+
155+
// Update selected conversation ID if needed
156+
if (newId && !selectedConversationId.value) {
157+
await loadConversations();
158+
selectedConversationId.value = newId;
159+
}
160+
});
136161
137162
// Initialize
138163
onBeforeMount(async () => {
@@ -144,6 +169,7 @@ onBeforeMount(async () => {
144169
const savedConversationId = localStorage.getItem('currentConversationId');
145170
if (savedConversationId) {
146171
await loadConversationMessages(savedConversationId);
172+
await loadCurrentConversation(savedConversationId);
147173
selectedConversationId.value = savedConversationId;
148174
}
149175
});
@@ -157,7 +183,10 @@ const extractErrorMessage = (errorData: string): string => {
157183
// Try to parse the error data as JSON
158184
const parsed = JSON.parse(errorData);
159185
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);
161190
if (parsed.text?.startsWith('Error:')) return parsed.text;
162191
if (typeof parsed === 'object') return JSON.stringify(parsed, null, 2);
163192
return errorData;
@@ -176,7 +205,10 @@ const loadLLMProviders = debounce(async () => {
176205
if (availableProviders.value.length > 0) {
177206
chatbotConfig.value.provider = availableProviders.value[0].key!;
178207
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+
);
180212
}
181213
}
182214
} catch (error) {
@@ -235,9 +267,10 @@ const sendMessage = async (message: string) => {
235267
if (error instanceof DOMException && error.name === 'AbortError') {
236268
chatHistory.splice(responseIndex, 1);
237269
} 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';
241274
242275
streamError.value = errorMessage;
243276
@@ -274,10 +307,13 @@ const sendStreamingRequest = async (
274307
responseIndex: number
275308
): Promise<void> => {
276309
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;
278312
279313
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+
);
281317
return;
282318
}
283319
@@ -352,7 +388,8 @@ const sendStreamingRequest = async (
352388
fullResponse += chunk.content;
353389
if (chatHistory[responseIndex]) {
354390
chatHistory[responseIndex].content = fullResponse;
355-
chatHistory[responseIndex].conversationId = chunk.conversation_id;
391+
chatHistory[responseIndex].conversationId =
392+
chunk.conversation_id;
356393
// Scroll to bottom during streaming
357394
messageListRef.value?.scrollToBottom();
358395
}
@@ -431,61 +468,50 @@ defineOptions({ name: 'ClChatConsole' });
431468
<template>
432469
<div class="chat-console">
433470
<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 }}
488476
</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>
489515
</div>
490516

491517
<div class="chat-container">
@@ -532,25 +558,11 @@ defineOptions({ name: 'ClChatConsole' });
532558
justify-content: space-between;
533559
align-items: center;
534560
padding: 16px;
561+
gap: 8px;
535562
border-bottom: 1px solid var(--el-border-color-light);
536563
background-color: var(--el-color-white);
537564
}
538565
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-
554566
.chat-container {
555567
display: flex;
556568
flex: 1;
@@ -567,9 +579,17 @@ defineOptions({ name: 'ClChatConsole' });
567579
.chat-toggle-btn {
568580
display: flex;
569581
align-items: center;
570-
border-radius: 20px;
571-
padding: 8px 16px;
582+
padding: 8px;
572583
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+
}
573593
}
574594
575595
.chat-toggle-btn .button-text {
@@ -590,6 +610,15 @@ defineOptions({ name: 'ClChatConsole' });
590610
transform: rotate(180deg);
591611
}
592612
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+
593622
@keyframes fadeIn {
594623
from {
595624
opacity: 0;

src/components/ui/chat/ChatSidebar.vue

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const props = defineProps<{
1515
}>();
1616
1717
const emit = defineEmits<{
18-
(e: 'toggle'): void;
18+
(e: 'close'): void;
1919
(e: 'resize', width: number): void;
2020
(e: 'resize-start'): void;
2121
(e: 'resize-end'): void;
@@ -113,10 +113,14 @@ defineOptions({ name: 'ClChatSidebar' });
113113
<div
114114
class="chat-sidebar"
115115
:class="{ visible, resizing: isResizing }"
116-
:style="visible ? { width: `${sidebarWidth}px`, right: 0 } : {}"
116+
:style="
117+
visible
118+
? { width: `${sidebarWidth}px`, right: 0 }
119+
: { width: `${sidebarWidth}px`, right: `-${sidebarWidth}px` }
120+
"
117121
>
118122
<div class="resize-handle" @mousedown="onResizeStart"></div>
119-
<cl-chat-console :visible="visible" @toggle="() => emit('toggle')" />
123+
<cl-chat-console :visible="visible" @close="emit('close')" />
120124
</div>
121125
</template>
122126

src/layouts/NormalLayout.vue

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@ const closeChatSidebar = () => {
1414
store.commit('layout/setChatbotSidebarVisible', false);
1515
};
1616
17-
const toggleChatSidebar = () => {
18-
store.commit('layout/setChatbotSidebarVisible', !chatSidebarVisible.value);
19-
};
20-
2117
const resizeChatSidebar = (width: number) => {
2218
store.commit('layout/setChatbotSidebarWidth', width);
2319
};
@@ -45,7 +41,6 @@ defineOptions({ name: 'ClNormalLayout' });
4541
<div
4642
:class="[
4743
sidebarCollapsed ? 'collapsed' : '',
48-
chatSidebarVisible ? 'chat-visible' : '',
4944
chatSidebarResizing ? 'chat-resizing' : '',
5045
]"
5146
class="main-content"
@@ -61,7 +56,6 @@ defineOptions({ name: 'ClNormalLayout' });
6156
:visible="chatSidebarVisible"
6257
:default-width="chatSidebarWidth"
6358
@close="closeChatSidebar"
64-
@toggle="toggleChatSidebar"
6559
@resize="resizeChatSidebar"
6660
@resize-start="chatSidebarResizing = true"
6761
@resize-end="chatSidebarResizing = false"

0 commit comments

Comments
 (0)