ai-query对接新版freesun-agent接口的分支
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.
 
 
 

277 lines
10 KiB

'use strict';
const { v4: uuidv4 } = require('uuid');
const { AI_WRITING_STATUS, AI_WRITING_FAILED_STATUS, TEMP_POSITION_BASE } = require('./constants');
const { hasOwn, toInt, normalizeParentId } = require('./helpers');
const { requestFastGptTextWithApp } = require('./ai');
const { reportBusinessCall, resolveContextUserId } = require('../../services/dashboardReporter');
const { ensureInstance, ensureChapter, getInstanceChapterTree } = require('./repository');
const {
ensureNoChapterCycle,
reorderChapterSiblingsWithInsert,
reorderChapterSiblings,
} = require('./ordering');
module.exports.getInstanceChapters = async (ctx, next) => {
try {
const instanceId = toInt(ctx.params.instanceId);
if (!instanceId) throw '缺少参数: instanceId';
await ensureInstance(ctx, instanceId);
const chapters = await getInstanceChapterTree(ctx, instanceId);
ctx.body = chapters;
ctx.status = 200;
} catch (error) {
ctx.logger.log(error);
ctx.status = 400;
ctx.body = { message: typeof error === 'string' ? error : '获取章节列表失败' };
}
};
module.exports.createChapter = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const instanceId = toInt(ctx.params.instanceId);
if (!instanceId) throw '缺少参数: instanceId';
await ensureInstance(ctx, instanceId, transaction);
const { title, position, prompts } = ctx.request.body || {};
if (!String(title || '').trim()) throw '缺少参数: title';
const parentId = normalizeParentId(ctx.request.body?.parentId);
await ensureNoChapterCycle(ctx, instanceId, null, parentId, transaction);
const created = await models.ReportChapter.create({
instanceId,
parentId,
title: String(title).trim(),
position: TEMP_POSITION_BASE - 1,
prompts: prompts || null,
status: null,
}, {
transaction,
returning: true,
});
await reorderChapterSiblingsWithInsert(ctx, instanceId, parentId, created.id, position, transaction);
const fresh = await models.ReportChapter.findByPk(created.id, { transaction });
await transaction.commit();
ctx.body = fresh;
ctx.status = 200;
} catch (error) {
await transaction.rollback();
ctx.logger.log(error);
ctx.status = 400;
ctx.body = { message: typeof error === 'string' ? error : '创建章节失败' };
}
};
module.exports.updateChapter = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const instanceId = toInt(ctx.params.instanceId);
const chapterId = toInt(ctx.params.chapterId);
if (!instanceId || !chapterId) throw '缺少参数';
const chapter = await ensureChapter(ctx, instanceId, chapterId, transaction);
const body = ctx.request.body || {};
const updateData = {};
if (hasOwn(body, 'title')) updateData.title = body.title ? String(body.title).trim() : '';
if (hasOwn(body, 'prompts')) updateData.prompts = body.prompts || null;
if (hasOwn(body, 'status')) updateData.status = body.status || null;
const hasContentField = hasOwn(body, 'content');
const nextContent = hasContentField ? String(body.content || '') : null;
const oldParentId = chapter.parentId;
const nextParentId = hasOwn(body, 'parentId') ? normalizeParentId(body.parentId) : chapter.parentId;
await ensureNoChapterCycle(ctx, instanceId, chapterId, nextParentId, transaction);
const needMove = hasOwn(body, 'parentId') || hasOwn(body, 'position');
if (!needMove) {
await models.ReportChapter.update(updateData, { where: { id: chapterId }, transaction });
if (hasContentField) {
const chapterBlocks = await models.ReportBlock.findAll({
where: { instanceId, chapterId },
order: [['position', 'ASC'], ['id', 'ASC']],
raw: true,
transaction,
});
const textBlock = chapterBlocks.find(
(block) => String(block?.blockType || '').trim() === 'text'
);
if (textBlock?.id) {
await models.ReportBlock.update({
contentSnapshot: {
...(textBlock?.contentSnapshot || {}),
content: nextContent,
},
updatedAt: new Date(),
}, {
where: { id: textBlock.id, instanceId },
transaction,
});
} else {
const nextPosition = chapterBlocks.reduce((max, item) => {
const pos = Number(item?.position || 0);
return pos > max ? pos : max;
}, 0) + 1;
await models.ReportBlock.create({
instanceId,
chapterId,
blockType: 'text',
position: nextPosition,
config: {
title: '正文',
},
contentSnapshot: {
content: nextContent,
},
updatedAt: new Date(),
}, {
transaction,
});
}
}
await transaction.commit();
ctx.status = 204;
return;
}
updateData.parentId = nextParentId;
updateData.position = TEMP_POSITION_BASE - 1;
await models.ReportChapter.update(updateData, { where: { id: chapterId }, transaction });
if (oldParentId !== nextParentId) {
await reorderChapterSiblings(ctx, instanceId, oldParentId, transaction);
}
const targetPosition = hasOwn(body, 'position')
? body.position
: (oldParentId === nextParentId ? chapter.position : undefined);
await reorderChapterSiblingsWithInsert(ctx, instanceId, nextParentId, chapterId, targetPosition, transaction);
await transaction.commit();
ctx.status = 204;
} catch (error) {
await transaction.rollback();
ctx.logger.log(error);
ctx.status = 400;
ctx.body = { message: typeof error === 'string' ? error : '更新章节失败' };
}
};
module.exports.deleteChapter = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const instanceId = toInt(ctx.params.instanceId);
const chapterId = toInt(ctx.params.chapterId);
if (!instanceId || !chapterId) throw '缺少参数';
const chapter = await ensureChapter(ctx, instanceId, chapterId, transaction);
await models.ReportChapter.destroy({
where: { id: chapterId, instanceId },
transaction,
});
await reorderChapterSiblings(ctx, instanceId, chapter.parentId, transaction);
await transaction.commit();
ctx.status = 204;
} catch (error) {
await transaction.rollback();
ctx.logger.log(error);
ctx.status = 400;
ctx.body = { message: typeof error === 'string' ? error : '删除章节失败' };
}
};
module.exports.generateChapterAiContent = async (ctx, next) => {
try {
const { models } = ctx.app.fs.dc;
const instanceId = toInt(ctx.params.instanceId);
const chapterId = toInt(ctx.params.chapterId);
const { prompt, blockType, config } = ctx.request.body || {};
if (!instanceId || !chapterId) throw '缺少参数';
const promptText = String(prompt || '').trim();
if (!promptText) throw '缺少参数: prompt';
const chapter = await ensureChapter(ctx, instanceId, chapterId);
await models.ReportChapter.update({
prompts: promptText,
status: AI_WRITING_STATUS,
}, {
where: { id: chapterId, instanceId },
});
ctx.body = {
instanceId,
chapterId,
status: AI_WRITING_STATUS,
message: 'AI 写作任务已开始',
};
ctx.status = 202;
const app = ctx.app;
const logger = ctx.logger;
const analyticsUserId = resolveContextUserId(ctx);
const actionId = `report-chapter:${instanceId}:${chapterId}:${uuidv4()}`;
const finalBlockType = String(blockType || 'ai_analysis');
const finalConfig = config || {};
setImmediate(async () => {
const transaction = await app.fs.dc.orm.transaction();
try {
const curReport = await app.fs.dc.models.ReportInstance.findByPk(instanceId, { transaction });
const chapters = await getInstanceChapterTree(ctx, instanceId, transaction);
const aiText = await requestFastGptTextWithApp(
ctx,
`章节标题: ${chapter.title}\n写作提示: ${promptText}`,
{
actionId,
userId: analyticsUserId,
variables: {
type: '章节内容生成',
reportTitle: curReport.name,
// chapters,
}
}
);
await app.fs.dc.models.ReportBlock.destroy({
where: { instanceId, chapterId },
transaction,
});
await app.fs.dc.models.ReportBlock.create({
instanceId,
chapterId,
blockType: finalBlockType,
position: 1,
config: finalConfig,
contentSnapshot: {
content: aiText,
},
updatedAt: new Date(),
}, { transaction });
await app.fs.dc.models.ReportChapter.update({
status: null,
}, {
where: { id: chapterId, instanceId },
transaction,
});
await transaction.commit();
await reportBusinessCall({
ctx,
applicationId: 'exp-report',
eventId: actionId,
traceId: actionId,
userId: analyticsUserId,
reportContext: { action: 'chapter_generation', instanceId, chapterId },
});
} catch (error) {
await transaction.rollback();
logger.log(error);
await app.fs.dc.models.ReportChapter.update({
status: AI_WRITING_FAILED_STATUS,
}, {
where: { id: chapterId, instanceId },
});
}
});
} catch (error) {
ctx.logger.log(error);
ctx.status = 400;
ctx.body = { message: typeof error === 'string' ? error : '触发章节 AI 写作失败' };
}
};