diff --git a/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/llm.ts b/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/llm.ts new file mode 100644 index 0000000..a5a0e23 --- /dev/null +++ b/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/llm.ts @@ -0,0 +1,22 @@ +import { GigaChat as GigaChatLang} from 'langchain-gigachat'; +import { GigaChat } from 'gigachat'; +import { Agent } from 'node:https'; + +const httpsAgent = new Agent({ + rejectUnauthorized: false, +}); + +export const llm_mod = (GIGA_AUTH) => + new GigaChatLang({ + credentials: GIGA_AUTH, + temperature: 0.2, + model: 'GigaChat-2-Max', + httpsAgent, +}); + +export const llm_gen = (GIGA_AUTH) => + new GigaChat({ + credentials: GIGA_AUTH, + model: 'GigaChat-2', + httpsAgent, +}); \ No newline at end of file diff --git a/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/moderation.ts b/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/moderation.ts index 7a34fdb..dc2022c 100644 --- a/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/moderation.ts +++ b/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/moderation.ts @@ -1,32 +1,23 @@ -import { Agent } from 'node:https'; -import { GigaChat } from "langchain-gigachat"; +import { llm_mod } from './llm' import { z } from "zod"; -const httpsAgent = new Agent({ - rejectUnauthorized: false, -}); - -const llm = new GigaChat({ - credentials: process.env.GIGA_AUTH, - temperature: 0.2, - model: 'GigaChat-2', - httpsAgent, -}); // возвращаю комментарий + исправленное предложение + булево значение -const moderationLlm = llm.withStructuredOutput(z.object({ - comment: z.string(), - fixedText: z.string().optional(), - isApproved: z.boolean(), -}) as any) -export const moderationText = async (title: string, body: string): Promise<[string, string | undefined, boolean]> => { +export const moderationText = async (title: string, description: string, GIGA_AUTH): Promise<[string, string | undefined, boolean]> => { + + const moderationLlm = llm_mod(GIGA_AUTH).withStructuredOutput(z.object({ + comment: z.string(), + fixedText: z.string().optional(), + isApproved: z.boolean(), + }) as any) + const prompt = ` Представь, что ты модерируешь предложения от жильцов многоквартирного дома (это личная инициатива по улучшения, не имеющая отношения к Управляющей компании). Заголовок: ${title} - Основной текст: ${body} + Основной текст: ${description} Твои задачи: 1. Проверь предложение и заголовок на спам. @@ -58,9 +49,9 @@ export const moderationText = async (title: string, body: string): Promise<[stri const result = await moderationLlm.invoke(prompt); console.log(result) // Дополнительная проверка - if(!result.isApproved && result.comment.trim() === '' && result.fixedText.trim() === '') { + if(!result.isApproved && result.comment.trim() === '' && (!result.fixedText || result.fixedText.trim() === '')) { result.comment = 'Предложение отклонено. Причина: несоблюдение требований к оформлению или содержанию.', - result.fixedText = body + result.fixedText = description } return [result.comment, result.fixedText, result.isApproved]; diff --git a/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/picture.ts b/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/picture.ts index 2544dd3..d216c5d 100644 --- a/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/picture.ts +++ b/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/picture.ts @@ -1,19 +1,8 @@ -import { GigaChat, detectImage } from 'gigachat'; -import { Agent } from 'node:https'; +import { llm_gen } from './llm' +import { detectImage } from 'gigachat'; -const httpsAgent = new Agent({ - rejectUnauthorized: false, - timeout: 60000 -}); - -export const llm = new GigaChat({ - credentials: process.env.GIGA_AUTH, - model: 'GigaChat-2', - httpsAgent, -}); - -export const generatePicture = async (prompt: string) => { - const resp = await llm.chat({ +export const generatePicture = async (prompt: string, GIGA_AUTH) => { + const resp = await llm_gen(GIGA_AUTH).chat({ messages: [ { "role": "system", @@ -36,7 +25,7 @@ export const generatePicture = async (prompt: string) => { throw new Error('Не удалось получить UUID изображения из ответа GigaChat'); } - const image = await llm.getImage(detectedImage.uuid); + const image = await llm_gen(GIGA_AUTH).getImage(detectedImage.uuid); // Возвращаем содержимое изображения, убеждаясь что это Buffer if (Buffer.isBuffer(image.content)) { diff --git a/server/routers/kfu-m-24-1/sber_mobile/initiatives.js b/server/routers/kfu-m-24-1/sber_mobile/initiatives.js index 3ca0562..42f82e4 100644 --- a/server/routers/kfu-m-24-1/sber_mobile/initiatives.js +++ b/server/routers/kfu-m-24-1/sber_mobile/initiatives.js @@ -38,9 +38,9 @@ router.get('/initiatives/:id', async (req, res) => { // Создать инициативу router.post('/initiatives', async (req, res) => { const supabase = getSupabaseClient(); - const { building_id, creator_id, title, description, status, target_amount, image_url } = req.body; + const { building_id, creator_id, title, description, status, target_amount, current_amount, image_url } = req.body; const { data, error } = await supabase.from('initiatives').insert([ - { building_id, creator_id, title, description, status, target_amount, image_url } + { building_id, creator_id, title, description, status, target_amount, current_amount: current_amount || 0, image_url } ]).select().single(); if (error) return res.status(400).json({ error: error.message }); res.json(data); diff --git a/server/routers/kfu-m-24-1/sber_mobile/moderate.js b/server/routers/kfu-m-24-1/sber_mobile/moderate.js index 25b5e3a..0e8b3f8 100644 --- a/server/routers/kfu-m-24-1/sber_mobile/moderate.js +++ b/server/routers/kfu-m-24-1/sber_mobile/moderate.js @@ -2,63 +2,76 @@ const router = require('express').Router(); const { moderationText } = require('./initiatives-ai-agents/moderation.ts'); const { generatePicture } = require('./initiatives-ai-agents/picture.ts'); const { getSupabaseClient } = require('./supabaseClient'); +const { getGigaAuth } = require('./get-constants'); -// Обработчик для модерации текста +async function getGigaKey() { + const GIGA_AUTH = await getGigaAuth(); + return GIGA_AUTH; + } + +// Обработчик для модерации и создания инициативы router.post('/moderate', async (req, res) => { + + const GIGA_AUTH = await getGigaKey(); + try { - const { title, body } = req.body; - if (!title || !body) { - res.status(400).json({ error: 'Заголовок и текст обязательны' }); + const { title, description, building_id, creator_id, target_amount, status } = req.body; + + if (!title || !description) { + res.status(400).json({ error: 'Заголовок и описание обязательны' }); return; } - console.log('Запрос на модерацию:', { title: title.substring(0, 50), body: body.substring(0, 100) }); + if (!building_id || !creator_id) { + res.status(400).json({ error: 'ID дома и создателя обязательны' }); + return; + } - const [comment, fixedText, isApproved] = await moderationText(title, body); + // Валидация статуса, если передан + const validStatuses = ['moderation', 'review', 'fundraising', 'approved', 'rejected']; + if (status && !validStatuses.includes(status)) { + res.status(400).json({ error: `Недопустимый статус. Допустимые значения: ${validStatuses.join(', ')}` }); + return; + } + + console.log('Запрос на модерацию:', { title: title.substring(0, 50), description: description.substring(0, 100) }); + + // Модерация текста (передаем title и description как body) + const [comment, fixedText, isApproved] = await moderationText(title, description, GIGA_AUTH); console.log('Результат модерации получен:', { comment, fixedText: fixedText?.substring(0, 100), isApproved }); - // Дополнительная проверка на стороне сервера - if (!isApproved && (!comment || comment.trim() === '')) { - console.warn('Обнаружен некорректный результат модерации - пустой комментарий при отклонении'); - } - - res.json({ - comment, - fixedText, - isApproved - }); - } catch (error) { - console.error('Error in moderation:', error); - res.status(500).json({ error: 'Внутренняя ошибка сервера', details: error.message }); - } -}); - -// Обработчик для генерации изображений -router.post('/generate-image', async (req, res) => { - try { - const { prompt, userId } = req.body; - if (!prompt) { - res.status(400).json({ error: 'Необходимо указать запрос для генерации' }); + // Если модерация не прошла, возвращаем undefined + if (!isApproved) { + if (!comment || comment.trim() === '') { + console.warn('Обнаружен некорректный результат модерации - пустой комментарий при отклонении'); + } + + res.json({ + comment, + fixedText, + isApproved, + initiative: undefined + }); return; } - - // Генерируем изображение - const imageBuffer = await generatePicture(prompt); - //console.log('Изображение получено, размер буфера:', imageBuffer?.length || 0, 'байт'); + // Модерация прошла, генерируем изображение используя заголовок как промпт + console.log('Модерация прошла, генерируем изображение с промптом:', title); + + const imageBuffer = await generatePicture(title, GIGA_AUTH); + if (!imageBuffer || imageBuffer.length === 0) { res.status(500).json({ error: 'Получен пустой буфер изображения' }); return; } - //console.log('Начинаем загрузку в Supabase Storage...'); - // Получаем Supabase клиент и создаем имя файла const supabase = getSupabaseClient(); const timestamp = Date.now(); - const filename = `image_${userId || 'user'}_${timestamp}.jpg`; + const filename = `image_${creator_id}_${timestamp}.jpg`; + // Загружаем изображение в Supabase Storage let uploadResult; let retries = 0; const maxRetries = 5; @@ -76,7 +89,6 @@ router.post('/generate-image', async (req, res) => { break; // Успешная загрузка } - //console.warn(`Попытка загрузки ${retries + 1} неудачна:`, uploadResult.error); retries++; if (retries < maxRetries) { @@ -84,7 +96,7 @@ router.post('/generate-image', async (req, res) => { await new Promise(resolve => setTimeout(resolve, 1000 * retries)); } } catch (error) { - //console.warn(`Попытка загрузки ${retries + 1} неудачна (исключение):`, error.message); + console.warn(`Попытка загрузки ${retries + 1} неудачна (исключение):`, error.message); retries++; if (retries < maxRetries) { @@ -97,26 +109,54 @@ router.post('/generate-image', async (req, res) => { } if (uploadResult?.error) { - //console.error('Supabase storage error after all retries:', uploadResult.error); + console.error('Supabase storage error after all retries:', uploadResult.error); res.status(500).json({ error: 'Ошибка при сохранении изображения после нескольких попыток' }); return; } - //console.log('Изображение успешно загружено в Supabase Storage:', filename); + console.log('Изображение успешно загружено в Supabase Storage:', filename); // Получаем публичный URL const { data: urlData } = supabase.storage .from('images') .getPublicUrl(filename); - + + // Определяем статус: если передан в запросе, используем его, иначе 'review' + const finalStatus = status || 'review'; + + // Создаем инициативу в базе данных + const { data: initiative, error: initiativeError } = await supabase + .from('initiatives') + .insert([{ + building_id, + creator_id, + title: fixedText || title, + description, + status: finalStatus, + target_amount: target_amount || null, + current_amount: 0, + image_url: urlData.publicUrl + }]) + .select() + .single(); + + if (initiativeError) { + console.error('Ошибка создания инициативы:', initiativeError); + res.status(500).json({ error: 'Ошибка при создании инициативы', details: initiativeError.message }); + return; + } + + console.log('Инициатива успешно создана:', initiative.id); + res.json({ - success: true, - imageUrl: urlData.publicUrl, - imagePath: filename + comment, + fixedText, + isApproved, + initiative }); } catch (error) { - //console.error('Error in image generation:', error); + console.error('Error in moderation and initiative creation:', error); res.status(500).json({ error: 'Внутренняя ошибка сервера', details: error.message }); } });