From 09174abaa42bf581951305d2829c368dbfda9f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D1=8F?= Date: Thu, 12 Jun 2025 19:39:57 +0300 Subject: [PATCH] add ai_initiatives --- .../initiatives-ai-agents/moderation.ts | 57 +++++++++++++++ .../initiatives-ai-agents/picture.ts | 37 ++++++++++ .../kfu-m-24-1/sber_mobile/moderate.js | 73 +++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/moderation.ts create mode 100644 server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/picture.ts create mode 100644 server/routers/kfu-m-24-1/sber_mobile/moderate.js 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 new file mode 100644 index 0000000..00e5e0b --- /dev/null +++ b/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/moderation.ts @@ -0,0 +1,57 @@ +import { Agent } from 'node:https'; +import { GigaChat } from "langchain-gigachat"; +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]> => { + 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]; +}; \ No newline at end of file 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 new file mode 100644 index 0000000..febea0b --- /dev/null +++ b/server/routers/kfu-m-24-1/sber_mobile/initiatives-ai-agents/picture.ts @@ -0,0 +1,37 @@ +import { GigaChat, detectImage } from 'gigachat'; +import { Agent } from 'node:https'; + +const httpsAgent = new Agent({ + rejectUnauthorized: false, +}); + +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({ + messages: [ + { + "role": "system", + "content": "Ты — Василий Кандинский для жильцов многоквартирного дома" + }, + { + role: "user", + content: `Старайся передать атмосферу уюта и безопасности. + Нарисуй картинку подходящую для такого события: ${prompt} + В картинке не должно быть текста, только изображение.`, + }, + ], + function_call: 'auto', + }); + + // Получение изображения по идентификатору + const detectedImage = detectImage(resp.choices[0]?.message.content ?? ''); + const image = await llm.getImage(detectedImage?.uuid ?? ''); + + // Возвращаем содержимое изображения + return 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..76cccd3 --- /dev/null +++ b/server/routers/kfu-m-24-1/sber_mobile/moderate.js @@ -0,0 +1,73 @@ +const router = require('express').Router(); +const { moderationText } = require('./initiatives-ai-agents/moderation'); +const { generatePicture } = require('./initiatives-ai-agents/picture'); +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; + } + + const [comment, fixedText, isApproved] = await moderationText(title, body); + res.json({ + comment, + fixedText, + isApproved + }); + } catch (error) { + res.status(500).json({ error: 'Внутренняя ошибка сервера' }); + } +}); + +// Обработчик для генерации изображений +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); + + // Получаем 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) { + res.status(500).json({ error: 'Внутренняя ошибка сервера' }); + } +}); + +module.exports = router; \ No newline at end of file