306 lines
12 KiB
JavaScript
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
|