Skip to content

Commit 66d6b7c

Browse files
author
Marvin Zhang
committed
feat: enhance chat console and input functionality
- Refactored conversation management by replacing selectedConversationId with currentConversationId for consistency in ChatConsole - Implemented debouncing for loadConversationMessages and loadCurrentConversation methods to optimize performance - Added a new button in ChatInput for adding models when no providers are available, enhancing user experience - Updated internationalization files to include new labels and tooltips for the add model feature - Improved error handling in loadCurrentConversation to manage 404 responses effectively
1 parent e375475 commit 66d6b7c

File tree

7 files changed

+83
-20
lines changed

7 files changed

+83
-20
lines changed

src/components/ui/chat/ChatConsole.vue

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import useRequest from '@/services/request';
66
import { getRequestBaseUrl } from '@/utils';
77
import { debounce } from 'lodash';
88
import { ElMessage } from 'element-plus';
9-
9+
import { AxiosError } from 'axios';
10+
import { useRouter } from 'vue-router';
1011
const { t } = useI18n();
1112
const { get } = useRequest();
1213
14+
const router = useRouter();
15+
1316
// Add current conversation ref
1417
const currentConversation = ref<ChatConversation | null>(null);
1518
@@ -43,7 +46,6 @@ const streamError = ref('');
4346
4447
// Add conversation management
4548
const conversations = ref<ChatConversation[]>([]);
46-
const selectedConversationId = ref<string>('');
4749
const currentConversationId = ref<string>('');
4850
const isLoadingConversations = ref(false);
4951
const isLoadingMessages = ref(false);
@@ -74,7 +76,7 @@ const loadConversations = async () => {
7476
};
7577
7678
// Load messages for a conversation
77-
const loadConversationMessages = async (conversationId: string) => {
79+
const loadConversationMessages = debounce(async (conversationId: string) => {
7880
if (!conversationId) return;
7981
8082
isLoadingMessages.value = true;
@@ -92,34 +94,32 @@ const loadConversationMessages = async (conversationId: string) => {
9294
(a: ChatMessageType, b: ChatMessageType) =>
9395
a.timestamp.getTime() - b.timestamp.getTime()
9496
);
95-
console.debug(messages[messages.length - 1].content);
9697
9798
chatHistory.splice(0, chatHistory.length, ...messages);
9899
currentConversationId.value = conversationId;
99100
} catch (error) {
100101
console.error('Failed to load conversation messages:', error);
101-
streamError.value =
102+
const errorMessage =
102103
error instanceof Error ? error.message : 'Failed to load messages';
103104
} finally {
104105
isLoadingMessages.value = false;
105106
// Scroll to bottom after loading messages
106107
messageListRef.value?.scrollToBottom();
107108
}
108-
};
109+
});
109110
110111
// Select conversation
111112
const selectConversation = async (conversationId: string) => {
112-
if (selectedConversationId.value === conversationId) return;
113+
if (currentConversationId.value === conversationId) return;
113114
114-
selectedConversationId.value = conversationId;
115+
currentConversationId.value = conversationId;
115116
streamError.value = '';
116117
await loadConversationMessages(conversationId);
117118
messageListRef.value?.scrollToBottom();
118119
};
119120
120121
// Create new conversation
121122
const createNewConversation = () => {
122-
selectedConversationId.value = '';
123123
currentConversationId.value = '';
124124
localStorage.removeItem('currentConversationId');
125125
streamError.value = '';
@@ -128,7 +128,7 @@ const createNewConversation = () => {
128128
};
129129
130130
// Load current conversation details
131-
const loadCurrentConversation = async (conversationId: string) => {
131+
const loadCurrentConversation = debounce(async (conversationId: string) => {
132132
if (!conversationId) {
133133
currentConversation.value = null;
134134
return;
@@ -137,10 +137,14 @@ const loadCurrentConversation = async (conversationId: string) => {
137137
const res = await get(`/ai/chat/conversations/${conversationId}`);
138138
currentConversation.value = res.data;
139139
} catch (error) {
140+
if ((error as AxiosError)?.response?.status === 404) {
141+
currentConversationId.value = '';
142+
return;
143+
}
140144
console.error('Failed to load conversation details:', error);
141145
currentConversation.value = null;
142146
}
143-
};
147+
});
144148
145149
// Watch for conversation ID changes to load details
146150
watch(currentConversationId, async newId => {
@@ -154,9 +158,9 @@ watch(currentConversationId, async newId => {
154158
}
155159
156160
// Update selected conversation ID if needed
157-
if (newId && !selectedConversationId.value) {
161+
if (newId && !currentConversationId.value) {
158162
await loadConversations();
159-
selectedConversationId.value = newId;
163+
currentConversationId.value = newId;
160164
}
161165
});
162166
@@ -171,7 +175,7 @@ onBeforeMount(async () => {
171175
if (savedConversationId) {
172176
await loadConversationMessages(savedConversationId);
173177
await loadCurrentConversation(savedConversationId);
174-
selectedConversationId.value = savedConversationId;
178+
currentConversationId.value = savedConversationId;
175179
}
176180
});
177181
@@ -202,6 +206,11 @@ const loadLLMProviders = debounce(async () => {
202206
const res = await get('/ai/llm/providers', { available: true });
203207
availableProviders.value = res.data || [];
204208
209+
if (!availableProviders.value.length) {
210+
// Reset provider and model if no providers are available
211+
resetChatbotConfig();
212+
}
213+
205214
if (!chatbotConfig.value.provider || !chatbotConfig.value.model) {
206215
if (availableProviders.value.length > 0) {
207216
chatbotConfig.value.provider = availableProviders.value[0].key!;
@@ -238,6 +247,12 @@ const saveChatbotConfig = (config: ChatbotConfig) => {
238247
ElMessage.success(t('common.message.success.save'));
239248
};
240249
250+
// Reset chatbot configuration
251+
const resetChatbotConfig = () => {
252+
chatbotConfig.value = {};
253+
localStorage.removeItem('chatbotConfig');
254+
};
255+
241256
// Initialize chat history
242257
const chatHistory = reactive<ChatMessageType[]>([]);
243258
@@ -453,6 +468,10 @@ const selectProviderModel = ({
453468
localStorage.setItem('chatbotConfig', JSON.stringify(chatbotConfig.value));
454469
};
455470
471+
const addProviderModel = () => {
472+
router.push('/system/ai');
473+
};
474+
456475
// Configuration dialog
457476
const configDialogVisible = ref(false);
458477
@@ -510,7 +529,7 @@ defineOptions({ name: 'ClChatConsole' });
510529
</template>
511530
<cl-chat-history
512531
:conversations="conversations"
513-
:selected-conversation-id="selectedConversationId"
532+
:selected-conversation-id="currentConversationId"
514533
:is-loading="isLoadingConversations"
515534
@select="selectConversation"
516535
@close="historyDialogVisible = false"
@@ -541,6 +560,7 @@ defineOptions({ name: 'ClChatConsole' });
541560
@send="sendMessage"
542561
@cancel="cancelMessage"
543562
@model-change="selectProviderModel"
563+
@add-model="addProviderModel"
544564
/>
545565
</div>
546566
</div>

src/components/ui/chat/ChatInput.vue

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { ref, nextTick, onMounted, watch, computed, onUnmounted } from 'vue';
33
import { useI18n } from 'vue-i18n';
44
import { getLLMProviderIcon, getLLMProviderName } from '@/utils/ai';
5+
import ClLabelButton from '@/components/ui/button/LabelButton.vue';
56
67
// Add TypeScript interface for tree node
78
interface TreeNode {
@@ -26,6 +27,7 @@ const emit = defineEmits<{
2627
(e: 'send', message: string): void;
2728
(e: 'model-change', value: { provider: string; model: string }): void;
2829
(e: 'cancel'): void;
30+
(e: 'add-model'): void;
2931
}>();
3032
3133
const selectedProviderModel = ref<string>();
@@ -143,6 +145,14 @@ const focus = () => {
143145
textareaRef.value?.focus();
144146
};
145147
148+
const inputBoxDisabled = computed(() => {
149+
return props.isLoading || props.providers?.length === 0;
150+
});
151+
152+
const hasProviders = computed(() => {
153+
return !!props.providers?.length;
154+
});
155+
146156
// Expose focus method
147157
defineExpose({ focus });
148158
@@ -151,22 +161,22 @@ defineOptions({ name: 'ClChatInput' });
151161

152162
<template>
153163
<div class="chat-input">
154-
<div class="input-container">
164+
<div class="input-container" :class="{ disabled: inputBoxDisabled }">
155165
<textarea
156166
ref="textareaRef"
157167
v-model="userInput"
158168
class="message-textarea"
159169
:placeholder="t('components.ai.chatbot.inputPlaceholder')"
160170
@input="adjustTextareaHeight"
161171
@keydown="handleKeydown"
162-
:disabled="props.isLoading"
172+
:disabled="inputBoxDisabled"
163173
rows="1"
164174
/>
165175
</div>
166176

167177
<div class="input-footer">
168178
<div class="left-content">
169-
<div class="model-select-wrapper">
179+
<div v-if="hasProviders" class="model-select-wrapper">
170180
<el-popover
171181
placement="top"
172182
:width="280"
@@ -216,6 +226,16 @@ defineOptions({ name: 'ClChatInput' });
216226
</div>
217227
</el-popover>
218228
</div>
229+
<div v-else>
230+
<cl-label-button
231+
:label="t('components.ai.chatbot.addModel.label')"
232+
:tooltip="t('components.ai.chatbot.addModel.tooltip')"
233+
:icon="['fas', 'plus']"
234+
type="text"
235+
size="small"
236+
@click="() => emit('add-model')"
237+
/>
238+
</div>
219239
</div>
220240
<div class="right-content">
221241
<!-- Cancel button when request is loading -->
@@ -260,6 +280,12 @@ defineOptions({ name: 'ClChatInput' });
260280
border-color 0.2s,
261281
box-shadow 0.2s;
262282
overflow: hidden;
283+
284+
&.disabled {
285+
textarea {
286+
cursor: not-allowed;
287+
}
288+
}
263289
}
264290
265291
.message-textarea {

src/i18n/lang/en/components/ai.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ const ai: LangAi = {
2626
searchHistory: 'Search history',
2727
noConversations: 'No chat history found',
2828
newChat: 'New Chat',
29+
addModel: {
30+
label: 'Add Model',
31+
tooltip: 'You haven\'t configured any models yet. Add a model to get started.',
32+
},
2933
},
3034
};
3135

src/i18n/lang/zh/components/ai.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ const ai: LangAi = {
2626
searchHistory: '搜索历史',
2727
noConversations: '没有聊天记录',
2828
newChat: '新聊天',
29+
addModel: {
30+
label: '添加模型',
31+
tooltip: '您还没有配置任何模型。添加一个模型开始聊天',
32+
},
2933
},
3034
};
3135

src/interfaces/i18n/components/ai.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ export declare global {
2727
searchHistory: string;
2828
noConversations: string;
2929
newChat: string;
30+
addModel: {
31+
label: string;
32+
tooltip: string;
33+
};
3034
};
3135
}
3236
}

src/views/system/detail/tabs/SystemDetailTabAi.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,12 @@ defineOptions({ name: 'ClSystemDetailTabAi' });
258258
<cl-form-item :label="$t('views.system.ai.enabled')" :span="4">
259259
<cl-switch v-model="form.enabled" @change="saveLLMProvider" />
260260
</cl-form-item>
261-
<cl-form-item :label="$t('views.system.ai.apiKey')" :span="4" required>
261+
<cl-form-item
262+
:label="$t('views.system.ai.apiKey')"
263+
:span="4"
264+
prop="api_key"
265+
required
266+
>
262267
<cl-edit-input
263268
v-model="form.api_key"
264269
:display-value="

src/views/system/detail/tabs/SystemDetailTabCustomize.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ defineOptions({ name: 'ClSystemDetailTabCustomize' });
8080
</script>
8181

8282
<template>
83-
<cl-form v-if="form" ref="formRef" :model="form.value" label-width="200px">
83+
<cl-form v-if="form?.value" ref="formRef" :model="form.value" label-width="200px">
8484
<cl-form-item
8585
:span="4"
8686
:label="t('views.system.customize.showCustomTitle')"

0 commit comments

Comments
 (0)