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.
 
 
 

1766 lines
56 KiB

'use strict';
const superagent = require('superagent');
const moment = require('moment');
const { reportBusinessCall, reportFastgptResponse } = require('../services/dashboardReporter');
const PATENT_STATUS_ENUM = {
'created': '刚创建',
'chaptersGenerating': '预览目录生成中',
'chaptersGenerateSuccess': '预览目录生成成功',
'chaptersGenerateFailed': '预览目录生成失败',
'chaptersCreated': '已确认创建目录',
'contentGenerating': '内容生成中',
'contentGenerateSuccess': '内容生成成功',
'contentGenerateFailed': '内容生成失败',
};
const DEFAULT_BASE_FIELDS = [
{ fieldKey: 'invention_content', fieldTitle: '发明内容', sortOrder: 0 },
{ fieldKey: 'background_technology', fieldTitle: '背景技术', sortOrder: 1 },
{ fieldKey: 'closest_tech_problems', fieldTitle: '最接近技术存在问题及技术原因', sortOrder: 2 },
{ fieldKey: 'technical_problem_to_solve', fieldTitle: '本申请要解决的技术问题', sortOrder: 3 },
{ fieldKey: 'key_points_and_protection', fieldTitle: '关键点和保护点', sortOrder: 4 },
{ fieldKey: 'full_technical_solution', fieldTitle: '完整技术方案', sortOrder: 5 },
{ fieldKey: 'alternative_solutions', fieldTitle: '替代方案', sortOrder: 6 },
{ fieldKey: 'specific_implementation', fieldTitle: '具体实施方式', sortOrder: 7 },
{ fieldKey: 'beneficial_effects', fieldTitle: '有益效果', sortOrder: 8 },
{ fieldKey: 'drawings_and_notes', fieldTitle: '附图及说明', sortOrder: 9 },
{ fieldKey: 'other_materials', fieldTitle: '其他有助于理解资料', sortOrder: 10 },
];
const PATENT_DISCLOSURE_PREFACE_HTML = `
<table style="width:100%;border-collapse:collapse;">
<tbody>
<tr>
<td style="border:1px solid #000;">专利名称</td>
<td colspan="5" style="border:1px solid #000;"></td>
</tr>
<tr>
<td style="border:1px solid #000;">技术联系人</td>
<td style="border:1px solid #000;"></td>
<td style="border:1px solid #000;">电话</td>
<td style="border:1px solid #000;"></td>
<td style="border:1px solid #000;">申请类型(发明、实用、外观)</td>
<td style="border:1px solid #000;"></td>
</tr>
<tr>
<td style="border:1px solid #000;">发明人</td>
<td colspan="3" style="border:1px solid #000;"></td>
<td style="border:1px solid #000;">本申请所属的技术领域(比如化妆品、汽车、电子通信、计算机)</td>
<td style="border:1px solid #000;"></td>
</tr>
<tr>
<td style="border:1px solid #000;">专利权人</td>
<td style="border:1px solid #000;"></td>
<td style="border:1px solid #000;">研发立项项目</td>
<td style="border:1px solid #000;"></td>
<td style="border:1px solid #000;">关联产品</td>
<td style="border:1px solid #000;"></td>
</tr>
<tr>
<td style="border:1px solid #000;">申请专利的目的</td>
<td colspan="5" style="border:1px solid #000;">□保护, □干扰竞争对手, □对抗/进攻对手, □其他___________</td>
</tr>
</tbody>
</table>
`.trim();
const PATENT_INFO_CHAPTER_TITLE = '专利信息';
const normalizeChapterTitle = (value = '') =>
String(value || '').trim().replace(/\s+/g, '');
const isPatentInfoChapter = (chapter = {}) =>
normalizeChapterTitle(chapter?.title || chapter?.name) ===
normalizeChapterTitle(PATENT_INFO_CHAPTER_TITLE);
const getFastGptClient = (ctx) => {
const { apiUrl, knowledgeAppKey } = ctx.app.fs.config.fastGpt;
if (!apiUrl || !knowledgeAppKey) {
throw 'FASTGPT 配置缺失,请检查 FASTGPT_URL 与 FASTGPT_KNOWLEDGE_APP_KEY';
}
return { apiUrl, appKey: knowledgeAppKey };
};
const callFastGpt = async (ctx, {
variables = {},
messages = [],
userId = '',
actionId = '',
includeCall = true,
reportContext = {},
}) => {
const { apiUrl, appKey } = getFastGptClient(ctx);
const res = await superagent
.post(`${apiUrl}/api/v1/chat/completions`)
.send({
stream: false,
detail: true,
variables,
messages,
})
.set({
Authorization: `Bearer ${appKey}`,
'Content-Type': 'application/json',
});
await reportFastgptResponse({
ctx,
applicationId: 'stable-patent',
actionId: actionId || `patent:${res?.body?.responseData?.id || res?.body?.dataId || Date.now()}`,
responseBody: res?.body,
appKey,
userId,
includeCall,
reportContext,
});
return res?.body?.choices?.[0]?.message?.content || '';
};
const parseJsonMaybe = (text) => {
if (!text) return null;
try {
return JSON.parse(text);
} catch (e) {
const codeBlock = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
if (codeBlock?.[1]) {
try {
return JSON.parse(codeBlock[1]);
} catch (err) {
return null;
}
}
return null;
}
};
const toProjectInfoText = (projectInfo) => {
if (!projectInfo) return '';
if (typeof projectInfo === 'string') return projectInfo;
if (!Array.isArray(projectInfo)) return '';
return projectInfo
.map(item => `${item?.title || item?.fieldTitle || ''}${item?.content || item?.fieldValue || ''}`)
.filter(Boolean)
.join('\n');
};
const buildChapterMap = (chapters = []) => {
const chapterMap = new Map();
chapters.forEach(chapter => chapterMap.set(chapter.id, chapter));
return chapterMap;
};
const buildChapterPath = (chapter, chapterMap) => {
const path = [];
let current = chapter;
while (current) {
path.unshift(current.title);
current = current.parentId ? chapterMap.get(current.parentId) : null;
}
return path.join('-');
};
const buildChapterStructure = (chapters = []) => {
const chaptersByParent = new Map();
chapters.forEach(chapter => {
const parentId = chapter.parentId || 'root';
if (!chaptersByParent.has(parentId)) {
chaptersByParent.set(parentId, []);
}
chaptersByParent.get(parentId).push(chapter);
});
chaptersByParent.forEach(siblings => {
siblings.sort((a, b) => {
if (a.sortOrder !== b.sortOrder) return a.sortOrder - b.sortOrder;
return a.id - b.id;
});
});
const lines = [];
const walk = (parentId, prefix = '') => {
const children = chaptersByParent.get(parentId) || [];
children.forEach((chapter, index) => {
const num = prefix ? `${prefix}.${index + 1}` : `${index + 1}`;
lines.push(`${num} ${chapter.title}`);
walk(chapter.id, num);
});
};
walk('root');
return lines.join('\n');
};
const getOrderedLeafChapters = (chapters = []) => {
const chapterIdsAsParent = new Set(chapters.filter(item => item.parentId).map(item => item.parentId));
const chaptersByParent = new Map();
chapters.forEach(chapter => {
const parentId = chapter.parentId || 'root';
if (!chaptersByParent.has(parentId)) {
chaptersByParent.set(parentId, []);
}
chaptersByParent.get(parentId).push(chapter);
});
chaptersByParent.forEach(siblings => {
siblings.sort((a, b) => {
if (a.sortOrder !== b.sortOrder) return a.sortOrder - b.sortOrder;
return a.id - b.id;
});
});
const leaf = [];
const walk = (parentId) => {
const children = chaptersByParent.get(parentId) || [];
children.forEach(chapter => {
if (chapterIdsAsParent.has(chapter.id)) {
walk(chapter.id);
} else {
leaf.push(chapter);
}
});
};
walk('root');
return leaf;
};
const parseAnswerFields = (content = '') => {
const result = [];
const regex = /([^\n]+?)----([\s\S]*?)(?=\n[^\n]+?----|$)/g;
let match = regex.exec(content);
while (match) {
const fieldTitle = String(match[1] || '').trim();
const fieldValue = String(match[2] || '').trim();
if (fieldTitle) {
result.push({ fieldTitle, fieldValue });
}
match = regex.exec(content);
}
return result;
};
const saveChapterVersion = async (models, {
solutionId,
chapterId,
content,
triggerType = 'generate',
fromVersion = null,
promptSnapshot = null,
transaction = null,
}) => {
const whereOptions = {
where: { chapterId: Number(chapterId), solutionId: Number(solutionId) },
attributes: ['version'],
order: [['version', 'DESC']],
raw: true,
};
if (transaction) whereOptions.transaction = transaction;
const existingVersion = await models.PatentSolutionChapterVersions.findOne(whereOptions);
const newVersion = existingVersion ? existingVersion.version + 1 : 1;
const updateCurrentOptions = {
where: { chapterId: Number(chapterId), solutionId: Number(solutionId), isCurrent: true },
};
if (transaction) updateCurrentOptions.transaction = transaction;
await models.PatentSolutionChapterVersions.update({ isCurrent: false }, updateCurrentOptions);
const createData = {
solutionId: Number(solutionId),
chapterId: Number(chapterId),
version: newVersion,
isCurrent: true,
triggerType,
fromVersion,
content,
promptSnapshot,
};
const createOptions = {};
if (transaction) createOptions.transaction = transaction;
return await models.PatentSolutionChapterVersions.create(createData, createOptions);
};
const composeDocByCurrentChapters = async (models, solutionId) => {
const chapters = await models.PatentSolutionChapters.findAll({
where: { solutionId: Number(solutionId) },
order: [['sortOrder', 'ASC'], ['id', 'ASC']],
raw: true,
});
if (!chapters.length) {
return {
content: '',
chapterSnapshot: [],
};
}
const versions = await models.PatentSolutionChapterVersions.findAll({
where: { solutionId: Number(solutionId), isCurrent: true },
raw: true,
});
const versionMap = new Map();
versions.forEach(item => {
versionMap.set(item.chapterId, item.content || '');
});
const chapterMap = buildChapterMap(chapters);
const leafChapters = getOrderedLeafChapters(chapters);
const blocks = leafChapters.map((chapter) => {
const title = buildChapterPath(chapter, chapterMap);
const content = versionMap.get(chapter.id) || '';
return `${title}\n${content}`.trim();
}).filter(Boolean);
return {
content: blocks.join('\n\n'),
chapterSnapshot: chapters,
};
};
const saveDocVersion = async (models, {
solutionId,
content,
chapterSnapshot = null,
triggerType = 'generate',
fromVersion = null,
promptSnapshot = null,
transaction = null,
}) => {
const findOptions = {
where: { solutionId: Number(solutionId) },
attributes: ['version'],
order: [['version', 'DESC']],
raw: true,
};
if (transaction) findOptions.transaction = transaction;
const latest = await models.PatentSolutionDocVersions.findOne(findOptions);
const newVersion = latest ? latest.version + 1 : 1;
const updateDocOptions = { where: { solutionId: Number(solutionId), isCurrent: true } };
const updateSolutionOptions = { where: { id: Number(solutionId) } };
if (transaction) {
updateDocOptions.transaction = transaction;
updateSolutionOptions.transaction = transaction;
}
await models.PatentSolutionDocVersions.update({ isCurrent: false }, updateDocOptions);
const createOptions = {};
if (transaction) createOptions.transaction = transaction;
const row = await models.PatentSolutionDocVersions.create({
solutionId: Number(solutionId),
version: newVersion,
isCurrent: true,
triggerType,
fromVersion,
content,
chapterSnapshot,
promptSnapshot,
}, createOptions);
await models.PatentSolutions.update(
{ currentDocVersion: newVersion, updateAt: moment() },
updateSolutionOptions
);
return row;
};
const ensureDefaultBaseFields = async (models, solutionId, transaction = null) => {
const findOptions = {
where: { solutionId: Number(solutionId) },
raw: true,
order: [['sortOrder', 'ASC'], ['id', 'ASC']],
};
if (transaction) findOptions.transaction = transaction;
const exists = await models.PatentSolutionBaseFields.findAll(findOptions);
if (exists.length) return exists;
const rows = DEFAULT_BASE_FIELDS.map((item, idx) => ({
solutionId: Number(solutionId),
fieldKey: item.fieldKey,
fieldTitle: item.fieldTitle,
sortOrder: item.sortOrder,
isSelected: idx < 5,
sourceType: 'manual',
}));
const createOptions = {};
if (transaction) createOptions.transaction = transaction;
await models.PatentSolutionBaseFields.bulkCreate(rows, createOptions);
return await models.PatentSolutionBaseFields.findAll(findOptions);
};
const generateAllContent = async (ctx, solutionId) => {
const { models } = ctx.app.fs.dc;
try {
const solution = await models.PatentSolutions.findOne({
where: { id: Number(solutionId) },
raw: true,
});
if (!solution) {
return;
}
const allChapters = await models.PatentSolutionChapters.findAll({
where: { solutionId: Number(solutionId) },
order: [['sortOrder', 'ASC'], ['id', 'ASC']],
raw: true,
});
await models.PatentSolutions.update(
{ status: 'contentGenerating', updateAt: moment() },
{ where: { id: Number(solutionId) } }
);
const leafChapters = getOrderedLeafChapters(allChapters);
const chapterMap = buildChapterMap(allChapters);
const chaptersText = buildChapterStructure(allChapters);
const projectInfoText = toProjectInfoText(solution.projectInfo);
let lastContent = '';
for (let chapterIndex = 0; chapterIndex < leafChapters.length; chapterIndex += 1) {
const chapter = leafChapters[chapterIndex];
if (isPatentInfoChapter(chapter)) {
await saveChapterVersion(models, {
solutionId,
chapterId: chapter.id,
content: PATENT_DISCLOSURE_PREFACE_HTML,
triggerType: 'generate',
promptSnapshot: { source: 'system:patent-info-template' },
});
continue;
}
const variables = {
generation_type: '内容生成',
chapters: chaptersText,
lastContent,
};
if (projectInfoText) {
variables.projectInfo = projectInfoText;
}
if (solution.globalRequirements) {
variables.globalRequirements = solution.globalRequirements;
}
if (chapter.requirements) {
variables.chapterRequirements = chapter.requirements;
}
if (chapter.writingDirection) {
variables.chapterWritingDirection = chapter.writingDirection;
}
let sendText = `编写章节:${buildChapterPath(chapter, chapterMap)}`;
if (chapter.targetWords) {
sendText += `\n目标字数:约${chapter.targetWords}`;
}
if (chapter.targetCharts) {
sendText += `\n图表数:${chapter.targetCharts}`;
}
if (chapter.targetTables) {
sendText += `\n表格数:${chapter.targetTables}`;
}
const generatedContent = await callFastGpt(ctx, {
variables,
messages: [{ role: 'user', content: [{ type: 'text', text: sendText }] }],
userId: solution.creator,
actionId: `patent:${solutionId}:content:chapter:${chapter.id}:${Date.now()}`,
includeCall: false,
reportContext: {
action: 'generateContent',
solutionId,
chapterId: chapter.id,
},
});
await saveChapterVersion(models, {
solutionId,
chapterId: chapter.id,
content: generatedContent,
triggerType: 'generate',
promptSnapshot: { variables, sendText },
});
lastContent = generatedContent;
}
const docData = await composeDocByCurrentChapters(models, solutionId);
await saveDocVersion(models, {
solutionId,
content: docData.content,
chapterSnapshot: docData.chapterSnapshot,
triggerType: 'generate',
});
await models.PatentSolutions.update(
{ status: 'contentGenerateSuccess', updateAt: moment() },
{ where: { id: Number(solutionId) } }
);
await reportBusinessCall({
ctx,
applicationId: 'stable-patent',
eventId: `patent:${solutionId}:content:${Date.now()}`,
traceId: `patent:${solutionId}:content`,
userId: solution.creator,
reportContext: { action: 'generateContent', solutionId },
});
} catch (error) {
ctx.logger.log(error);
await models.PatentSolutions.update(
{ status: 'contentGenerateFailed', updateAt: moment() },
{ where: { id: Number(solutionId) } }
);
}
};
module.exports.getPatentSolutionList = async (ctx, next) => {
try {
const { models, ORM: { Op } } = ctx.app.fs.dc;
const { page, pageSize, creator, name } = ctx.request.query;
const where = {};
if (creator) {
where.creator = creator;
}
if (name) {
where.name = { [Op.like]: `%${name}%` };
}
const options = {
where,
order: [['updateAt', 'DESC']],
raw: true,
};
if (page && pageSize) {
options.offset = (page - 1) * pageSize;
options.limit = parseInt(pageSize);
}
const list = await models.PatentSolutions.findAndCountAll(options);
ctx.body = list;
ctx.status = 200;
} catch (error) {
ctx.logger.log(error);
ctx.status = 400;
ctx.body = {
message: typeof error === 'string' ? error : '获取专利方案列表失败'
};
}
};
module.exports.createPatentSolution = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const {
name,
patentType,
industry,
projectInfo,
globalRequirements,
messages,
creator,
curIp,
baseFields,
} = ctx.request.body;
if (!name) { throw '缺少参数: name'; }
const solution = await models.PatentSolutions.create({
name,
patentType,
industry,
projectInfo,
globalRequirements,
messages,
creator,
status: 'created',
createAt: moment(),
updateAt: moment(),
}, {
returning: true,
transaction,
});
if (Array.isArray(baseFields) && baseFields.length) {
await models.PatentSolutionBaseFields.bulkCreate(
baseFields.map((item, index) => ({
solutionId: solution.id,
fieldKey: item.fieldKey || `field_${index + 1}`,
fieldTitle: item.fieldTitle || `字段${index + 1}`,
fieldDesc: item.fieldDesc || null,
fieldValue: item.fieldValue || null,
sortOrder: item.sortOrder ?? index,
isSelected: item.isSelected ?? index < 5,
isRequired: item.isRequired ?? false,
sourceType: item.sourceType || 'manual',
})),
{ transaction }
);
} else {
await ensureDefaultBaseFields(models, solution.id, transaction);
}
if (creator || curIp) {
await models.AiQueryRecord.create({
userId: creator,
ipAddress: curIp,
clientId: 'pep-feixiaoshang',
feature: '专利方案生成',
time: moment(),
}, { transaction });
}
await transaction.commit();
ctx.body = solution;
ctx.status = 200;
} catch (error) {
await transaction.rollback();
ctx.logger.log(error);
ctx.status = 400;
ctx.body = {
message: typeof error === 'string' ? error : '创建专利方案失败'
};
}
};
module.exports.modifyPatentSolution = async (ctx, next) => {
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
const body = ctx.request.body;
if (!solutionId) { throw '缺少参数: solutionId'; }
await models.PatentSolutions.update(
{ ...body, updateAt: moment() },
{ where: { id: Number(solutionId) } }
);
ctx.status = 204;
} catch (error) {
ctx.logger.log(error);
ctx.status = 400;
ctx.body = {
message: typeof error === 'string' ? error : '修改专利方案失败'
};
}
};
module.exports.delPatentSolution = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
if (!solutionId) { throw '缺少参数: solutionId'; }
await models.PatentSolutions.destroy({
where: { id: Number(solutionId) },
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.getBaseFields = async (ctx, next) => {
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
if (!solutionId) { throw '缺少参数: solutionId'; }
const rows = await ensureDefaultBaseFields(models, solutionId);
ctx.body = rows;
ctx.status = 200;
} catch (error) {
ctx.logger.log(error);
ctx.status = 400;
ctx.body = {
message: typeof error === 'string' ? error : '获取基础信息字段失败'
};
}
};
module.exports.saveBaseFields = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
const { fields } = ctx.request.body;
if (!solutionId || !Array.isArray(fields)) { throw '参数错误'; }
await models.PatentSolutionBaseFields.destroy({
where: { solutionId: Number(solutionId) },
transaction,
});
if (fields.length) {
await models.PatentSolutionBaseFields.bulkCreate(fields.map((item, index) => ({
solutionId: Number(solutionId),
fieldKey: item.fieldKey || `field_${index + 1}`,
fieldTitle: item.fieldTitle || `字段${index + 1}`,
fieldDesc: item.fieldDesc || null,
fieldValue: item.fieldValue || null,
sortOrder: item.sortOrder ?? index,
isSelected: item.isSelected ?? index < 5,
isRequired: item.isRequired ?? false,
sourceType: item.sourceType || 'manual',
sourceChatId: item.sourceChatId || null,
createAt: moment(),
updateAt: moment(),
})), { transaction });
}
await models.PatentSolutions.update(
{ updateAt: moment() },
{ where: { id: Number(solutionId) }, 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.applyAiFields = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
const { parsedFields = [], sourceChatId } = ctx.request.body;
if (!solutionId || !Array.isArray(parsedFields)) { throw '参数错误'; }
const fields = await models.PatentSolutionBaseFields.findAll({
where: { solutionId: Number(solutionId) },
raw: true,
transaction,
});
if (!fields.length) {
await ensureDefaultBaseFields(models, solutionId, transaction);
}
const latestFields = await models.PatentSolutionBaseFields.findAll({
where: { solutionId: Number(solutionId) },
raw: true,
transaction,
});
for (const item of parsedFields) {
const target = latestFields.find(field =>
(item.fieldKey && field.fieldKey === item.fieldKey) ||
(item.fieldTitle && field.fieldTitle === item.fieldTitle)
);
if (target) {
await models.PatentSolutionBaseFields.update({
fieldValue: item.fieldValue || '',
sourceType: 'ai',
sourceChatId: sourceChatId || null,
updateAt: moment(),
}, {
where: { id: target.id },
transaction,
});
}
}
const updated = await models.PatentSolutionBaseFields.findAll({
where: { solutionId: Number(solutionId) },
order: [['sortOrder', 'ASC'], ['id', 'ASC']],
raw: true,
transaction,
});
await transaction.commit();
ctx.body = updated;
ctx.status = 200;
} catch (error) {
await transaction.rollback();
ctx.logger.log(error);
ctx.status = 400;
ctx.body = {
message: typeof error === 'string' ? error : '应用 AI 字段失败'
};
}
};
module.exports.generateChapters = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
const { projectInfo, globalRequirements } = ctx.request.body;
if (!solutionId) { throw '缺少参数: solutionId'; }
const solution = await models.PatentSolutions.findOne({
where: { id: Number(solutionId) },
raw: true,
transaction,
});
if (!solution) { throw '方案不存在'; }
const baseFields = await models.PatentSolutionBaseFields.findAll({
where: { solutionId: Number(solutionId), isSelected: true },
order: [['sortOrder', 'ASC'], ['id', 'ASC']],
raw: true,
transaction,
});
const mergedProjectInfo = projectInfo ?? solution.projectInfo;
const mergedRequirements = globalRequirements ?? solution.globalRequirements;
await models.PatentSolutions.update({
status: 'chaptersGenerating',
projectInfo: mergedProjectInfo,
globalRequirements: mergedRequirements,
updateAt: moment(),
}, {
where: { id: Number(solutionId) },
transaction,
});
const projectInfoText = toProjectInfoText(mergedProjectInfo);
const baseFieldText = baseFields
.map(item => `${item.fieldTitle}${item.fieldValue || ''}`)
.join('\n');
const sendText = [
`专利方案名称:${solution.name}`,
projectInfoText ? `项目信息:\n${projectInfoText}` : '',
mergedRequirements ? `整体要求:\n${mergedRequirements}` : '',
baseFieldText ? `基础信息:\n${baseFieldText}` : '',
].filter(Boolean).join('\n\n');
const resContent = await callFastGpt(ctx, {
variables: { generation_type: '目录生成' },
messages: [{ role: 'user', content: [{ type: 'text', text: sendText }] }],
userId: solution.creator,
actionId: `patent:${solutionId}:chapters:${Date.now()}`,
reportContext: { action: 'generateChapters', solutionId },
});
const previewChapters = parseJsonMaybe(resContent);
if (!Array.isArray(previewChapters)) {
throw '目录生成失败,返回格式不是 JSON 数组';
}
await models.PatentSolutions.update({
status: 'chaptersGenerateSuccess',
updateAt: moment(),
}, {
where: { id: Number(solutionId) },
transaction,
});
await transaction.commit();
ctx.body = previewChapters;
ctx.status = 200;
} catch (error) {
await transaction.rollback();
const { models } = ctx.app.fs.dc;
if (ctx.params?.solutionId) {
await models.PatentSolutions.update(
{ status: 'chaptersGenerateFailed', updateAt: moment() },
{ where: { id: Number(ctx.params.solutionId) } }
);
}
ctx.logger.log(error);
ctx.status = 400;
ctx.body = {
message: typeof error === 'string' ? error : '生成目录失败'
};
}
};
module.exports.sortChapters = async (ctx, next) => {
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
const previewChapters = ctx.request.body;
if (!solutionId || !previewChapters) { throw '参数错误'; }
const solution = await models.PatentSolutions.findOne({
attributes: ['id', 'creator'],
where: { id: Number(solutionId) },
raw: true,
});
const resContent = await callFastGpt(ctx, {
variables: { generation_type: '整理预览目录' },
messages: [{ role: 'user', content: JSON.stringify(previewChapters) }],
userId: solution?.creator,
actionId: `patent:${solutionId}:sort:${Date.now()}`,
reportContext: { action: 'sortChapters', solutionId },
});
const newPreviewChapters = parseJsonMaybe(resContent);
if (!Array.isArray(newPreviewChapters)) {
throw '整理目录失败,返回格式不是 JSON 数组';
}
await models.PatentSolutions.update({
updateAt: moment(),
}, {
where: { id: Number(solutionId) },
});
ctx.body = newPreviewChapters;
ctx.status = 200;
} catch (error) {
ctx.logger.log(error);
ctx.status = 400;
ctx.body = {
message: typeof error === 'string' ? error : '整理目录失败'
};
}
};
module.exports.createChapters = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
const { previewChapters } = ctx.request.body;
if (!solutionId || !Array.isArray(previewChapters)) { throw '参数错误'; }
const normalizedPreviewChapters = [
{ name: PATENT_INFO_CHAPTER_TITLE, child: [] },
...previewChapters.filter((item) => !isPatentInfoChapter(item)),
];
await models.PatentSolutionChapterVersions.destroy({
where: { solutionId: Number(solutionId) },
transaction,
});
await models.PatentSolutionChapters.destroy({
where: { solutionId: Number(solutionId) },
transaction,
});
const insertChaptersRecursively = async (chapters, parentId = null, sortOrder = 0, level = 0) => {
for (let i = 0; i < chapters.length; i++) {
const chapter = chapters[i];
const inserted = await models.PatentSolutionChapters.create({
solutionId: Number(solutionId),
title: chapter.name || chapter.title,
chapterKey: chapter.chapterKey || null,
parentId,
sortOrder: sortOrder + i,
level,
}, { transaction });
if (Array.isArray(chapter.child) && chapter.child.length) {
await insertChaptersRecursively(chapter.child, inserted.id, 0, level + 1);
}
}
};
await insertChaptersRecursively(normalizedPreviewChapters);
await models.PatentSolutions.update({
status: 'chaptersCreated',
updateAt: moment(),
}, {
where: { id: Number(solutionId) },
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.getPatentChapters = async (ctx, next) => {
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
if (!solutionId) { throw '缺少参数: solutionId'; }
const chapters = await models.PatentSolutionChapters.findAll({
where: { solutionId: Number(solutionId) },
order: [['id', 'ASC']],
include: [{
model: models.PatentSolutionChapterVersions,
where: { isCurrent: true },
required: false,
}],
});
ctx.body = chapters;
ctx.status = 200;
} catch (error) {
ctx.logger.log(error);
ctx.status = 400;
ctx.body = {
message: typeof error === 'string' ? error : '获取目录失败'
};
}
};
module.exports.addPatentChapters = async (ctx, next) => {
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
const body = ctx.request.body;
if (!solutionId || !body?.title) { throw '缺少参数'; }
body.solutionId = Number(solutionId);
const lastChild = await models.PatentSolutionChapters.findOne({
where: { solutionId: Number(solutionId), parentId: body.parentId || null },
attributes: ['sortOrder'],
order: [['sortOrder', 'DESC']],
raw: true,
});
body.sortOrder = lastChild ? lastChild.sortOrder + 1 : 0;
await models.PatentSolutionChapters.create(body);
ctx.status = 204;
} catch (error) {
ctx.logger.log(error);
ctx.status = 400;
ctx.body = {
message: typeof error === 'string' ? error : '添加目录失败'
};
}
};
module.exports.modifyPatentChapters = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models, ORM: { Op } } = ctx.app.fs.dc;
const { solutionId, chapterId } = ctx.params;
const body = ctx.request.body;
if (!solutionId || !chapterId) { throw '缺少参数'; }
if (body.sortOrder !== undefined || body.parentId !== undefined) {
const targetChapter = await models.PatentSolutionChapters.findOne({
where: { id: Number(chapterId), solutionId: Number(solutionId) },
attributes: ['parentId', 'sortOrder'],
raw: true,
transaction,
});
if (!targetChapter) { throw '章节未找到'; }
const originalParentId = targetChapter.parentId;
const originalSortOrder = targetChapter.sortOrder;
const newParentId = body.parentId !== undefined ? body.parentId : originalParentId;
const newSortOrder = body.sortOrder !== undefined ? body.sortOrder : originalSortOrder;
const applySortUpdatesSequentially = async (updates = []) => {
for (const update of updates) {
await models.PatentSolutionChapters.update(
{ sortOrder: update.sortOrder, updateAt: moment() },
{ where: { id: update.id }, transaction }
);
}
};
if (newParentId !== originalParentId) {
await models.PatentSolutionChapters.update(
{ parentId: newParentId },
{ where: { id: Number(chapterId) }, transaction }
);
const originalSiblingChapters = await models.PatentSolutionChapters.findAll({
attributes: ['id', 'sortOrder'],
where: {
solutionId: Number(solutionId),
parentId: originalParentId,
},
order: [['sortOrder', 'ASC']],
raw: true,
transaction,
});
const originalUpdates = originalSiblingChapters
.filter(chapter => chapter.id !== Number(chapterId))
.map((chapter, index) => ({ id: chapter.id, sortOrder: index }));
if (originalUpdates.length > 0) {
await applySortUpdatesSequentially(originalUpdates);
}
const newSiblingChapters = await models.PatentSolutionChapters.findAll({
attributes: ['id', 'sortOrder'],
where: {
solutionId: Number(solutionId),
parentId: newParentId,
id: { [Op.ne]: Number(chapterId) }
},
order: [['sortOrder', 'ASC']],
raw: true,
transaction,
});
const movingChapter = { id: Number(chapterId) };
newSiblingChapters.splice(newSortOrder, 0, movingChapter);
const newUpdates = newSiblingChapters.map((chapter, index) => ({
id: chapter.id,
sortOrder: index,
}));
await applySortUpdatesSequentially(newUpdates);
} else {
const siblingChapters = await models.PatentSolutionChapters.findAll({
attributes: ['id', 'sortOrder'],
where: {
solutionId: Number(solutionId),
parentId: originalParentId,
},
order: [['sortOrder', 'ASC']],
raw: true,
transaction,
});
const currentChapterIndex = siblingChapters.findIndex(chapter => chapter.id === Number(chapterId));
const [chapterToMove] = siblingChapters.splice(currentChapterIndex, 1);
siblingChapters.splice(newSortOrder, 0, chapterToMove);
const updates = siblingChapters.map((chapter, index) => ({
id: chapter.id,
sortOrder: index,
}));
await applySortUpdatesSequentially(updates);
}
} else {
await models.PatentSolutionChapters.update(
{ ...body, updateAt: moment() },
{
where: {
id: Number(chapterId),
solutionId: Number(solutionId),
},
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.delPatentChapters = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models, ORM: { Op } } = ctx.app.fs.dc;
const { solutionId, chapterId } = ctx.params;
if (!solutionId || !chapterId) { throw '缺少参数'; }
const getChildIds = async (parentId, allChildIds = []) => {
const children = await models.PatentSolutionChapters.findAll({
attributes: ['id'],
where: { parentId },
transaction,
});
if (children.length > 0) {
const childIds = children.map(child => child.id);
allChildIds.push(...childIds);
for (const childId of childIds) {
await getChildIds(childId, allChildIds);
}
}
return allChildIds;
};
const allIdsToDelete = [Number(chapterId)];
const childIds = await getChildIds(Number(chapterId));
allIdsToDelete.push(...childIds);
await models.PatentSolutionChapterVersions.destroy({
where: {
chapterId: { [Op.in]: allIdsToDelete },
solutionId: Number(solutionId),
},
transaction,
});
await models.PatentSolutionChapters.destroy({
where: {
id: { [Op.in]: allIdsToDelete },
solutionId: Number(solutionId),
},
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.startGenerateContent = async (ctx, next) => {
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
const { globalRequirements, projectInfo } = ctx.request.body;
if (!solutionId) { throw '缺少参数: solutionId'; }
if (globalRequirements !== undefined || projectInfo !== undefined) {
const nextUpdate = { updateAt: moment() };
if (globalRequirements !== undefined) nextUpdate.globalRequirements = globalRequirements;
if (projectInfo !== undefined) nextUpdate.projectInfo = projectInfo;
await models.PatentSolutions.update(
nextUpdate,
{ where: { id: Number(solutionId) } }
);
}
generateAllContent(ctx, solutionId);
ctx.status = 204;
} catch (error) {
ctx.logger.log(error);
ctx.status = 400;
ctx.body = {
message: typeof error === 'string' ? error : '启动正文生成失败'
};
}
};
module.exports.reWriteChapterContent = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const { solutionId, chapterId } = ctx.params;
const { writingDirection, requirements, targetWords, targetCharts, targetTables } = ctx.request.body;
if (!solutionId || !chapterId) { throw '缺少参数'; }
await models.PatentSolutionChapters.update({
writingDirection,
requirements,
targetWords,
targetCharts,
targetTables,
updateAt: moment(),
}, {
where: { id: Number(chapterId), solutionId: Number(solutionId) },
transaction,
});
const solution = await models.PatentSolutions.findOne({
where: { id: Number(solutionId) },
raw: true,
transaction,
});
const chapter = await models.PatentSolutionChapters.findOne({
where: { id: Number(chapterId), solutionId: Number(solutionId) },
raw: true,
transaction,
});
if (!solution || !chapter) { throw '方案或章节不存在'; }
const allChapters = await models.PatentSolutionChapters.findAll({
where: { solutionId: Number(solutionId) },
order: [['sortOrder', 'ASC'], ['id', 'ASC']],
raw: true,
transaction,
});
const chapterMap = buildChapterMap(allChapters);
const projectInfoText = toProjectInfoText(solution.projectInfo);
const chaptersText = buildChapterStructure(allChapters);
const variables = {
generation_type: '内容生成',
chapters: chaptersText,
};
if (projectInfoText) variables.projectInfo = projectInfoText;
if (solution.globalRequirements) variables.globalRequirements = solution.globalRequirements;
if (chapter.requirements) variables.chapterRequirements = chapter.requirements;
if (chapter.writingDirection) variables.chapterWritingDirection = chapter.writingDirection;
let sendText = `编写章节:${buildChapterPath(chapter, chapterMap)}`;
if (chapter.targetWords) sendText += `\n目标字数:约${chapter.targetWords}`;
if (chapter.targetCharts) sendText += `\n图表数:${chapter.targetCharts}`;
if (chapter.targetTables) sendText += `\n表格数:${chapter.targetTables}`;
const content = isPatentInfoChapter(chapter)
? PATENT_DISCLOSURE_PREFACE_HTML
: await callFastGpt(ctx, {
variables,
messages: [{ role: 'user', content: [{ type: 'text', text: sendText }] }],
userId: solution.creator,
actionId: `patent:${solutionId}:rewrite:chapter:${chapterId}:${Date.now()}`,
reportContext: {
action: 'rewriteChapter',
solutionId,
chapterId,
},
});
await saveChapterVersion(models, {
solutionId,
chapterId,
content,
triggerType: 'rewrite',
promptSnapshot: { variables, sendText },
transaction,
});
const docData = await composeDocByCurrentChapters(models, solutionId);
await saveDocVersion(models, {
solutionId,
content: docData.content,
chapterSnapshot: docData.chapterSnapshot,
triggerType: 'rewrite',
promptSnapshot: { source: 'chapterRewrite', chapterId: Number(chapterId) },
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.getChapterVersions = async (ctx, next) => {
try {
const { models } = ctx.app.fs.dc;
const { chapterId } = ctx.params;
const versions = await models.PatentSolutionChapterVersions.findAll({
where: { chapterId: Number(chapterId) },
order: [['version', 'DESC']],
raw: true,
});
ctx.body = versions;
ctx.status = 200;
} catch (error) {
ctx.logger.log(error);
ctx.status = 400;
ctx.body = { message: '获取章节版本失败' };
}
};
module.exports.saveChapterEdit = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const { solutionId, chapterId } = ctx.params;
const { content } = ctx.request.body;
if (!content) { throw '缺少参数: content'; }
await saveChapterVersion(models, {
solutionId,
chapterId,
content,
triggerType: 'manual_save',
transaction,
});
const docData = await composeDocByCurrentChapters(models, solutionId);
await saveDocVersion(models, {
solutionId,
content: docData.content,
chapterSnapshot: docData.chapterSnapshot,
triggerType: 'manual_save',
promptSnapshot: { source: 'saveChapterEdit', chapterId: Number(chapterId) },
transaction,
});
await transaction.commit();
ctx.status = 204;
} catch (error) {
await transaction.rollback();
ctx.logger.log(error);
ctx.status = 400;
ctx.body = { message: '保存章节编辑失败' };
}
};
module.exports.restoreChapterVersion = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const { solutionId, chapterId, version } = ctx.params;
const targetVersion = await models.PatentSolutionChapterVersions.findOne({
where: { chapterId: Number(chapterId), version: Number(version) },
raw: true,
transaction,
});
if (!targetVersion) { throw '目标版本不存在'; }
await saveChapterVersion(models, {
solutionId,
chapterId,
content: targetVersion.content,
triggerType: 'restore',
fromVersion: Number(version),
promptSnapshot: { source: 'restoreChapterVersion' },
transaction,
});
const docData = await composeDocByCurrentChapters(models, solutionId);
await saveDocVersion(models, {
solutionId,
content: docData.content,
chapterSnapshot: docData.chapterSnapshot,
triggerType: 'restore',
promptSnapshot: { source: 'restoreChapterVersion', chapterId: Number(chapterId), version: Number(version) },
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.getDocVersions = async (ctx, next) => {
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
if (!solutionId) { throw '缺少参数: solutionId'; }
const versions = await models.PatentSolutionDocVersions.findAll({
where: { solutionId: Number(solutionId) },
order: [['version', 'DESC']],
raw: true,
});
ctx.body = versions;
ctx.status = 200;
} catch (error) {
ctx.logger.log(error);
ctx.status = 400;
ctx.body = { message: typeof error === 'string' ? error : '获取正文版本失败' };
}
};
module.exports.saveDocEdit = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
const { content } = ctx.request.body;
if (!solutionId || !content) { throw '参数错误'; }
await saveDocVersion(models, {
solutionId,
content,
chapterSnapshot: null,
triggerType: 'manual_save',
promptSnapshot: { source: 'saveDocEdit' },
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.reWriteDoc = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
const { prompt } = ctx.request.body;
if (!solutionId) { throw '缺少参数: solutionId'; }
const solution = await models.PatentSolutions.findOne({
where: { id: Number(solutionId) },
raw: true,
transaction,
});
if (!solution) { throw '方案不存在'; }
const chapters = await models.PatentSolutionChapters.findAll({
where: { solutionId: Number(solutionId) },
order: [['sortOrder', 'ASC'], ['id', 'ASC']],
raw: true,
transaction,
});
const chapterTreeText = buildChapterStructure(chapters);
const baseFields = await models.PatentSolutionBaseFields.findAll({
where: { solutionId: Number(solutionId), isSelected: true },
order: [['sortOrder', 'ASC'], ['id', 'ASC']],
raw: true,
transaction,
});
const baseFieldText = baseFields.map(item => `${item.fieldTitle}${item.fieldValue || ''}`).join('\n');
const projectInfoText = toProjectInfoText(solution.projectInfo);
const variables = {
generation_type: '内容生成',
};
if (projectInfoText) variables.projectInfo = projectInfoText;
if (solution.globalRequirements) variables.globalRequirements = solution.globalRequirements;
if (chapterTreeText) variables.chapters = chapterTreeText;
const userPrompt = prompt || '请基于当前目录和基础信息重新生成完整正文';
const sendText = [
userPrompt,
chapterTreeText ? `目录:\n${chapterTreeText}` : '',
baseFieldText ? `基础信息:\n${baseFieldText}` : '',
].filter(Boolean).join('\n\n');
const content = await callFastGpt(ctx, {
variables,
messages: [{ role: 'user', content: [{ type: 'text', text: sendText }] }],
userId: solution.creator,
actionId: `patent:${solutionId}:rewrite:doc:${Date.now()}`,
reportContext: { action: 'rewriteDoc', solutionId },
});
await saveDocVersion(models, {
solutionId,
content,
chapterSnapshot: chapters,
triggerType: 'rewrite',
promptSnapshot: { variables, sendText },
transaction,
});
await transaction.commit();
ctx.body = { content };
ctx.status = 200;
} catch (error) {
await transaction.rollback();
ctx.logger.log(error);
ctx.status = 400;
ctx.body = { message: typeof error === 'string' ? error : '重编正文失败' };
}
};
module.exports.restoreDocVersion = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const { solutionId, version } = ctx.params;
if (!solutionId || !version) { throw '缺少参数'; }
const target = await models.PatentSolutionDocVersions.findOne({
where: { solutionId: Number(solutionId), version: Number(version) },
raw: true,
transaction,
});
if (!target) { throw '目标版本不存在'; }
await saveDocVersion(models, {
solutionId,
content: target.content,
chapterSnapshot: target.chapterSnapshot,
triggerType: 'restore',
fromVersion: Number(version),
promptSnapshot: { source: 'restoreDocVersion' },
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.patentChat = async (ctx, next) => {
const transaction = await ctx.app.fs.dc.orm.transaction();
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
const { question, generationType = '前期对话', model, fileUrlList = [] } = ctx.request.body;
if (!solutionId || !question) { throw '缺少参数'; }
const solution = await models.PatentSolutions.findOne({
where: { id: Number(solutionId) },
raw: true,
transaction,
});
if (!solution) { throw '方案不存在'; }
const maxRound = await models.PatentSolutionChatRecords.max('roundNo', {
where: { solutionId: Number(solutionId) },
transaction,
});
const roundNo = Number(maxRound || 0) + 1;
const variables = {
generation_type: generationType,
};
if (model) variables.model = model;
if (solution.projectInfo) variables.projectInfo = toProjectInfoText(solution.projectInfo);
if (solution.globalRequirements) variables.globalRequirements = solution.globalRequirements;
const content = [{ type: 'text', text: question }];
if (Array.isArray(fileUrlList) && fileUrlList.length) {
fileUrlList.forEach(url => {
if (url) {
content.push({ type: 'file_url', url });
}
});
}
const answer = await callFastGpt(ctx, {
variables,
messages: [{ role: 'user', content }],
userId: solution.creator,
actionId: `patent:${solutionId}:chat:${roundNo}:${Date.now()}`,
reportContext: {
action: 'patentChat',
solutionId,
roundNo,
},
});
const parsedFields = parseAnswerFields(answer);
await models.PatentSolutionChatRecords.create({
solutionId: Number(solutionId),
roundNo,
role: 'user',
generationType,
content: question,
}, { transaction });
const assistantRow = await models.PatentSolutionChatRecords.create({
solutionId: Number(solutionId),
roundNo,
role: 'assistant',
generationType,
content: answer,
parsedFields,
}, { transaction });
const oldMessages = Array.isArray(solution.messages) ? solution.messages : [];
oldMessages.push({ role: 'user', content: question });
oldMessages.push({ role: 'assistant', content: answer });
await models.PatentSolutions.update({
messages: oldMessages,
updateAt: moment(),
}, {
where: { id: Number(solutionId) },
transaction,
});
await transaction.commit();
ctx.body = {
answer,
parsedFields,
assistantChatId: assistantRow.id,
};
ctx.status = 200;
} catch (error) {
await transaction.rollback();
ctx.logger.log(error);
ctx.status = 400;
ctx.body = {
message: typeof error === 'string' ? error : '问答失败'
};
}
};
module.exports.getChatRecords = async (ctx, next) => {
try {
const { models } = ctx.app.fs.dc;
const { solutionId } = ctx.params;
if (!solutionId) { throw '缺少参数: solutionId'; }
const records = await models.PatentSolutionChatRecords.findAll({
where: { solutionId: Number(solutionId) },
order: [['roundNo', 'ASC'], ['id', 'ASC']],
raw: true,
});
ctx.body = records;
ctx.status = 200;
} catch (error) {
ctx.logger.log(error);
ctx.status = 400;
ctx.body = {
message: typeof error === 'string' ? error : '获取问答记录失败'
};
}
};
module.exports.PATENT_STATUS_ENUM = PATENT_STATUS_ENUM;