From a7be7936085d1af15b4ddc46ba485c3c53fa1200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D1=8F?= Date: Sat, 14 Jun 2025 02:01:19 +0300 Subject: [PATCH] change file type and fix agents --- .../routers/kfu-m-24-1/sber_mobile/index.js | 2 +- .../initiatives-ai-agents/moderation.js | 56 -------- .../initiatives-ai-agents/moderation.ts | 24 +++- .../initiatives-ai-agents/picture.js | 36 ----- .../initiatives-ai-agents/picture.ts | 18 ++- .../kfu-m-24-1/sber_mobile/moderate.js | 124 ++++++++++++++++++ .../kfu-m-24-1/sber_mobile/moderate.ts | 78 ----------- 7 files changed, 157 insertions(+), 181 deletions(-) delete mode 100644 server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/moderation.js delete mode 100644 server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/picture.js create mode 100644 server/routers/kfu-m-24-1/sber_mobile/moderate.js delete mode 100644 server/routers/kfu-m-24-1/sber_mobile/moderate.ts diff --git a/server/routers/kfu-m-24-1/sber_mobile/index.js b/server/routers/kfu-m-24-1/sber_mobile/index.js index a13e53e..da592c7 100644 --- a/server/routers/kfu-m-24-1/sber_mobile/index.js +++ b/server/routers/kfu-m-24-1/sber_mobile/index.js @@ -15,7 +15,7 @@ const buildingsRouter = require('./buildings'); const userApartmentsRouter = require('./user_apartments'); const avatarRouter = require('./media'); const supportRouter = require('./supportApi'); -const moderateRouter = require('./moderate.ts').default; +const moderateRouter = require('./moderate.js'); module.exports = router; diff --git a/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/moderation.js b/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/moderation.js deleted file mode 100644 index 88896b8..0000000 --- a/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/moderation.js +++ /dev/null @@ -1,56 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.moderationText = void 0; -const node_https_1 = require("node:https"); -const langchain_gigachat_1 = require("langchain-gigachat"); -const zod_1 = require("zod"); -const httpsAgent = new node_https_1.Agent({ - rejectUnauthorized: false, -}); -const llm = new langchain_gigachat_1.GigaChat({ - credentials: process.env.GIGA_AUTH, - temperature: 0.2, - model: 'GigaChat-2', - httpsAgent, -}); -// возвращаю комментарий + исправленное предложение + булево значение -const moderationLlm = llm.withStructuredOutput(zod_1.z.object({ - comment: zod_1.z.string(), - fixedText: zod_1.z.string().optional(), - isApproved: zod_1.z.boolean(), -})); -const moderationText = async (title, body) => { - const prompt = ` - Представь, что ты модерируешь предложения от жильцов многоквартирного дома (это личная инициатива по улучшения, - не имеющая отношения к Управляющей компании). - - Заголовок: ${title} - Основной текст: ${body} - - Твои задачи: - 1. Проверь предложение и заголовок на спам. - 2. Проверь, чтобы заголовок и текст были на одну тему. - 3. Проверь само предложение пользователя на отсутствие грубой лексики и пошлостей. - 4. Проверь грамматику. - 5. Проверь на бессмысленность предложения. Оно не должно содержать только случайные символы. - 6. Не должно быть рекламы, ссылок и т.д. - 7. Проверь предложение на информативность, оно не должно быть слишком коротким. - 8. Предложение должно быть в вежливой форме. - - - Если все правила соблюдены, то предложение принимается! - - Правила написания комментария: - - Если предложение отклоняется, верни комментарий со следующей формулировкой: - "Предложение отклонено. Причина: (укажи проблему)" - Правила написания fixedBody: - - Если предложение отклонено, то верни в поле "fixedBody" новый текст, который будет соответствовать правилам. - - Если предложение отклонено и содержит запрещённый контент (рекламу, личные данные), удали всю информацию, - которая противоречит правилам, и верни в только подходящий фрагмент, сохраняя общий смысл. - - Если текст не представляет никакой ценности, возврати в поле "fixedBody" правило, - по которому оно не прошло. - -Если предложение принимается, то ничего не возвращай в поле fixedBody. - `; - const result = await moderationLlm.invoke(prompt); - return [result.comment, result.fixedText, result.isApproved]; -}; -exports.moderationText = moderationText; 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 00e5e0b..7a34fdb 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 @@ -35,23 +35,33 @@ export const moderationText = async (title: string, body: string): Promise<[stri 4. Проверь грамматику. 5. Проверь на бессмысленность предложения. Оно не должно содержать только случайные символы. 6. Не должно быть рекламы, ссылок и т.д. - 7. Проверь предложение на информативность, оно не должно быть слишком коротким. + 7. Проверь предложение на информативность, предложение не может быть коротким, оно должно ясно отражжать суть инициативы. 8. Предложение должно быть в вежливой форме. - Если все правила соблюдены, то предложение принимается! + - Если предложение отклонено, всегда пиши комментарий и fixedText! + Правила написания комментария: - - Если предложение отклоняется, верни комментарий со следующей формулировкой: + - Если предложение отклоняется, пиши комментарий со следующей формулировкой: "Предложение отклонено. Причина: (укажи проблему)" - Правила написания fixedBody: - - Если предложение отклонено, то верни в поле "fixedBody" новый текст, который будет соответствовать правилам. + + Правила написания fixedText: + - Если предложение отклонено, то верни в поле "fixedText" измененный текст, который будет соответствовать правилам. - Если предложение отклонено и содержит запрещённый контент (рекламу, личные данные), удали всю информацию, которая противоречит правилам, и верни в только подходящий фрагмент, сохраняя общий смысл. - - Если текст не представляет никакой ценности, возврати в поле "fixedBody" правило, + - Если текст не представляет никакой ценности, возврати в поле "fixedText" правило, по которому оно не прошло. - -Если предложение принимается, то ничего не возвращай в поле fixedBody. + -Если предложение принимается, то ничего не возвращай в поле fixedText. ` + const result = await moderationLlm.invoke(prompt); - + console.log(result) + // Дополнительная проверка + if(!result.isApproved && result.comment.trim() === '' && result.fixedText.trim() === '') { + result.comment = 'Предложение отклонено. Причина: несоблюдение требований к оформлению или содержанию.', + result.fixedText = body + } + return [result.comment, result.fixedText, result.isApproved]; }; \ No newline at end of file diff --git a/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/picture.js b/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/picture.js deleted file mode 100644 index 0b54adc..0000000 --- a/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/picture.js +++ /dev/null @@ -1,36 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.generatePicture = exports.llm = void 0; -const gigachat_1 = require("gigachat"); -const node_https_1 = require("node:https"); -const httpsAgent = new node_https_1.Agent({ - rejectUnauthorized: false, -}); -exports.llm = new gigachat_1.GigaChat({ - credentials: process.env.GIGA_AUTH, - model: 'GigaChat-2', - httpsAgent, -}); -const generatePicture = async (prompt) => { - const resp = await exports.llm.chat({ - messages: [ - { - "role": "system", - "content": "Ты — Василий Кандинский для жильцов многоквартирного дома" - }, - { - role: "user", - content: `Старайся передать атмосферу уюта и безопасности. - Нарисуй картинку подходящую для такого события: ${prompt} - В картинке не должно быть текста, только изображение.`, - }, - ], - function_call: 'auto', - }); - // Получение изображения по идентификатору - const detectedImage = (0, gigachat_1.detectImage)(resp.choices[0]?.message.content ?? ''); - const image = await exports.llm.getImage(detectedImage?.uuid ?? ''); - // Возвращаем содержимое изображения - return image.content; -}; -exports.generatePicture = generatePicture; 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 febea0b..2544dd3 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 @@ -3,6 +3,7 @@ import { Agent } from 'node:https'; const httpsAgent = new Agent({ rejectUnauthorized: false, + timeout: 60000 }); export const llm = new GigaChat({ @@ -30,8 +31,19 @@ export const generatePicture = async (prompt: string) => { // Получение изображения по идентификатору const detectedImage = detectImage(resp.choices[0]?.message.content ?? ''); - const image = await llm.getImage(detectedImage?.uuid ?? ''); + + if (!detectedImage?.uuid) { + throw new Error('Не удалось получить UUID изображения из ответа GigaChat'); + } + + const image = await llm.getImage(detectedImage.uuid); - // Возвращаем содержимое изображения - return image.content; + // Возвращаем содержимое изображения, убеждаясь что это Buffer + if (Buffer.isBuffer(image.content)) { + return image.content; + } else if (typeof image.content === 'string') { + return Buffer.from(image.content, 'binary'); + } else { + throw new Error('Unexpected image content type: ' + typeof image.content); + } } diff --git a/server/routers/kfu-m-24-1/sber_mobile/moderate.js b/server/routers/kfu-m-24-1/sber_mobile/moderate.js new file mode 100644 index 0000000..25b5e3a --- /dev/null +++ b/server/routers/kfu-m-24-1/sber_mobile/moderate.js @@ -0,0 +1,124 @@ +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'); + +// Обработчик для модерации текста +router.post('/moderate', async (req, res) => { + try { + const { title, body } = req.body; + if (!title || !body) { + res.status(400).json({ error: 'Заголовок и текст обязательны' }); + return; + } + + console.log('Запрос на модерацию:', { title: title.substring(0, 50), body: body.substring(0, 100) }); + + const [comment, fixedText, isApproved] = await moderationText(title, body); + + 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: 'Необходимо указать запрос для генерации' }); + return; + } + + // Генерируем изображение + const imageBuffer = await generatePicture(prompt); + + //console.log('Изображение получено, размер буфера:', imageBuffer?.length || 0, 'байт'); + 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`; + + let uploadResult; + let retries = 0; + const maxRetries = 5; + + while (retries < maxRetries) { + try { + uploadResult = await supabase.storage + .from('images') + .upload(filename, imageBuffer, { + contentType: 'image/jpeg', + upsert: true + }); + + if (!uploadResult.error) { + break; // Успешная загрузка + } + + //console.warn(`Попытка загрузки ${retries + 1} неудачна:`, uploadResult.error); + retries++; + + if (retries < maxRetries) { + // Ждем перед повторной попыткой + await new Promise(resolve => setTimeout(resolve, 1000 * retries)); + } + } catch (error) { + //console.warn(`Попытка загрузки ${retries + 1} неудачна (исключение):`, error.message); + retries++; + + if (retries < maxRetries) { + // Ждем перед повторной попыткой + await new Promise(resolve => setTimeout(resolve, 1000 * retries)); + } else { + throw error; // Перебрасываем ошибку после всех попыток + } + } + } + + if (uploadResult?.error) { + //console.error('Supabase storage error after all retries:', uploadResult.error); + res.status(500).json({ error: 'Ошибка при сохранении изображения после нескольких попыток' }); + return; + } + + //console.log('Изображение успешно загружено в Supabase Storage:', filename); + + // Получаем публичный URL + const { data: urlData } = supabase.storage + .from('images') + .getPublicUrl(filename); + + res.json({ + success: true, + imageUrl: urlData.publicUrl, + imagePath: filename + }); + + } catch (error) { + //console.error('Error in image generation:', error); + res.status(500).json({ error: 'Внутренняя ошибка сервера', details: error.message }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/server/routers/kfu-m-24-1/sber_mobile/moderate.ts b/server/routers/kfu-m-24-1/sber_mobile/moderate.ts deleted file mode 100644 index 332554a..0000000 --- a/server/routers/kfu-m-24-1/sber_mobile/moderate.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Router, Request, Response } from 'express'; -import { moderationText } from './initiatives-ai-agents/moderation'; -import { generatePicture } from './initiatives-ai-agents/picture'; - -const { getSupabaseClient } = require('./supabaseClient'); - -const router = Router(); - -// Обработчик для модерации текста -router.post('/moderate', async (req: Request, res: Response) => { - try { - const { title, body } = req.body; - if (!title || !body) { - res.status(400).json({ error: 'Заголовок и текст обязательны' }); - return; - } - - const [comment, fixedText, isApproved] = await moderationText(title, body); - res.json({ - comment, - fixedText, - isApproved - }); - } catch (error: any) { - console.error('Error in moderation:', error); - res.status(500).json({ error: 'Внутренняя ошибка сервера', details: error.message }); - } -}); - -// Обработчик для генерации изображений -router.post('/generate-image', async (req: Request, res: Response) => { - try { - const { prompt, userId } = req.body; - if (!prompt) { - res.status(400).json({ error: 'Необходимо указать запрос для генерации' }); - return; - } - - // Получаем изображение - const imageBuffer = await generatePicture(prompt); - - // Получаем Supabase клиент - const supabase = getSupabaseClient(); - - // Генерируем уникальное имя файла - const timestamp = Date.now(); - const filename = `image_${userId || 'user'}_${timestamp}.jpg`; - - // Загружаем в Supabase - const { data, error } = await supabase.storage - .from('images') - .upload(filename, imageBuffer, { - contentType: 'image/jpeg', - upsert: true - }); - - if (error) { - res.status(500).json({ error: 'Ошибка при сохранении изображения' }); - return; - } - - // Получаем публичный URL изображения - const { data: urlData } = supabase.storage - .from('images') - .getPublicUrl(filename); - - res.json({ - success: true, - imageUrl: urlData.publicUrl, - imagePath: filename - }); - } catch (error: any) { - console.error('Error in image generation:', error); - res.status(500).json({ error: 'Внутренняя ошибка сервера', details: error.message }); - } -}); - -export default router; \ No newline at end of file