Описание изменений

This commit is contained in:
2025-03-18 16:35:13 +03:00
parent 513db4e206
commit 31df93e8b1
15 changed files with 1636 additions and 1073 deletions

View File

@@ -1,108 +1,54 @@
<template>
<div class="telegram-auth">
<div v-if="!isAuthenticating">
<a :href="telegramBotLink" target="_blank" class="telegram-btn" @click="startAuth">
<span class="auth-icon">📱</span> Подключить Telegram
</a>
</div>
<button v-if="!showVerification" class="auth-btn telegram-btn" @click="startTelegramAuth">
<span class="auth-icon">📱</span> Подключить Telegram
</button>
<div v-else class="auth-progress">
<p>Для завершения авторизации:</p>
<ol>
<li>Перейдите в Telegram-бота <strong>@{{ botUsername }}</strong></li>
<li>Если бот не открылся автоматически, скопируйте и отправьте ему команду:</li>
</ol>
<div class="auth-code">
/auth {{ authToken }}
</div>
<button class="copy-btn" @click="copyAuthCommand">Копировать команду</button>
<div class="auth-actions">
<button class="cancel-btn" @click="cancelAuth">Отмена</button>
<button class="check-btn" @click="checkAuthStatus">Проверить статус</button>
</div>
<div v-if="errorMessage" class="error-message">
{{ errorMessage }}
</div>
<div v-else class="verification-form">
<input
type="text"
v-model="verificationCode"
placeholder="Введите код из Telegram"
/>
<button class="auth-btn verify-btn" @click="verifyCode">Подтвердить</button>
<button class="auth-btn cancel-btn" @click="cancelVerification">Отмена</button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { ref } from 'vue';
import { useAuthStore } from '../stores/auth';
import axios from '../api/axios';
const auth = useAuthStore();
const showVerification = ref(false);
const verificationCode = ref('');
const isAuthenticating = ref(false);
const authToken = ref('');
const botUsername = ref(process.env.VUE_APP_TELEGRAM_BOT_USERNAME || 'HB3_Accelerator_Bot');
const errorMessage = ref('');
const checkInterval = ref(null);
const startTelegramAuth = () => {
// Открываем Telegram бота в новом окне
window.open('https://t.me/your_bot_username', '_blank');
showVerification.value = true;
};
// Формируем ссылку на бота с параметром авторизации
const telegramBotLink = computed(() => {
// Возвращаем ссылку только если есть токен
if (!authToken.value) return `https://t.me/${botUsername.value}`;
return `https://t.me/${botUsername.value}?start=auth_${authToken.value}`;
});
async function startAuth() {
const verifyCode = async () => {
try {
// Сначала запрашиваем токен
const response = await auth.createTelegramAuthToken();
const response = await axios.post('/api/auth/telegram/verify', {
code: verificationCode.value
});
if (response.success) {
authToken.value = response.token;
// Теперь можно включить режим авторизации
isAuthenticating.value = true;
// И запустить проверку
checkInterval.value = setInterval(checkAuthStatus, 3000);
// Открываем Telegram
console.log(`Открывается ссылка на Telegram: ${telegramBotLink.value}`);
window.open(telegramBotLink.value, '_blank');
} else {
errorMessage.value = response.error || 'Не удалось начать авторизацию';
if (response.data.success) {
auth.setTelegramAuth(response.data);
}
} catch (error) {
console.error('Error starting Telegram auth:', error);
errorMessage.value = 'Ошибка при инициализации авторизации';
console.error('Error verifying Telegram code:', error);
}
}
};
async function checkAuthStatus() {
try {
const response = await auth.checkTelegramAuthStatus(authToken.value);
if (response.success && response.authenticated) {
// Авторизация успешна, очищаем интервал и состояние
clearInterval(checkInterval.value);
isAuthenticating.value = false;
// Здесь можно добавить дополнительные действия после успешной авторизации
}
} catch (error) {
console.error('Error checking auth status:', error);
}
}
function cancelAuth() {
clearInterval(checkInterval.value);
isAuthenticating.value = false;
authToken.value = '';
errorMessage.value = '';
}
function copyAuthCommand() {
const command = `/auth ${authToken.value}`;
navigator.clipboard.writeText(command);
// Можно добавить уведомление о копировании
}
const cancelVerification = () => {
showVerification.value = false;
verificationCode.value = '';
};
</script>
<style scoped>

View File

@@ -5,7 +5,7 @@
</div>
<div v-if="!authStore.isAuthenticated">
<button @click="handleConnectWallet" class="connect-button" :disabled="loading">
<button @click="connectWallet" class="connect-button" :disabled="loading">
<div v-if="loading" class="spinner"></div>
{{ loading ? 'Подключение...' : 'Подключить кошелек' }}
</button>
@@ -19,7 +19,7 @@
<script setup>
import { ref, onMounted } from 'vue';
import { connectWallet } from '../utils/wallet';
import { connectWithWallet } from '../utils/wallet';
import { useAuthStore } from '../stores/auth';
import { useRouter } from 'vue-router';
@@ -29,6 +29,17 @@ const loading = ref(false);
const error = ref('');
const isConnecting = ref(false);
const props = defineProps({
onWalletAuth: {
type: Function,
required: true
},
isAuthenticated: {
type: Boolean,
required: true
}
});
// Форматирование адреса кошелька
const formatAddress = (address) => {
if (!address) return '';
@@ -36,29 +47,17 @@ const formatAddress = (address) => {
};
// Функция для подключения кошелька
const handleConnectWallet = async () => {
console.log('Нажата кнопка "Подключить кошелек"');
isConnecting.value = true;
const connectWallet = async () => {
loading.value = true;
error.value = '';
try {
const result = await connectWallet();
console.log('Результат подключения:', result);
if (result.success) {
authStore.isAuthenticated = true;
authStore.user = { address: result.address };
authStore.isAdmin = result.isAdmin;
authStore.authType = result.authType;
router.push({ name: 'home' });
} else {
error.value = result.error || 'Ошибка подключения кошелька';
}
await props.onWalletAuth();
} catch (err) {
console.error('Ошибка при подключении кошелька:', err);
error.value = 'Ошибка подключения кошелька';
} finally {
isConnecting.value = false;
loading.value = false;
}
};
@@ -112,7 +111,22 @@ onMounted(async () => {
// Функция для отключения кошелька
const disconnectWallet = async () => {
await authStore.logout();
try {
// Сначала отключаем MetaMask
if (window.ethereum) {
try {
// Просто очищаем слушатели событий
window.ethereum.removeAllListeners();
} catch (error) {
console.error('Error disconnecting MetaMask:', error);
}
}
// Затем выполняем выход из системы
await authStore.disconnect(router);
} catch (error) {
console.error('Error disconnecting wallet:', error);
}
};
</script>

View File

@@ -1,41 +1,46 @@
<template>
<div class="conversation-list">
<div class="list-header">
<h3>Диалоги</h3>
<button @click="createNewConversation" class="new-conversation-btn">
<span>+</span> Новый диалог
</button>
</div>
<div v-if="authStore.isAuthenticated">
<div class="conversation-list">
<div class="list-header">
<h3>Диалоги</h3>
<button @click="createNewConversation" class="new-conversation-btn">
<span>+</span> Новый диалог
</button>
</div>
<div v-if="loading" class="loading">Загрузка диалогов...</div>
<div v-if="loading" class="loading">Загрузка диалогов...</div>
<div v-else-if="conversations.length === 0" class="empty-list">
<p>У вас пока нет диалогов.</p>
<p>Создайте новый диалог, чтобы начать общение с ИИ-ассистентом.</p>
</div>
<div v-else-if="conversations.length === 0" class="empty-list">
<p>У вас пока нет диалогов.</p>
<p>Создайте новый диалог, чтобы начать общение с ИИ-ассистентом.</p>
</div>
<div v-else class="conversations">
<div
v-for="conversation in conversations"
:key="conversation.conversation_id"
:class="[
'conversation-item',
{ active: selectedConversationId === conversation.conversation_id },
]"
@click="selectConversation(conversation.conversation_id)"
>
<div class="conversation-title">{{ conversation.title }}</div>
<div class="conversation-meta">
<span class="message-count">{{ conversation.message_count }} сообщений</span>
<span class="time">{{ formatTime(conversation.last_activity) }}</span>
<div v-else class="conversations">
<div
v-for="conversation in conversations"
:key="conversation.conversation_id"
:class="[
'conversation-item',
{ active: selectedConversationId === conversation.conversation_id },
]"
@click="selectConversation(conversation.conversation_id)"
>
<div class="conversation-title">{{ conversation.title }}</div>
<div class="conversation-meta">
<span class="message-count">{{ conversation.message_count }} сообщений</span>
<span class="time">{{ formatTime(conversation.last_activity) }}</span>
</div>
</div>
</div>
</div>
</div>
<div v-else class="connect-wallet-prompt">
<p>Подключите кошелек для просмотра бесед</p>
</div>
</template>
<script setup>
import { ref, onMounted, computed, defineEmits } from 'vue';
import { ref, onMounted, computed, defineEmits, watch } from 'vue';
import { useAuthStore } from '../../stores/auth';
import axios from 'axios';
@@ -46,6 +51,14 @@ const conversations = ref([]);
const loading = ref(true);
const selectedConversationId = ref(null);
// Следим за изменением статуса аутентификации
watch(() => authStore.isAuthenticated, (isAuthenticated) => {
if (!isAuthenticated) {
conversations.value = []; // Очищаем список бесед при отключении
selectedConversationId.value = null;
}
});
// Загрузка списка диалогов
const fetchConversations = async () => {
try {
@@ -222,4 +235,10 @@ defineExpose({
.time {
font-size: 0.8rem;
}
.connect-wallet-prompt {
text-align: center;
padding: 2rem;
color: #666;
}
</style>

View File

@@ -44,15 +44,40 @@ const handleEnter = (event) => {
// Отправка сообщения
const sendMessage = async () => {
if (!message.value.trim() || sending.value) return;
const messageText = message.value.trim();
if (!messageText) return;
const userMessage = {
id: Date.now(),
content: messageText,
role: auth.isAuthenticated ? 'user' : 'guest',
timestamp: new Date().toISOString()
};
messages.value.push(userMessage);
try {
sending.value = true;
// Логируем параметры запроса
console.log('Sending message to Ollama:', {
message: messageText,
language: userLanguage.value
});
const response = await axios.post(
`/api/messages/conversations/${props.conversationId}/messages`,
{ content: message.value }
);
const response = await axios.post('/api/chat/message', {
message: messageText,
language: userLanguage.value
});
// Логируем ответ от Ollama
console.log('Response from Ollama:', response.data);
// Обработка ответа
messages.value.push({
id: Date.now() + 1,
content: response.data.message,
role: 'assistant',
timestamp: new Date().toISOString()
});
// Очищаем поле ввода
message.value = '';
@@ -65,7 +90,7 @@ const sendMessage = async () => {
// Уведомляем родительский компонент о новых сообщениях
emit('message-sent', [response.data.userMessage, response.data.aiMessage]);
} catch (error) {
console.error('Error sending message:', error);
console.error('Ошибка при отправке сообщения:', error);
} finally {
sending.value = false;
}
@@ -81,6 +106,61 @@ defineExpose({
resetInput,
focus: () => textareaRef.value?.focus(),
});
const sendGuestMessage = async (messageText) => {
if (!messageText.trim()) return;
const userMessage = {
id: Date.now(),
content: messageText,
role: 'user',
timestamp: new Date().toISOString(),
isGuest: true
};
// Добавляем сообщение пользователя в локальную историю
messages.value.push(userMessage);
// Сохраняем сообщение в массиве гостевых сообщений
guestMessages.value.push(userMessage);
// Сохраняем гостевые сообщения в localStorage
localStorage.setItem('guestMessages', JSON.stringify(guestMessages.value));
// Очищаем поле ввода
newMessage.value = '';
// Прокрутка вниз
await nextTick();
if (messagesContainer.value) {
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
}
// Устанавливаем состояние загрузки
isLoading.value = true;
// Вместо отправки запроса к Ollama, отправляем сообщение с кнопками для аутентификации
const authMessage = {
id: Date.now() + 1,
content: 'Чтобы продолжить, пожалуйста, аутентифицируйтесь.',
role: 'assistant',
timestamp: new Date().toISOString(),
isGuest: true,
showAuthOptions: true // Указываем, что нужно показать кнопки аутентификации
};
messages.value.push(authMessage);
guestMessages.value.push(authMessage);
localStorage.setItem('guestMessages', JSON.stringify(guestMessages.value));
// Прокрутка вниз
await nextTick();
if (messagesContainer.value) {
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
}
isLoading.value = false;
};
</script>
<style scoped>

View File

@@ -1,28 +1,34 @@
<template>
<div class="message-thread" ref="threadContainer">
<div v-if="loading" class="loading">Загрузка сообщений...</div>
<div v-if="authStore.isAuthenticated">
<div class="message-thread" ref="threadContainer">
<div v-if="loading" class="loading">Загрузка сообщений...</div>
<div v-else-if="messages.length === 0" class="empty-thread">
<p>Нет сообщений. Начните диалог, отправив сообщение.</p>
</div>
<div v-else-if="messages.length === 0" class="empty-thread">
<p>Нет сообщений. Начните диалог, отправив сообщение.</p>
</div>
<div v-else class="messages">
<div v-for="message in messages" :key="message.id" :class="['message', message.sender_type]">
<div class="message-content">{{ message.content }}</div>
<div class="message-meta">
<span class="time">{{ formatTime(message.created_at) }}</span>
<span v-if="message.channel" class="channel">
{{ channelName(message.channel) }}
</span>
<div v-else class="messages">
<div v-for="message in messages" :key="message.id" :class="['message', message.sender_type]">
<div class="message-content">{{ message.content }}</div>
<div class="message-meta">
<span class="time">{{ formatTime(message.created_at) }}</span>
<span v-if="message.channel" class="channel">
{{ channelName(message.channel) }}
</span>
</div>
</div>
</div>
</div>
</div>
<div v-else class="connect-wallet-prompt">
<p>Пожалуйста, подключите кошелек для просмотра сообщений</p>
</div>
</template>
<script setup>
import { ref, onMounted, watch, nextTick, defineExpose } from 'vue';
import axios from 'axios';
import { useAuthStore } from '@/stores/auth'
const props = defineProps({
conversationId: {
@@ -34,6 +40,7 @@ const props = defineProps({
const messages = ref([]);
const loading = ref(true);
const threadContainer = ref(null);
const authStore = useAuthStore()
// Загрузка сообщений диалога
const fetchMessages = async () => {
@@ -104,6 +111,13 @@ watch(
}
);
// Следим за изменением статуса аутентификации
watch(() => authStore.isAuthenticated, (isAuthenticated) => {
if (!isAuthenticated) {
messages.value = []; // Очищаем сообщения при отключении
}
});
// Загрузка сообщений при монтировании компонента
onMounted(() => {
if (props.conversationId) {
@@ -191,4 +205,10 @@ defineExpose({
border-radius: 3px;
background-color: #f0f0f0;
}
.connect-wallet-prompt {
text-align: center;
padding: 2rem;
color: #666;
}
</style>