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

1101 lines
33 KiB
Vue
Raw Normal View History

2025-04-12 18:50:34 +03:30
<template>
2025-05-04 01:07:39 +03:30
<v-toolbar color="toolbar" title="هوش مصنوعی حسابیکس">
2025-07-18 02:30:04 +03:30
<v-spacer></v-spacer>
2025-07-18 06:29:39 +03:30
2025-07-18 02:30:04 +03:30
<!-- نمایش وضعیت سرویس -->
2025-07-18 06:29:39 +03:30
<v-tooltip location="bottom">
<template v-slot:activator="{ props }">
<div v-bind="props" class="d-flex align-center mr-3">
<v-chip
:color="aiSettings.aiEnabled ? 'success' : 'error'"
size="small"
variant="flat"
class="status-chip"
>
<v-icon start size="16">
{{ aiSettings.aiEnabled ? 'mdi-robot' : 'mdi-robot-off' }}
</v-icon>
{{ aiSettings.aiEnabled ? 'فعال' : 'غیرفعال' }}
</v-chip>
</div>
</template>
<span>وضعیت سرویس هوش مصنوعی</span>
</v-tooltip>
<!-- نمایش اعتبار کاربر -->
<v-tooltip location="bottom">
<template v-slot:activator="{ props }">
<div v-bind="props" class="d-flex align-center mr-3">
<v-chip
:color="userBalance < 1000 ? 'warning' : 'success'"
size="small"
variant="flat"
class="balance-chip"
>
<v-icon start size="16">mdi-wallet</v-icon>
{{ formatBalance(userBalance) }}
</v-chip>
</div>
</template>
<span>اعتبار باقیمانده حساب شما</span>
</v-tooltip>
2025-07-18 02:30:04 +03:30
<!-- نمایش وضعیت اتصال -->
2025-07-18 06:29:39 +03:30
<v-tooltip location="bottom">
<template v-slot:activator="{ props }">
<div v-bind="props" class="d-flex align-center mr-3">
<v-chip
:color="connectionStatus.color"
size="small"
variant="flat"
class="connection-chip"
>
<v-icon start size="16">
{{ connectionStatus.icon }}
</v-icon>
{{ connectionStatus.text }}
</v-chip>
</div>
</template>
<span>وضعیت اتصال به سرور</span>
</v-tooltip>
<!-- دکمه آرشیو گفتگوها -->
<v-tooltip location="bottom">
<template v-slot:activator="{ props }">
<v-btn
v-bind="props"
icon
@click="showConversationsDialog = true"
class="mr-2 archive-btn"
color="primary"
variant="tonal"
>
<v-icon>mdi-archive</v-icon>
</v-btn>
</template>
<span>مشاهده آرشیو گفتگوها</span>
</v-tooltip>
2025-05-04 01:07:39 +03:30
</v-toolbar>
<div class="page-container">
2025-07-18 06:29:39 +03:30
<!-- کارت اطلاعات قیمتگذاری و آمار استفاده -->
<div class="info-cards" v-if="aiSettings.aiEnabled">
<v-card variant="outlined" class="ma-4 info-card" elevation="0">
<v-card-text class="pa-3">
<div class="d-flex align-center justify-space-between flex-wrap">
<!-- بخش قیمتگذاری -->
<div class="pricing-section d-flex align-center">
<v-icon color="info" size="20" class="mr-2">mdi-currency-usd</v-icon>
<div class="d-flex align-center gap-3">
<div class="text-center">
<div class="text-caption text-medium-emphasis">ورودی</div>
<div class="text-body-2 font-weight-medium">{{ aiSettings.inputTokenPrice.toLocaleString('fa-IR') }}</div>
</div>
<v-divider vertical></v-divider>
<div class="text-center">
<div class="text-caption text-medium-emphasis">خروجی</div>
<div class="text-body-2 font-weight-medium">{{ aiSettings.outputTokenPrice.toLocaleString('fa-IR') }}</div>
</div>
<div class="text-caption text-medium-emphasis">ریال/1000 توکن</div>
2025-07-18 02:30:04 +03:30
</div>
2025-07-18 06:29:39 +03:30
</div>
<!-- خط جداکننده -->
<v-divider vertical class="mx-4" v-if="userMessages.length > 0"></v-divider>
<!-- بخش آمار استفاده -->
<div class="stats-section d-flex align-center" v-if="userMessages.length > 0">
<v-icon color="success" size="20" class="mr-2">mdi-chart-line</v-icon>
<div class="d-flex align-center gap-3">
<div class="text-center">
<div class="text-caption text-medium-emphasis">پیامها</div>
<div class="text-body-2 font-weight-medium">{{ userMessages.length }}</div>
</div>
<v-divider vertical></v-divider>
<div class="text-center">
<div class="text-caption text-medium-emphasis">توکنها</div>
<div class="text-body-2 font-weight-medium">{{ usageStats.totalTokens.toLocaleString('fa-IR') }}</div>
</div>
<v-divider vertical></v-divider>
<div class="text-center">
<div class="text-caption text-medium-emphasis">هزینه</div>
<div class="text-body-2 font-weight-medium">{{ usageStats.totalCost.toLocaleString('fa-IR') }} ریال</div>
</div>
2025-07-18 02:30:04 +03:30
</div>
</div>
</div>
</v-card-text>
</v-card>
</div>
2025-07-18 06:29:39 +03:30
<div class="content-container">
<v-card class="chat-container" elevation="0">
<!-- هدر گفتگو -->
<div class="chat-header" v-if="currentConversation">
<div class="d-flex align-center justify-space-between pa-4">
2025-07-18 02:30:04 +03:30
<div class="d-flex align-center">
2025-07-18 06:29:39 +03:30
<v-icon color="primary" class="mr-2">mdi-chat</v-icon>
<span class="text-subtitle-1 font-weight-medium">{{ currentConversation.title }}</span>
<v-chip size="small" variant="outlined" class="ml-2">{{ currentConversation.category }}</v-chip>
2025-07-18 02:30:04 +03:30
</div>
2025-07-18 06:29:39 +03:30
<div class="d-flex align-center">
<v-btn
size="small"
variant="text"
color="grey"
@click="newConversation"
>
<v-icon start size="16">mdi-plus</v-icon>
گفتگوی جدید
</v-btn>
2025-07-18 02:30:04 +03:30
</div>
</div>
2025-07-18 06:29:39 +03:30
</div>
2025-05-04 01:07:39 +03:30
<div class="chat-box">
<div class="messages-container" ref="messagesContainer">
<!-- پیام هوش مصنوعی -->
<div class="message ai-message" v-if="displayWelcome">
<div class="message-content">
2025-07-18 06:29:39 +03:30
<div class="message-text typing-text" v-html="renderMarkdown(displayWelcome)"></div>
2025-05-04 01:07:39 +03:30
</div>
2025-07-18 06:29:39 +03:30
<v-avatar color="#1a237e" size="36" class="ml-2">
<v-icon color="white" size="20">mdi-robot</v-icon>
</v-avatar>
2025-05-04 01:07:39 +03:30
</div>
<!-- پیامهای کاربر و پاسخهای هوش مصنوعی -->
<template v-for="(message, index) in userMessages" :key="index">
<!-- پیام کاربر -->
<div class="message user-message" v-if="typeof message === 'string'">
2025-07-18 06:29:39 +03:30
<v-avatar color="grey lighten-2" size="36" class="mr-2">
<v-icon color="grey darken-1" size="20">mdi-account</v-icon>
</v-avatar>
2025-05-04 01:07:39 +03:30
<div class="message-content">
<div class="message-text">{{ message }}</div>
2025-07-18 02:30:04 +03:30
<div class="message-time">{{ formatTime(new Date()) }}</div>
2025-05-04 01:07:39 +03:30
</div>
</div>
<!-- پیام هوش مصنوعی -->
<div class="message ai-message" v-else-if="message && message.isAI">
2025-07-18 06:29:39 +03:30
<div class="message-content" :class="{
'details-message': message.isDetails,
'error-message': message.text && message.text.startsWith('خطا:')
}">
<div class="message-text" v-html="renderMarkdown(message.text)"></div>
<!-- دکمه شارژ برای پیامهای خطای اعتبار -->
<div v-if="message.showChargeButton" class="charge-button-container mt-3">
<v-btn
color="success"
variant="elevated"
size="small"
@click="goToChargePage"
prepend-icon="mdi-credit-card"
class="charge-btn"
>
شارژ حساب
</v-btn>
</div>
2025-07-18 02:30:04 +03:30
<div class="message-time">{{ formatTime(new Date()) }}</div>
2025-05-04 01:07:39 +03:30
</div>
2025-07-18 06:29:39 +03:30
<v-avatar :color="message.text && message.text.startsWith('خطا:') ? '#f44336' : '#1a237e'" size="36" class="ml-2">
<v-icon color="white" size="20">{{ message.text && message.text.startsWith('خطا:') ? 'mdi-alert-circle' : 'mdi-robot' }}</v-icon>
</v-avatar>
2025-05-04 01:07:39 +03:30
</div>
</template>
<!-- نشانگر تایپ -->
<div class="message ai-message" v-if="isTyping">
<div class="message-content">
<div class="message-text">
<span class="typing-indicator">
<span></span>
<span></span>
<span></span>
</span>
</div>
</div>
2025-07-18 06:29:39 +03:30
<v-avatar color="#1a237e" size="36" class="ml-2">
<v-icon color="white" size="20">mdi-robot</v-icon>
</v-avatar>
2025-05-04 01:07:39 +03:30
</div>
</div>
2025-07-18 02:30:04 +03:30
<!-- پیشنهادات سوالات -->
2025-07-18 07:15:33 +03:30
<div class="suggestions-container" v-if="userMessages.length === 0 && aiSettings.aiEnabled">
2025-07-18 06:29:39 +03:30
<div class="d-flex flex-wrap gap-1">
2025-07-18 02:30:04 +03:30
<v-chip
v-for="suggestion in suggestions"
:key="suggestion"
2025-07-18 06:29:39 +03:30
size="x-small"
variant="tonal"
2025-07-18 02:30:04 +03:30
@click="useSuggestion(suggestion)"
class="suggestion-chip"
2025-07-18 06:29:39 +03:30
color="primary"
2025-07-18 02:30:04 +03:30
>
{{ suggestion }}
</v-chip>
</div>
2025-05-04 01:07:39 +03:30
</div>
2025-07-18 06:29:39 +03:30
<!-- بخش ورودی پیام -->
<div class="input-container d-flex align-center flex-row-reverse">
<v-btn
color="primary"
class="send-button ml-2"
2025-07-18 07:15:33 +03:30
:disabled="isLoading || !userMessage.trim() || !aiSettings.aiEnabled"
2025-07-18 06:29:39 +03:30
@click="sendMessage"
icon
>
<v-icon class="send-icon-rotate">mdi-send</v-icon>
</v-btn>
<v-text-field
v-model="userMessage"
class="flex-grow-1 message-input"
2025-07-18 07:15:33 +03:30
:placeholder="aiSettings.aiEnabled ? 'پیام خود را بنویسید...' : 'سرویس هوش مصنوعی غیرفعال است'"
2025-07-18 06:29:39 +03:30
hide-details="auto"
variant="outlined"
@keyup.enter="sendMessage"
2025-07-18 07:15:33 +03:30
:disabled="isLoading || !aiSettings.aiEnabled"
2025-07-18 06:29:39 +03:30
></v-text-field>
</div>
2025-05-04 01:07:39 +03:30
</div>
2025-04-12 18:50:34 +03:30
</v-card>
2025-05-04 01:07:39 +03:30
</div>
</div>
2025-07-18 06:29:39 +03:30
<!-- دیالوگ آرشیو گفتگوها -->
<v-dialog v-model="showConversationsDialog" max-width="800px">
<v-card>
<v-card-title class="d-flex align-center justify-space-between">
<span>آرشیو گفتگوها</span>
<v-btn icon @click="showConversationsDialog = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<!-- فیلترها -->
<div class="d-flex align-center gap-4 mb-4">
<v-text-field
v-model="searchTerm"
placeholder="جستجو در گفتگوها..."
prepend-inner-icon="mdi-magnify"
variant="outlined"
density="compact"
hide-details
class="flex-grow-1"
@input="searchConversations"
></v-text-field>
<v-select
v-model="selectedCategory"
:items="categories"
placeholder="دسته‌بندی"
variant="outlined"
density="compact"
hide-details
class="flex-grow-1"
@update:model-value="filterByCategory"
></v-select>
</div>
<!-- لیست گفتگوها -->
<div class="conversations-list">
<v-list>
<v-list-item
v-for="conversation in filteredConversations"
:key="conversation.id"
class="conversation-item"
>
<template #prepend>
<v-avatar color="primary" size="40" @click="loadConversation(conversation)" style="cursor: pointer;">
<v-icon color="white">mdi-chat</v-icon>
</v-avatar>
</template>
<v-list-item-title @click="loadConversation(conversation)" style="cursor: pointer;">{{ conversation.title }}</v-list-item-title>
<v-list-item-subtitle>
<div class="d-flex align-center justify-space-between">
<span>{{ conversation.category }}</span>
<span>{{ conversation.updatedAt }}</span>
</div>
<div class="text-caption text-medium-emphasis">
{{ conversation.messageCount || 0 }} پیام {{ (conversation.stats?.totalTokens || 0).toLocaleString('fa-IR') }} توکن {{ (conversation.stats?.totalCost || 0).toLocaleString('fa-IR') }} ریال
</div>
</v-list-item-subtitle>
<template #append>
<v-btn
variant="text"
size="small"
color="error"
@click.stop="deleteConversation(conversation)"
class="delete-btn"
icon
>
<v-icon size="18">mdi-delete</v-icon>
<v-tooltip activator="parent" location="top">حذف گفتگو</v-tooltip>
</v-btn>
</template>
</v-list-item>
</v-list>
</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="primary"
@click="newConversation"
prepend-icon="mdi-plus"
>
گفتگوی جدید
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- دیالوگ تأیید حذف گفتگو -->
<v-dialog v-model="showDeleteDialog" max-width="400px">
<v-card>
<v-card-title class="d-flex align-center">
<v-icon color="error" class="mr-2">mdi-delete</v-icon>
حذف گفتگو
</v-card-title>
<v-card-text>
<p class="text-body-1">
آیا مطمئن هستید که میخواهید گفتگوی
<strong>"{{ conversationToDelete?.title }}"</strong>
را حذف کنید؟
</p>
<p class="text-caption text-medium-emphasis mt-2">
این عملیات قابل بازگشت نیست و گفتگو از لیست شما حذف خواهد شد.
</p>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
variant="text"
@click="showDeleteDialog = false"
color="grey"
>
انصراف
</v-btn>
<v-btn
color="error"
@click="confirmDeleteConversation"
:loading="isLoading"
prepend-icon="mdi-delete"
>
حذف
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
2025-04-12 18:50:34 +03:30
</template>
<script>
2025-07-18 06:29:39 +03:30
import { marked } from 'marked';
import DOMPurify from 'dompurify';
import axios from 'axios';
2025-07-18 02:30:04 +03:30
2025-04-12 18:50:34 +03:30
export default {
name: 'WizardHome',
data() {
return {
2025-05-04 01:07:39 +03:30
userMessage: '',
userMessages: [],
2025-07-18 06:29:39 +03:30
isLoading: false,
isTyping: false,
2025-07-18 02:30:04 +03:30
aiSettings: {
aiEnabled: false,
2025-07-18 06:29:39 +03:30
aiAgentSource: '',
aiModel: '',
2025-07-18 02:30:04 +03:30
inputTokenPrice: 0,
outputTokenPrice: 0,
aiPrompt: ''
},
2025-07-18 06:29:39 +03:30
connectionStatus: {
color: 'grey',
icon: 'mdi-help-circle',
text: 'در حال بررسی...'
},
2025-05-04 01:07:39 +03:30
displayWelcome: '',
2025-07-18 02:30:04 +03:30
suggestions: [
2025-07-18 06:29:39 +03:30
'چگونه سند حسابداری ثبت کنم؟',
'راهنمای انبارداری',
'ثبت خرید و فروش',
'گزارش‌های مالی',
'تنظیمات کاربران'
2025-07-18 02:30:04 +03:30
],
2025-07-18 06:29:39 +03:30
// متغیرهای جدید برای گفتگوها
currentConversation: null,
showConversationsDialog: false,
conversations: [],
filteredConversations: [],
searchTerm: '',
selectedCategory: '',
categories: [],
// متغیر اعتبار کاربر
userBalance: 0,
// متغیرهای دیالوگ حذف
showDeleteDialog: false,
conversationToDelete: null
};
2025-07-18 02:30:04 +03:30
},
computed: {
aiMessageCount() {
2025-07-18 06:29:39 +03:30
return this.userMessages.filter(msg => msg && msg.isAI).length;
2025-07-18 02:30:04 +03:30
},
userMessageCount() {
2025-07-18 06:29:39 +03:30
return this.userMessages.filter(msg => typeof msg === 'string').length;
},
usageStats() {
let totalTokens = 0;
let totalCost = 0;
this.userMessages.forEach(msg => {
if (msg && msg.isAI && msg.usage) {
totalTokens += (msg.usage.prompt_tokens || 0) + (msg.usage.completion_tokens || 0);
totalCost += msg.cost?.total_cost || 0;
}
});
return {
totalTokens,
totalCost
};
2025-05-04 01:07:39 +03:30
}
},
watch: {
2025-07-18 06:29:39 +03:30
searchTerm() {
this.searchConversations();
2025-05-04 01:07:39 +03:30
},
2025-07-18 06:29:39 +03:30
selectedCategory() {
this.filterByCategory();
2025-04-12 18:50:34 +03:30
}
},
2025-07-18 06:29:39 +03:30
async mounted() {
await this.loadSettings();
await this.checkConnection();
await this.loadConversations();
await this.loadBalance();
this.setWelcomeMessage();
},
2025-04-12 18:50:34 +03:30
methods: {
2025-07-18 06:29:39 +03:30
async loadSettings() {
2025-07-18 02:30:04 +03:30
try {
2025-07-18 06:29:39 +03:30
const response = await axios.get('/api/wizard/settings');
2025-07-18 02:30:04 +03:30
if (response.data.success) {
2025-07-18 06:29:39 +03:30
this.aiSettings = response.data.settings;
2025-07-18 02:30:04 +03:30
}
} catch (error) {
2025-07-18 06:29:39 +03:30
console.error('خطا در بارگذاری تنظیمات:', error);
2025-07-18 02:30:04 +03:30
}
},
2025-07-18 06:29:39 +03:30
async checkConnection() {
2025-07-18 02:30:04 +03:30
try {
2025-07-18 06:29:39 +03:30
const response = await axios.get('/api/wizard/status');
2025-07-18 02:30:04 +03:30
if (response.data.success) {
2025-07-18 06:29:39 +03:30
const status = response.data.status;
2025-07-18 02:30:04 +03:30
2025-07-18 06:29:39 +03:30
if (status === 'available') {
this.connectionStatus = {
color: 'success',
icon: 'mdi-check-circle',
text: 'متصل'
};
} else if (status === 'disabled') {
this.connectionStatus = {
color: 'error',
icon: 'mdi-robot-off',
text: 'غیرفعال'
};
} else if (status === 'no_api_key') {
this.connectionStatus = {
color: 'warning',
icon: 'mdi-key-remove',
text: 'بدون کلید API'
};
} else {
this.connectionStatus = {
color: 'error',
icon: 'mdi-alert-circle',
text: 'خطا در اتصال'
};
2025-07-18 02:30:04 +03:30
}
}
} catch (error) {
this.connectionStatus = {
color: 'error',
icon: 'mdi-alert-circle',
2025-07-18 06:29:39 +03:30
text: 'خطا در اتصال'
};
console.error('خطا در بررسی وضعیت:', error);
}
},
async loadConversations() {
try {
const response = await axios.post('/api/ai/conversations/list');
this.conversations = response.data;
this.filteredConversations = [...this.conversations];
// استخراج دسته‌بندی‌ها
const categorySet = new Set();
this.conversations.forEach(conv => {
if (conv.category) {
categorySet.add(conv.category);
}
});
this.categories = Array.from(categorySet);
} catch (error) {
console.error('خطا در بارگذاری گفتگوها:', error);
}
},
async loadBalance() {
try {
const response = await axios.get('/api/wizard/balance');
if (response.data.success) {
this.userBalance = response.data.balance;
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
} catch (error) {
console.error('خطا در بارگذاری اعتبار:', error);
2025-07-18 02:30:04 +03:30
}
2025-05-04 01:07:39 +03:30
},
2025-07-18 06:29:39 +03:30
formatBalance(balance) {
// تبدیل به عدد صحیح
const intBalance = Math.round(balance);
if (intBalance < 1000) {
return `${intBalance.toLocaleString('fa-IR')} ریال`;
} else {
return `${(intBalance / 1000).toFixed(1)} هزار ریال`;
}
2025-05-04 01:07:39 +03:30
},
2025-07-18 06:29:39 +03:30
async loadConversation(conversation) {
try {
const response = await axios.post(`/api/ai/conversations/${conversation.id}/messages`);
this.currentConversation = conversation;
this.userMessages = [];
// تبدیل پیام‌های دیتابیس به فرمت نمایش
response.data.forEach(msg => {
if (msg.role === 'user') {
this.userMessages.push(msg.content);
} else if (msg.role === 'assistant') {
this.userMessages.push({
isAI: true,
text: msg.content,
usage: {
prompt_tokens: msg.inputTokens,
completion_tokens: msg.outputTokens
},
cost: {
total_cost: msg.totalCost
}
});
}
});
this.showConversationsDialog = false;
this.$nextTick(() => {
this.scrollToBottom();
});
} catch (error) {
console.error('خطا در بارگذاری گفتگو:', error);
2025-05-04 01:07:39 +03:30
}
},
2025-07-18 06:29:39 +03:30
deleteConversation(conversation) {
this.conversationToDelete = conversation;
this.showDeleteDialog = true;
2025-05-04 01:07:39 +03:30
},
2025-07-18 06:29:39 +03:30
async confirmDeleteConversation() {
if (!this.conversationToDelete) return;
try {
this.isLoading = true;
const response = await axios.post(`/api/ai/conversations/${this.conversationToDelete.id}/delete`);
if (response.data.success) {
// اگر گفتگوی حذف شده، گفتگوی فعلی است، گفتگوی جدید ایجاد کن
if (this.currentConversation && this.currentConversation.id === this.conversationToDelete.id) {
this.newConversation();
}
// به‌روزرسانی کامل لیست گفتگوها
await this.loadConversations();
// نمایش پیام موفقیت
this.$emit('show-snackbar', {
text: 'گفتگو با موفقیت حذف شد',
color: 'success'
});
} else {
this.$emit('show-snackbar', {
text: response.data.error || 'خطا در حذف گفتگو',
color: 'error'
});
}
} catch (error) {
console.error('خطا در حذف گفتگو:', error);
this.$emit('show-snackbar', {
text: 'خطا در حذف گفتگو',
color: 'error'
});
} finally {
this.isLoading = false;
this.showDeleteDialog = false;
this.conversationToDelete = null;
}
},
async newConversation() {
this.currentConversation = null;
this.userMessages = [];
this.showConversationsDialog = false;
this.setWelcomeMessage();
},
searchConversations() {
if (!this.searchTerm) {
this.filteredConversations = [...this.conversations];
} else {
this.filteredConversations = this.conversations.filter(conv =>
conv.title.toLowerCase().includes(this.searchTerm.toLowerCase())
);
}
},
filterByCategory() {
if (!this.selectedCategory) {
this.filteredConversations = [...this.conversations];
} else {
this.filteredConversations = this.conversations.filter(conv =>
conv.category === this.selectedCategory
);
}
},
setWelcomeMessage() {
2025-07-18 07:15:33 +03:30
if (!this.aiSettings.aiEnabled) {
this.displayWelcome = 'سرویس هوش مصنوعی در حال حاضر غیرفعال است. لطفاً با مدیر سیستم تماس بگیرید تا این سرویس را فعال کند.';
} else {
this.displayWelcome = 'سلام! من دستیار هوشمند حسابیکس هستم. چطور می‌توانم به شما کمک کنم؟';
}
2025-07-18 06:29:39 +03:30
},
2025-05-04 01:07:39 +03:30
async sendMessage() {
2025-07-18 06:29:39 +03:30
if (!this.userMessage.trim() || this.isLoading) return;
2025-05-04 01:07:39 +03:30
2025-07-18 07:15:33 +03:30
// بررسی فعال بودن هوش مصنوعی
if (!this.aiSettings.aiEnabled) {
this.userMessages.push({
isAI: true,
text: 'سرویس هوش مصنوعی در حال حاضر غیرفعال است. لطفاً با مدیر سیستم تماس بگیرید تا این سرویس را فعال کند.',
isError: true
});
this.userMessage = '';
this.$nextTick(() => {
this.scrollToBottom();
});
return;
}
2025-07-18 06:29:39 +03:30
// بررسی اعتبار قبل از ارسال
if (this.userBalance < 100) {
this.userMessages.push({
isAI: true,
text: `خطا: اعتبار شما کافی نیست (${this.formatBalance(this.userBalance)}). برای شارژ حساب خود روی دکمه زیر کلیک کنید:`,
isError: true,
showChargeButton: true
});
this.userMessage = '';
this.$nextTick(() => {
this.scrollToBottom();
});
return;
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
const message = this.userMessage;
this.userMessage = '';
this.userMessages.push(message);
this.isLoading = true;
this.isTyping = true;
this.$nextTick(() => {
this.scrollToBottom();
});
2025-05-04 01:07:39 +03:30
2025-07-18 02:30:04 +03:30
try {
const response = await axios.post('/api/wizard/talk', {
message: message,
2025-07-18 06:29:39 +03:30
conversationId: this.currentConversation?.id || null
});
2025-05-04 01:07:39 +03:30
2025-07-18 02:30:04 +03:30
if (response.data.success) {
this.userMessages.push({
2025-07-18 06:29:39 +03:30
isAI: true,
2025-07-18 02:30:04 +03:30
text: response.data.response,
2025-07-18 06:29:39 +03:30
usage: response.data.usage,
cost: response.data.cost
});
// به‌روزرسانی اعتبار کاربر
await this.loadBalance();
// به‌روزرسانی لیست گفتگوها
await this.loadConversations();
2025-07-18 02:30:04 +03:30
} else {
2025-07-18 06:29:39 +03:30
// بررسی خطای اعتبار
if (response.data.error && response.data.error.includes('اعتبار')) {
this.userMessages.push({
isAI: true,
text: `خطا: ${response.data.error}`,
isError: true,
showChargeButton: response.data.showChargeButton || false
});
// به‌روزرسانی اعتبار کاربر
await this.loadBalance();
} else {
this.userMessages.push({
isAI: true,
text: `خطا: ${response.data.error}`,
isError: true
});
}
2025-07-18 02:30:04 +03:30
}
} catch (error) {
2025-07-18 06:29:39 +03:30
console.error('خطا در ارسال پیام:', error);
2025-07-18 02:30:04 +03:30
this.userMessages.push({
isAI: true,
2025-07-18 06:29:39 +03:30
text: 'خطا: خطا در ارتباط با سرور',
isError: true
});
} finally {
this.isLoading = false;
this.isTyping = false;
this.$nextTick(() => {
this.scrollToBottom();
});
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
},
2025-05-04 01:07:39 +03:30
2025-07-18 06:29:39 +03:30
useSuggestion(suggestion) {
this.userMessage = suggestion;
2025-05-04 01:07:39 +03:30
},
2025-07-18 06:29:39 +03:30
clearChat() {
this.userMessages = [];
this.currentConversation = null;
this.setWelcomeMessage();
},
2025-05-04 01:07:39 +03:30
scrollToBottom() {
2025-07-18 06:29:39 +03:30
if (this.$refs.messagesContainer) {
this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
2025-05-04 01:07:39 +03:30
}
2025-07-18 02:30:04 +03:30
},
2025-07-18 06:29:39 +03:30
renderMarkdown(text) {
if (!text) return '';
const html = marked(text);
return DOMPurify.sanitize(html);
2025-07-18 02:30:04 +03:30
},
2025-07-18 06:29:39 +03:30
2025-07-18 02:30:04 +03:30
formatTime(date) {
2025-07-18 06:29:39 +03:30
return date.toLocaleTimeString('fa-IR', {
2025-07-18 02:30:04 +03:30
hour: '2-digit',
2025-07-18 06:29:39 +03:30
minute: '2-digit'
});
},
goToChargePage() {
// هدایت به صفحه شارژ با vue-router
this.$router.push('/acc/sms/panel');
2025-05-04 01:07:39 +03:30
}
2025-04-12 18:50:34 +03:30
}
2025-07-18 06:29:39 +03:30
};
2025-04-12 18:50:34 +03:30
</script>
<style scoped>
2025-05-04 01:07:39 +03:30
.page-container {
2025-07-18 06:29:39 +03:30
height: calc(100vh - 64px);
2025-05-04 01:07:39 +03:30
display: flex;
flex-direction: column;
}
2025-07-18 06:29:39 +03:30
.info-cards {
margin-bottom: 8px;
}
.info-card {
border-radius: 12px;
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border: 1px solid rgba(0, 0, 0, 0.08);
}
.pricing-section, .stats-section {
flex: 1;
min-width: 200px;
}
@media (max-width: 768px) {
.pricing-section, .stats-section {
min-width: 100%;
margin-bottom: 8px;
}
.info-card .v-card-text {
padding: 12px;
}
}
2025-05-04 01:07:39 +03:30
.content-container {
flex: 1;
2025-07-18 06:29:39 +03:30
display: flex;
flex-direction: column;
overflow: hidden;
2025-05-04 01:07:39 +03:30
}
.chat-container {
2025-07-18 06:29:39 +03:30
flex: 1;
display: flex;
flex-direction: column;
margin: 16px;
border-radius: 12px;
overflow: hidden;
}
.chat-header {
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
background: rgba(0, 0, 0, 0.02);
2025-05-04 01:07:39 +03:30
}
.chat-box {
2025-07-18 06:29:39 +03:30
flex: 1;
2025-05-04 01:07:39 +03:30
display: flex;
flex-direction: column;
2025-07-18 06:29:39 +03:30
overflow: hidden;
2025-05-04 01:07:39 +03:30
}
.messages-container {
flex: 1;
overflow-y: auto;
2025-07-18 06:29:39 +03:30
padding: 16px;
2025-05-04 01:07:39 +03:30
display: flex;
flex-direction: column;
gap: 16px;
}
.message {
display: flex;
align-items: flex-start;
2025-07-18 06:29:39 +03:30
gap: 8px;
2025-05-04 01:07:39 +03:30
max-width: 80%;
2025-04-12 18:50:34 +03:30
}
2025-05-04 01:07:39 +03:30
.user-message {
align-self: flex-end;
flex-direction: row-reverse;
2025-04-12 18:50:34 +03:30
}
2025-07-18 06:29:39 +03:30
.ai-message {
align-self: flex-start;
2025-05-04 01:07:39 +03:30
}
2025-07-18 06:29:39 +03:30
.message-content {
background: #f5f5f5;
padding: 12px 16px;
border-radius: 18px;
border-top-left-radius: 4px;
position: relative;
2025-05-04 01:07:39 +03:30
}
.user-message .message-content {
2025-07-18 06:29:39 +03:30
background: #1a237e;
2025-05-04 01:07:39 +03:30
color: white;
2025-07-18 06:29:39 +03:30
border-top-left-radius: 18px;
border-top-right-radius: 4px;
2025-04-12 18:50:34 +03:30
}
2025-05-04 01:07:39 +03:30
.message-text {
line-height: 1.5;
2025-07-18 06:29:39 +03:30
white-space: pre-wrap;
2025-04-12 18:50:34 +03:30
}
2025-07-18 02:30:04 +03:30
.message-time {
font-size: 0.75rem;
2025-07-18 06:29:39 +03:30
opacity: 0.7;
2025-07-18 02:30:04 +03:30
margin-top: 4px;
}
2025-07-18 06:29:39 +03:30
.input-container {
display: flex;
flex-direction: row-reverse;
align-items: center;
padding: 16px;
border-top: 1px solid rgba(0, 0, 0, 0.12);
background: white;
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
.message-input {
flex-grow: 1;
margin-left: 12px;
margin-right: 0;
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
.send-button {
min-width: 48px;
min-height: 48px;
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
.suggestions-container {
padding: 8px 16px;
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
2025-04-12 18:50:34 +03:30
}
2025-07-18 06:29:39 +03:30
.suggestion-chip {
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.75rem;
2025-05-04 01:07:39 +03:30
}
2025-07-18 06:29:39 +03:30
.suggestion-chip:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
2025-05-04 01:07:39 +03:30
}
2025-07-18 06:29:39 +03:30
.delete-btn {
opacity: 0.7;
transition: all 0.2s ease;
2025-05-04 01:07:39 +03:30
}
2025-07-18 06:29:39 +03:30
.delete-btn:hover {
opacity: 1;
2025-04-12 18:50:34 +03:30
transform: scale(1.1);
}
2025-07-18 06:29:39 +03:30
.conversation-item:hover .delete-btn {
opacity: 1;
}
2025-05-04 01:07:39 +03:30
.typing-indicator {
2025-07-18 06:29:39 +03:30
display: inline-flex;
2025-05-04 01:07:39 +03:30
gap: 4px;
}
.typing-indicator span {
width: 8px;
height: 8px;
border-radius: 50%;
2025-07-18 06:29:39 +03:30
background: #666;
animation: typing 1.4s infinite ease-in-out;
2025-05-04 01:07:39 +03:30
}
2025-07-18 06:29:39 +03:30
.typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
.typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
2025-05-04 01:07:39 +03:30
@keyframes typing {
2025-07-18 06:29:39 +03:30
0%, 80%, 100% {
transform: scale(0.8);
opacity: 0.5;
}
40% {
transform: scale(1);
opacity: 1;
}
2025-04-12 18:50:34 +03:30
}
2025-07-18 06:29:39 +03:30
.error-message {
background: #ffebee !important;
border: 1px solid #ffcdd2;
2025-04-12 18:50:34 +03:30
}
2025-07-18 02:30:04 +03:30
2025-07-18 06:29:39 +03:30
.details-message {
background: #e8f5e8 !important;
border: 1px solid #c8e6c9;
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
/* استایل‌های آرشیو گفتگوها */
.conversations-list {
max-height: 400px;
overflow-y: auto;
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
/* استایل‌های بهبود یافته تولبار */
.status-chip, .balance-chip, .connection-chip {
2025-07-18 02:30:04 +03:30
transition: all 0.3s ease;
2025-07-18 06:29:39 +03:30
cursor: pointer;
border-radius: 8px;
font-weight: 500;
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
.status-chip:hover, .balance-chip:hover, .connection-chip:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
.archive-btn {
transition: all 0.3s ease;
border-radius: 8px;
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
.archive-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
/* بهبود tooltip */
.v-tooltip {
font-size: 0.875rem;
font-weight: 500;
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
/* استایل دکمه شارژ */
.charge-button-container {
display: flex;
justify-content: center;
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
.charge-btn {
2025-07-18 02:30:04 +03:30
transition: all 0.3s ease;
2025-07-18 06:29:39 +03:30
border-radius: 8px;
font-weight: 500;
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
.charge-btn:hover {
2025-07-18 02:30:04 +03:30
transform: translateY(-1px);
2025-07-18 06:29:39 +03:30
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
.conversation-item {
cursor: pointer;
transition: background-color 0.2s ease;
border-radius: 8px;
margin-bottom: 4px;
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
.conversation-item:hover {
background-color: rgba(26, 35, 126, 0.1);
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
.conversation-item:active {
background-color: rgba(26, 35, 126, 0.2);
2025-07-18 02:30:04 +03:30
}
2025-07-18 06:29:39 +03:30
.send-icon-rotate {
transform: rotate(180deg);
2025-07-18 02:30:04 +03:30
}
2025-04-12 18:50:34 +03:30
</style>