change moderate and initiatives
This commit is contained in:
@@ -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,
|
||||||
|
});
|
||||||
@@ -1,32 +1,23 @@
|
|||||||
import { Agent } from 'node:https';
|
import { llm_mod } from './llm'
|
||||||
import { GigaChat } from "langchain-gigachat";
|
|
||||||
import { z } from "zod";
|
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 = `
|
const prompt = `
|
||||||
Представь, что ты модерируешь предложения от жильцов многоквартирного дома (это личная инициатива по улучшения,
|
Представь, что ты модерируешь предложения от жильцов многоквартирного дома (это личная инициатива по улучшения,
|
||||||
не имеющая отношения к Управляющей компании).
|
не имеющая отношения к Управляющей компании).
|
||||||
|
|
||||||
Заголовок: ${title}
|
Заголовок: ${title}
|
||||||
Основной текст: ${body}
|
Основной текст: ${description}
|
||||||
|
|
||||||
Твои задачи:
|
Твои задачи:
|
||||||
1. Проверь предложение и заголовок на спам.
|
1. Проверь предложение и заголовок на спам.
|
||||||
@@ -58,9 +49,9 @@ export const moderationText = async (title: string, body: string): Promise<[stri
|
|||||||
const result = await moderationLlm.invoke(prompt);
|
const result = await moderationLlm.invoke(prompt);
|
||||||
console.log(result)
|
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.comment = 'Предложение отклонено. Причина: несоблюдение требований к оформлению или содержанию.',
|
||||||
result.fixedText = body
|
result.fixedText = description
|
||||||
}
|
}
|
||||||
|
|
||||||
return [result.comment, result.fixedText, result.isApproved];
|
return [result.comment, result.fixedText, result.isApproved];
|
||||||
|
|||||||
@@ -1,19 +1,8 @@
|
|||||||
import { GigaChat, detectImage } from 'gigachat';
|
import { llm_gen } from './llm'
|
||||||
import { Agent } from 'node:https';
|
import { detectImage } from 'gigachat';
|
||||||
|
|
||||||
const httpsAgent = new Agent({
|
export const generatePicture = async (prompt: string, GIGA_AUTH) => {
|
||||||
rejectUnauthorized: false,
|
const resp = await llm_gen(GIGA_AUTH).chat({
|
||||||
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({
|
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
@@ -36,7 +25,7 @@ export const generatePicture = async (prompt: string) => {
|
|||||||
throw new Error('Не удалось получить UUID изображения из ответа GigaChat');
|
throw new Error('Не удалось получить UUID изображения из ответа GigaChat');
|
||||||
}
|
}
|
||||||
|
|
||||||
const image = await llm.getImage(detectedImage.uuid);
|
const image = await llm_gen(GIGA_AUTH).getImage(detectedImage.uuid);
|
||||||
|
|
||||||
// Возвращаем содержимое изображения, убеждаясь что это Buffer
|
// Возвращаем содержимое изображения, убеждаясь что это Buffer
|
||||||
if (Buffer.isBuffer(image.content)) {
|
if (Buffer.isBuffer(image.content)) {
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ router.get('/initiatives/:id', async (req, res) => {
|
|||||||
// Создать инициативу
|
// Создать инициативу
|
||||||
router.post('/initiatives', async (req, res) => {
|
router.post('/initiatives', async (req, res) => {
|
||||||
const supabase = getSupabaseClient();
|
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([
|
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();
|
]).select().single();
|
||||||
if (error) return res.status(400).json({ error: error.message });
|
if (error) return res.status(400).json({ error: error.message });
|
||||||
res.json(data);
|
res.json(data);
|
||||||
|
|||||||
@@ -2,63 +2,76 @@ const router = require('express').Router();
|
|||||||
const { moderationText } = require('./initiatives-ai-agents/moderation.ts');
|
const { moderationText } = require('./initiatives-ai-agents/moderation.ts');
|
||||||
const { generatePicture } = require('./initiatives-ai-agents/picture.ts');
|
const { generatePicture } = require('./initiatives-ai-agents/picture.ts');
|
||||||
const { getSupabaseClient } = require('./supabaseClient');
|
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) => {
|
router.post('/moderate', async (req, res) => {
|
||||||
|
|
||||||
|
const GIGA_AUTH = await getGigaKey();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { title, body } = req.body;
|
const { title, description, building_id, creator_id, target_amount, status } = req.body;
|
||||||
if (!title || !body) {
|
|
||||||
res.status(400).json({ error: 'Заголовок и текст обязательны' });
|
if (!title || !description) {
|
||||||
|
res.status(400).json({ error: 'Заголовок и описание обязательны' });
|
||||||
return;
|
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 });
|
console.log('Результат модерации получен:', { comment, fixedText: fixedText?.substring(0, 100), isApproved });
|
||||||
|
|
||||||
// Дополнительная проверка на стороне сервера
|
// Если модерация не прошла, возвращаем undefined
|
||||||
if (!isApproved && (!comment || comment.trim() === '')) {
|
if (!isApproved) {
|
||||||
console.warn('Обнаружен некорректный результат модерации - пустой комментарий при отклонении');
|
if (!comment || comment.trim() === '') {
|
||||||
}
|
console.warn('Обнаружен некорректный результат модерации - пустой комментарий при отклонении');
|
||||||
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
comment,
|
comment,
|
||||||
fixedText,
|
fixedText,
|
||||||
isApproved
|
isApproved,
|
||||||
});
|
initiative: undefined
|
||||||
} 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Генерируем изображение
|
// Модерация прошла, генерируем изображение используя заголовок как промпт
|
||||||
const imageBuffer = await generatePicture(prompt);
|
console.log('Модерация прошла, генерируем изображение с промптом:', title);
|
||||||
|
|
||||||
|
const imageBuffer = await generatePicture(title, GIGA_AUTH);
|
||||||
|
|
||||||
//console.log('Изображение получено, размер буфера:', imageBuffer?.length || 0, 'байт');
|
|
||||||
if (!imageBuffer || imageBuffer.length === 0) {
|
if (!imageBuffer || imageBuffer.length === 0) {
|
||||||
res.status(500).json({ error: 'Получен пустой буфер изображения' });
|
res.status(500).json({ error: 'Получен пустой буфер изображения' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log('Начинаем загрузку в Supabase Storage...');
|
|
||||||
|
|
||||||
// Получаем Supabase клиент и создаем имя файла
|
// Получаем Supabase клиент и создаем имя файла
|
||||||
const supabase = getSupabaseClient();
|
const supabase = getSupabaseClient();
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
const filename = `image_${userId || 'user'}_${timestamp}.jpg`;
|
const filename = `image_${creator_id}_${timestamp}.jpg`;
|
||||||
|
|
||||||
|
// Загружаем изображение в Supabase Storage
|
||||||
let uploadResult;
|
let uploadResult;
|
||||||
let retries = 0;
|
let retries = 0;
|
||||||
const maxRetries = 5;
|
const maxRetries = 5;
|
||||||
@@ -76,7 +89,6 @@ router.post('/generate-image', async (req, res) => {
|
|||||||
break; // Успешная загрузка
|
break; // Успешная загрузка
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.warn(`Попытка загрузки ${retries + 1} неудачна:`, uploadResult.error);
|
|
||||||
retries++;
|
retries++;
|
||||||
|
|
||||||
if (retries < maxRetries) {
|
if (retries < maxRetries) {
|
||||||
@@ -84,7 +96,7 @@ router.post('/generate-image', async (req, res) => {
|
|||||||
await new Promise(resolve => setTimeout(resolve, 1000 * retries));
|
await new Promise(resolve => setTimeout(resolve, 1000 * retries));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//console.warn(`Попытка загрузки ${retries + 1} неудачна (исключение):`, error.message);
|
console.warn(`Попытка загрузки ${retries + 1} неудачна (исключение):`, error.message);
|
||||||
retries++;
|
retries++;
|
||||||
|
|
||||||
if (retries < maxRetries) {
|
if (retries < maxRetries) {
|
||||||
@@ -97,26 +109,54 @@ router.post('/generate-image', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (uploadResult?.error) {
|
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: 'Ошибка при сохранении изображения после нескольких попыток' });
|
res.status(500).json({ error: 'Ошибка при сохранении изображения после нескольких попыток' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log('Изображение успешно загружено в Supabase Storage:', filename);
|
console.log('Изображение успешно загружено в Supabase Storage:', filename);
|
||||||
|
|
||||||
// Получаем публичный URL
|
// Получаем публичный URL
|
||||||
const { data: urlData } = supabase.storage
|
const { data: urlData } = supabase.storage
|
||||||
.from('images')
|
.from('images')
|
||||||
.getPublicUrl(filename);
|
.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({
|
res.json({
|
||||||
success: true,
|
comment,
|
||||||
imageUrl: urlData.publicUrl,
|
fixedText,
|
||||||
imagePath: filename
|
isApproved,
|
||||||
|
initiative
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} 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 });
|
res.status(500).json({ error: 'Внутренняя ошибка сервера', details: error.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user