Files
CreaBook/server/dist/api/books.js
2026-04-05 03:11:25 +02:00

306 lines
12 KiB
JavaScript

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.bookRoutes = void 0;
const express_1 = require("express");
const axios_1 = __importDefault(require("axios"));
const genreTemplates_js_1 = require("../prompts/genreTemplates.js");
exports.bookRoutes = (0, express_1.Router)();
// Get all available genre templates
exports.bookRoutes.get('/genres', (req, res) => {
const genres = Object.entries(genreTemplates_js_1.genreTemplates).map(([key, template]) => ({
id: key,
name: template.name,
description: template.description,
icon: template.icon
}));
res.json({ genres });
});
// Get a specific genre template
exports.bookRoutes.get('/genres/:genreId', (req, res) => {
const { genreId } = req.params;
const template = genreTemplates_js_1.genreTemplates[genreId];
if (!template) {
return res.status(404).json({ error: 'Genre not found' });
}
res.json({ template });
});
// Generate book outline based on genre and idea
exports.bookRoutes.post('/outline', async (req, res) => {
try {
const { genre, idea, title, language } = req.body;
const targetLang = language && language.startsWith('es') ? 'Spanish' : 'English';
if (!genre || !idea) {
return res.status(400).json({ error: 'genre and idea are required' });
}
const template = genreTemplates_js_1.genreTemplates[genre];
if (!template) {
return res.status(400).json({ error: 'Invalid genre' });
}
const prompt = `${template.prompts.outline}
Book Title: ${title || 'Untitled'}
Core Idea: ${idea}
Generate a detailed chapter outline following the structure: ${template.structure.join(' → ')}
IMPORTANT: The response MUST be written in ${targetLang}. All text values (title, logline, chapter summaries, etc) must be translated to ${targetLang}. Keep the exact JSON KEYS in English.
Return the response in JSON format:
{
"title": "Book Title",
"genre": "${genre}",
"logline": "One sentence summary",
"chapters": [
{"number": 1, "title": "Chapter Title", "summary": "Brief description"},
...
]
}`;
const response = await axios_1.default.post('https://openrouter.ai/api/v1/chat/completions', {
model: 'nvidia/nemotron-3-nano-30b-a3b:free', // Free model on OpenRouter
messages: [{ role: 'user', content: prompt }],
temperature: 0.7,
max_tokens: 2000
}, {
headers: {
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://creabook.app',
'X-Title': 'CreaBook'
}
});
const content = response.data.choices[0].message.content;
console.log('=== Outline AI Response ===');
console.log('Raw content:', content);
// Try to parse JSON from response
let outline;
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
try {
outline = JSON.parse(jsonMatch[0]);
console.log('Parsed outline:', outline);
}
catch (parseError) {
console.error('JSON parse error:', parseError);
outline = { raw: content, error: 'Failed to parse JSON' };
}
}
else {
console.error('No JSON object found in response');
outline = { raw: content, error: 'No JSON found' };
}
res.json({ outline });
}
catch (error) {
console.error('Outline generation error:', error);
res.status(500).json({
error: 'Failed to generate outline',
details: error instanceof Error ? error.message : String(error)
});
}
});
// Generate a chapter based on outline
exports.bookRoutes.post('/chapter', async (req, res) => {
try {
const { genre, chapterTitle, chapterSummary, previousContent, language } = req.body;
const targetLang = language && language.startsWith('es') ? 'Spanish' : 'English';
if (!genre || !chapterTitle || !chapterSummary) {
return res.status(400).json({ error: 'genre, chapterTitle, and chapterSummary are required' });
}
const template = genreTemplates_js_1.genreTemplates[genre];
let prompt = `${template.prompts.chapter}
Chapter: ${chapterTitle}
Summary: ${chapterSummary}
Tone: ${template.defaults.tone}
POV: ${template.defaults.pov}
IMPORTANT: The entire chapter content MUST be written strictly in ${targetLang}.
`;
if (previousContent) {
prompt += `\n\nPrevious content for context:\n${previousContent.substring(0, 2000)}...`;
}
const response = await axios_1.default.post('https://openrouter.ai/api/v1/chat/completions', {
model: 'nvidia/nemotron-3-nano-30b-a3b:free',
messages: [{ role: 'user', content: prompt }],
temperature: 0.7,
max_tokens: 3000
}, {
headers: {
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://creabook.app',
'X-Title': 'CreaBook'
}
});
const content = response.data.choices[0].message.content;
res.json({
content,
chapterTitle
});
}
catch (error) {
console.error('Chapter generation error:', error);
res.status(500).json({
error: 'Failed to generate chapter',
details: error instanceof Error ? error.message : String(error)
});
}
});
// Expand or refine text
exports.bookRoutes.post('/expand', async (req, res) => {
try {
const { text, instruction = 'Expand and improve this text', language } = req.body;
const targetLang = language && language.startsWith('es') ? 'Spanish' : 'English';
if (!text) {
return res.status(400).json({ error: 'text is required' });
}
const prompt = `${instruction}:
Original text:
${text}
Provide an expanded and improved version. IMPORTANT: Write the expanded text entirely in ${targetLang}:`;
const response = await axios_1.default.post('https://openrouter.ai/api/v1/chat/completions', {
model: 'nvidia/nemotron-3-nano-30b-a3b:free',
messages: [{ role: 'user', content: prompt }],
temperature: 0.7,
max_tokens: 2000
}, {
headers: {
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://creabook.app',
'X-Title': 'CreaBook'
}
});
const content = response.data.choices[0].message.content;
res.json({ expanded: content });
}
catch (error) {
console.error('Expand error:', error);
res.status(500).json({
error: 'Failed to expand text',
details: error instanceof Error ? error.message : String(error)
});
}
});
// Generate character suggestions
exports.bookRoutes.post('/characters', async (req, res) => {
try {
const { genre, storyIdea, language } = req.body;
const targetLang = language && language.startsWith('es') ? 'Spanish' : 'English';
if (!genre || !storyIdea) {
return res.status(400).json({ error: 'genre and storyIdea are required' });
}
const template = genreTemplates_js_1.genreTemplates[genre];
const prompt = `Based on this ${genre} story idea, suggest 3-5 main characters.
Story Idea: ${storyIdea}
Genre conventions: ${template.description}
IMPORTANT: Write the names, traits, motivation, and backstory values completely in ${targetLang}. Keep the required JSON keys strictly in English.
Return ONLY a valid JSON array with this exact format:
[
{
"name": "Character Name",
"role": "protagonist|antagonist|supporting",
"traits": ["trait1", "trait2"],
"motivation": "What drives them",
"backstory": "Brief backstory"
}
]
Do not include any text outside the JSON array.`;
console.log('=== Character Generation Prompt ===');
console.log(prompt);
const response = await axios_1.default.post('https://openrouter.ai/api/v1/chat/completions', {
model: 'nvidia/nemotron-3-nano-30b-a3b:free',
messages: [{ role: 'user', content: prompt }],
temperature: 0.7,
max_tokens: 1500
}, {
headers: {
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://creabook.app',
'X-Title': 'CreaBook'
}
});
const content = response.data.choices[0].message.content;
console.log('=== Character AI Response ===');
console.log('Raw content:', content);
let characters;
const jsonMatch = content.match(/\[[\s\S]*\]/);
if (jsonMatch) {
try {
characters = JSON.parse(jsonMatch[0]);
console.log('Parsed characters:', characters);
}
catch (parseError) {
console.error('JSON parse error:', parseError);
characters = { raw: content, error: 'Failed to parse JSON' };
}
}
else {
console.error('No JSON array found in response');
characters = { raw: content, error: 'No JSON array found' };
}
res.json({ characters });
}
catch (error) {
console.error('Character generation error:', error);
res.status(500).json({
error: 'Failed to generate characters',
details: error instanceof Error ? error.message : String(error)
});
}
});
// Generate plot suggestions
exports.bookRoutes.post('/plot', async (req, res) => {
try {
const { genre, currentPlot, issue, language } = req.body;
const targetLang = language && language.startsWith('es') ? 'Spanish' : 'English';
if (!genre || !currentPlot) {
return res.status(400).json({ error: 'genre and currentPlot are required' });
}
const template = genreTemplates_js_1.genreTemplates[genre];
const prompt = `Help develop the plot for this ${genre} story:
Current Plot:
${currentPlot}
${issue ? `Specific Issue: ${issue}` : 'Suggest plot developments and twists.'}
Consider genre conventions: ${template.description}
Structure: ${template.structure.join(' → ')}
IMPORTANT: Provide all specific, actionable plot suggestions completely in ${targetLang}:`;
const response = await axios_1.default.post('https://openrouter.ai/api/v1/chat/completions', {
model: 'nvidia/nemotron-3-nano-30b-a3b:free',
messages: [{ role: 'user', content: prompt }],
temperature: 0.7,
max_tokens: 1500
}, {
headers: {
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://creabook.app',
'X-Title': 'CreaBook'
}
});
const content = response.data.choices[0].message.content;
res.json({ suggestions: content });
}
catch (error) {
console.error('Plot suggestion error:', error);
res.status(500).json({
error: 'Failed to generate plot suggestions',
details: error instanceof Error ? error.message : String(error)
});
}
});
//# sourceMappingURL=books.js.map