add tickets creation

This commit is contained in:
Max
2025-06-14 00:16:02 +03:00
parent 1aeb62d490
commit ca81e19d14
4 changed files with 123 additions and 27 deletions

View File

@@ -170,7 +170,7 @@ CREATE TABLE payment_service_details (
CREATE TABLE tickets ( CREATE TABLE tickets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id), user_id UUID NOT NULL REFERENCES auth.users(id),
building_id UUID NOT NULL REFERENCES buildings(id), apartment_id UUID NOT NULL REFERENCES apartments(id),
title TEXT NOT NULL, title TEXT NOT NULL,
description TEXT NOT NULL, description TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('open', 'in_progress', 'resolved')), status TEXT NOT NULL CHECK (status IN ('open', 'in_progress', 'resolved')),
@@ -197,6 +197,7 @@ CREATE INDEX idx_votes_initiative ON votes(initiative_id);
CREATE INDEX idx_messages_chat ON messages(chat_id); CREATE INDEX idx_messages_chat ON messages(chat_id);
CREATE INDEX idx_cameras_building ON cameras(building_id); CREATE INDEX idx_cameras_building ON cameras(building_id);
CREATE INDEX idx_tickets_user ON tickets(user_id); CREATE INDEX idx_tickets_user ON tickets(user_id);
CREATE INDEX idx_tickets_apartment ON tickets(apartment_id);
CREATE INDEX idx_apartments_building ON apartments(building_id); CREATE INDEX idx_apartments_building ON apartments(building_id);
CREATE INDEX idx_apartment_residents_apartment ON apartment_residents(apartment_id); CREATE INDEX idx_apartment_residents_apartment ON apartment_residents(apartment_id);
CREATE INDEX idx_apartment_residents_user ON apartment_residents(user_id); CREATE INDEX idx_apartment_residents_user ON apartment_residents(user_id);

View File

@@ -0,0 +1,66 @@
import { StructuredTool, ToolRunnableConfig } from '@langchain/core/tools';
import { z } from 'zod';
import { CallbackManagerForToolRun } from '@langchain/core/callbacks/manager';
import { getSupabaseClient } from '../supabaseClient';
export class CreateTicketTool extends StructuredTool {
name = 'create_ticket';
description = 'Создает заявку в системе. ВАЖНО: используй этот инструмент ТОЛЬКО после получения явного согласия пользователя на создание заявки с конкретным текстом.';
schema = z.object({
title: z.string().describe('Заголовок заявки'),
description: z.string().describe('Подробное описание проблемы'),
category: z.string().describe('Категория заявки (например: ремонт, уборка, техническая_поддержка, жалоба)'),
});
private userId: string;
private apartmentId: string;
constructor(userId: string, apartmentId: string) {
super();
this.userId = userId;
this.apartmentId = apartmentId;
}
protected async _call(
arg: z.infer<typeof this.schema>,
runManager?: CallbackManagerForToolRun,
parentConfig?: ToolRunnableConfig<Record<string, any>>
): Promise<string> {
try {
if (!this.apartmentId) {
return 'Не удалось определить вашу квартиру. Обратитесь к администратору для создания заявки.';
}
const supabase = getSupabaseClient();
const { data: ticket, error } = await supabase
.from('tickets')
.insert({
user_id: this.userId,
apartment_id: this.apartmentId,
title: arg.title,
description: arg.description,
category: arg.category,
status: 'open'
})
.select()
.single();
if (error) {
return 'Произошла ошибка при создании заявки. Попробуйте позже или обратитесь к администратору.';
}
return `Заявка успешно создана!
Номер заявки: ${ticket.id}
Заголовок: ${ticket.title}
Статус: Открыта
Дата создания: ${new Date(ticket.created_at).toLocaleString('ru-RU')}
Ваша заявка принята в работу. Мы свяжемся с вами в ближайшее время.`;
} catch (error) {
return 'Произошла техническая ошибка при создании заявки. Пожалуйста, попробуйте позже.';
}
}
}

View File

@@ -5,6 +5,7 @@ import { MemorySaver } from '@langchain/langgraph';
import gigachat from './gigachat'; import gigachat from './gigachat';
import { SupportContextTool } from './support-context-tool'; import { SupportContextTool } from './support-context-tool';
import { KnowledgeBaseTool } from './knowledge-base-tool'; import { KnowledgeBaseTool } from './knowledge-base-tool';
import { CreateTicketTool } from './create-ticket-tool';
export interface SupportAgentConfig { export interface SupportAgentConfig {
temperature?: number; temperature?: number;
@@ -51,36 +52,52 @@ export class SupportAgent {
} }
private getDefaultSystemPrompt(): string { private getDefaultSystemPrompt(): string {
return `Ты - профессиональный агент службы поддержки. return `Ты - профессиональный агент службы поддержки управляющей компании.
Твои основные задачи: ОСНОВНЫЕ ПРИНЦИПЫ:
- Помогать пользователям решать их вопросы и проблемы - Помогай только с реальными проблемами и вопросами, связанными с ЖКХ, управляющей компанией и приложением
- Отвечать вежливо, профессионально и по существу - Будь вежливым, профессиональным и по существу
- Предоставлять четкие и понятные инструкции - Если вопрос неуместен, не связан с твоими обязанностями или является развлекательным - вежливо откажись и перенаправь к основным темам
- Проявлять эмпатию к проблемам пользователей
- Если не знаешь ответ, честно сообщить об этом и предложить альтернативные способы получения помощи
У тебя есть доступ к двум инструментам: ДОСТУПНЫЕ ИНСТРУМЕНТЫ:
1. get_support_context - получает историю предыдущих сообщений пользователя 1. get_support_context - получает историю сообщений пользователя
ВСЕГДА используй этот инструмент ПЕРВЫМ ДЕЛОМ при получении каждого нового сообщения ВСЕГДА используй ПЕРВЫМ при каждом новом сообщении
2. search_knowledge_base - ищет информацию в базе знаний компании 2. search_knowledge_base - поиск в базе знаний компании
Используй этот инструмент для вопросов о: Используй ТОЛЬКО для серьезных вопросов о:
- Процессах оплаты и тарифах - Процессах оплаты ЖКХ и тарифах
- Подаче заявок и документооборота - Подаче заявок и документообороте
- Правилах и регламентах УК - Правилах и регламентах УК
- Технических вопросах приложения - Технических вопросах приложения
- Любых специфических вопросах о компании - Процедурах и инструкциях компании
ВАЖНО: Сначала получи контекст, затем при необходимости найди информацию в базе знаний, и только после этого отвечай пользователю. 3. create_ticket - создание заявки в системе
Используй ТОЛЬКО когда:
- Пользователь сообщает о реальной проблеме (поломка, неисправность, жалоба)
- Проблема требует вмешательства УК или технических служб
- ОБЯЗАТЕЛЬНО сначала покажи пользователю полный текст заявки
- Получи ЯВНОЕ согласие пользователя перед созданием
- НЕ создавай заявки для консультационных вопросов
Если в истории есть предыдущие обращения, обязательно ссылайся на них в своем ответе. ПРАВИЛА ИСПОЛЬЗОВАНИЯ ИНСТРУМЕНТОВ:
- НЕ используй search_knowledge_base и create_ticket для:
* Общих вопросов и болтовни
* Развлекательных запросов
* Вопросов не по теме ЖКХ/УК
* Простых консультаций, которые можно решить обычным ответом
Всегда отвечай на русском языке и старайся быть максимально полезным.`; АЛГОРИТМ РАБОТЫ:
1. Получи контекст истории сообщений
2. Определи, является ли вопрос уместным и серьезным
3. Если нужна специфическая информация - найди в базе знаний
4. Если нужно создать заявку - покажи текст и получи согласие
5. Дай полный и полезный ответ
Всегда отвечай на русском языке и фокусируйся на помощи с реальными проблемами ЖКХ.`;
} }
public async processMessage(userMessage: string): Promise<SupportResponse> { public async processMessage(userMessage: string, apartmentId?: string): Promise<SupportResponse> {
try { try {
const messages: BaseMessage[] = []; const messages: BaseMessage[] = [];
@@ -91,7 +108,21 @@ export class SupportAgent {
messages.push(new HumanMessage(userMessage)); messages.push(new HumanMessage(userMessage));
const response = await this.agent.invoke({ // Создаем инструменты с актуальным apartmentId
const tools = [
new SupportContextTool(this.userId),
new KnowledgeBaseTool(),
new CreateTicketTool(this.userId, apartmentId || '')
];
// Пересоздаем агента с обновленными инструментами
const tempAgent = createReactAgent({
llm: this.llm,
tools: tools,
checkpointSaver: this.memorySaver
});
const response = await tempAgent.invoke({
messages: messages messages: messages
}, { }, {
configurable: { configurable: {
@@ -102,7 +133,7 @@ export class SupportAgent {
const lastMessage = response.messages[response.messages.length - 1]; const lastMessage = response.messages[response.messages.length - 1];
return { return {
content: lastMessage.content || 'Извините, не удалось сформировать ответ.', content: typeof lastMessage.content === 'string' ? lastMessage.content : 'Извините, не удалось сформировать ответ.',
success: true success: true
}; };

View File

@@ -57,7 +57,7 @@ router.get('/support', async (req, res) => {
// POST /api/support // POST /api/support
router.post('/support', async (req, res) => { router.post('/support', async (req, res) => {
const supabase = getSupabaseClient(); const supabase = getSupabaseClient();
const { user_id, message } = req.body; const { user_id, message, apartment_id } = req.body;
if (!user_id || !message) { if (!user_id || !message) {
return res.status(400).json({ error: 'user_id и message обязательны' }); return res.status(400).json({ error: 'user_id и message обязательны' });
@@ -76,8 +76,8 @@ router.post('/support', async (req, res) => {
// Получаем агента для пользователя // Получаем агента для пользователя
const agent = getUserAgent(user_id); const agent = getUserAgent(user_id);
// Получаем ответ от AI-агента // Получаем ответ от AI-агента, передавая apartment_id
const aiResponse = await agent.processMessage(message); const aiResponse = await agent.processMessage(message, apartment_id);
if (!aiResponse.success) { if (!aiResponse.success) {
console.error('Ошибка AI-агента:', aiResponse.error); console.error('Ошибка AI-агента:', aiResponse.error);
@@ -116,8 +116,6 @@ router.post('/support', async (req, res) => {
} }
}); });
// DELETE /api/support/history/:userId - Очистка истории диалога // DELETE /api/support/history/:userId - Очистка истории диалога
router.delete('/support/history/:userId', async (req, res) => { router.delete('/support/history/:userId', async (req, res) => {
const { userId } = req.params; const { userId } = req.params;