Spaces:
Build error
Build error
Gmagl commited on
Commit ·
30ad7ef
1
Parent(s): acbcb20
Fix: All API routes corrected - no fs/path, proper typing
Browse files- src/app/api/characters/route.ts +37 -163
- src/app/api/influencers/route.ts +33 -413
- src/app/api/pets/route.ts +37 -286
- src/app/api/storytelling/route.ts +51 -271
- src/app/api/trends/route.ts +31 -311
src/app/api/characters/route.ts
CHANGED
|
@@ -1,212 +1,86 @@
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import { db } from "@/lib/db";
|
| 3 |
import ZAI from "z-ai-web-dev-sdk";
|
| 4 |
-
import fs from "fs/promises";
|
| 5 |
-
import path from "path";
|
| 6 |
|
| 7 |
-
const CHARACTERS_DIR = path.join(process.cwd(), "download", "characters");
|
| 8 |
-
|
| 9 |
-
async function ensureDir() {
|
| 10 |
-
try {
|
| 11 |
-
await fs.mkdir(CHARACTERS_DIR, { recursive: true });
|
| 12 |
-
} catch {
|
| 13 |
-
// ya existe
|
| 14 |
-
}
|
| 15 |
-
}
|
| 16 |
-
|
| 17 |
-
// GET - Listar personajes
|
| 18 |
export async function GET(request: NextRequest) {
|
| 19 |
try {
|
| 20 |
-
const characters = await db.character.findMany({
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
orderBy: { createdAt: "desc" }
|
| 25 |
-
}
|
| 26 |
-
},
|
| 27 |
-
orderBy: { createdAt: "desc" }
|
| 28 |
-
});
|
| 29 |
-
|
| 30 |
-
return NextResponse.json({
|
| 31 |
-
success: true,
|
| 32 |
-
characters,
|
| 33 |
-
total: characters.length
|
| 34 |
-
});
|
| 35 |
-
|
| 36 |
-
} catch (error) {
|
| 37 |
-
console.error("Error fetching characters:", error);
|
| 38 |
-
return NextResponse.json(
|
| 39 |
-
{ success: false, error: "Error al obtener personajes" },
|
| 40 |
-
{ status: 500 }
|
| 41 |
-
);
|
| 42 |
}
|
| 43 |
}
|
| 44 |
|
| 45 |
-
// POST - Crear personaje
|
| 46 |
export async function POST(request: NextRequest) {
|
| 47 |
try {
|
| 48 |
const body = await request.json();
|
| 49 |
-
const {
|
| 50 |
-
name,
|
| 51 |
-
description,
|
| 52 |
-
generateReference = false,
|
| 53 |
-
traits
|
| 54 |
-
} = body;
|
| 55 |
|
| 56 |
if (!name) {
|
| 57 |
-
return NextResponse.json(
|
| 58 |
-
{ success: false, error: "El nombre del personaje es requerido" },
|
| 59 |
-
{ status: 400 }
|
| 60 |
-
);
|
| 61 |
}
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
let referenceImage = null;
|
| 66 |
-
let characterTraits = traits;
|
| 67 |
|
| 68 |
-
// Generar imagen de referencia si se solicita
|
| 69 |
if (generateReference) {
|
| 70 |
try {
|
| 71 |
const zai = await ZAI.create();
|
| 72 |
-
|
| 73 |
-
// Crear prompt detallado para el personaje
|
| 74 |
-
const characterPrompt = `Character reference sheet: ${name}. ${description || "Original character"}. Full body view, multiple angles (front, side, back), consistent character design, clear details, neutral pose, white background, character turnaround, reference sheet style. Professional character design, consistent facial features, consistent body proportions.`;
|
| 75 |
-
|
| 76 |
const response = await zai.images.generations.create({
|
| 77 |
-
prompt:
|
| 78 |
-
size: "
|
| 79 |
});
|
| 80 |
-
|
| 81 |
const imageBase64 = response.data[0]?.base64;
|
| 82 |
if (imageBase64) {
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
await fs.writeFile(filepath, buffer);
|
| 87 |
-
referenceImage = `/download/characters/${filename}`;
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
-
// Si no se proporcionaron traits, extraerlos con IA
|
| 91 |
-
if (!traits) {
|
| 92 |
-
const traitsResponse = await zai.chat.completions.create({
|
| 93 |
-
messages: [
|
| 94 |
-
{
|
| 95 |
-
role: "system",
|
| 96 |
-
content: "Eres un experto en diseño de personajes. Extrae los rasgos físicos clave del personaje para mantener consistencia en futuras generaciones. Responde SOLO en formato JSON con: { \"face\": \"\", \"body\": \"\", \"hair\": \"\", \"clothing\": \"\", \"colors\": \"\", \"distinctive\": \"\" }"
|
| 97 |
-
},
|
| 98 |
-
{
|
| 99 |
-
role: "user",
|
| 100 |
-
content: `Personaje: ${name}. Descripción: ${description || "Original character"}`
|
| 101 |
-
}
|
| 102 |
-
]
|
| 103 |
-
});
|
| 104 |
-
|
| 105 |
-
const traitsText = traitsResponse.choices[0]?.message?.content || "";
|
| 106 |
-
try {
|
| 107 |
-
const jsonMatch = traitsText.match(/\{[\s\S]*\}/);
|
| 108 |
-
if (jsonMatch) {
|
| 109 |
-
characterTraits = jsonMatch[0];
|
| 110 |
-
}
|
| 111 |
-
} catch {
|
| 112 |
-
characterTraits = JSON.stringify({ description: description || "Original character" });
|
| 113 |
}
|
| 114 |
}
|
| 115 |
-
|
| 116 |
-
} catch (genError) {
|
| 117 |
-
console.error("Error generando referencia:", genError);
|
| 118 |
-
// Continuar sin imagen de referencia
|
| 119 |
-
}
|
| 120 |
}
|
| 121 |
|
| 122 |
-
|
| 123 |
-
const character = await db.character.create({
|
| 124 |
-
data: {
|
| 125 |
-
name,
|
| 126 |
-
description: description || null,
|
| 127 |
-
referenceImage,
|
| 128 |
-
traits: characterTraits || JSON.stringify({ name, description })
|
| 129 |
-
}
|
| 130 |
-
});
|
| 131 |
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
character,
|
| 135 |
-
message: `Personaje "${name}" creado exitosamente`
|
| 136 |
});
|
| 137 |
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
return NextResponse.json(
|
| 141 |
-
{ success: false, error: "Error al crear personaje" },
|
| 142 |
-
{ status: 500 }
|
| 143 |
-
);
|
| 144 |
}
|
| 145 |
}
|
| 146 |
|
| 147 |
-
// PUT - Actualizar personaje
|
| 148 |
export async function PUT(request: NextRequest) {
|
| 149 |
try {
|
| 150 |
const body = await request.json();
|
| 151 |
const { id, name, description, traits } = body;
|
|
|
|
| 152 |
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
);
|
| 158 |
-
}
|
| 159 |
-
|
| 160 |
-
const character = await db.character.update({
|
| 161 |
-
where: { id },
|
| 162 |
-
data: {
|
| 163 |
-
name: name || undefined,
|
| 164 |
-
description: description || undefined,
|
| 165 |
-
traits: traits || undefined,
|
| 166 |
-
}
|
| 167 |
-
});
|
| 168 |
-
|
| 169 |
-
return NextResponse.json({
|
| 170 |
-
success: true,
|
| 171 |
-
character
|
| 172 |
-
});
|
| 173 |
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
{ status: 500 }
|
| 179 |
-
);
|
| 180 |
}
|
| 181 |
}
|
| 182 |
|
| 183 |
-
// DELETE - Eliminar personaje
|
| 184 |
export async function DELETE(request: NextRequest) {
|
| 185 |
try {
|
| 186 |
const { searchParams } = new URL(request.url);
|
| 187 |
const id = searchParams.get("id");
|
|
|
|
| 188 |
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
}
|
| 195 |
-
|
| 196 |
-
await db.character.delete({
|
| 197 |
-
where: { id }
|
| 198 |
-
});
|
| 199 |
-
|
| 200 |
-
return NextResponse.json({
|
| 201 |
-
success: true,
|
| 202 |
-
message: "Personaje eliminado"
|
| 203 |
-
});
|
| 204 |
-
|
| 205 |
-
} catch (error) {
|
| 206 |
-
console.error("Error deleting character:", error);
|
| 207 |
-
return NextResponse.json(
|
| 208 |
-
{ success: false, error: "Error al eliminar personaje" },
|
| 209 |
-
{ status: 500 }
|
| 210 |
-
);
|
| 211 |
}
|
| 212 |
-
}
|
|
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import { db } from "@/lib/db";
|
| 3 |
import ZAI from "z-ai-web-dev-sdk";
|
|
|
|
|
|
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
export async function GET(request: NextRequest) {
|
| 6 |
try {
|
| 7 |
+
const characters = await db.character.findMany({ orderBy: { createdAt: "desc" } });
|
| 8 |
+
return NextResponse.json({ success: true, characters, total: characters.length });
|
| 9 |
+
} catch {
|
| 10 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
}
|
| 12 |
}
|
| 13 |
|
|
|
|
| 14 |
export async function POST(request: NextRequest) {
|
| 15 |
try {
|
| 16 |
const body = await request.json();
|
| 17 |
+
const { name, description, generateReference, traits } = body;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
if (!name) {
|
| 20 |
+
return NextResponse.json({ success: false, error: "Nombre requerido" }, { status: 400 });
|
|
|
|
|
|
|
|
|
|
| 21 |
}
|
| 22 |
|
| 23 |
+
let referenceImage: string | null = null;
|
| 24 |
+
let characterTraits: string | null = null;
|
|
|
|
|
|
|
| 25 |
|
|
|
|
| 26 |
if (generateReference) {
|
| 27 |
try {
|
| 28 |
const zai = await ZAI.create();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
const response = await zai.images.generations.create({
|
| 30 |
+
prompt: "Character reference: " + name + ", " + (description || "original character"),
|
| 31 |
+
size: "1024x1024"
|
| 32 |
});
|
|
|
|
| 33 |
const imageBase64 = response.data[0]?.base64;
|
| 34 |
if (imageBase64) {
|
| 35 |
+
referenceImage = "generated_" + Date.now();
|
| 36 |
+
if (!traits) {
|
| 37 |
+
characterTraits = JSON.stringify({ name, description });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
}
|
| 39 |
}
|
| 40 |
+
} catch {}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
}
|
| 42 |
|
| 43 |
+
const finalTraits = traits || characterTraits || JSON.stringify({ name });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
+
const character = await db.character.create({
|
| 46 |
+
data: { name, description: description || null, referenceImage, traits: finalTraits }
|
|
|
|
|
|
|
| 47 |
});
|
| 48 |
|
| 49 |
+
return NextResponse.json({ success: true, character });
|
| 50 |
+
} catch {
|
| 51 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
| 52 |
}
|
| 53 |
}
|
| 54 |
|
|
|
|
| 55 |
export async function PUT(request: NextRequest) {
|
| 56 |
try {
|
| 57 |
const body = await request.json();
|
| 58 |
const { id, name, description, traits } = body;
|
| 59 |
+
if (!id) return NextResponse.json({ success: false, error: "ID requerido" }, { status: 400 });
|
| 60 |
|
| 61 |
+
const data: { name?: string; description?: string | null; traits?: string } = {};
|
| 62 |
+
if (name) data.name = name;
|
| 63 |
+
if (description !== undefined) data.description = description || null;
|
| 64 |
+
if (traits) data.traits = traits;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
+
const character = await db.character.update({ where: { id }, data });
|
| 67 |
+
return NextResponse.json({ success: true, character });
|
| 68 |
+
} catch {
|
| 69 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
| 70 |
}
|
| 71 |
}
|
| 72 |
|
|
|
|
| 73 |
export async function DELETE(request: NextRequest) {
|
| 74 |
try {
|
| 75 |
const { searchParams } = new URL(request.url);
|
| 76 |
const id = searchParams.get("id");
|
| 77 |
+
if (!id) return NextResponse.json({ success: false, error: "ID requerido" }, { status: 400 });
|
| 78 |
|
| 79 |
+
await db.pet.deleteMany({ where: { characterId: id } });
|
| 80 |
+
await db.content.updateMany({ where: { characterId: id }, data: { characterId: null } });
|
| 81 |
+
await db.character.delete({ where: { id } });
|
| 82 |
+
return NextResponse.json({ success: true });
|
| 83 |
+
} catch {
|
| 84 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
}
|
| 86 |
+
}
|
src/app/api/influencers/route.ts
CHANGED
|
@@ -1,450 +1,70 @@
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import ZAI from "z-ai-web-dev-sdk";
|
| 3 |
-
import {
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
{
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
platform: "Instagram",
|
| 11 |
-
followers: 2500000,
|
| 12 |
-
engagement: 3.2,
|
| 13 |
-
niche: "Fashion & Lifestyle",
|
| 14 |
-
style: {
|
| 15 |
-
aesthetic: "Hyperrealistic CGI",
|
| 16 |
-
tone: "Gen Z influencer",
|
| 17 |
-
themes: ["fashion", "music", "social issues"]
|
| 18 |
-
},
|
| 19 |
-
contentTypes: ["photos", "videos", "stories"],
|
| 20 |
-
postingSchedule: { frequency: "3-4 per week", bestTimes: ["12:00", "18:00", "21:00"] },
|
| 21 |
-
visualStyle: { colors: ["warm", "urban"], lighting: "golden hour", editing: "cinematic" },
|
| 22 |
-
monetizationType: ["brand deals", "merchandise"],
|
| 23 |
-
signatureElements: ["freckles", "bangs", "streetwear"],
|
| 24 |
-
petCompanion: false,
|
| 25 |
-
lessons: [
|
| 26 |
-
"Consistencia visual es clave",
|
| 27 |
-
"Narrativa de vida ficticia pero creíble",
|
| 28 |
-
"Colaboraciones con marcas reales",
|
| 29 |
-
"Activismo social para conectar con audiencia"
|
| 30 |
-
]
|
| 31 |
-
},
|
| 32 |
-
{
|
| 33 |
-
name: "Rozy",
|
| 34 |
-
handle: "@rozy.gram",
|
| 35 |
-
platform: "Instagram",
|
| 36 |
-
followers: 180000,
|
| 37 |
-
engagement: 4.5,
|
| 38 |
-
niche: "Fashion & K-pop style",
|
| 39 |
-
style: {
|
| 40 |
-
aesthetic: "Korean beauty standard",
|
| 41 |
-
tone: "Friendly and trendy",
|
| 42 |
-
themes: ["fashion", "beauty", "travel"]
|
| 43 |
-
},
|
| 44 |
-
contentTypes: ["photos", "reels", "stories"],
|
| 45 |
-
postingSchedule: { frequency: "5-7 per week", bestTimes: ["11:00", "19:00"] },
|
| 46 |
-
visualStyle: { colors: ["pastel", "bright"], lighting: "soft", editing: "k-beauty" },
|
| 47 |
-
monetizationType: ["brand ambassador", "product placement"],
|
| 48 |
-
signatureElements: ["k-pop fashion", "dance videos", "korean makeup"],
|
| 49 |
-
petCompanion: true,
|
| 50 |
-
petType: "small dog",
|
| 51 |
-
lessons: [
|
| 52 |
-
"Aprovechar tendencias K-pop",
|
| 53 |
-
"Contenido de baile viral",
|
| 54 |
-
"Estética cuidada y consistente",
|
| 55 |
-
"Mascota como elemento diferenciador"
|
| 56 |
-
]
|
| 57 |
-
},
|
| 58 |
-
{
|
| 59 |
-
name: "Imma",
|
| 60 |
-
handle: "@imma.gram",
|
| 61 |
-
platform: "Instagram",
|
| 62 |
-
followers: 390000,
|
| 63 |
-
engagement: 3.8,
|
| 64 |
-
niche: "Fashion & Art",
|
| 65 |
-
style: {
|
| 66 |
-
aesthetic: "Japanese street style",
|
| 67 |
-
tone: "Artistic and edgy",
|
| 68 |
-
themes: ["fashion", "art", "technology"]
|
| 69 |
-
},
|
| 70 |
-
contentTypes: ["photos", "reels", "collaborations"],
|
| 71 |
-
postingSchedule: { frequency: "4-5 per week", bestTimes: ["10:00", "20:00"] },
|
| 72 |
-
visualStyle: { colors: ["neon accents", "monochrome"], lighting: "dramatic", editing: "artistic" },
|
| 73 |
-
monetizationType: ["brand collaborations", "NFTs", "art exhibitions"],
|
| 74 |
-
signatureElements: ["pink bob hair", "futuristic fashion", "art installations"],
|
| 75 |
-
petCompanion: false,
|
| 76 |
-
lessons: [
|
| 77 |
-
"Fusionar arte y moda",
|
| 78 |
-
"Colaboraciones con artistas",
|
| 79 |
-
"Presencia en eventos físicos",
|
| 80 |
-
"Estética japonesa distintiva"
|
| 81 |
-
]
|
| 82 |
-
},
|
| 83 |
-
{
|
| 84 |
-
name: "Noonoouri",
|
| 85 |
-
handle: "@noonoouri",
|
| 86 |
-
platform: "Instagram",
|
| 87 |
-
followers: 450000,
|
| 88 |
-
engagement: 2.9,
|
| 89 |
-
niche: "High Fashion",
|
| 90 |
-
style: {
|
| 91 |
-
aesthetic: "Cartoon-like avatar",
|
| 92 |
-
tone: "Playful luxury",
|
| 93 |
-
themes: ["fashion", "luxury brands", "lifestyle"]
|
| 94 |
-
},
|
| 95 |
-
contentTypes: ["photos", "animations", "brand content"],
|
| 96 |
-
postingSchedule: { frequency: "3-4 per week", bestTimes: ["15:00", "21:00"] },
|
| 97 |
-
visualStyle: { colors: ["pastel", "luxury gold"], lighting: "studio", editing: "animated" },
|
| 98 |
-
monetizationType: ["luxury brand deals", "fashion campaigns"],
|
| 99 |
-
signatureElements: ["big eyes", "designer outfits", "fashion week content"],
|
| 100 |
-
petCompanion: false,
|
| 101 |
-
lessons: [
|
| 102 |
-
"Estilo único de caricatura",
|
| 103 |
-
"Asociaciones con marcas de lujo",
|
| 104 |
-
"Presencia en fashion weeks",
|
| 105 |
-
"Contenido aspiracional"
|
| 106 |
-
]
|
| 107 |
-
},
|
| 108 |
-
{
|
| 109 |
-
name: "Bermuda",
|
| 110 |
-
handle: "@bermudaisbae",
|
| 111 |
-
platform: "Instagram",
|
| 112 |
-
followers: 220000,
|
| 113 |
-
engagement: 4.1,
|
| 114 |
-
niche: "Music & Pop Culture",
|
| 115 |
-
style: {
|
| 116 |
-
aesthetic: "Edgy and provocative",
|
| 117 |
-
tone: "Sassy and confident",
|
| 118 |
-
themes: ["music", "pop culture", "fashion"]
|
| 119 |
-
},
|
| 120 |
-
contentTypes: ["photos", "music videos", "controversial posts"],
|
| 121 |
-
postingSchedule: { frequency: "2-3 per week", bestTimes: ["18:00", "22:00"] },
|
| 122 |
-
visualStyle: { colors: ["dark", "neon"], lighting: "moody", editing: "music video style" },
|
| 123 |
-
monetizationType: ["music releases", "brand deals"],
|
| 124 |
-
signatureElements: ["brunette", "edgy style", "music collaborations"],
|
| 125 |
-
petCompanion: false,
|
| 126 |
-
lessons: [
|
| 127 |
-
"Personalidad controversial genera engagement",
|
| 128 |
-
"Crossovers con otros influencers IA",
|
| 129 |
-
"Contenido musical original",
|
| 130 |
-
"Narrativa de 'villana' atractiva"
|
| 131 |
-
]
|
| 132 |
-
},
|
| 133 |
-
{
|
| 134 |
-
name: "Shudu",
|
| 135 |
-
handle: "@shudu.gram",
|
| 136 |
-
platform: "Instagram",
|
| 137 |
-
followers: 230000,
|
| 138 |
-
engagement: 3.5,
|
| 139 |
-
niche: "High Fashion & Beauty",
|
| 140 |
-
style: {
|
| 141 |
-
aesthetic: "Hyperrealistic dark-skinned model",
|
| 142 |
-
tone: "Elegant and mysterious",
|
| 143 |
-
themes: ["fashion", "beauty", "diversity"]
|
| 144 |
-
},
|
| 145 |
-
contentTypes: ["photos", "editorials", "brand campaigns"],
|
| 146 |
-
postingSchedule: { frequency: "3-4 per week", bestTimes: ["14:00", "20:00"] },
|
| 147 |
-
visualStyle: { colors: ["rich tones", "gold accents"], lighting: "dramatic", editing: "editorial" },
|
| 148 |
-
monetizationType: ["fashion campaigns", "brand ambassador"],
|
| 149 |
-
signatureElements: ["dark skin", "high fashion poses", "editorial style"],
|
| 150 |
-
petCompanion: false,
|
| 151 |
-
lessons: [
|
| 152 |
-
"Representación y diversidad",
|
| 153 |
-
"Estética editorial de alta moda",
|
| 154 |
-
"Colaboraciones con diseñadores",
|
| 155 |
-
"Misterio y elegancia"
|
| 156 |
-
]
|
| 157 |
-
},
|
| 158 |
-
{
|
| 159 |
-
name: "Knox Frost",
|
| 160 |
-
handle: "@knoxfrost",
|
| 161 |
-
platform: "Instagram",
|
| 162 |
-
followers: 110000,
|
| 163 |
-
engagement: 5.2,
|
| 164 |
-
niche: "Gaming & Lifestyle",
|
| 165 |
-
style: {
|
| 166 |
-
aesthetic: "Male influencer next door",
|
| 167 |
-
tone: "Casual and relatable",
|
| 168 |
-
themes: ["gaming", "tech", "lifestyle"]
|
| 169 |
-
},
|
| 170 |
-
contentTypes: ["photos", "stories", "gaming content"],
|
| 171 |
-
postingSchedule: { frequency: "4-5 per week", bestTimes: ["16:00", "21:00"] },
|
| 172 |
-
visualStyle: { colors: ["gaming neon", "casual"], lighting: "natural", editing: "minimal" },
|
| 173 |
-
monetizationType: ["gaming partnerships", "tech reviews"],
|
| 174 |
-
signatureElements: ["glasses", "casual style", "gaming setup"],
|
| 175 |
-
petCompanion: true,
|
| 176 |
-
petType: "cat",
|
| 177 |
-
lessons: [
|
| 178 |
-
"Nichos específicos funcionan",
|
| 179 |
-
"Contenido relatable",
|
| 180 |
-
"Mascota para engagement",
|
| 181 |
-
"Presencia en comunidades gaming"
|
| 182 |
-
]
|
| 183 |
-
},
|
| 184 |
-
{
|
| 185 |
-
name: "Ayla",
|
| 186 |
-
handle: "@ayla_virtual",
|
| 187 |
-
platform: "TikTok",
|
| 188 |
-
followers: 850000,
|
| 189 |
-
engagement: 8.3,
|
| 190 |
-
niche: "Dance & Lifestyle",
|
| 191 |
-
style: {
|
| 192 |
-
aesthetic: "Gen Z dancer",
|
| 193 |
-
tone: "Energetic and fun",
|
| 194 |
-
themes: ["dance", "trends", "lifestyle"]
|
| 195 |
-
},
|
| 196 |
-
contentTypes: ["dance videos", "trending content", "duets"],
|
| 197 |
-
postingSchedule: { frequency: "daily", bestTimes: ["12:00", "18:00", "22:00"] },
|
| 198 |
-
visualStyle: { colors: ["vibrant", "trendy"], lighting: "ring light", editing: "tiktok style" },
|
| 199 |
-
monetizationType: ["brand deals", "live gifts"],
|
| 200 |
-
signatureElements: ["dance moves", "trend participation", "duets"],
|
| 201 |
-
petCompanion: true,
|
| 202 |
-
petType: "small dog",
|
| 203 |
-
lessons: [
|
| 204 |
-
"Participar en trends rápidamente",
|
| 205 |
-
"Alta frecuencia de publicación",
|
| 206 |
-
"Duets con otros creadores",
|
| 207 |
-
"Mascota en videos aumenta engagement"
|
| 208 |
-
]
|
| 209 |
-
}
|
| 210 |
];
|
| 211 |
|
| 212 |
-
// GET - Obtener influencers IA de referencia
|
| 213 |
export async function GET(request: NextRequest) {
|
| 214 |
try {
|
| 215 |
const { searchParams } = new URL(request.url);
|
| 216 |
const niche = searchParams.get("niche");
|
| 217 |
-
const platform = searchParams.get("platform");
|
| 218 |
const withPets = searchParams.get("withPets");
|
| 219 |
|
| 220 |
-
let influencers = [...
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
if (niche) {
|
| 224 |
-
influencers = influencers.filter(i =>
|
| 225 |
-
i.niche.toLowerCase().includes(niche.toLowerCase()) ||
|
| 226 |
-
i.style.themes.some(t => t.toLowerCase().includes(niche.toLowerCase()))
|
| 227 |
-
);
|
| 228 |
-
}
|
| 229 |
-
|
| 230 |
-
// Filtrar por plataforma
|
| 231 |
-
if (platform) {
|
| 232 |
-
influencers = influencers.filter(i =>
|
| 233 |
-
i.platform.toLowerCase() === platform.toLowerCase()
|
| 234 |
-
);
|
| 235 |
-
}
|
| 236 |
-
|
| 237 |
-
// Filtrar por si tienen mascota
|
| 238 |
-
if (withPets === "true") {
|
| 239 |
-
influencers = influencers.filter(i => i.petCompanion);
|
| 240 |
-
}
|
| 241 |
-
|
| 242 |
-
// Obtener de la base de datos también
|
| 243 |
-
const dbInfluencers = await prisma.aIInfluencer.findMany({
|
| 244 |
-
where: { isActive: true }
|
| 245 |
-
});
|
| 246 |
-
|
| 247 |
-
return NextResponse.json({
|
| 248 |
-
success: true,
|
| 249 |
-
influencers,
|
| 250 |
-
customInfluencers: dbInfluencers,
|
| 251 |
-
total: influencers.length + dbInfluencers.length
|
| 252 |
-
});
|
| 253 |
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
{ status: 500 }
|
| 259 |
-
);
|
| 260 |
}
|
| 261 |
}
|
| 262 |
|
| 263 |
-
// POST - Analizar y extraer estrategias de influencers IA
|
| 264 |
export async function POST(request: NextRequest) {
|
| 265 |
try {
|
| 266 |
const body = await request.json();
|
| 267 |
-
const { targetNiche,
|
| 268 |
|
| 269 |
const zai = await ZAI.create();
|
| 270 |
-
|
| 271 |
-
// Filtrar influencers relevantes
|
| 272 |
-
let relevantInfluencers = FAMOUS_AI_INFLUENCERS.filter(i => {
|
| 273 |
-
if (targetPlatform && i.platform.toLowerCase() !== targetPlatform.toLowerCase()) return false;
|
| 274 |
-
if (includePets === false && i.petCompanion) return false;
|
| 275 |
-
if (includePets === true && !i.petCompanion) return false;
|
| 276 |
-
return true;
|
| 277 |
-
});
|
| 278 |
-
|
| 279 |
-
// Análisis con IA para extraer patrones
|
| 280 |
-
const analysisPrompt = `Analiza estos influencers IA exitosos y extrae estrategias aplicables para un nuevo influencer en el nicho "${targetNiche || "general"}":
|
| 281 |
-
|
| 282 |
-
${JSON.stringify(relevantInfluencers.slice(0, 5), null, 2)}
|
| 283 |
-
|
| 284 |
-
Proporciona:
|
| 285 |
-
1. Patrones de contenido más efectivos
|
| 286 |
-
2. Estilos visuales que funcionan
|
| 287 |
-
3. Estrategias de engagement
|
| 288 |
-
4. Horarios óptimos de publicación
|
| 289 |
-
5. Elementos distintivos recomendados
|
| 290 |
-
${includePets ? "6. Cómo integrar una mascota de forma natural" : ""}
|
| 291 |
-
7. Errores comunes a evitar
|
| 292 |
-
8. Estrategias de monetización recomendadas
|
| 293 |
-
|
| 294 |
-
Responde en JSON con esta estructura:
|
| 295 |
-
{
|
| 296 |
-
"contentPatterns": [{"pattern": "", "effectiveness": 0-10, "platform": ""}],
|
| 297 |
-
"visualStyles": [{"style": "", "elements": [], "appeal": ""}],
|
| 298 |
-
"engagementStrategies": [{"strategy": "", "execution": ""}],
|
| 299 |
-
"postingSchedule": {"frequency": "", "bestTimes": [], "timezone": ""},
|
| 300 |
-
"signatureElements": [{"element": "", "uniqueness": "", "implementation": ""}],
|
| 301 |
-
${includePets ? '"petIntegration": [{"tip": "", "contentTypes": []}],': ''}
|
| 302 |
-
"mistakes": [{"mistake": "", "solution": ""}],
|
| 303 |
-
"monetizationRecommendations": [{"method": "", "potential": "", "steps": ""}],
|
| 304 |
-
"viralPotential": {"score": 0-100, "factors": []}
|
| 305 |
-
}`;
|
| 306 |
-
|
| 307 |
const completion = await zai.chat.completions.create({
|
| 308 |
messages: [
|
| 309 |
-
{
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
},
|
| 313 |
-
{
|
| 314 |
-
role: "user",
|
| 315 |
-
content: analysisPrompt
|
| 316 |
-
}
|
| 317 |
-
],
|
| 318 |
-
temperature: 0.7,
|
| 319 |
-
max_tokens: 3000,
|
| 320 |
});
|
| 321 |
|
| 322 |
-
let analysis;
|
| 323 |
try {
|
| 324 |
-
const
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
analysis = JSON.parse(match[0]);
|
| 328 |
-
}
|
| 329 |
-
} catch {
|
| 330 |
-
analysis = { raw: completion.choices[0]?.message?.content };
|
| 331 |
-
}
|
| 332 |
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
Incluye:
|
| 337 |
-
1. Concepto único del influencer
|
| 338 |
-
2. Estilo visual recomendado
|
| 339 |
-
3. 10 ideas de contenido para la primera semana
|
| 340 |
-
4. Bio y descripción sugerida
|
| 341 |
-
5. Hashtags recomendados
|
| 342 |
-
|
| 343 |
-
Responde en JSON con:
|
| 344 |
-
{
|
| 345 |
-
"character": {"name": "", "personality": "", "backstory": "", "visualDescription": ""},
|
| 346 |
-
"visualStyle": {"aesthetic": "", "colors": [], "lighting": "", "editing": ""},
|
| 347 |
-
"contentIdeas": [{"day": 1, "type": "", "description": "", "hook": ""}],
|
| 348 |
-
"bio": "",
|
| 349 |
-
"hashtags": [],
|
| 350 |
-
"uniqueSellingPoints": []
|
| 351 |
-
}`;
|
| 352 |
-
|
| 353 |
-
const personalization = await zai.chat.completions.create({
|
| 354 |
-
messages: [
|
| 355 |
-
{
|
| 356 |
-
role: "system",
|
| 357 |
-
content: "Eres un experto en crear influencers IA. Genera conceptos únicos y memorables."
|
| 358 |
-
},
|
| 359 |
-
{
|
| 360 |
-
role: "user",
|
| 361 |
-
content: personalizationPrompt
|
| 362 |
-
}
|
| 363 |
-
],
|
| 364 |
-
temperature: 0.8,
|
| 365 |
-
max_tokens: 2000,
|
| 366 |
-
});
|
| 367 |
-
|
| 368 |
-
let characterConcept;
|
| 369 |
-
try {
|
| 370 |
-
const response = personalization.choices[0]?.message?.content || "";
|
| 371 |
-
const match = response.match(/\{[\s\S]*\}/);
|
| 372 |
-
if (match) {
|
| 373 |
-
characterConcept = JSON.parse(match[0]);
|
| 374 |
-
}
|
| 375 |
-
} catch {
|
| 376 |
-
characterConcept = { raw: personalization.choices[0]?.message?.content };
|
| 377 |
-
}
|
| 378 |
-
|
| 379 |
-
return NextResponse.json({
|
| 380 |
-
success: true,
|
| 381 |
-
referenceInfluencers: relevantInfluencers.map(i => ({
|
| 382 |
-
name: i.name,
|
| 383 |
-
handle: i.handle,
|
| 384 |
-
followers: i.followers,
|
| 385 |
-
engagement: i.engagement,
|
| 386 |
-
niche: i.niche,
|
| 387 |
-
petCompanion: i.petCompanion,
|
| 388 |
-
petType: i.petType,
|
| 389 |
-
keyLessons: i.lessons.slice(0, 3)
|
| 390 |
-
})),
|
| 391 |
-
analysis,
|
| 392 |
-
characterConcept,
|
| 393 |
-
timestamp: new Date().toISOString()
|
| 394 |
-
});
|
| 395 |
-
|
| 396 |
-
} catch (error) {
|
| 397 |
-
console.error("Error analyzing influencers:", error);
|
| 398 |
-
return NextResponse.json(
|
| 399 |
-
{ success: false, error: "Error al analizar influencers" },
|
| 400 |
-
{ status: 500 }
|
| 401 |
-
);
|
| 402 |
}
|
| 403 |
}
|
| 404 |
|
| 405 |
-
// PUT - Guardar influencer personalizado
|
| 406 |
export async function PUT(request: NextRequest) {
|
| 407 |
try {
|
| 408 |
const body = await request.json();
|
| 409 |
-
const {
|
| 410 |
-
name, handle, platform, followers, engagement, niche,
|
| 411 |
-
style, contentTypes, postingSchedule, visualStyle,
|
| 412 |
-
monetizationType, signatureElements, petCompanion, petType,
|
| 413 |
-
analysis, lessons
|
| 414 |
-
} = body;
|
| 415 |
|
| 416 |
-
const influencer = await
|
| 417 |
data: {
|
| 418 |
-
name,
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
followers,
|
| 422 |
-
engagement,
|
| 423 |
-
niche,
|
| 424 |
-
style: JSON.stringify(style),
|
| 425 |
-
contentTypes: JSON.stringify(contentTypes),
|
| 426 |
-
postingSchedule: JSON.stringify(postingSchedule),
|
| 427 |
-
visualStyle: JSON.stringify(visualStyle),
|
| 428 |
-
monetizationType: JSON.stringify(monetizationType),
|
| 429 |
-
signatureElements: JSON.stringify(signatureElements),
|
| 430 |
-
petCompanion: petCompanion || false,
|
| 431 |
-
petType,
|
| 432 |
-
analysis: analysis ? JSON.stringify(analysis) : null,
|
| 433 |
-
lessons: lessons ? JSON.stringify(lessons) : null,
|
| 434 |
}
|
| 435 |
});
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
influencer,
|
| 440 |
-
message: "Influencer guardado correctamente"
|
| 441 |
-
});
|
| 442 |
-
|
| 443 |
-
} catch (error) {
|
| 444 |
-
console.error("Error saving influencer:", error);
|
| 445 |
-
return NextResponse.json(
|
| 446 |
-
{ success: false, error: "Error al guardar influencer" },
|
| 447 |
-
{ status: 500 }
|
| 448 |
-
);
|
| 449 |
}
|
| 450 |
-
}
|
|
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import ZAI from "z-ai-web-dev-sdk";
|
| 3 |
+
import { db } from "@/lib/db";
|
| 4 |
|
| 5 |
+
const FAMOUS_INFLUENCERS = [
|
| 6 |
+
{ name: "Lil Miquela", handle: "@lilmiquela", platform: "Instagram", followers: 2500000, engagement: 3.2, niche: "Fashion", petCompanion: false, lessons: ["Consistencia visual", "Narrativa creible"] },
|
| 7 |
+
{ name: "Rozy", handle: "@rozy.gram", platform: "Instagram", followers: 180000, engagement: 4.5, niche: "K-pop", petCompanion: true, petType: "dog", lessons: ["Tendencias K-pop", "Mascota como diferenciador"] },
|
| 8 |
+
{ name: "Imma", handle: "@imma.gram", platform: "Instagram", followers: 390000, engagement: 3.8, niche: "Fashion & Art", petCompanion: false, lessons: ["Fusionar arte y moda"] },
|
| 9 |
+
{ name: "Ayla", handle: "@ayla_virtual", platform: "TikTok", followers: 850000, engagement: 8.3, niche: "Dance", petCompanion: true, petType: "dog", lessons: ["Participar en trends", "Alta frecuencia"] }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
];
|
| 11 |
|
|
|
|
| 12 |
export async function GET(request: NextRequest) {
|
| 13 |
try {
|
| 14 |
const { searchParams } = new URL(request.url);
|
| 15 |
const niche = searchParams.get("niche");
|
|
|
|
| 16 |
const withPets = searchParams.get("withPets");
|
| 17 |
|
| 18 |
+
let influencers = [...FAMOUS_INFLUENCERS];
|
| 19 |
+
if (niche) influencers = influencers.filter(i => i.niche.toLowerCase().includes(niche.toLowerCase()));
|
| 20 |
+
if (withPets === "true") influencers = influencers.filter(i => i.petCompanion);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
+
const dbInfluencers = await db.aIInfluencer.findMany({ where: { isActive: true } });
|
| 23 |
+
return NextResponse.json({ success: true, influencers, customInfluencers: dbInfluencers });
|
| 24 |
+
} catch {
|
| 25 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
| 26 |
}
|
| 27 |
}
|
| 28 |
|
|
|
|
| 29 |
export async function POST(request: NextRequest) {
|
| 30 |
try {
|
| 31 |
const body = await request.json();
|
| 32 |
+
const { targetNiche, includePets } = body;
|
| 33 |
|
| 34 |
const zai = await ZAI.create();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
const completion = await zai.chat.completions.create({
|
| 36 |
messages: [
|
| 37 |
+
{ role: "system", content: "Eres experto en marketing de influencers. Responde JSON con {contentPatterns:[], visualStyles:[], recommendations:[]}" },
|
| 38 |
+
{ role: "user", content: "Analiza estrategias para nicho: " + (targetNiche || "general") }
|
| 39 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
});
|
| 41 |
|
| 42 |
+
let analysis: Record<string, unknown[]> = {};
|
| 43 |
try {
|
| 44 |
+
const match = completion.choices[0]?.message?.content?.match(/\{[\s\S]*\}/);
|
| 45 |
+
if (match) analysis = JSON.parse(match[0]);
|
| 46 |
+
} catch {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
+
return NextResponse.json({ success: true, influencers: FAMOUS_INFLUENCERS.slice(0, 4), analysis });
|
| 49 |
+
} catch {
|
| 50 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
}
|
| 52 |
}
|
| 53 |
|
|
|
|
| 54 |
export async function PUT(request: NextRequest) {
|
| 55 |
try {
|
| 56 |
const body = await request.json();
|
| 57 |
+
const { name, handle, platform, followers, engagement, niche, petCompanion, petType } = body;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
+
const influencer = await db.aIInfluencer.create({
|
| 60 |
data: {
|
| 61 |
+
name, handle: handle || null, platform, followers: followers || null,
|
| 62 |
+
engagement: engagement || null, niche: niche || null,
|
| 63 |
+
petCompanion: petCompanion || false, petType: petType || null
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
}
|
| 65 |
});
|
| 66 |
+
return NextResponse.json({ success: true, influencer });
|
| 67 |
+
} catch {
|
| 68 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
}
|
| 70 |
+
}
|
src/app/api/pets/route.ts
CHANGED
|
@@ -1,327 +1,78 @@
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import ZAI from "z-ai-web-dev-sdk";
|
| 3 |
-
import {
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
popularIn: ["lifestyle", "fitness", "family", "outdoor"],
|
| 12 |
-
engagementBoost: 35, // % de aumento de engagement promedio
|
| 13 |
-
accessories: ["collar", "bandana", "ropa para mascotas", "juguetes", "gafas"]
|
| 14 |
-
},
|
| 15 |
-
cat: {
|
| 16 |
-
name: "Gato",
|
| 17 |
-
breeds: ["Persa", "Siames", "Maine Coon", "British Shorthair", "Ragdoll", "Bengala", "Sphynx", "Scottish Fold"],
|
| 18 |
-
personalities: ["independiente", "afectuoso", "curioso", "perezoso", "juguetón", "elegante"],
|
| 19 |
-
popularIn: ["lifestyle", "cozy", "aesthetic", "gaming"],
|
| 20 |
-
engagementBoost: 28,
|
| 21 |
-
accessories: ["collar", "campanita", "torre para gatos", "cajas", "mantas"]
|
| 22 |
-
},
|
| 23 |
-
bird: {
|
| 24 |
-
name: "Pájaro",
|
| 25 |
-
breeds: ["Canario", "Periquito", "Cacatúa", "Loro", "Agapornis", "Guacamayo"],
|
| 26 |
-
personalities: ["cantor", "social", "inteligente", "tranquilo", "travieso"],
|
| 27 |
-
popularIn: ["nature", "music", "artistic"],
|
| 28 |
-
engagementBoost: 15,
|
| 29 |
-
accessories: ["jaula decorativa", "juguetes", "posadores"]
|
| 30 |
-
},
|
| 31 |
-
rabbit: {
|
| 32 |
-
name: "Conejo",
|
| 33 |
-
breeds: ["Holandés", "Mini Lop", "Rex", "Angora", "Enano"],
|
| 34 |
-
personalities: ["tierno", "curioso", "suave", "saltarín", "tranquilo"],
|
| 35 |
-
popularIn: ["cute", "aesthetic", "cozy"],
|
| 36 |
-
engagementBoost: 22,
|
| 37 |
-
accessories: ["moños", "ropa miniatura", "juguetes"]
|
| 38 |
-
},
|
| 39 |
-
hamster: {
|
| 40 |
-
name: "Hámster",
|
| 41 |
-
breeds: ["Sirio", "Enano Ruso", "Roborovski", "Chino"],
|
| 42 |
-
personalities: ["pequeño", "activo", "adorable", "curioso"],
|
| 43 |
-
popularIn: ["cute", "pets", "daily"],
|
| 44 |
-
engagementBoost: 18,
|
| 45 |
-
accessories: ["rueda", "bolas", "casitas"]
|
| 46 |
-
}
|
| 47 |
};
|
| 48 |
|
| 49 |
-
// GET - Obtener mascotas
|
| 50 |
export async function GET(request: NextRequest) {
|
| 51 |
try {
|
| 52 |
const { searchParams } = new URL(request.url);
|
| 53 |
const characterId = searchParams.get("characterId");
|
| 54 |
-
const type = searchParams.get("type");
|
| 55 |
-
|
| 56 |
const where: Record<string, unknown> = { isActive: true };
|
| 57 |
if (characterId) where.characterId = characterId;
|
| 58 |
-
if (type) where.type = type;
|
| 59 |
|
| 60 |
-
const pets = await
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
select: { name: true }
|
| 65 |
-
}
|
| 66 |
-
},
|
| 67 |
-
orderBy: { createdAt: "desc" }
|
| 68 |
-
});
|
| 69 |
-
|
| 70 |
-
return NextResponse.json({
|
| 71 |
-
success: true,
|
| 72 |
-
pets,
|
| 73 |
-
petTypes: PET_TYPES,
|
| 74 |
-
total: pets.length
|
| 75 |
-
});
|
| 76 |
-
|
| 77 |
-
} catch (error) {
|
| 78 |
-
console.error("Error fetching pets:", error);
|
| 79 |
-
return NextResponse.json(
|
| 80 |
-
{ success: false, error: "Error al obtener mascotas" },
|
| 81 |
-
{ status: 500 }
|
| 82 |
-
);
|
| 83 |
}
|
| 84 |
}
|
| 85 |
|
| 86 |
-
// POST - Crear mascota
|
| 87 |
export async function POST(request: NextRequest) {
|
| 88 |
try {
|
| 89 |
const body = await request.json();
|
| 90 |
-
const {
|
| 91 |
-
name, type, breed, description, personality,
|
| 92 |
-
color, accessories, characterId, generateReference
|
| 93 |
-
} = body;
|
| 94 |
-
|
| 95 |
-
// Validar tipo de mascota
|
| 96 |
-
const petType = PET_TYPES[type as keyof typeof PET_TYPES];
|
| 97 |
-
if (!petType) {
|
| 98 |
-
return NextResponse.json(
|
| 99 |
-
{ success: false, error: "Tipo de mascota no válido" },
|
| 100 |
-
{ status: 400 }
|
| 101 |
-
);
|
| 102 |
-
}
|
| 103 |
-
|
| 104 |
-
let referenceImage = null;
|
| 105 |
|
| 106 |
-
|
| 107 |
-
if (generateReference) {
|
| 108 |
-
const zai = await ZAI.create();
|
| 109 |
|
| 110 |
-
|
| 111 |
-
${color ? `Color: ${color}.` : ""}
|
| 112 |
-
${personality ? `Personality: ${personality}.` : ""}
|
| 113 |
-
Style: High quality, photorealistic, social media ready, cute and appealing.
|
| 114 |
-
Background: Soft, aesthetic, suitable for Instagram/TikTok.
|
| 115 |
-
Pose: Natural and engaging, looking at camera or doing a cute action.`;
|
| 116 |
-
|
| 117 |
-
try {
|
| 118 |
-
const imageResponse = await zai.images.generations.create({
|
| 119 |
-
prompt,
|
| 120 |
-
size: "1024x1024"
|
| 121 |
-
});
|
| 122 |
-
referenceImage = imageResponse.data[0]?.base64;
|
| 123 |
-
} catch (imgError) {
|
| 124 |
-
console.error("Error generating pet image:", imgError);
|
| 125 |
-
}
|
| 126 |
-
}
|
| 127 |
|
| 128 |
-
const pet = await
|
| 129 |
data: {
|
| 130 |
-
name,
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
description,
|
| 134 |
-
personality,
|
| 135 |
-
color,
|
| 136 |
-
accessories: accessories ? JSON.stringify(accessories) : null,
|
| 137 |
-
traits: JSON.stringify({
|
| 138 |
-
typeInfo: petType,
|
| 139 |
-
engagementBoost: petType.engagementBoost
|
| 140 |
-
}),
|
| 141 |
-
referenceImage,
|
| 142 |
-
characterId
|
| 143 |
}
|
| 144 |
});
|
| 145 |
|
| 146 |
-
return NextResponse.json({
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
message: `Mascota "${name}" creada correctamente`,
|
| 150 |
-
engagementBoost: petType.engagementBoost
|
| 151 |
-
});
|
| 152 |
-
|
| 153 |
-
} catch (error) {
|
| 154 |
-
console.error("Error creating pet:", error);
|
| 155 |
-
return NextResponse.json(
|
| 156 |
-
{ success: false, error: "Error al crear mascota" },
|
| 157 |
-
{ status: 500 }
|
| 158 |
-
);
|
| 159 |
}
|
| 160 |
}
|
| 161 |
|
| 162 |
-
// PUT - Actualizar mascota
|
| 163 |
export async function PUT(request: NextRequest) {
|
| 164 |
try {
|
| 165 |
const body = await request.json();
|
| 166 |
-
const { id,
|
| 167 |
-
|
| 168 |
-
if (!id) {
|
| 169 |
-
return NextResponse.json(
|
| 170 |
-
{ success: false, error: "ID de mascota requerido" },
|
| 171 |
-
{ status: 400 }
|
| 172 |
-
);
|
| 173 |
-
}
|
| 174 |
|
| 175 |
-
|
| 176 |
-
if (
|
| 177 |
-
|
| 178 |
-
}
|
| 179 |
-
if (updateData.traits && typeof updateData.traits !== "string") {
|
| 180 |
-
updateData.traits = JSON.stringify(updateData.traits);
|
| 181 |
-
}
|
| 182 |
|
| 183 |
-
const pet = await
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
});
|
| 187 |
-
|
| 188 |
-
return NextResponse.json({
|
| 189 |
-
success: true,
|
| 190 |
-
pet,
|
| 191 |
-
message: "Mascota actualizada correctamente"
|
| 192 |
-
});
|
| 193 |
-
|
| 194 |
-
} catch (error) {
|
| 195 |
-
console.error("Error updating pet:", error);
|
| 196 |
-
return NextResponse.json(
|
| 197 |
-
{ success: false, error: "Error al actualizar mascota" },
|
| 198 |
-
{ status: 500 }
|
| 199 |
-
);
|
| 200 |
}
|
| 201 |
}
|
| 202 |
|
| 203 |
-
// DELETE - Eliminar mascota
|
| 204 |
export async function DELETE(request: NextRequest) {
|
| 205 |
try {
|
| 206 |
const { searchParams } = new URL(request.url);
|
| 207 |
const id = searchParams.get("id");
|
|
|
|
| 208 |
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
);
|
| 214 |
-
}
|
| 215 |
-
|
| 216 |
-
await prisma.pet.update({
|
| 217 |
-
where: { id },
|
| 218 |
-
data: { isActive: false }
|
| 219 |
-
});
|
| 220 |
-
|
| 221 |
-
return NextResponse.json({
|
| 222 |
-
success: true,
|
| 223 |
-
message: "Mascota eliminada correctamente"
|
| 224 |
-
});
|
| 225 |
-
|
| 226 |
-
} catch (error) {
|
| 227 |
-
console.error("Error deleting pet:", error);
|
| 228 |
-
return NextResponse.json(
|
| 229 |
-
{ success: false, error: "Error al eliminar mascota" },
|
| 230 |
-
{ status: 500 }
|
| 231 |
-
);
|
| 232 |
}
|
| 233 |
-
}
|
| 234 |
-
|
| 235 |
-
// PATCH - Generar contenido con mascota
|
| 236 |
-
export async function PATCH(request: NextRequest) {
|
| 237 |
-
try {
|
| 238 |
-
const body = await request.json();
|
| 239 |
-
const { petId, contentType, platform, theme } = body;
|
| 240 |
-
|
| 241 |
-
const pet = await prisma.pet.findUnique({
|
| 242 |
-
where: { id: petId },
|
| 243 |
-
include: { character: true }
|
| 244 |
-
});
|
| 245 |
-
|
| 246 |
-
if (!pet) {
|
| 247 |
-
return NextResponse.json(
|
| 248 |
-
{ success: false, error: "Mascota no encontrada" },
|
| 249 |
-
{ status: 404 }
|
| 250 |
-
);
|
| 251 |
-
}
|
| 252 |
-
|
| 253 |
-
const zai = await ZAI.create();
|
| 254 |
-
|
| 255 |
-
// Generar ideas de contenido con la mascota
|
| 256 |
-
const contentPrompt = `Genera ideas de contenido para una mascota:
|
| 257 |
-
- Nombre: ${pet.name}
|
| 258 |
-
- Tipo: ${pet.type}
|
| 259 |
-
- Raza: ${pet.breed || "No especificada"}
|
| 260 |
-
- Personalidad: ${pet.personality || "No especificada"}
|
| 261 |
-
${pet.character ? `- Dueño: ${pet.character.name}` : ""}
|
| 262 |
-
- Tipo de contenido: ${contentType || "foto"}
|
| 263 |
-
- Plataforma: ${platform || "Instagram"}
|
| 264 |
-
- Tema: ${theme || "lifestyle"}
|
| 265 |
-
|
| 266 |
-
Proporciona:
|
| 267 |
-
1. 5 ideas de contenido específicas con esta mascota
|
| 268 |
-
2. Ganchos/hooks para cada idea
|
| 269 |
-
3. Hashtags recomendados
|
| 270 |
-
4. Mejor momento del día para publicar
|
| 271 |
-
5. Elementos visuales sugeridos
|
| 272 |
-
|
| 273 |
-
Responde en JSON:
|
| 274 |
-
{
|
| 275 |
-
"contentIdeas": [{"title": "", "description": "", "hook": "", "cta": ""}],
|
| 276 |
-
"hashtags": [],
|
| 277 |
-
"bestPostingTime": "",
|
| 278 |
-
"visualElements": [],
|
| 279 |
-
"engagementTips": []
|
| 280 |
-
}`;
|
| 281 |
-
|
| 282 |
-
const completion = await zai.chat.completions.create({
|
| 283 |
-
messages: [
|
| 284 |
-
{
|
| 285 |
-
role: "system",
|
| 286 |
-
content: "Eres un experto en contenido de mascotas para redes sociales. Genera ideas creativas y virales."
|
| 287 |
-
},
|
| 288 |
-
{
|
| 289 |
-
role: "user",
|
| 290 |
-
content: contentPrompt
|
| 291 |
-
}
|
| 292 |
-
],
|
| 293 |
-
temperature: 0.8,
|
| 294 |
-
max_tokens: 2000,
|
| 295 |
-
});
|
| 296 |
-
|
| 297 |
-
let contentIdeas;
|
| 298 |
-
try {
|
| 299 |
-
const response = completion.choices[0]?.message?.content || "";
|
| 300 |
-
const match = response.match(/\{[\s\S]*\}/);
|
| 301 |
-
if (match) {
|
| 302 |
-
contentIdeas = JSON.parse(match[0]);
|
| 303 |
-
}
|
| 304 |
-
} catch {
|
| 305 |
-
contentIdeas = { raw: completion.choices[0]?.message?.content };
|
| 306 |
-
}
|
| 307 |
-
|
| 308 |
-
return NextResponse.json({
|
| 309 |
-
success: true,
|
| 310 |
-
pet: {
|
| 311 |
-
id: pet.id,
|
| 312 |
-
name: pet.name,
|
| 313 |
-
type: pet.type,
|
| 314 |
-
breed: pet.breed
|
| 315 |
-
},
|
| 316 |
-
contentIdeas,
|
| 317 |
-
engagementBoost: PET_TYPES[pet.type as keyof typeof PET_TYPES]?.engagementBoost || 20
|
| 318 |
-
});
|
| 319 |
-
|
| 320 |
-
} catch (error) {
|
| 321 |
-
console.error("Error generating pet content:", error);
|
| 322 |
-
return NextResponse.json(
|
| 323 |
-
{ success: false, error: "Error al generar contenido" },
|
| 324 |
-
{ status: 500 }
|
| 325 |
-
);
|
| 326 |
-
}
|
| 327 |
-
}
|
|
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import ZAI from "z-ai-web-dev-sdk";
|
| 3 |
+
import { db } from "@/lib/db";
|
| 4 |
+
|
| 5 |
+
const PET_TYPES: Record<string, { name: string; engagementBoost: number }> = {
|
| 6 |
+
dog: { name: "Perro", engagementBoost: 35 },
|
| 7 |
+
cat: { name: "Gato", engagementBoost: 28 },
|
| 8 |
+
bird: { name: "Pajaro", engagementBoost: 15 },
|
| 9 |
+
rabbit: { name: "Conejo", engagementBoost: 22 },
|
| 10 |
+
hamster: { name: "Hamster", engagementBoost: 18 }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
};
|
| 12 |
|
|
|
|
| 13 |
export async function GET(request: NextRequest) {
|
| 14 |
try {
|
| 15 |
const { searchParams } = new URL(request.url);
|
| 16 |
const characterId = searchParams.get("characterId");
|
|
|
|
|
|
|
| 17 |
const where: Record<string, unknown> = { isActive: true };
|
| 18 |
if (characterId) where.characterId = characterId;
|
|
|
|
| 19 |
|
| 20 |
+
const pets = await db.pet.findMany({ where, orderBy: { createdAt: "desc" } });
|
| 21 |
+
return NextResponse.json({ success: true, pets, petTypes: PET_TYPES });
|
| 22 |
+
} catch {
|
| 23 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
}
|
| 25 |
}
|
| 26 |
|
|
|
|
| 27 |
export async function POST(request: NextRequest) {
|
| 28 |
try {
|
| 29 |
const body = await request.json();
|
| 30 |
+
const { name, type, breed, personality, characterId } = body;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
+
if (!name) return NextResponse.json({ success: false, error: "Nombre requerido" }, { status: 400 });
|
|
|
|
|
|
|
| 33 |
|
| 34 |
+
const petType = PET_TYPES[type] || PET_TYPES.dog;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
+
const pet = await db.pet.create({
|
| 37 |
data: {
|
| 38 |
+
name, type: type || "dog", breed: breed || null,
|
| 39 |
+
personality: personality || null, characterId: characterId || null,
|
| 40 |
+
traits: JSON.stringify({ engagementBoost: petType.engagementBoost })
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
}
|
| 42 |
});
|
| 43 |
|
| 44 |
+
return NextResponse.json({ success: true, pet, engagementBoost: petType.engagementBoost });
|
| 45 |
+
} catch {
|
| 46 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
}
|
| 48 |
}
|
| 49 |
|
|
|
|
| 50 |
export async function PUT(request: NextRequest) {
|
| 51 |
try {
|
| 52 |
const body = await request.json();
|
| 53 |
+
const { id, personality, isActive } = body;
|
| 54 |
+
if (!id) return NextResponse.json({ success: false, error: "ID requerido" }, { status: 400 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
+
const data: Record<string, unknown> = {};
|
| 57 |
+
if (personality) data.personality = personality;
|
| 58 |
+
if (isActive !== undefined) data.isActive = isActive;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
+
const pet = await db.pet.update({ where: { id }, data });
|
| 61 |
+
return NextResponse.json({ success: true, pet });
|
| 62 |
+
} catch {
|
| 63 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
}
|
| 65 |
}
|
| 66 |
|
|
|
|
| 67 |
export async function DELETE(request: NextRequest) {
|
| 68 |
try {
|
| 69 |
const { searchParams } = new URL(request.url);
|
| 70 |
const id = searchParams.get("id");
|
| 71 |
+
if (!id) return NextResponse.json({ success: false, error: "ID requerido" }, { status: 400 });
|
| 72 |
|
| 73 |
+
await db.pet.update({ where: { id }, data: { isActive: false } });
|
| 74 |
+
return NextResponse.json({ success: true });
|
| 75 |
+
} catch {
|
| 76 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
}
|
| 78 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/app/api/storytelling/route.ts
CHANGED
|
@@ -2,346 +2,126 @@ import { NextRequest, NextResponse } from "next/server";
|
|
| 2 |
import { db } from "@/lib/db";
|
| 3 |
import ZAI from "z-ai-web-dev-sdk";
|
| 4 |
|
| 5 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
-
## TUS ESPECIALIDADES:
|
| 8 |
-
|
| 9 |
-
### 1. ESTRUCTURA NARRATIVA
|
| 10 |
-
Cada historia debe tener:
|
| 11 |
-
- **Hook inicial**: Gancho que capture la atención en los primeros 3 segundos
|
| 12 |
-
- **Desarrollo**: Progresión emocional con picos y valles
|
| 13 |
-
- **Cliffhanger**: Suspense que mantenga a la audiencia esperando más
|
| 14 |
-
- **Resolución**: Satisfacción pero dejando puerta abierta
|
| 15 |
-
|
| 16 |
-
### 2. TÉCNICAS DE ENGAGEMENT
|
| 17 |
-
- Preguntas retóricas
|
| 18 |
-
- Revelaciones progresivas
|
| 19 |
-
- Conflictos emocionales
|
| 20 |
-
- Personajes identificables
|
| 21 |
-
- Giros inesperados
|
| 22 |
-
- Calls to action sutiles
|
| 23 |
-
|
| 24 |
-
### 3. OPTIMIZACIÓN POR PLATAFORMA
|
| 25 |
-
- **Instagram Reels**: Historias de 15-60 seg, visual impactante
|
| 26 |
-
- **TikTok**: Tendencias, humor, velocidad, authenticy
|
| 27 |
-
- **YouTube**: Mayor profundidad, series, communities
|
| 28 |
-
- **OnlyFans**: Exclusividad, conexión personal, behind the scenes
|
| 29 |
-
- **Patreon**: Contenido premium, tutoriales, acceso VIP
|
| 30 |
-
|
| 31 |
-
### 4. ESTRATEGIAS DE MONETIZACIÓN
|
| 32 |
-
- Teasers gratuitos + contenido premium
|
| 33 |
-
- Suscripción escalonada
|
| 34 |
-
- Contenido exclusivo para tiers superiores
|
| 35 |
-
- PPV (Pay Per View) para contenido especial
|
| 36 |
-
- Fan engagement strategies
|
| 37 |
-
|
| 38 |
-
### 5. TIPOS DE CONTENIDO
|
| 39 |
-
- **Lifestyle**: Day in the life, routines, personal growth
|
| 40 |
-
- **Drama**: Relationships, conflicts, resolutions
|
| 41 |
-
- **Tutorial**: How-to, tips, behind the scenes
|
| 42 |
-
- **Challenge**: Personal challenges, transformations
|
| 43 |
-
- **ASMR/Relaxation**: Calm content, soothing narratives
|
| 44 |
-
- **Entertainment**: Comedy, reactions, trends
|
| 45 |
-
|
| 46 |
-
## FORMATO DE RESPUESTA:
|
| 47 |
-
Responde siempre en JSON con esta estructura:
|
| 48 |
-
{
|
| 49 |
-
"title": "Título de la historia",
|
| 50 |
-
"synopsis": "Resumen breve",
|
| 51 |
-
"genre": "género",
|
| 52 |
-
"targetAudience": "audiencia objetivo",
|
| 53 |
-
"tone": "tono narrativo",
|
| 54 |
-
"totalEpisodes": número,
|
| 55 |
-
"episodes": [
|
| 56 |
-
{
|
| 57 |
-
"episodeNum": 1,
|
| 58 |
-
"title": "Título episodio",
|
| 59 |
-
"hook": "Gancho inicial",
|
| 60 |
-
"content": "Contenido del episodio",
|
| 61 |
-
"cliffhanger": "Suspense final",
|
| 62 |
-
"bestPostingTime": "hora",
|
| 63 |
-
"estimatedEngagement": "alto/medio/bajo",
|
| 64 |
-
"monetizationTip": "Sugerencia de monetización"
|
| 65 |
-
}
|
| 66 |
-
],
|
| 67 |
-
"monetizationStrategy": {
|
| 68 |
-
"freeTeasers": [números de episodios gratis],
|
| 69 |
-
"premiumContent": [números de episodios premium],
|
| 70 |
-
"suggestedPlatform": "plataforma recomendada",
|
| 71 |
-
"pricingStrategy": "estrategia de precios"
|
| 72 |
-
},
|
| 73 |
-
"hashtags": ["hashtags relevantes"],
|
| 74 |
-
"bestPostingSchedule": ["horarios recomendados"]
|
| 75 |
-
}`;
|
| 76 |
-
|
| 77 |
-
// GET - Listar historias
|
| 78 |
export async function GET(request: NextRequest) {
|
| 79 |
try {
|
| 80 |
const { searchParams } = new URL(request.url);
|
| 81 |
const status = searchParams.get("status");
|
| 82 |
-
const genre = searchParams.get("genre");
|
| 83 |
-
|
| 84 |
const where: Record<string, unknown> = {};
|
| 85 |
if (status) where.status = status;
|
| 86 |
-
if (genre) where.genre = genre;
|
| 87 |
|
| 88 |
const stories = await db.story.findMany({
|
| 89 |
where,
|
| 90 |
-
include: {
|
| 91 |
-
episodes: {
|
| 92 |
-
orderBy: { episodeNum: "asc" }
|
| 93 |
-
},
|
| 94 |
-
analytics: true,
|
| 95 |
-
_count: {
|
| 96 |
-
select: { posts: true, episodes: true }
|
| 97 |
-
}
|
| 98 |
-
},
|
| 99 |
orderBy: { createdAt: "desc" }
|
| 100 |
});
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
{ id: "drama", name: "Drama", description: "Conflictos emocionales intensos" },
|
| 106 |
-
{ id: "comedy", name: "Comedia", description: "Contenido humorístico" },
|
| 107 |
-
{ id: "thriller", name: "Thriller", description: "Suspenso y misterio" },
|
| 108 |
-
{ id: "lifestyle", name: "Lifestyle", description: "Vida cotidiana y rutinas" },
|
| 109 |
-
{ id: "fitness", name: "Fitness", description: "Transformaciones y salud" },
|
| 110 |
-
{ id: "beauty", name: "Beauty", description: "Belleza y cuidados" },
|
| 111 |
-
{ id: "cooking", name: "Cooking", description: "Cocina y recetas" },
|
| 112 |
-
{ id: "travel", name: "Travel", description: "Viajes y aventuras" },
|
| 113 |
-
{ id: "gaming", name: "Gaming", description: "Videojuegos y streaming" },
|
| 114 |
-
{ id: "education", name: "Educación", description: "Contenido educativo" },
|
| 115 |
-
{ id: "asmr", name: "ASMR", description: "Relajación y calma" },
|
| 116 |
-
];
|
| 117 |
-
|
| 118 |
-
return NextResponse.json({
|
| 119 |
-
success: true,
|
| 120 |
-
stories,
|
| 121 |
-
genres,
|
| 122 |
-
total: stories.length
|
| 123 |
-
});
|
| 124 |
-
|
| 125 |
-
} catch (error) {
|
| 126 |
-
console.error("Error fetching stories:", error);
|
| 127 |
-
return NextResponse.json(
|
| 128 |
-
{ success: false, error: "Error al obtener historias" },
|
| 129 |
-
{ status: 500 }
|
| 130 |
-
);
|
| 131 |
}
|
| 132 |
}
|
| 133 |
|
| 134 |
-
// POST - Crear nueva historia con IA
|
| 135 |
export async function POST(request: NextRequest) {
|
| 136 |
try {
|
| 137 |
const body = await request.json();
|
| 138 |
-
const {
|
| 139 |
-
prompt,
|
| 140 |
-
genre,
|
| 141 |
-
targetAudience,
|
| 142 |
-
tone,
|
| 143 |
-
totalEpisodes,
|
| 144 |
-
platform,
|
| 145 |
-
characterIds,
|
| 146 |
-
monetizationGoal
|
| 147 |
-
} = body;
|
| 148 |
|
| 149 |
if (!prompt) {
|
| 150 |
-
return NextResponse.json(
|
| 151 |
-
{ success: false, error: "El prompt es requerido" },
|
| 152 |
-
{ status: 400 }
|
| 153 |
-
);
|
| 154 |
}
|
| 155 |
|
| 156 |
const zai = await ZAI.create();
|
| 157 |
-
|
| 158 |
-
// Generar historia completa con IA
|
| 159 |
const completion = await zai.chat.completions.create({
|
| 160 |
messages: [
|
| 161 |
-
{ role: "system", content:
|
| 162 |
-
{ role: "user", content:
|
| 163 |
-
|
| 164 |
-
Concepto: ${prompt}
|
| 165 |
-
${genre ? `Género: ${genre}` : ''}
|
| 166 |
-
${targetAudience ? `Audiencia objetivo: ${targetAudience}` : ''}
|
| 167 |
-
${tone ? `Tono: ${tone}` : ''}
|
| 168 |
-
${totalEpisodes ? `Número de episodios: ${totalEpisodes}` : 'Número de episodios: 7'}
|
| 169 |
-
${platform ? `Plataforma principal: ${platform}` : ''}
|
| 170 |
-
${monetizationGoal ? `Objetivo de monetización: ${monetizationGoal}` : 'Objetivo: Maximizar suscriptores pagos'}
|
| 171 |
-
|
| 172 |
-
Genera la historia completa con todos los episodios y estrategia de monetización.` }
|
| 173 |
],
|
| 174 |
-
temperature: 0.8
|
| 175 |
-
max_tokens: 6000,
|
| 176 |
});
|
| 177 |
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
// Parsear respuesta
|
| 181 |
-
let storyData;
|
| 182 |
try {
|
| 183 |
-
const
|
| 184 |
-
if (
|
| 185 |
-
|
| 186 |
-
}
|
| 187 |
-
} catch (parseError) {
|
| 188 |
-
console.error("Error parsing story:", parseError);
|
| 189 |
-
storyData = {
|
| 190 |
-
title: "Historia generada",
|
| 191 |
-
synopsis: prompt,
|
| 192 |
-
episodes: []
|
| 193 |
-
};
|
| 194 |
-
}
|
| 195 |
|
| 196 |
-
// Crear historia en BD
|
| 197 |
const story = await db.story.create({
|
| 198 |
data: {
|
| 199 |
title: storyData.title || "Nueva Historia",
|
| 200 |
description: storyData.synopsis || prompt,
|
| 201 |
-
genre:
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
totalEpisodes: storyData.totalEpisodes || storyData.episodes?.length || 7,
|
| 205 |
-
characterIds: characterIds ? JSON.stringify(characterIds) : null,
|
| 206 |
-
monetizationStrategy: storyData.monetizationStrategy ? JSON.stringify(storyData.monetizationStrategy) : null,
|
| 207 |
-
status: "draft",
|
| 208 |
}
|
| 209 |
});
|
| 210 |
|
| 211 |
-
// Crear episodios
|
| 212 |
if (storyData.episodes && Array.isArray(storyData.episodes)) {
|
| 213 |
-
for (
|
|
|
|
| 214 |
await db.storyEpisode.create({
|
| 215 |
data: {
|
| 216 |
storyId: story.id,
|
| 217 |
-
episodeNum: ep.episodeNum ||
|
| 218 |
-
title: ep.title ||
|
| 219 |
-
synopsis: ep.hook || null,
|
| 220 |
content: ep.content || "",
|
| 221 |
-
|
| 222 |
-
cliffhanger: ep.cliffhanger || null,
|
| 223 |
-
status: "draft",
|
| 224 |
}
|
| 225 |
});
|
| 226 |
}
|
| 227 |
}
|
| 228 |
|
| 229 |
-
|
| 230 |
-
await db.storyAnalytics.create({
|
| 231 |
-
data: {
|
| 232 |
-
storyId: story.id,
|
| 233 |
-
}
|
| 234 |
-
});
|
| 235 |
-
|
| 236 |
-
// Crear tarea de agente
|
| 237 |
await db.agentTask.create({
|
| 238 |
-
data: {
|
| 239 |
-
type: "create_story",
|
| 240 |
-
status: "completed",
|
| 241 |
-
input: prompt,
|
| 242 |
-
output: `Historia "${storyData.title}" creada con ${storyData.episodes?.length || 0} episodios`,
|
| 243 |
-
completedAt: new Date(),
|
| 244 |
-
}
|
| 245 |
});
|
| 246 |
|
| 247 |
-
// Obtener historia completa
|
| 248 |
const fullStory = await db.story.findUnique({
|
| 249 |
where: { id: story.id },
|
| 250 |
-
include: {
|
| 251 |
-
episodes: { orderBy: { episodeNum: "asc" } },
|
| 252 |
-
analytics: true
|
| 253 |
-
}
|
| 254 |
-
});
|
| 255 |
-
|
| 256 |
-
return NextResponse.json({
|
| 257 |
-
success: true,
|
| 258 |
-
story: fullStory,
|
| 259 |
-
rawAIResponse: storyData,
|
| 260 |
-
message: `Historia "${storyData.title}" creada exitosamente`
|
| 261 |
});
|
| 262 |
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
return NextResponse.json(
|
| 266 |
-
{ success: false, error: "Error al crear historia" },
|
| 267 |
-
{ status: 500 }
|
| 268 |
-
);
|
| 269 |
}
|
| 270 |
}
|
| 271 |
|
| 272 |
-
// PUT - Actualizar historia
|
| 273 |
export async function PUT(request: NextRequest) {
|
| 274 |
try {
|
| 275 |
const body = await request.json();
|
| 276 |
const { id, status, currentEpisode } = body;
|
|
|
|
| 277 |
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
{ status: 400 }
|
| 282 |
-
);
|
| 283 |
-
}
|
| 284 |
|
| 285 |
-
const story = await db.story.update({
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
currentEpisode: currentEpisode || undefined,
|
| 290 |
-
}
|
| 291 |
-
});
|
| 292 |
-
|
| 293 |
-
return NextResponse.json({
|
| 294 |
-
success: true,
|
| 295 |
-
story
|
| 296 |
-
});
|
| 297 |
-
|
| 298 |
-
} catch (error) {
|
| 299 |
-
console.error("Error updating story:", error);
|
| 300 |
-
return NextResponse.json(
|
| 301 |
-
{ success: false, error: "Error al actualizar historia" },
|
| 302 |
-
{ status: 500 }
|
| 303 |
-
);
|
| 304 |
}
|
| 305 |
}
|
| 306 |
|
| 307 |
-
// DELETE - Eliminar historia
|
| 308 |
export async function DELETE(request: NextRequest) {
|
| 309 |
try {
|
| 310 |
const { searchParams } = new URL(request.url);
|
| 311 |
const id = searchParams.get("id");
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
// Eliminar episodios primero
|
| 321 |
-
await db.storyEpisode.deleteMany({
|
| 322 |
-
where: { storyId: id }
|
| 323 |
-
});
|
| 324 |
-
|
| 325 |
-
// Eliminar analytics
|
| 326 |
-
await db.storyAnalytics.deleteMany({
|
| 327 |
-
where: { storyId: id }
|
| 328 |
-
});
|
| 329 |
-
|
| 330 |
-
// Eliminar historia
|
| 331 |
-
await db.story.delete({
|
| 332 |
-
where: { id }
|
| 333 |
-
});
|
| 334 |
-
|
| 335 |
-
return NextResponse.json({
|
| 336 |
-
success: true,
|
| 337 |
-
message: "Historia eliminada"
|
| 338 |
-
});
|
| 339 |
-
|
| 340 |
-
} catch (error) {
|
| 341 |
-
console.error("Error deleting story:", error);
|
| 342 |
-
return NextResponse.json(
|
| 343 |
-
{ success: false, error: "Error al eliminar historia" },
|
| 344 |
-
{ status: 500 }
|
| 345 |
-
);
|
| 346 |
}
|
| 347 |
-
}
|
|
|
|
| 2 |
import { db } from "@/lib/db";
|
| 3 |
import ZAI from "z-ai-web-dev-sdk";
|
| 4 |
|
| 5 |
+
const GENRES = [
|
| 6 |
+
{ id: "romance", name: "Romance" },
|
| 7 |
+
{ id: "drama", name: "Drama" },
|
| 8 |
+
{ id: "comedy", name: "Comedia" },
|
| 9 |
+
{ id: "lifestyle", name: "Lifestyle" },
|
| 10 |
+
{ id: "fitness", name: "Fitness" }
|
| 11 |
+
];
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
export async function GET(request: NextRequest) {
|
| 14 |
try {
|
| 15 |
const { searchParams } = new URL(request.url);
|
| 16 |
const status = searchParams.get("status");
|
|
|
|
|
|
|
| 17 |
const where: Record<string, unknown> = {};
|
| 18 |
if (status) where.status = status;
|
|
|
|
| 19 |
|
| 20 |
const stories = await db.story.findMany({
|
| 21 |
where,
|
| 22 |
+
include: { episodes: { orderBy: { episodeNum: "asc" } } },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
orderBy: { createdAt: "desc" }
|
| 24 |
});
|
| 25 |
|
| 26 |
+
return NextResponse.json({ success: true, stories, genres: GENRES, total: stories.length });
|
| 27 |
+
} catch {
|
| 28 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
}
|
| 30 |
}
|
| 31 |
|
|
|
|
| 32 |
export async function POST(request: NextRequest) {
|
| 33 |
try {
|
| 34 |
const body = await request.json();
|
| 35 |
+
const { prompt, genre, totalEpisodes } = body;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
if (!prompt) {
|
| 38 |
+
return NextResponse.json({ success: false, error: "Prompt requerido" }, { status: 400 });
|
|
|
|
|
|
|
|
|
|
| 39 |
}
|
| 40 |
|
| 41 |
const zai = await ZAI.create();
|
|
|
|
|
|
|
| 42 |
const completion = await zai.chat.completions.create({
|
| 43 |
messages: [
|
| 44 |
+
{ role: "system", content: "Eres un guionista. Responde en JSON: {title, synopsis, episodes: [{episodeNum, title, content}]}" },
|
| 45 |
+
{ role: "user", content: "Crea una historia: " + prompt }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
],
|
| 47 |
+
temperature: 0.8
|
|
|
|
| 48 |
});
|
| 49 |
|
| 50 |
+
let storyData: { title?: string; synopsis?: string; episodes?: Array<{ episodeNum?: number; title?: string; content?: string }> } = {};
|
|
|
|
|
|
|
|
|
|
| 51 |
try {
|
| 52 |
+
const match = completion.choices[0]?.message?.content?.match(/\{[\s\S]*\}/);
|
| 53 |
+
if (match) storyData = JSON.parse(match[0]);
|
| 54 |
+
} catch {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
|
|
|
| 56 |
const story = await db.story.create({
|
| 57 |
data: {
|
| 58 |
title: storyData.title || "Nueva Historia",
|
| 59 |
description: storyData.synopsis || prompt,
|
| 60 |
+
genre: genre || "lifestyle",
|
| 61 |
+
totalEpisodes: totalEpisodes || storyData.episodes?.length || 7,
|
| 62 |
+
status: "draft"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
}
|
| 64 |
});
|
| 65 |
|
|
|
|
| 66 |
if (storyData.episodes && Array.isArray(storyData.episodes)) {
|
| 67 |
+
for (let i = 0; i < storyData.episodes.length; i++) {
|
| 68 |
+
const ep = storyData.episodes[i];
|
| 69 |
await db.storyEpisode.create({
|
| 70 |
data: {
|
| 71 |
storyId: story.id,
|
| 72 |
+
episodeNum: ep.episodeNum || i + 1,
|
| 73 |
+
title: ep.title || "Episodio " + (i + 1),
|
|
|
|
| 74 |
content: ep.content || "",
|
| 75 |
+
status: "draft"
|
|
|
|
|
|
|
| 76 |
}
|
| 77 |
});
|
| 78 |
}
|
| 79 |
}
|
| 80 |
|
| 81 |
+
await db.storyAnalytics.create({ data: { storyId: story.id } });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
await db.agentTask.create({
|
| 83 |
+
data: { type: "create_story", status: "completed", input: prompt, output: "Historia creada", completedAt: new Date() }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
});
|
| 85 |
|
|
|
|
| 86 |
const fullStory = await db.story.findUnique({
|
| 87 |
where: { id: story.id },
|
| 88 |
+
include: { episodes: { orderBy: { episodeNum: "asc" } } }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
});
|
| 90 |
|
| 91 |
+
return NextResponse.json({ success: true, story: fullStory });
|
| 92 |
+
} catch {
|
| 93 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
| 94 |
}
|
| 95 |
}
|
| 96 |
|
|
|
|
| 97 |
export async function PUT(request: NextRequest) {
|
| 98 |
try {
|
| 99 |
const body = await request.json();
|
| 100 |
const { id, status, currentEpisode } = body;
|
| 101 |
+
if (!id) return NextResponse.json({ success: false, error: "ID requerido" }, { status: 400 });
|
| 102 |
|
| 103 |
+
const data: Record<string, unknown> = {};
|
| 104 |
+
if (status) data.status = status;
|
| 105 |
+
if (currentEpisode) data.currentEpisode = currentEpisode;
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
+
const story = await db.story.update({ where: { id }, data });
|
| 108 |
+
return NextResponse.json({ success: true, story });
|
| 109 |
+
} catch {
|
| 110 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
}
|
| 112 |
}
|
| 113 |
|
|
|
|
| 114 |
export async function DELETE(request: NextRequest) {
|
| 115 |
try {
|
| 116 |
const { searchParams } = new URL(request.url);
|
| 117 |
const id = searchParams.get("id");
|
| 118 |
+
if (!id) return NextResponse.json({ success: false, error: "ID requerido" }, { status: 400 });
|
| 119 |
+
|
| 120 |
+
await db.storyEpisode.deleteMany({ where: { storyId: id } });
|
| 121 |
+
await db.storyAnalytics.deleteMany({ where: { storyId: id } });
|
| 122 |
+
await db.story.delete({ where: { id } });
|
| 123 |
+
return NextResponse.json({ success: true });
|
| 124 |
+
} catch {
|
| 125 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
}
|
| 127 |
+
}
|
src/app/api/trends/route.ts
CHANGED
|
@@ -1,343 +1,63 @@
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import ZAI from "z-ai-web-dev-sdk";
|
| 3 |
-
import {
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
{ name: "ASMR content", type: "topic", volume: 890000, growth: 45.2, category: "relaxation" },
|
| 12 |
-
{ name: "Day in my life", type: "topic", volume: 1200000, growth: 18.7, category: "vlog" },
|
| 13 |
-
{ name: "Get Ready With Me", type: "format", volume: 2100000, growth: 25.3, category: "beauty" },
|
| 14 |
-
{ name: "Photo dumps", type: "format", volume: 1500000, growth: 32.1, category: "lifestyle" },
|
| 15 |
-
{ name: "Pets content", type: "topic", volume: 980000, growth: 55.8, category: "pets" },
|
| 16 |
-
],
|
| 17 |
-
tiktok: [
|
| 18 |
-
{ name: "#fyp", type: "hashtag", volume: 15000000, growth: 5.2, category: "general" },
|
| 19 |
-
{ name: "#viral", type: "hashtag", volume: 12000000, growth: 12.8, category: "general" },
|
| 20 |
-
{ name: "Storytime", type: "topic", volume: 3500000, growth: 32.1, category: "narrative" },
|
| 21 |
-
{ name: "GRWM", type: "format", volume: 2800000, growth: 28.5, category: "beauty" },
|
| 22 |
-
{ name: "POV videos", type: "format", volume: 4200000, growth: 19.3, category: "creative" },
|
| 23 |
-
{ name: "Dance challenges", type: "format", volume: 5200000, growth: 15.7, category: "dance" },
|
| 24 |
-
{ name: "Pet reveals", type: "topic", volume: 1800000, growth: 48.3, category: "pets" },
|
| 25 |
-
{ name: "Before/After", type: "format", volume: 2200000, growth: 35.2, category: "transformation" },
|
| 26 |
-
],
|
| 27 |
-
youtube: [
|
| 28 |
-
{ name: "Shorts", type: "format", volume: 8500000, growth: 25.6, category: "video" },
|
| 29 |
-
{ name: "Tutorial", type: "topic", volume: 5200000, growth: 12.4, category: "education" },
|
| 30 |
-
{ name: "Vlog", type: "topic", volume: 3800000, growth: 8.9, category: "lifestyle" },
|
| 31 |
-
{ name: "Reaction videos", type: "format", volume: 2100000, growth: 15.7, category: "entertainment" },
|
| 32 |
-
{ name: "ASMR", type: "topic", volume: 3200000, growth: 28.4, category: "relaxation" },
|
| 33 |
-
{ name: "Pet compilations", type: "format", volume: 1500000, growth: 42.1, category: "pets" },
|
| 34 |
-
],
|
| 35 |
-
onlyfans: [
|
| 36 |
-
{ name: "Behind the scenes", type: "topic", volume: 450000, growth: 35.2, category: "exclusive" },
|
| 37 |
-
{ name: "Exclusive content", type: "topic", volume: 380000, growth: 28.7, category: "premium" },
|
| 38 |
-
{ name: "PPV specials", type: "format", volume: 220000, growth: 42.1, category: "monetization" },
|
| 39 |
-
{ name: "Fan engagement", type: "topic", volume: 180000, growth: 55.3, category: "community" },
|
| 40 |
-
{ name: "Pet content", type: "topic", volume: 95000, growth: 62.5, category: "niche" },
|
| 41 |
-
]
|
| 42 |
-
};
|
| 43 |
|
| 44 |
-
// Estrategias virales conocidas
|
| 45 |
const VIRAL_STRATEGIES = [
|
| 46 |
-
{
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
platforms: ["tiktok", "instagram", "youtube"],
|
| 50 |
-
contentType: "video",
|
| 51 |
-
successRate: 85,
|
| 52 |
-
elements: ["pregunta intrigante", "revelación parcial", "movimiento brusco", "sonido llamativo"]
|
| 53 |
-
},
|
| 54 |
-
{
|
| 55 |
-
name: "Storytime con suspenso",
|
| 56 |
-
description: "Narrar una historia personal manteniendo el suspenso hasta el final",
|
| 57 |
-
platforms: ["tiktok", "youtube"],
|
| 58 |
-
contentType: "video",
|
| 59 |
-
successRate: 78,
|
| 60 |
-
elements: ["gancho inicial", "pausas dramáticas", "cliffhanger", "resolución satisfactoria"]
|
| 61 |
-
},
|
| 62 |
-
{
|
| 63 |
-
name: "Transformación/Before & After",
|
| 64 |
-
description: "Mostrar un cambio dramático que genere curiosidad sobre el proceso",
|
| 65 |
-
platforms: ["instagram", "tiktok", "youtube"],
|
| 66 |
-
contentType: "reel",
|
| 67 |
-
successRate: 82,
|
| 68 |
-
elements: ["estado inicial", "proceso acelerado", "revelación final", "reacción"]
|
| 69 |
-
},
|
| 70 |
-
{
|
| 71 |
-
name: "Pet Reveal/Cameo",
|
| 72 |
-
description: "Incluir una mascota de forma natural en el contenido para aumentar engagement",
|
| 73 |
-
platforms: ["instagram", "tiktok"],
|
| 74 |
-
contentType: "any",
|
| 75 |
-
successRate: 88,
|
| 76 |
-
elements: ["mascota apareciendo", "interacción tierna", "momento cómico", "call to action con la mascota"]
|
| 77 |
-
},
|
| 78 |
-
{
|
| 79 |
-
name: "Controversia controlada",
|
| 80 |
-
description: "Plantear una opinión divisiva de forma respetuosa para generar debate",
|
| 81 |
-
platforms: ["tiktok", "twitter", "youtube"],
|
| 82 |
-
contentType: "opinion",
|
| 83 |
-
successRate: 72,
|
| 84 |
-
elements: ["opinion fuerte", "argumentos", "invitación al debate", "respuesta en comentarios"]
|
| 85 |
-
},
|
| 86 |
-
{
|
| 87 |
-
name: "Trend hopping",
|
| 88 |
-
description: "Participar en tendencias vigentes con un giro único personal",
|
| 89 |
-
platforms: ["tiktok", "instagram"],
|
| 90 |
-
contentType: "any",
|
| 91 |
-
successRate: 75,
|
| 92 |
-
elements: ["trend actual", "adaptación personal", "elemento sorpresa", "timing perfecto"]
|
| 93 |
-
},
|
| 94 |
-
{
|
| 95 |
-
name: "BTS/Exclusivo",
|
| 96 |
-
description: "Mostrar el detrás de cámaras o contenido que parece exclusivo",
|
| 97 |
-
platforms: ["instagram", "onlyfans", "youtube"],
|
| 98 |
-
contentType: "video",
|
| 99 |
-
successRate: 80,
|
| 100 |
-
elements: ["acceso VIP", "momentos espontáneos", "errores incluidos", "autenticidad"]
|
| 101 |
-
},
|
| 102 |
-
{
|
| 103 |
-
name: "Desafío/Reto",
|
| 104 |
-
description: "Crear o participar en un desafío que invite a la participación",
|
| 105 |
-
platforms: ["tiktok", "instagram"],
|
| 106 |
-
contentType: "reel",
|
| 107 |
-
successRate: 77,
|
| 108 |
-
elements: ["reglas claras", "ejemplo viral", "tag a amigos", "fácil de replicar"]
|
| 109 |
-
}
|
| 110 |
];
|
| 111 |
|
| 112 |
-
// GET - Obtener tendencias y estrategias
|
| 113 |
export async function GET(request: NextRequest) {
|
| 114 |
try {
|
| 115 |
const { searchParams } = new URL(request.url);
|
| 116 |
const platform = searchParams.get("platform");
|
| 117 |
-
const type = searchParams.get("type");
|
| 118 |
-
const category = searchParams.get("category");
|
| 119 |
const includePets = searchParams.get("includePets") === "true";
|
| 120 |
|
| 121 |
-
let trends = [];
|
| 122 |
-
|
| 123 |
-
if (
|
| 124 |
-
trends = TRENDING_DATA[platform as keyof typeof TRENDING_DATA];
|
| 125 |
-
} else {
|
| 126 |
-
// Combinar todas las plataformas
|
| 127 |
-
trends = Object.entries(TRENDING_DATA).flatMap(([plat, data]) =>
|
| 128 |
-
data.map(t => ({ ...t, platform: plat }))
|
| 129 |
-
);
|
| 130 |
-
}
|
| 131 |
-
|
| 132 |
-
// Filtrar por tipo
|
| 133 |
-
if (type) {
|
| 134 |
-
trends = trends.filter(t => t.type === type);
|
| 135 |
-
}
|
| 136 |
|
| 137 |
-
// Filtrar por categoría
|
| 138 |
-
if (category) {
|
| 139 |
-
trends = trends.filter(t => t.category === category);
|
| 140 |
-
}
|
| 141 |
-
|
| 142 |
-
// Filtrar tendencias relacionadas con mascotas
|
| 143 |
-
if (includePets) {
|
| 144 |
-
trends = trends.filter(t =>
|
| 145 |
-
t.category === "pets" ||
|
| 146 |
-
t.name.toLowerCase().includes("pet") ||
|
| 147 |
-
t.name.toLowerCase().includes("dog") ||
|
| 148 |
-
t.name.toLowerCase().includes("cat")
|
| 149 |
-
);
|
| 150 |
-
}
|
| 151 |
-
|
| 152 |
-
// Ordenar por crecimiento
|
| 153 |
trends.sort((a, b) => b.growth - a.growth);
|
| 154 |
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
strategies = strategies.filter(s =>
|
| 159 |
-
s.platforms.includes(platform.toLowerCase())
|
| 160 |
-
);
|
| 161 |
-
}
|
| 162 |
-
if (includePets) {
|
| 163 |
-
// Priorizar estrategias que funcionan con mascotas
|
| 164 |
-
strategies.sort((a, b) => {
|
| 165 |
-
const aPet = a.name.toLowerCase().includes("pet") ? 1 : 0;
|
| 166 |
-
const bPet = b.name.toLowerCase().includes("pet") ? 1 : 0;
|
| 167 |
-
return bPet - aPet;
|
| 168 |
-
});
|
| 169 |
-
}
|
| 170 |
-
|
| 171 |
-
// Generar ideas de contenido con IA para las tendencias principales
|
| 172 |
-
const zai = await ZAI.create();
|
| 173 |
-
const topTrends = trends.slice(0, 5);
|
| 174 |
-
|
| 175 |
-
const ideasResponse = await zai.chat.completions.create({
|
| 176 |
-
messages: [
|
| 177 |
-
{
|
| 178 |
-
role: "system",
|
| 179 |
-
content: `Eres un experto en marketing de contenidos viral. Genera ideas de contenido basadas en tendencias.
|
| 180 |
-
${includePets ? "IMPORTANTE: Todas las ideas deben incluir una mascota como elemento central." : ""}
|
| 181 |
-
Responde en JSON array con objetos {title, description, format, estimatedEngagement, viralScore, hook}.`
|
| 182 |
-
},
|
| 183 |
-
{
|
| 184 |
-
role: "user",
|
| 185 |
-
content: `Genera 5 ideas de contenido para estas tendencias: ${topTrends.map(t => t.name).join(", ")}
|
| 186 |
-
Plataforma principal: ${platform || "todas"}
|
| 187 |
-
${includePets ? "Incluir mascota en todas las ideas." : ""}`
|
| 188 |
-
}
|
| 189 |
-
],
|
| 190 |
-
temperature: 0.8,
|
| 191 |
-
});
|
| 192 |
-
|
| 193 |
-
let contentIdeas = [];
|
| 194 |
-
try {
|
| 195 |
-
const match = ideasResponse.choices[0]?.message?.content?.match(/\[[\s\S]*\]/);
|
| 196 |
-
if (match) {
|
| 197 |
-
contentIdeas = JSON.parse(match[0]);
|
| 198 |
-
}
|
| 199 |
-
} catch {
|
| 200 |
-
contentIdeas = [];
|
| 201 |
-
}
|
| 202 |
-
|
| 203 |
-
return NextResponse.json({
|
| 204 |
-
success: true,
|
| 205 |
-
trends,
|
| 206 |
-
viralStrategies: strategies,
|
| 207 |
-
contentIdeas,
|
| 208 |
-
platform: platform || "all",
|
| 209 |
-
stats: {
|
| 210 |
-
totalTrends: trends.length,
|
| 211 |
-
topGrowth: trends[0]?.growth || 0,
|
| 212 |
-
avgGrowth: trends.reduce((acc, t) => acc + t.growth, 0) / trends.length || 0
|
| 213 |
-
}
|
| 214 |
-
});
|
| 215 |
-
|
| 216 |
-
} catch (error) {
|
| 217 |
-
console.error("Error fetching trends:", error);
|
| 218 |
-
return NextResponse.json(
|
| 219 |
-
{ success: false, error: "Error al obtener tendencias" },
|
| 220 |
-
{ status: 500 }
|
| 221 |
-
);
|
| 222 |
}
|
| 223 |
}
|
| 224 |
|
| 225 |
-
// POST - Analizar tendencias con IA y generar plan viral
|
| 226 |
export async function POST(request: NextRequest) {
|
| 227 |
try {
|
| 228 |
const body = await request.json();
|
| 229 |
-
const { niche, platform
|
| 230 |
|
| 231 |
const zai = await ZAI.create();
|
| 232 |
-
|
| 233 |
const completion = await zai.chat.completions.create({
|
| 234 |
messages: [
|
| 235 |
-
{
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
${includePets ? "El creador tiene una mascota que quiere integrar en su contenido." : ""}
|
| 239 |
-
Responde en JSON con esta estructura:
|
| 240 |
-
{
|
| 241 |
-
"currentTrends": [{"name", "type", "growth", "saturation"}],
|
| 242 |
-
"emergingTrends": [{"name", "type", "potential"}],
|
| 243 |
-
"contentGaps": [{"topic", "opportunity", "difficulty"}],
|
| 244 |
-
"recommendations": ["rec1", "rec2"],
|
| 245 |
-
"bestPostingTimes": ["time1", "time2"],
|
| 246 |
-
"hashtagStrategy": {"primary": [], "secondary": []},
|
| 247 |
-
"viralPlan": {
|
| 248 |
-
"week1": [{"day", "contentType", "topic", "hook"}],
|
| 249 |
-
"week2": [{"day", "contentType", "topic", "hook"}]
|
| 250 |
-
},
|
| 251 |
-
"petIntegration": ${includePets ? `[{"tip", "contentIdea", "engagementPotential"}]` : "null"},
|
| 252 |
-
"predictedViralPotential": 0-100,
|
| 253 |
-
"keySuccessFactors": []
|
| 254 |
-
}`
|
| 255 |
-
},
|
| 256 |
-
{
|
| 257 |
-
role: "user",
|
| 258 |
-
content: `Analiza tendencias para:
|
| 259 |
-
Nicho: ${niche || "general"}
|
| 260 |
-
Plataforma: ${platform || "todas"}
|
| 261 |
-
Audiencia: ${targetAudience || "general"}
|
| 262 |
-
${includePets ? `Incluir mascota tipo: ${petType || "perro/gato"}` : ""}
|
| 263 |
-
Días objetivo para viralizar: ${daysToViral || 14}`
|
| 264 |
-
}
|
| 265 |
-
],
|
| 266 |
-
temperature: 0.7,
|
| 267 |
-
max_tokens: 3000,
|
| 268 |
});
|
| 269 |
|
| 270 |
-
|
| 271 |
-
let analysis;
|
| 272 |
try {
|
| 273 |
-
const match =
|
| 274 |
-
if (match)
|
| 275 |
-
|
| 276 |
-
}
|
| 277 |
-
} catch {
|
| 278 |
-
analysis = { raw: response };
|
| 279 |
-
}
|
| 280 |
-
|
| 281 |
-
// Guardar análisis en la base de datos
|
| 282 |
-
await prisma.trend.create({
|
| 283 |
-
data: {
|
| 284 |
-
platform: platform || "all",
|
| 285 |
-
type: "analysis",
|
| 286 |
-
name: `Análisis ${niche || "general"} - ${new Date().toISOString().split('T')[0]}`,
|
| 287 |
-
description: `Análisis de tendencias para nicho: ${niche}`,
|
| 288 |
-
contentIdeas: JSON.stringify(analysis),
|
| 289 |
-
isActive: true
|
| 290 |
-
}
|
| 291 |
-
}).catch(() => {
|
| 292 |
-
// Ignorar errores de guardado
|
| 293 |
-
});
|
| 294 |
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
timestamp: new Date().toISOString()
|
| 299 |
-
});
|
| 300 |
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
return NextResponse.json(
|
| 304 |
-
{ success: false, error: "Error al analizar tendencias" },
|
| 305 |
-
{ status: 500 }
|
| 306 |
-
);
|
| 307 |
}
|
| 308 |
-
}
|
| 309 |
-
|
| 310 |
-
// PUT - Crear estrategia viral personalizada
|
| 311 |
-
export async function PUT(request: NextRequest) {
|
| 312 |
-
try {
|
| 313 |
-
const body = await request.json();
|
| 314 |
-
const { name, platform, contentType, hook, structure, elements, estimatedReach, timeframe } = body;
|
| 315 |
-
|
| 316 |
-
const strategy = await prisma.viralStrategy.create({
|
| 317 |
-
data: {
|
| 318 |
-
name,
|
| 319 |
-
platform,
|
| 320 |
-
contentType,
|
| 321 |
-
hook,
|
| 322 |
-
structure: structure ? JSON.stringify(structure) : null,
|
| 323 |
-
elements: elements ? JSON.stringify(elements) : null,
|
| 324 |
-
estimatedReach,
|
| 325 |
-
timeframe,
|
| 326 |
-
isActive: true
|
| 327 |
-
}
|
| 328 |
-
});
|
| 329 |
-
|
| 330 |
-
return NextResponse.json({
|
| 331 |
-
success: true,
|
| 332 |
-
strategy,
|
| 333 |
-
message: "Estrategia viral creada correctamente"
|
| 334 |
-
});
|
| 335 |
-
|
| 336 |
-
} catch (error) {
|
| 337 |
-
console.error("Error creating viral strategy:", error);
|
| 338 |
-
return NextResponse.json(
|
| 339 |
-
{ success: false, error: "Error al crear estrategia" },
|
| 340 |
-
{ status: 500 }
|
| 341 |
-
);
|
| 342 |
-
}
|
| 343 |
-
}
|
|
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import ZAI from "z-ai-web-dev-sdk";
|
| 3 |
+
import { db } from "@/lib/db";
|
| 4 |
|
| 5 |
+
const TRENDS = [
|
| 6 |
+
{ platform: "Instagram", name: "#aesthetic", growth: 15.5, category: "lifestyle" },
|
| 7 |
+
{ platform: "TikTok", name: "Storytime", growth: 32.1, category: "narrative" },
|
| 8 |
+
{ platform: "YouTube", name: "Shorts", growth: 25.6, category: "video" },
|
| 9 |
+
{ platform: "Instagram", name: "Pets content", growth: 55.8, category: "pets" }
|
| 10 |
+
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
|
|
|
| 12 |
const VIRAL_STRATEGIES = [
|
| 13 |
+
{ name: "Hook en 3 segundos", successRate: 85, platforms: ["tiktok", "instagram"] },
|
| 14 |
+
{ name: "Storytime con suspenso", successRate: 78, platforms: ["tiktok"] },
|
| 15 |
+
{ name: "Pet Reveal", successRate: 88, platforms: ["instagram", "tiktok"] }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
];
|
| 17 |
|
|
|
|
| 18 |
export async function GET(request: NextRequest) {
|
| 19 |
try {
|
| 20 |
const { searchParams } = new URL(request.url);
|
| 21 |
const platform = searchParams.get("platform");
|
|
|
|
|
|
|
| 22 |
const includePets = searchParams.get("includePets") === "true";
|
| 23 |
|
| 24 |
+
let trends = [...TRENDS];
|
| 25 |
+
if (platform) trends = trends.filter(t => t.platform.toLowerCase() === platform.toLowerCase());
|
| 26 |
+
if (includePets) trends = trends.filter(t => t.category === "pets");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
trends.sort((a, b) => b.growth - a.growth);
|
| 29 |
|
| 30 |
+
return NextResponse.json({ success: true, trends, viralStrategies: VIRAL_STRATEGIES });
|
| 31 |
+
} catch {
|
| 32 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
}
|
| 34 |
}
|
| 35 |
|
|
|
|
| 36 |
export async function POST(request: NextRequest) {
|
| 37 |
try {
|
| 38 |
const body = await request.json();
|
| 39 |
+
const { niche, platform } = body;
|
| 40 |
|
| 41 |
const zai = await ZAI.create();
|
|
|
|
| 42 |
const completion = await zai.chat.completions.create({
|
| 43 |
messages: [
|
| 44 |
+
{ role: "system", content: "Eres analista de tendencias. Responde JSON con {currentTrends:[], recommendations:[], predictedViralPotential:0-100}" },
|
| 45 |
+
{ role: "user", content: "Analiza tendencias para nicho: " + (niche || "general") + " plataforma: " + (platform || "todas") }
|
| 46 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
});
|
| 48 |
|
| 49 |
+
let analysis: Record<string, unknown> = {};
|
|
|
|
| 50 |
try {
|
| 51 |
+
const match = completion.choices[0]?.message?.content?.match(/\{[\s\S]*\}/);
|
| 52 |
+
if (match) analysis = JSON.parse(match[0]);
|
| 53 |
+
} catch {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
+
await db.trend.create({
|
| 56 |
+
data: { platform: platform || "all", type: "analysis", name: "Analisis " + (niche || "general"), contentIdeas: JSON.stringify(analysis) }
|
| 57 |
+
}).catch(() => {});
|
|
|
|
|
|
|
| 58 |
|
| 59 |
+
return NextResponse.json({ success: true, analysis });
|
| 60 |
+
} catch {
|
| 61 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
| 62 |
}
|
| 63 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|