You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
530 lines
15 KiB
530 lines
15 KiB
'use strict';
|
|
|
|
const superagent = require('superagent');
|
|
|
|
module.exports.getBooks = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.fs.dc;
|
|
const books = await models.Book.findAll({
|
|
order: [['createdAt', 'DESC']]
|
|
});
|
|
|
|
ctx.status = 200;
|
|
ctx.body = books;
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '获取书籍列表失败'
|
|
};
|
|
}
|
|
};
|
|
|
|
module.exports.createBook = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.fs.dc;
|
|
const { name, requirements } = ctx.request.body;
|
|
|
|
if (!name) {
|
|
throw '书籍名称不能为空';
|
|
}
|
|
|
|
const book = await models.Book.create({
|
|
name,
|
|
requirements
|
|
});
|
|
|
|
ctx.status = 200;
|
|
ctx.body = book;
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '创建书籍失败'
|
|
};
|
|
}
|
|
};
|
|
|
|
module.exports.updateBook = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.fs.dc;
|
|
const { id } = ctx.params;
|
|
const { name, requirements } = ctx.request.body;
|
|
|
|
const book = await models.Book.findByPk(id);
|
|
if (!book) {
|
|
throw '书籍不存在';
|
|
}
|
|
|
|
await book.update({
|
|
name,
|
|
requirements
|
|
});
|
|
|
|
ctx.status = 200;
|
|
ctx.body = book;
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '更新书籍失败'
|
|
};
|
|
}
|
|
};
|
|
|
|
module.exports.deleteBook = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.fs.dc;
|
|
const { id } = ctx.params;
|
|
|
|
const book = await models.Book.findByPk(id);
|
|
if (!book) {
|
|
throw '书籍不存在';
|
|
}
|
|
|
|
await book.destroy();
|
|
|
|
ctx.status = 204;
|
|
ctx.body = {};
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '删除书籍失败'
|
|
};
|
|
}
|
|
};
|
|
|
|
module.exports.getBookChapters = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.fs.dc;
|
|
const { bookId } = ctx.params;
|
|
|
|
const chapters = await models.BookChapter.findAll({
|
|
where: { bookId },
|
|
order: [['level', 'ASC'], ['sortOrder', 'ASC'], ['createdAt', 'ASC']],
|
|
include: [{
|
|
model: models.BookChapterContent,
|
|
as: 'bookChapterContents',
|
|
separate: true,
|
|
order: [['version', 'DESC']],
|
|
limit: 1
|
|
}]
|
|
});
|
|
|
|
// 构建树状结构
|
|
const buildTree = (chapters, parentId = null) => {
|
|
return chapters
|
|
.filter(chapter => chapter.parentId === parentId)
|
|
.map(chapter => ({
|
|
...chapter.toJSON(),
|
|
children: buildTree(chapters, chapter.id),
|
|
latestContent: chapter.bookChapterContents?.[0] || null
|
|
}));
|
|
};
|
|
|
|
const treeData = buildTree(chapters);
|
|
|
|
ctx.status = 200;
|
|
ctx.body = treeData;
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '获取章节列表失败'
|
|
};
|
|
}
|
|
};
|
|
|
|
module.exports.createChapter = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.fs.dc;
|
|
const { bookId } = ctx.params;
|
|
const { parentId, level, title, requirements, sortOrder = 0 } = ctx.request.body;
|
|
|
|
if (!title) {
|
|
throw '章节标题不能为空';
|
|
}
|
|
|
|
const chapter = await models.BookChapter.create({
|
|
bookId,
|
|
parentId,
|
|
level,
|
|
title,
|
|
requirements,
|
|
sortOrder
|
|
});
|
|
|
|
ctx.status = 200;
|
|
ctx.body = chapter;
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '创建章节失败'
|
|
};
|
|
}
|
|
};
|
|
|
|
module.exports.updateChapter = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.fs.dc;
|
|
const { bookId, id } = ctx.params;
|
|
const { title, requirements, sortOrder, parentId, level } = ctx.request.body;
|
|
|
|
const chapter = await models.BookChapter.findByPk(id);
|
|
if (!chapter) {
|
|
throw '章节不存在';
|
|
}
|
|
|
|
const updateData = {};
|
|
if (title !== undefined) updateData.title = title;
|
|
if (requirements !== undefined) updateData.requirements = requirements;
|
|
if (sortOrder !== undefined) updateData.sortOrder = sortOrder;
|
|
if (parentId !== undefined) updateData.parentId = parentId;
|
|
if (level !== undefined) updateData.level = level;
|
|
|
|
await chapter.update(updateData);
|
|
|
|
ctx.status = 200;
|
|
ctx.body = chapter;
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '更新章节失败'
|
|
};
|
|
}
|
|
};
|
|
|
|
module.exports.deleteChapter = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.fs.dc;
|
|
const { bookId, id } = ctx.params;
|
|
|
|
const chapter = await models.BookChapter.findByPk(id);
|
|
if (!chapter) {
|
|
throw '章节不存在';
|
|
}
|
|
|
|
await chapter.destroy();
|
|
|
|
ctx.status = 204;
|
|
ctx.body = {};
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '删除章节失败'
|
|
};
|
|
}
|
|
};
|
|
|
|
module.exports.generateChapterContent = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.fs.dc;
|
|
const { bookId, id } = ctx.params;
|
|
|
|
const chapter = await models.BookChapter.findByPk(id, {
|
|
include: [{ model: models.Book, as: 'book' }]
|
|
});
|
|
|
|
if (!chapter) {
|
|
throw '章节不存在';
|
|
}
|
|
|
|
// 更新状态为生成中
|
|
await chapter.update({ generationStatus: 'generating' });
|
|
|
|
// 立即返回响应,避免前端超时
|
|
ctx.status = 200;
|
|
ctx.body = {
|
|
message: '已开始生成,请稍后查看',
|
|
status: 'generating'
|
|
};
|
|
|
|
// 异步执行生成逻辑
|
|
(async () => {
|
|
try {
|
|
// 获取书籍整体要求
|
|
const book = chapter.book;
|
|
|
|
// 获取父章节信息 (如果存在)
|
|
let topChapterName = chapter.title;
|
|
let topChapterRequirements = chapter.requirements || '';
|
|
|
|
if (chapter.parentId) {
|
|
const parentChapter = await models.BookChapter.findByPk(chapter.parentId);
|
|
if (parentChapter) {
|
|
topChapterName = parentChapter.title;
|
|
topChapterRequirements = parentChapter.requirements || '';
|
|
}
|
|
}
|
|
|
|
// 真实调用逻辑
|
|
const requestBody = {
|
|
stream: false,
|
|
detail: false,
|
|
variables: {
|
|
outline: book.requirements || '',
|
|
topChapterName: topChapterName,
|
|
topChapterRequirements: topChapterRequirements,
|
|
chapterRequirements: chapter.requirements || ''
|
|
},
|
|
messages: [{
|
|
role: 'user',
|
|
content: [{
|
|
type: 'text',
|
|
text: chapter.title
|
|
}]
|
|
}]
|
|
};
|
|
|
|
const response = await superagent
|
|
.post(`${ctx.app.fs.config.fastGpt.apiUrl}/api/v1/chat/completions`)
|
|
.send(requestBody)
|
|
.set({
|
|
'Authorization': `Bearer ${ctx.app.fs.config.fastGpt.bookWriterAppKey}`,
|
|
'Content-Type': 'application/json'
|
|
});
|
|
|
|
const data = JSON.parse(response.text);
|
|
const content = data?.choices?.[0]?.message?.content || '';
|
|
|
|
// 模拟生成延迟 (10秒)
|
|
// await new Promise(resolve => setTimeout(resolve, 10000));
|
|
// const content = `## ${chapter.title}\n\nThis is a simulated generated content for **${chapter.title}**.\n\nTime: ${new Date().toLocaleString()}\n\nRequirements: ${chapter.requirements || 'None'}`;
|
|
|
|
// 创建新内容版本
|
|
const latestContent = await models.BookChapterContent.findOne({
|
|
where: { chapterId: chapter.id },
|
|
order: [['version', 'DESC']]
|
|
});
|
|
|
|
const newVersion = latestContent ? latestContent.version + 1 : 1;
|
|
|
|
await models.BookChapterContent.create({
|
|
chapterId: chapter.id,
|
|
content: content,
|
|
version: newVersion
|
|
});
|
|
|
|
// 更新状态为成功
|
|
await chapter.update({ generationStatus: 'success' });
|
|
|
|
} catch (error) {
|
|
console.error('Generation failed:', error);
|
|
// 更新状态为失败
|
|
try {
|
|
await chapter.update({ generationStatus: 'failed' });
|
|
} catch (e) {
|
|
console.error('Update status failed:', e);
|
|
}
|
|
}
|
|
})();
|
|
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '生成章节内容失败'
|
|
};
|
|
}
|
|
};
|
|
|
|
module.exports.getChapterContent = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.fs.dc;
|
|
const { bookId, chapterId } = ctx.params;
|
|
|
|
const contents = await models.BookChapterContent.findAll({
|
|
where: { chapterId },
|
|
order: [['version', 'DESC']]
|
|
});
|
|
|
|
ctx.status = 200;
|
|
ctx.body = contents;
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '获取章节内容失败'
|
|
};
|
|
}
|
|
};
|
|
|
|
module.exports.polishContent = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.fs.dc;
|
|
const { bookId, chapterId } = ctx.params;
|
|
const { requirements } = ctx.request.body;
|
|
|
|
const chapter = await models.BookChapter.findByPk(chapterId, {
|
|
include: [{
|
|
model: models.BookChapterContent,
|
|
order: [['version', 'DESC']],
|
|
limit: 1
|
|
}]
|
|
});
|
|
|
|
const latestContent = chapter.bookChapterContents?.[0];
|
|
|
|
if (!chapter) {
|
|
throw '章节不存在';
|
|
}
|
|
if (!latestContent) {
|
|
throw '章节内容不存在';
|
|
}
|
|
|
|
// 更新状态为正在润色
|
|
await chapter.update({ generationStatus: 'polishing' });
|
|
|
|
// 立即返回响应,避免前端超时
|
|
ctx.status = 200;
|
|
ctx.body = {
|
|
message: '内容正在润色中',
|
|
};
|
|
|
|
// 异步执行润色
|
|
(async () => {
|
|
try {
|
|
// 真实调用逻辑
|
|
const requestBody = {
|
|
stream: false,
|
|
detail: false,
|
|
variables: {
|
|
requirements: requirements || '',
|
|
},
|
|
messages: [{
|
|
role: 'user',
|
|
content: [{
|
|
type: 'text',
|
|
text: latestContent.content || ''
|
|
}]
|
|
}]
|
|
};
|
|
|
|
const response = await superagent
|
|
.post(`${ctx.app.fs.config.fastGpt.apiUrl}/api/v1/chat/completions`)
|
|
.send(requestBody)
|
|
.set({
|
|
'Authorization': `Bearer ${ctx.app.fs.config.fastGpt.polishAppKey}`,
|
|
'Content-Type': 'application/json'
|
|
});
|
|
|
|
const data = JSON.parse(response.text);
|
|
const polishedContent = data?.choices?.[0]?.message?.content || '';
|
|
|
|
// 模拟润色延迟
|
|
// await new Promise(resolve => setTimeout(resolve, 5000));
|
|
// const polishedContent = latestContent.content;
|
|
|
|
// 创建新章节内容
|
|
await models.BookChapterContent.create({
|
|
chapterId: chapter.id,
|
|
content: polishedContent,
|
|
version: latestContent.version + 1
|
|
});
|
|
|
|
// 更新状态为成功
|
|
await chapter.update({ generationStatus: 'success' });
|
|
|
|
} catch (error) {
|
|
console.error('Polish failed:', error);
|
|
// 更新状态为失败
|
|
try {
|
|
await chapter.update({ generationStatus: 'polishFailed' });
|
|
} catch (e) {
|
|
console.error('Update status polishFailed:', e);
|
|
}
|
|
}
|
|
})();
|
|
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '润色失败'
|
|
};
|
|
}
|
|
};
|
|
|
|
module.exports.restoreChapterVersion = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.fs.dc;
|
|
const { bookId, chapterId, contentId } = ctx.params;
|
|
|
|
const targetContent = await models.BookChapterContent.findByPk(contentId);
|
|
if (!targetContent) {
|
|
throw '指定版本不存在';
|
|
}
|
|
if (parseInt(targetContent.chapterId) !== parseInt(chapterId)) {
|
|
throw '版本与章节不匹配';
|
|
}
|
|
|
|
const latestContent = await models.BookChapterContent.findOne({
|
|
where: { chapterId },
|
|
order: [['version', 'DESC']]
|
|
});
|
|
|
|
const newVersion = latestContent ? latestContent.version + 1 : 1;
|
|
|
|
await models.BookChapterContent.create({
|
|
chapterId,
|
|
content: targetContent.content,
|
|
version: newVersion
|
|
});
|
|
|
|
// 同时也应该把章节状态重置为success,防止处在failed状态
|
|
await models.BookChapter.update(
|
|
{ generationStatus: 'success' },
|
|
{ where: { id: chapterId } }
|
|
);
|
|
|
|
ctx.status = 200;
|
|
ctx.body = { message: '版本回退成功' };
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '版本回退失败'
|
|
};
|
|
}
|
|
};
|
|
|
|
module.exports.saveChapterContent = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.fs.dc;
|
|
const { bookId, chapterId } = ctx.params;
|
|
const { content } = ctx.request.body;
|
|
|
|
const chapter = await models.BookChapter.findByPk(chapterId);
|
|
if (!chapter) {
|
|
throw '章节不存在';
|
|
}
|
|
|
|
const latestContent = await models.BookChapterContent.findOne({
|
|
where: { chapterId },
|
|
order: [['version', 'DESC']]
|
|
});
|
|
|
|
const newVersion = latestContent ? latestContent.version + 1 : 1;
|
|
|
|
await models.BookChapterContent.create({
|
|
chapterId,
|
|
content,
|
|
version: newVersion
|
|
});
|
|
|
|
await chapter.update({ generationStatus: 'success' });
|
|
|
|
ctx.status = 200;
|
|
ctx.body = { message: '保存成功', version: newVersion };
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = {
|
|
message: typeof error == 'string' ? error : '保存章节内容失败'
|
|
};
|
|
}
|
|
};
|
|
|