hesabixCore/webUI/src/views/wizard/home.vue

557 lines
13 KiB
Vue
Raw Normal View History

2025-04-12 18:50:34 +03:30
<template>
2025-07-21 03:32:19 +03:30
<div class="chat-container">
<!-- ناحیه پیامها -->
<div class="messages-container" ref="messagesContainer">
<div
v-for="(message, index) in messages"
:key="index"
:class="['message', message.type]"
>
<div class="message-avatar">
<v-icon v-if="message.type === 'user'" size="24" color="white">mdi-account</v-icon>
<v-icon v-else size="24" color="primary">mdi-robot</v-icon>
2025-07-18 06:29:39 +03:30
</div>
2025-07-21 03:32:19 +03:30
<div class="message-content">
<div class="message-bubble">
<p class="message-text">{{ message.text }}</p>
<span class="message-time">{{ formatTime(message.timestamp) }}</span>
2025-07-19 16:34:23 +03:30
</div>
2025-07-21 03:32:19 +03:30
</div>
2025-07-19 16:34:23 +03:30
</div>
2025-07-21 03:32:19 +03:30
<!-- نشانگر تایپ -->
<div v-if="isTyping" class="message ai">
<div class="message-avatar">
<v-icon size="24" color="primary">mdi-robot</v-icon>
2025-07-18 06:29:39 +03:30
</div>
2025-07-21 03:32:19 +03:30
<div class="message-content">
<div class="message-bubble typing">
<div class="typing-indicator">
<span></span>
<span></span>
<span></span>
2025-07-18 02:30:04 +03:30
</div>
2025-05-04 01:07:39 +03:30
</div>
2025-07-19 16:34:23 +03:30
</div>
</div>
2025-05-04 01:07:39 +03:30
</div>
2025-07-18 06:29:39 +03:30
2025-07-21 03:32:19 +03:30
<!-- ناحیه ورودی -->
<div class="input-container">
<div class="input-wrapper">
<v-textarea
v-model="newMessage"
:placeholder="aiEnabled ? 'پیام خود را بنویسید...' : 'هوش مصنوعی غیرفعال است'"
variant="outlined"
rows="1"
auto-grow
hide-details
class="message-input"
:disabled="!aiEnabled"
@keydown.enter="sendMessage"
></v-textarea>
<v-btn
:disabled="!newMessage.trim() || isTyping || !aiEnabled"
color="primary"
icon
size="small"
class="send-button"
@click="sendMessage"
2025-07-18 06:29:39 +03:30
>
2025-07-21 03:32:19 +03:30
<v-icon>mdi-send</v-icon>
2025-07-18 06:29:39 +03:30
</v-btn>
2025-07-21 03:32:19 +03:30
</div>
2025-07-18 06:29:39 +03:30
2025-07-21 03:32:19 +03:30
<!-- دکمههای سریع -->
<div v-if="aiEnabled" class="quick-actions">
<v-chip
v-for="suggestion in quickSuggestions"
:key="suggestion"
variant="outlined"
size="small"
class="quick-chip"
@click="sendQuickMessage(suggestion)"
2025-07-18 06:29:39 +03:30
>
2025-07-21 03:32:19 +03:30
{{ suggestion }}
</v-chip>
</div>
2025-07-18 19:59:35 +03:30
2025-07-21 03:32:19 +03:30
<!-- نشانگر وضعیت -->
<div v-if="!aiEnabled && aiStatus !== 'checking'" class="status-indicator">
<v-alert
:type="aiStatus === 'error' ? 'error' : 'warning'"
variant="tonal"
density="compact"
class="status-alert"
2025-07-19 16:34:23 +03:30
>
2025-07-21 03:32:19 +03:30
{{ getStatusMessage(aiStatus, '') }}
</v-alert>
</div>
</div>
</div>
2025-04-12 18:50:34 +03:30
</template>
<script>
2025-07-21 03:32:19 +03:30
import { useNavigationStore } from '@/stores/navigationStore';
2025-07-18 06:29:39 +03:30
import axios from 'axios';
2025-07-18 02:30:04 +03:30
2025-04-12 18:50:34 +03:30
export default {
2025-07-21 03:32:19 +03:30
name: "WizardHome",
2025-04-12 18:50:34 +03:30
data() {
return {
2025-07-21 03:32:19 +03:30
navigationStore: useNavigationStore(),
messages: [
{
type: 'ai',
text: 'سلام! من دستیار هوشمند شما هستم. چطور می‌تونم کمکتون کنم؟',
timestamp: new Date()
}
],
newMessage: '',
2025-07-18 06:29:39 +03:30
isTyping: false,
2025-07-21 03:32:19 +03:30
aiEnabled: false,
aiStatus: 'checking',
conversationId: null,
quickSuggestions: [
'چطور می‌تونم کمکتون کنم؟',
'سوالی دارید؟',
'نیاز به راهنمایی دارید؟',
'مشکلی پیش اومده؟'
2025-07-19 16:34:23 +03:30
]
2025-04-12 18:50:34 +03:30
}
},
methods: {
2025-07-21 03:32:19 +03:30
async checkAIStatus() {
2025-07-18 02:30:04 +03:30
try {
2025-07-21 03:32:19 +03:30
this.aiStatus = 'checking';
2025-07-18 06:29:39 +03:30
const response = await axios.get('/api/wizard/status');
2025-07-21 03:32:19 +03:30
const data = response.data;
if (data.success) {
this.aiEnabled = data.status === 'available';
this.aiStatus = data.status;
if (!this.aiEnabled) {
// تغییر پیام اولیه بر اساس وضعیت
this.messages[0] = {
type: 'ai',
text: this.getStatusMessage(data.status, data.message),
timestamp: new Date()
};
}
} else {
this.aiEnabled = false;
this.aiStatus = 'error';
this.messages[0] = {
type: 'ai',
text: 'خطا در بررسی وضعیت هوش مصنوعی. لطفاً دوباره تلاش کنید.',
timestamp: new Date()
};
2025-07-18 02:30:04 +03:30
}
} catch (error) {
2025-07-21 03:32:19 +03:30
this.aiEnabled = false;
this.aiStatus = 'error';
this.messages[0] = {
type: 'ai',
text: 'خطا در اتصال به سرور. لطفاً اتصال اینترنت خود را بررسی کنید.',
timestamp: new Date()
2025-07-18 06:29:39 +03:30
};
}
},
2025-07-21 03:32:19 +03:30
getStatusMessage(status, message) {
switch (status) {
case 'disabled':
return 'سرویس هوش مصنوعی در حال حاضر غیرفعال است. لطفاً با مدیر سیستم تماس بگیرید.';
case 'no_api_key':
return 'کلید API هوش مصنوعی تنظیم نشده است. لطفاً با مدیر سیستم تماس بگیرید.';
case 'error':
return 'خطا در سرویس هوش مصنوعی. لطفاً دوباره تلاش کنید.';
default:
return message || 'سرویس هوش مصنوعی در دسترس نیست.';
}
2025-07-18 06:29:39 +03:30
},
2025-07-21 03:32:19 +03:30
async sendMessage() {
if (!this.newMessage.trim() || this.isTyping || !this.aiEnabled) return;
// اضافه کردن پیام کاربر
this.messages.push({
type: 'user',
text: this.newMessage.trim(),
timestamp: new Date()
});
const userMessage = this.newMessage.trim();
this.newMessage = '';
// اسکرول به پایین بعد از اضافه کردن پیام کاربر
setTimeout(() => {
this.scrollToBottom();
}, 100);
// شبیه‌سازی تایپ کردن AI
this.isTyping = true;
// اسکرول به پایین برای نشان دادن نشانگر تایپ
setTimeout(() => {
this.scrollToBottom();
}, 300);
2025-07-18 06:29:39 +03:30
try {
2025-07-21 03:32:19 +03:30
// ارسال پیام به سرور
const response = await axios.post('/api/wizard/talk', {
message: userMessage,
conversationId: this.conversationId || null
});
this.isTyping = false;
2025-07-18 06:29:39 +03:30
if (response.data.success) {
2025-07-21 03:32:19 +03:30
// اضافه کردن پاسخ AI
this.messages.push({
type: 'ai',
text: response.data.response,
timestamp: new Date()
});
// ذخیره conversationId برای ادامه گفتگو
if (response.data.conversationId) {
this.conversationId = response.data.conversationId;
}
// نمایش اطلاعات هزینه در صورت وجود
if (response.data.cost) {
console.log('هزینه استفاده:', response.data.cost);
}
} else {
// نمایش خطا
this.messages.push({
type: 'ai',
text: `خطا: ${response.data.error}`,
timestamp: new Date()
});
2025-07-18 02:30:04 +03:30
}
2025-07-21 03:32:19 +03:30
// اسکرول به پایین بعد از دریافت پاسخ
setTimeout(() => {
this.scrollToBottom();
}, 200);
2025-07-18 06:29:39 +03:30
} catch (error) {
2025-07-21 03:32:19 +03:30
this.isTyping = false;
let errorMessage = 'خطا در ارتباط با سرور';
if (error.response) {
if (error.response.data && error.response.data.error) {
errorMessage = error.response.data.error;
} else if (error.response.status === 403) {
errorMessage = 'دسترسی غیرمجاز';
} else if (error.response.status === 500) {
errorMessage = 'خطای سرور';
}
} else if (error.request) {
errorMessage = 'خطا در اتصال به سرور';
2025-07-19 16:34:23 +03:30
}
2025-07-18 06:29:39 +03:30
2025-07-21 03:32:19 +03:30
this.messages.push({
type: 'ai',
text: errorMessage,
timestamp: new Date()
});
2025-07-18 06:29:39 +03:30
2025-07-21 03:32:19 +03:30
setTimeout(() => {
2025-07-18 06:29:39 +03:30
this.scrollToBottom();
2025-07-21 03:32:19 +03:30
}, 200);
2025-05-04 01:07:39 +03:30
}
},
2025-07-21 03:32:19 +03:30
async sendQuickMessage(suggestion) {
this.newMessage = suggestion;
await this.sendMessage();
2025-07-19 16:34:23 +03:30
},
2025-07-21 03:32:19 +03:30
generateAIResponse(userMessage) {
// این تابع دیگر استفاده نمی‌شود چون از API استفاده می‌کنیم
return 'پاسخ از سرور دریافت می‌شود';
2025-07-19 16:34:23 +03:30
},
2025-07-21 03:32:19 +03:30
2025-07-19 16:34:23 +03:30
2025-07-21 03:32:19 +03:30
formatTime(timestamp) {
return timestamp.toLocaleTimeString('fa-IR', {
hour: '2-digit',
minute: '2-digit'
});
2025-07-19 16:34:23 +03:30
},
2025-07-21 03:32:19 +03:30
scrollToBottom() {
this.$nextTick(() => {
setTimeout(() => {
const container = this.$refs.messagesContainer;
if (container) {
const rect = container.getBoundingClientRect();
const scrollTop = window.pageYOffset + rect.top + container.scrollHeight - window.innerHeight;
window.scrollTo({
top: scrollTop,
behavior: 'smooth'
});
}
}, 100);
});
}
},
async mounted() {
// بستن منو در دسکتاپ
if (!this.$vuetify.display.mobile) {
this.navigationStore.closeDrawer();
}
// بررسی وضعیت هوش مصنوعی
await this.checkAIStatus();
this.scrollToBottom();
},
beforeUnmount() {
// باز کردن منو در دسکتاپ
if (!this.$vuetify.display.mobile) {
this.navigationStore.openDrawer();
}
},
updated() {
this.scrollToBottom();
}
}
</script>
2025-07-19 16:34:23 +03:30
2025-07-21 03:32:19 +03:30
<style scoped>
.chat-container {
height: 100%;
display: flex;
flex-direction: column;
background: white;
}
2025-07-19 16:34:23 +03:30
2025-07-18 06:29:39 +03:30
2025-07-21 03:32:19 +03:30
.messages-container {
flex: 1;
padding: 24px;
padding-bottom: 160px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 16px;
min-height: 0;
}
2025-07-18 06:29:39 +03:30
2025-07-21 03:32:19 +03:30
.message {
display: flex;
gap: 12px;
max-width: 80%;
}
2025-07-18 06:29:39 +03:30
2025-07-21 03:32:19 +03:30
.message.user {
align-self: flex-end;
flex-direction: row-reverse;
}
2025-05-04 01:07:39 +03:30
2025-07-21 03:32:19 +03:30
.message.ai {
align-self: flex-start;
}
2025-07-18 07:15:33 +03:30
2025-07-21 03:32:19 +03:30
.message-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
2025-07-18 02:30:04 +03:30
2025-07-21 03:32:19 +03:30
.message.user .message-avatar {
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
}
2025-07-18 06:29:39 +03:30
2025-07-21 03:32:19 +03:30
.message.ai .message-avatar {
background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
border: 2px solid #e0e0e0;
}
2025-05-04 01:07:39 +03:30
2025-07-21 03:32:19 +03:30
.message-content {
2025-07-18 06:29:39 +03:30
flex: 1;
2025-05-04 01:07:39 +03:30
}
2025-07-21 03:32:19 +03:30
.message-bubble {
background: #f8f9fa;
padding: 14px 18px;
border-radius: 20px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
position: relative;
border: 1px solid #e9ecef;
2025-05-04 01:07:39 +03:30
}
2025-07-21 03:32:19 +03:30
.message.user .message-bubble {
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
color: white;
border: none;
2025-04-12 18:50:34 +03:30
}
2025-07-21 03:32:19 +03:30
.message-text {
margin: 0 0 4px 0;
line-height: 1.5;
font-size: 14px;
2025-04-12 18:50:34 +03:30
}
2025-07-21 03:32:19 +03:30
.message-time {
font-size: 11px;
opacity: 0.7;
display: block;
2025-05-04 01:07:39 +03:30
}
2025-07-21 03:32:19 +03:30
.typing-indicator {
2025-07-19 16:34:23 +03:30
display: flex;
2025-07-21 03:32:19 +03:30
gap: 4px;
padding: 8px 0;
2025-04-12 18:50:34 +03:30
}
2025-07-21 03:32:19 +03:30
.typing-indicator span {
width: 8px;
height: 8px;
background: #6c757d;
border-radius: 50%;
animation: typing 1.4s infinite ease-in-out;
2025-04-12 18:50:34 +03:30
}
2025-07-21 03:32:19 +03:30
.typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
.typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
2025-07-18 02:30:04 +03:30
2025-07-21 03:32:19 +03:30
@keyframes typing {
0%, 80%, 100% {
transform: scale(0.8);
opacity: 0.5;
}
40% {
transform: scale(1);
opacity: 1;
}
2025-07-19 16:34:23 +03:30
}
2025-07-21 03:32:19 +03:30
.input-container {
2025-07-18 06:29:39 +03:30
background: white;
2025-07-21 03:32:19 +03:30
padding: 20px 24px;
border-top: 1px solid #e9ecef;
box-shadow: 0 -4px 20px rgba(0,0,0,0.08);
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
2025-07-18 02:30:04 +03:30
}
2025-07-21 03:32:19 +03:30
.input-wrapper {
2025-07-19 16:34:23 +03:30
display: flex;
2025-07-21 03:32:19 +03:30
gap: 12px;
align-items: flex-end;
margin-bottom: 12px;
2025-07-19 16:34:23 +03:30
}
2025-07-21 03:32:19 +03:30
.message-input {
2025-07-19 16:34:23 +03:30
flex: 1;
}
2025-07-21 03:32:19 +03:30
.message-input :deep(.v-field__outline) {
border-radius: 24px;
border-color: #e9ecef;
2025-05-04 01:07:39 +03:30
}
2025-07-21 03:32:19 +03:30
.message-input :deep(.v-field--focused .v-field__outline) {
border-color: #1976d2;
2025-04-12 18:50:34 +03:30
}
2025-07-21 03:32:19 +03:30
.send-button {
border-radius: 50%;
box-shadow: 0 4px 16px rgba(25, 118, 210, 0.3);
transition: all 0.3s ease;
2025-07-18 06:29:39 +03:30
}
2025-07-21 03:32:19 +03:30
.send-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(25, 118, 210, 0.4);
2025-05-04 01:07:39 +03:30
}
2025-07-21 03:32:19 +03:30
.send-button .v-icon {
transform: rotate(180deg);
2025-05-04 01:07:39 +03:30
}
2025-07-21 03:32:19 +03:30
.quick-actions {
2025-07-19 16:34:23 +03:30
display: flex;
2025-07-21 03:32:19 +03:30
gap: 8px;
flex-wrap: wrap;
2025-04-12 18:50:34 +03:30
}
2025-07-18 02:30:04 +03:30
2025-07-21 03:32:19 +03:30
.quick-chip {
cursor: pointer;
2025-07-19 16:34:23 +03:30
transition: all 0.3s ease;
2025-07-21 03:32:19 +03:30
border-color: #e9ecef;
color: #6c757d;
2025-07-18 02:30:04 +03:30
}
2025-07-21 03:32:19 +03:30
.quick-chip:hover {
background: #f8f9fa;
border-color: #1976d2;
color: #1976d2;
2025-07-19 16:34:23 +03:30
transform: translateY(-1px);
}
2025-07-21 03:32:19 +03:30
.status-indicator {
margin-top: 12px;
2025-07-19 16:34:23 +03:30
}
2025-07-21 03:32:19 +03:30
.status-alert {
border-radius: 12px;
font-size: 13px;
2025-07-19 16:34:23 +03:30
}
2025-07-21 03:32:19 +03:30
/* اسکرول‌بار سفارشی */
.messages-container::-webkit-scrollbar {
width: 6px;
2025-07-19 16:34:23 +03:30
}
2025-07-21 03:32:19 +03:30
.messages-container::-webkit-scrollbar-track {
background: transparent;
2025-07-19 16:34:23 +03:30
}
2025-07-21 03:32:19 +03:30
.messages-container::-webkit-scrollbar-thumb {
background: rgba(0,0,0,0.2);
border-radius: 3px;
2025-07-19 16:34:23 +03:30
}
2025-07-21 03:32:19 +03:30
.messages-container::-webkit-scrollbar-thumb:hover {
background: rgba(0,0,0,0.3);
2025-07-19 16:34:23 +03:30
}
2025-07-21 03:32:19 +03:30
/* ریسپانسیو */
2025-07-19 16:34:23 +03:30
@media (max-width: 768px) {
.messages-container {
padding: 16px;
2025-07-21 03:32:19 +03:30
padding-bottom: 160px;
2025-07-19 16:34:23 +03:30
}
2025-07-21 03:32:19 +03:30
.input-container {
2025-07-19 16:34:23 +03:30
padding: 12px 16px;
}
.message {
2025-07-21 03:32:19 +03:30
max-width: 90%;
2025-07-19 16:34:23 +03:30
}
2025-07-19 13:49:33 +03:30
}
2025-04-12 18:50:34 +03:30
</style>