diff --git a/backend/src/modules/admin/services/tenantService.ts b/backend/src/modules/admin/services/tenantService.ts index 52d5fbbb..c10b4aca 100644 --- a/backend/src/modules/admin/services/tenantService.ts +++ b/backend/src/modules/admin/services/tenantService.ts @@ -385,6 +385,7 @@ class TenantService { backgroundImageUrl?: string; modules: string[]; isReviewOnly: boolean; + editorialDefaultStandard: 'zh' | 'en'; } | null> { // 根据 code 查找租户 const tenant = await prisma.tenants.findUnique({ @@ -414,6 +415,7 @@ class TenantService { backgroundImageUrl: tenant.login_background_url || undefined, modules, isReviewOnly, + editorialDefaultStandard: tenant.journal_language === 'ZH' ? 'zh' : 'en', }; } } diff --git a/backend/src/modules/rvw/controllers/reviewController.ts b/backend/src/modules/rvw/controllers/reviewController.ts index ce894765..cc41957c 100644 --- a/backend/src/modules/rvw/controllers/reviewController.ts +++ b/backend/src/modules/rvw/controllers/reviewController.ts @@ -190,19 +190,28 @@ export async function createTask( export async function runReview( request: FastifyRequest<{ Params: { taskId: string }; - Body: { agents: AgentType[] }; + Body: { agents: AgentType[]; editorialBaseStandard?: 'zh' | 'en' }; }>, reply: FastifyReply ) { try { const userId = getUserId(request); const { taskId } = request.params; - const { agents } = request.body; + const { agents, editorialBaseStandard } = request.body; + const normalizedEditorialBaseStandard = + editorialBaseStandard === 'zh' || editorialBaseStandard === 'en' + ? editorialBaseStandard + : undefined; - logger.info('[RVW:Controller] 运行审查', { taskId, agents }); + logger.info('[RVW:Controller] 运行审查', { taskId, agents, editorialBaseStandard: normalizedEditorialBaseStandard }); // ✅ 返回 jobId(Platform-Only架构) - const { jobId } = await reviewService.runReview({ taskId, agents, userId }); + const { jobId } = await reviewService.runReview({ + taskId, + agents, + editorialBaseStandard: normalizedEditorialBaseStandard, + userId, + }); return reply.send({ success: true, @@ -232,17 +241,31 @@ export async function batchRunReview( Body: { taskIds: string[]; agents: AgentType[]; + editorialBaseStandard?: 'zh' | 'en'; }; }>, reply: FastifyReply ) { try { const userId = getUserId(request); - const { taskIds, agents } = request.body; + const { taskIds, agents, editorialBaseStandard } = request.body; + const normalizedEditorialBaseStandard = + editorialBaseStandard === 'zh' || editorialBaseStandard === 'en' + ? editorialBaseStandard + : undefined; - logger.info('[RVW:Controller] 批量运行审查', { taskCount: taskIds.length, agents }); + logger.info('[RVW:Controller] 批量运行审查', { + taskCount: taskIds.length, + agents, + editorialBaseStandard: normalizedEditorialBaseStandard, + }); - const result = await reviewService.batchRunReview({ taskIds, agents, userId }); + const result = await reviewService.batchRunReview({ + taskIds, + agents, + editorialBaseStandard: normalizedEditorialBaseStandard, + userId, + }); return reply.send({ success: true, diff --git a/backend/src/modules/rvw/services/editorialService.ts b/backend/src/modules/rvw/services/editorialService.ts index 83e20a0d..93217e81 100644 --- a/backend/src/modules/rvw/services/editorialService.ts +++ b/backend/src/modules/rvw/services/editorialService.ts @@ -16,6 +16,8 @@ import { EditorialReview } from '../types/index.js'; import { parseJSONFromLLMResponse } from './utils.js'; import { composeRvwSystemPrompt, getRvwProtocol, sanitizeRvwBusinessPrompt } from './promptProtocols.js'; +const EDITORIAL_FALLBACK_MODEL: ModelType = 'qwen3-72b'; + function isValidEditorialReview(result: unknown): result is EditorialReview { if (!result || typeof result !== 'object') return false; const data = result as Record; @@ -25,6 +27,14 @@ function isValidEditorialReview(result: unknown): result is EditorialReview { return true; } +function buildContentObservability(content: string): { length: number; preview: string } { + const compact = (content || '').replace(/\s+/g, ' ').trim(); + return { + length: content?.length ?? 0, + preview: compact.slice(0, 500), + }; +} + async function repairEditorialToJson( rawContent: string, modelType: ModelType @@ -48,11 +58,44 @@ async function repairEditorialToJson( }); const repairedContent = repaired.content ?? ''; - const parsed = parseJSONFromLLMResponse(repairedContent); - if (!isValidEditorialReview(parsed)) { - throw new Error('稿约规范性评估结果结构化修复失败(JSON字段不完整)'); + try { + const parsed = parseJSONFromLLMResponse(repairedContent); + if (!isValidEditorialReview(parsed)) { + throw new Error('稿约规范性评估结果结构化修复失败(JSON字段不完整)'); + } + return parsed; + } catch { + // 二次兜底:使用更严格、更短的指令,强制输出单一 JSON 对象 + logger.warn('[RVW:Editorial] 第一次结构化修复失败,进入超严格兜底修复'); + const strictMessages = [ + { + role: 'system' as const, + content: + 'You are a JSON formatter. Output ONE valid JSON object only. No markdown, no prose, no code fence.\n' + + '{' + + '"overall_score":0,' + + '"summary":"",' + + '"items":[{"criterion":"","status":"pass","score":0,"issues":[],"suggestions":[]}]' + + '}\n' + + 'Rules: status must be pass|warning|fail; scores must be numbers 0-100; issues/suggestions must be arrays.', + }, + { + role: 'user' as const, + content: `Convert the following editorial review text into that JSON schema:\n\n${rawContent}\n\nReturn JSON object only.`, + }, + ]; + + const strictRepaired = await llmAdapter.chat(strictMessages, { + temperature: 0, + maxTokens: 2500, + }); + const strictContent = strictRepaired.content ?? ''; + const strictParsed = parseJSONFromLLMResponse(strictContent); + if (!isValidEditorialReview(strictParsed)) { + throw new Error('稿约规范性评估结果结构化修复失败(JSON字段不完整)'); + } + return strictParsed; } - return parsed; } /** @@ -94,38 +137,58 @@ export async function reviewEditorialStandards( logger.info('[RVW:Editorial] 使用 DRAFT 版本 Prompt(调试模式)', { userId }); } - // 2. 构建消息 - const messages = [ - { role: 'system' as const, content: composeRvwSystemPrompt('editorial', businessPrompt) }, - { role: 'user' as const, content: `请对以下稿件进行稿约规范性评估。\n\n稿件内容如下:\n${text}` }, - ]; + // 2. 按模型序列尝试(主模型 -> 备用模型) + const candidateModels = Array.from(new Set([modelType, EDITORIAL_FALLBACK_MODEL])); + let lastError: Error | null = null; - // 3. 调用LLM - logger.info('[RVW:Editorial] 开始稿约规范性评估', { modelType }); - const llmAdapter = LLMFactory.getAdapter(modelType); - const response = await llmAdapter.chat(messages, { - temperature: 0.3, // 较低温度以获得更稳定的评估 - maxTokens: 8000, // 确保完整输出 - }); - const editContent = response.content ?? ''; - logger.info('[RVW:Editorial] 评估完成', { - modelType, - responseLength: editContent.length - }); + for (const candidateModel of candidateModels) { + try { + const messages = [ + { role: 'system' as const, content: composeRvwSystemPrompt('editorial', businessPrompt) }, + { role: 'user' as const, content: `请对以下稿件进行稿约规范性评估。\n\n稿件内容如下:\n${text}` }, + ]; - // 4. 解析 JSON(失败则自动进入结构化修复) - try { - const result = parseJSONFromLLMResponse(editContent); - if (!isValidEditorialReview(result)) { - throw new Error('LLM返回的数据格式不正确'); + logger.info('[RVW:Editorial] 开始稿约规范性评估', { + modelType: candidateModel, + fallback: candidateModel !== modelType, + }); + const llmAdapter = LLMFactory.getAdapter(candidateModel); + const response = await llmAdapter.chat(messages, { + temperature: 0.2, + maxTokens: 8000, + }); + const editContent = response.content ?? ''; + logger.info('[RVW:Editorial] 评估完成', { + modelType: candidateModel, + responseLength: editContent.length, + }); + + try { + const result = parseJSONFromLLMResponse(editContent); + if (!isValidEditorialReview(result)) { + throw new Error('LLM返回的数据格式不正确'); + } + return result; + } catch (parseError) { + logger.warn('[RVW:Editorial] 原始响应解析失败,进入修复流程', { + modelType: candidateModel, + reason: parseError instanceof Error ? parseError.message : 'Unknown parse error', + ...buildContentObservability(editContent), + }); + return await repairEditorialToJson(editContent, candidateModel); + } + } catch (candidateError) { + const err = candidateError instanceof Error ? candidateError : new Error(String(candidateError)); + lastError = err; + logger.warn('[RVW:Editorial] 模型评估失败,尝试下一候选模型', { + modelType: candidateModel, + fallback: candidateModel !== modelType, + error: err.message, + }); } - return result; - } catch (parseError) { - logger.warn('[RVW:Editorial] 原始响应解析失败,进入修复流程', { - reason: parseError instanceof Error ? parseError.message : 'Unknown parse error', - }); - return await repairEditorialToJson(editContent, modelType); } + + throw lastError ?? new Error('稿约规范性评估失败(所有候选模型均失败)'); } catch (error) { logger.error('[RVW:Editorial] 稿约规范性评估失败', { error: error instanceof Error ? error.message : 'Unknown error', diff --git a/backend/src/modules/rvw/services/methodologyService.ts b/backend/src/modules/rvw/services/methodologyService.ts index f6a75457..ba2da1a8 100644 --- a/backend/src/modules/rvw/services/methodologyService.ts +++ b/backend/src/modules/rvw/services/methodologyService.ts @@ -17,6 +17,7 @@ import { MethodologyCheckpoint, MethodologyIssue, MethodologyPart, MethodologyRe import { parseJSONFromLLMResponse } from './utils.js'; import { composeRvwSystemPrompt, getRvwProtocol, sanitizeRvwBusinessPrompt } from './promptProtocols.js'; +const METHODOLOGY_FALLBACK_MODEL: ModelType = 'qwen3-72b'; const METHODOLOGY_CONCLUSIONS = ['直接接收', '小修', '大修', '拒稿'] as const; type MethodologyConclusion = typeof METHODOLOGY_CONCLUSIONS[number]; const METHODOLOGY_CHECKPOINT_ITEMS = [ @@ -228,6 +229,14 @@ function isValidMethodologyReview(result: unknown): result is MethodologyReview return true; } +function buildContentObservability(content: string): { length: number; preview: string } { + const compact = (content || '').replace(/\s+/g, ' ').trim(); + return { + length: content?.length ?? 0, + preview: compact.slice(0, 500), + }; +} + function normalizeMethodologyCheckpoints(input: unknown): MethodologyCheckpoint[] { const normalizedMap = new Map(); if (Array.isArray(input)) { @@ -319,48 +328,93 @@ function aggregateMethodologySections(sections: SectionReviewResult[]): Methodol } async function reviewMethodologySection( - llmAdapter: ReturnType, + modelType: ModelType, businessPrompt: string, text: string, section: MethodologySectionDef ): Promise { - const messages = [ - { role: 'system' as const, content: `${businessPrompt}\n\n${buildSectionProtocol(section)}` }, - { role: 'user' as const, content: `请仅评估“${section.part}”(检查点 ${section.start}-${section.end}),并按协议返回 JSON。\n\n稿件内容如下:\n${text}` }, - ]; - const response = await llmAdapter.chat(messages, { - temperature: 0.2, - maxTokens: 2800, - }); - const content = response.content ?? ''; - try { - const parsed = parseJSONFromLLMResponse(content); - if (!isValidSectionReview(parsed, section)) { - throw new Error('section json invalid'); + const candidateModels = Array.from(new Set([modelType, METHODOLOGY_FALLBACK_MODEL])); + let lastError: Error | null = null; + + for (const candidateModel of candidateModels) { + try { + const llmAdapter = LLMFactory.getAdapter(candidateModel); + const messages = [ + { role: 'system' as const, content: `${businessPrompt}\n\n${buildSectionProtocol(section)}` }, + { role: 'user' as const, content: `请仅评估“${section.part}”(检查点 ${section.start}-${section.end}),并按协议返回 JSON。\n\n稿件内容如下:\n${text}` }, + ]; + const response = await llmAdapter.chat(messages, { + temperature: 0.2, + maxTokens: 2800, + }); + const content = response.content ?? ''; + try { + const parsed = parseJSONFromLLMResponse(content); + if (!isValidSectionReview(parsed, section)) { + throw new Error('section json invalid'); + } + return normalizeSectionReview(parsed, section); + } catch { + const repairMessages = [ + { + role: 'system' as const, + content: `你是 JSON 结构化助手。把输入文本转成目标 JSON。\n\n${buildSectionProtocol(section)}`, + }, + { + role: 'user' as const, + content: `请将以下方法学评估文本重组为目标 JSON(仅检查点 ${section.start}-${section.end}):\n\n${content}`, + }, + ]; + const repaired = await llmAdapter.chat(repairMessages, { + temperature: 0.1, + maxTokens: 1800, + }); + const repairedContent = repaired.content ?? ''; + try { + const repairedParsed = parseJSONFromLLMResponse(repairedContent); + if (!isValidSectionReview(repairedParsed, section)) { + throw new Error('section repair invalid'); + } + return normalizeSectionReview(repairedParsed, section); + } catch { + // 二次兜底:严格 JSON 输出 + const strictMessages = [ + { + role: 'system' as const, + content: + 'You are a JSON formatter. Output ONE valid JSON object only. No markdown, no prose.\n' + + `Must include: part="${section.part}", score(0-100), issues[], checkpoints[] for ids ${section.start}-${section.end}.`, + }, + { + role: 'user' as const, + content: `Convert the following section review text into strict JSON:\n\n${content}`, + }, + ]; + const strictRepaired = await llmAdapter.chat(strictMessages, { + temperature: 0, + maxTokens: 1500, + }); + const strictContent = strictRepaired.content ?? ''; + const strictParsed = parseJSONFromLLMResponse(strictContent); + if (!isValidSectionReview(strictParsed, section)) { + throw new Error('section strict repair invalid'); + } + return normalizeSectionReview(strictParsed, section); + } + } + } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)); + lastError = err; + logger.warn('[RVW:Methodology] 分段评估模型失败,尝试下一候选模型', { + section: section.part, + modelType: candidateModel, + fallback: candidateModel !== modelType, + error: err.message, + }); } - return normalizeSectionReview(parsed, section); - } catch { - const repairMessages = [ - { - role: 'system' as const, - content: `你是 JSON 结构化助手。把输入文本转成目标 JSON。\n\n${buildSectionProtocol(section)}`, - }, - { - role: 'user' as const, - content: `请将以下方法学评估文本重组为目标 JSON(仅检查点 ${section.start}-${section.end}):\n\n${content}`, - }, - ]; - const repaired = await llmAdapter.chat(repairMessages, { - temperature: 0.1, - maxTokens: 1800, - }); - const repairedContent = repaired.content ?? ''; - const repairedParsed = parseJSONFromLLMResponse(repairedContent); - if (!isValidSectionReview(repairedParsed, section)) { - throw new Error('section repair invalid'); - } - return normalizeSectionReview(repairedParsed, section); } + + throw lastError ?? new Error('section evaluate failed'); } async function reviewMethodologyLegacy( @@ -368,23 +422,42 @@ async function reviewMethodologyLegacy( text: string, modelType: ModelType ): Promise { - const llmAdapter = LLMFactory.getAdapter(modelType); - const messages = [ - { role: 'system' as const, content: composeRvwSystemPrompt('methodology', businessPrompt) }, - { role: 'user' as const, content: `请对以下稿件进行方法学评估。\n\n稿件内容如下:\n${text}` }, - ]; - const response = await llmAdapter.chat(messages, { - temperature: 0.3, - maxTokens: 5000, - }); - const methContent = response.content ?? ''; - try { - const result = parseJSONFromLLMResponse(methContent); - if (!isValidMethodologyReview(result)) throw new Error('invalid json'); - return normalizeMethodologyReview(result); - } catch { - return repairMethodologyToJson(methContent, modelType); + const candidateModels = Array.from(new Set([modelType, METHODOLOGY_FALLBACK_MODEL])); + let lastError: Error | null = null; + for (const candidateModel of candidateModels) { + try { + const llmAdapter = LLMFactory.getAdapter(candidateModel); + const messages = [ + { role: 'system' as const, content: composeRvwSystemPrompt('methodology', businessPrompt) }, + { role: 'user' as const, content: `请对以下稿件进行方法学评估。\n\n稿件内容如下:\n${text}` }, + ]; + const response = await llmAdapter.chat(messages, { + temperature: 0.2, + maxTokens: 5000, + }); + const methContent = response.content ?? ''; + try { + const result = parseJSONFromLLMResponse(methContent); + if (!isValidMethodologyReview(result)) throw new Error('invalid json'); + return normalizeMethodologyReview(result); + } catch { + logger.warn('[RVW:Methodology] legacy原始响应解析失败,进入修复流程', { + modelType: candidateModel, + ...buildContentObservability(methContent), + }); + return repairMethodologyToJson(methContent, candidateModel); + } + } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)); + lastError = err; + logger.warn('[RVW:Methodology] legacy模型失败,尝试下一候选模型', { + modelType: candidateModel, + fallback: candidateModel !== modelType, + error: err.message, + }); + } } + throw lastError ?? new Error('legacy evaluate failed'); } async function repairMethodologyToJson( @@ -411,13 +484,37 @@ async function repairMethodologyToJson( }); const repairedContent = repaired.content ?? ''; - const parsed = parseJSONFromLLMResponse(repairedContent); - - if (!isValidMethodologyReview(parsed)) { - throw new Error('方法学评估结果结构化修复失败(JSON字段不完整)'); + try { + const parsed = parseJSONFromLLMResponse(repairedContent); + if (!isValidMethodologyReview(parsed)) { + throw new Error('方法学评估结果结构化修复失败(JSON字段不完整)'); + } + return normalizeMethodologyReview(parsed); + } catch { + logger.warn('[RVW:Methodology] 第一次结构化修复失败,进入超严格兜底修复'); + const strictMessages = [ + { + role: 'system' as const, + content: + 'You are a JSON formatter. Output ONE valid JSON object only. No markdown, no prose.\n' + + '{"overall_score":0,"summary":"","conclusion":"小修","checkpoints":[{"id":1,"item":"","status":"pass","finding":"","suggestion":""}],"parts":[{"part":"科研设计评估","score":0,"issues":[]}]}', + }, + { + role: 'user' as const, + content: `Convert the following methodology review text into strict JSON:\n\n${rawContent}`, + }, + ]; + const strictRepaired = await llmAdapter.chat(strictMessages, { + temperature: 0, + maxTokens: 2500, + }); + const strictContent = strictRepaired.content ?? ''; + const strictParsed = parseJSONFromLLMResponse(strictContent); + if (!isValidMethodologyReview(strictParsed)) { + throw new Error('方法学评估结果结构化修复失败(JSON字段不完整)'); + } + return normalizeMethodologyReview(strictParsed); } - - return normalizeMethodologyReview(parsed); } /** @@ -466,7 +563,6 @@ export async function reviewMethodology( } businessPrompt = sanitizeRvwBusinessPrompt('methodology', businessPrompt); - const llmAdapter = LLMFactory.getAdapter(modelType); logger.info('[RVW:Methodology] 开始分治并行评估', { modelType, sections: METHODOLOGY_SECTION_DEFS.map(section => `${section.part}(${section.start}-${section.end})`), @@ -474,7 +570,7 @@ export async function reviewMethodology( const settled = await Promise.allSettled( METHODOLOGY_SECTION_DEFS.map(section => - reviewMethodologySection(llmAdapter, businessPrompt, text, section) + reviewMethodologySection(modelType, businessPrompt, text, section) ) ); const sectionResults: SectionReviewResult[] = []; diff --git a/backend/src/modules/rvw/services/reviewService.ts b/backend/src/modules/rvw/services/reviewService.ts index cde00dae..f7d0f447 100644 --- a/backend/src/modules/rvw/services/reviewService.ts +++ b/backend/src/modules/rvw/services/reviewService.ts @@ -93,7 +93,9 @@ export async function createTask( logger.info('[RVW] 任务已创建', { taskId: task.id, status: task.status }); // 2. 生成 OSS 存储 Key 并上传文件 - const storageKey = generateRvwStorageKey(tenantId, userId, task.id, filename); + // 单租户历史数据 tenantId 可能为空,存储层使用固定命名空间兜底 + const storageTenantId = tenantId ?? 'public'; + const storageKey = generateRvwStorageKey(storageTenantId, userId, task.id, filename); let updatedTask = task; try { @@ -181,7 +183,7 @@ async function extractDocumentAsync(taskId: string, file: Buffer, filename: stri * @returns jobId 供前端轮询状态 */ export async function runReview(params: RunReviewParams): Promise<{ jobId: string }> { - const { taskId, agents, userId } = params; + const { taskId, agents, editorialBaseStandard, userId } = params; // 验证智能体选择 validateAgentSelection(agents); @@ -236,6 +238,7 @@ export async function runReview(params: RunReviewParams): Promise<{ jobId: strin taskId, userId, agents, + editorialBaseStandard, extractedText: task.extractedText, modelType: (task.modelUsed || 'deepseek-v3') as ModelType, __expireInSeconds: 15 * 60, // 15min: 串行(5min)+并行(5min)+提取+余量,长文档可达8-10min @@ -259,7 +262,7 @@ export async function batchRunReview(params: BatchRunParams): Promise<{ success: string[]; failed: { taskId: string; error: string }[] }> { - const { taskIds, agents, userId } = params; + const { taskIds, agents, editorialBaseStandard, userId } = params; // 验证智能体选择 validateAgentSelection(agents); @@ -283,7 +286,7 @@ export async function batchRunReview(params: BatchRunParams): Promise<{ const batch = taskIds.slice(i, i + MAX_CONCURRENT); const results = await Promise.allSettled( - batch.map(taskId => runReview({ taskId, agents, userId })) + batch.map(taskId => runReview({ taskId, agents, editorialBaseStandard, userId })) ); results.forEach((result, index) => { diff --git a/backend/src/modules/rvw/services/utils.ts b/backend/src/modules/rvw/services/utils.ts index 672c332f..eb650b2a 100644 --- a/backend/src/modules/rvw/services/utils.ts +++ b/backend/src/modules/rvw/services/utils.ts @@ -6,71 +6,111 @@ import { MethodologyReview, MethodologyStatus } from '../types/index.js'; import { jsonrepair } from 'jsonrepair'; +function tryParseJsonCandidate(candidate: string): T | null { + const normalized = candidate.trim().replace(/^\uFEFF/, ''); + if (!normalized) return null; + try { + return JSON.parse(normalized) as T; + } catch { + try { + const repaired = jsonrepair(normalized); + return JSON.parse(repaired) as T; + } catch { + return null; + } + } +} + +function extractBalancedJsonCandidates(content: string): string[] { + const text = content || ''; + const candidates: string[] = []; + const stack: string[] = []; + let start = -1; + let inString = false; + let escaped = false; + + for (let i = 0; i < text.length; i++) { + const ch = text[i]; + + if (inString) { + if (escaped) { + escaped = false; + } else if (ch === '\\') { + escaped = true; + } else if (ch === '"') { + inString = false; + } + continue; + } + + if (ch === '"') { + inString = true; + continue; + } + + if (ch === '{' || ch === '[') { + if (stack.length === 0) start = i; + stack.push(ch); + continue; + } + + if (ch === '}' || ch === ']') { + if (stack.length === 0) continue; + const open = stack[stack.length - 1]; + if ((open === '{' && ch === '}') || (open === '[' && ch === ']')) { + stack.pop(); + if (stack.length === 0 && start >= 0) { + candidates.push(text.slice(start, i + 1)); + start = -1; + } + } else { + // 栈失配时重置,继续寻找下一个合法片段 + stack.length = 0; + start = -1; + } + } + } + + return candidates; +} + /** * 从LLM响应中解析JSON * 支持多种格式:纯JSON、```json代码块、混合文本 */ export function parseJSONFromLLMResponse(content: string): T { - try { - // 1. 尝试直接解析 - return JSON.parse(content) as T; - } catch { - // 1.1 先尝试 jsonrepair(处理尾逗号、引号缺失等常见脏 JSON) - try { - const repaired = jsonrepair(content); - return JSON.parse(repaired) as T; - } catch { - // 继续后续提取策略 - } + // 1) 直接解析 + jsonrepair + const direct = tryParseJsonCandidate(content); + if (direct !== null) return direct; - // 2. 尝试提取```json代码块 - const jsonMatch = content.match(/```json\s*\n?([\s\S]*?)\n?```/); - if (jsonMatch) { - try { - return JSON.parse(jsonMatch[1].trim()) as T; - } catch { - // 尝试修复代码块 JSON - try { - const repaired = jsonrepair(jsonMatch[1].trim()); - return JSON.parse(repaired) as T; - } catch { - // 继续尝试其他方法 - } - } - } - - // 3. 尝试提取{}或[]包裹的内容 - const objectMatch = content.match(/(\{[\s\S]*\})/); - if (objectMatch) { - try { - return JSON.parse(objectMatch[1]) as T; - } catch { - try { - const repaired = jsonrepair(objectMatch[1]); - return JSON.parse(repaired) as T; - } catch { - // 继续尝试其他方法 - } - } - } - - const arrayMatch = content.match(/(\[[\s\S]*\])/); - if (arrayMatch) { - try { - return JSON.parse(arrayMatch[1]) as T; - } catch { - try { - const repaired = jsonrepair(arrayMatch[1]); - return JSON.parse(repaired) as T; - } catch { - // 失败 - } - } - } - - // 4. 所有尝试都失败 - throw new Error('无法从LLM响应中解析JSON'); + // 2) 提取 Markdown 代码块(```json / ```) + const fenceRegex = /```(?:json)?\s*\n?([\s\S]*?)\n?```/gi; + for (const match of content.matchAll(fenceRegex)) { + const parsed = tryParseJsonCandidate(match[1] || ''); + if (parsed !== null) return parsed; } + + // 3) 平衡括号提取,逐候选尝试 + const balancedCandidates = extractBalancedJsonCandidates(content); + for (const candidate of balancedCandidates) { + const parsed = tryParseJsonCandidate(candidate); + if (parsed !== null) return parsed; + } + + // 4) 最后兜底:贪婪正则对象 / 数组(兼容极端场景) + const objectMatch = content.match(/(\{[\s\S]*\})/); + if (objectMatch) { + const parsed = tryParseJsonCandidate(objectMatch[1]); + if (parsed !== null) return parsed; + } + const arrayMatch = content.match(/(\[[\s\S]*\])/); + if (arrayMatch) { + const parsed = tryParseJsonCandidate(arrayMatch[1]); + if (parsed !== null) return parsed; + } + + // 5) 所有尝试都失败 + throw new Error('无法从LLM响应中解析JSON'); } /** diff --git a/backend/src/modules/rvw/types/index.ts b/backend/src/modules/rvw/types/index.ts index 75715b6f..8b8752bc 100644 --- a/backend/src/modules/rvw/types/index.ts +++ b/backend/src/modules/rvw/types/index.ts @@ -125,6 +125,7 @@ export interface ForensicsResult { export interface RunReviewParams { taskId: string; agents: AgentType[]; // 可选1个或2个 + editorialBaseStandard?: 'zh' | 'en'; userId: string; } @@ -134,6 +135,7 @@ export interface RunReviewParams { export interface BatchRunParams { taskIds: string[]; agents: AgentType[]; + editorialBaseStandard?: 'zh' | 'en'; userId: string; } diff --git a/backend/src/modules/rvw/workers/reviewWorker.ts b/backend/src/modules/rvw/workers/reviewWorker.ts index 7be58765..077f0925 100644 --- a/backend/src/modules/rvw/workers/reviewWorker.ts +++ b/backend/src/modules/rvw/workers/reviewWorker.ts @@ -50,6 +50,7 @@ interface ReviewJob { taskId: string; userId: string; agents: AgentType[]; + editorialBaseStandard?: 'zh' | 'en'; extractedText: string; modelType: ModelType; } @@ -115,7 +116,7 @@ export async function registerReviewWorker() { // 注册审查Worker jobQueue.process('rvw_review_task', async (job: Job) => { - const { taskId, userId, agents, extractedText, modelType } = job.data; + const { taskId, userId, agents, editorialBaseStandard, extractedText, modelType } = job.data; const startTime = Date.now(); logger.info('[reviewWorker] Processing review job', { @@ -123,6 +124,7 @@ export async function registerReviewWorker() { taskId, userId, agents, + editorialBaseStandard, textLength: extractedText.length, useSkillsArchitecture: USE_SKILLS_ARCHITECTURE, }); @@ -188,6 +190,7 @@ export async function registerReviewWorker() { taskId, userId, agents, + editorialBaseStandard, extractedText, existingTask?.filePath || '', existingTask?.fileName || 'unknown.docx', @@ -229,7 +232,13 @@ export async function registerReviewWorker() { logger.info('[reviewWorker] Running editorial review (legacy)', { taskId }); console.log(' 🔍 运行稿约规范性智能体...'); - editorialResult = await reviewEditorialStandards(extractedText, modelType, userId); + editorialResult = await reviewEditorialStandards( + extractedText, + modelType, + userId, + undefined, + editorialBaseStandard + ); logger.info('[reviewWorker] Editorial review completed', { taskId, @@ -461,6 +470,7 @@ async function executeWithSkills( taskId: string, userId: string, agents: AgentType[], + taskEditorialBaseStandard: 'zh' | 'en' | undefined, extractedText: string, filePath: string, fileName: string, @@ -524,28 +534,43 @@ async function executeWithSkills( }, }); if (cfg) { + const resolvedEditorialBaseStandard = + taskEditorialBaseStandard ?? + (cfg.editorialBaseStandard === 'zh' ? 'zh' : inferredEditorialBase); tenantRvwConfig = { ...cfg, - editorialBaseStandard: cfg.editorialBaseStandard === 'zh' ? 'zh' : inferredEditorialBase, + editorialBaseStandard: resolvedEditorialBaseStandard, }; logger.info('[reviewWorker] 已加载租户审稿配置', { taskId, tenantId: taskWithTenant.tenantId, + taskEditorialBaseStandard, + resolvedEditorialBaseStandard, hasMethodologyPrompt: !!cfg.methodologyExpertPrompt, hasEditorialPrompt: !!cfg.editorialExpertPrompt, hasDataPrompt: !!cfg.dataForensicsExpertPrompt, hasClinicalPrompt: !!cfg.clinicalExpertPrompt, }); } else { + const resolvedEditorialBaseStandard = taskEditorialBaseStandard ?? inferredEditorialBase; tenantRvwConfig = { - editorialBaseStandard: inferredEditorialBase, + editorialBaseStandard: resolvedEditorialBaseStandard, }; logger.info('[reviewWorker] 未找到租户审稿配置,回退系统基线', { taskId, tenantId: taskWithTenant.tenantId, - editorialBaseStandard: inferredEditorialBase, + taskEditorialBaseStandard, + editorialBaseStandard: resolvedEditorialBaseStandard, }); } + } else if (taskEditorialBaseStandard) { + tenantRvwConfig = { + editorialBaseStandard: taskEditorialBaseStandard, + }; + logger.info('[reviewWorker] 使用任务级稿约基线语言', { + taskId, + editorialBaseStandard: taskEditorialBaseStandard, + }); } // 构建上下文(V4.0:注入租户配置实现 Hybrid Prompt) diff --git a/docs/05-部署文档/00-阿里云SAE最新真实状态记录.md b/docs/05-部署文档/00-阿里云SAE最新真实状态记录.md index 14c1857e..79e88a05 100644 --- a/docs/05-部署文档/00-阿里云SAE最新真实状态记录.md +++ b/docs/05-部署文档/00-阿里云SAE最新真实状态记录.md @@ -1,7 +1,7 @@ # 🚀 AI临床研究平台 - 阿里云SAE最新真实状态记录 > **文档用途**:记录阿里云SAE服务器最新真实状态 + 每次部署记录 -> **最后更新**:2026-03-10 +> **最后更新**:2026-03-16 > **维护人员**:开发团队 > **说明**:本文档准确记录SAE上所有应用的当前状态,包括内网地址、镜像版本、用户名密码等关键资源信息 @@ -11,10 +11,10 @@ | 服务名称 | 部署状态 | 镜像版本 | 部署位置 | 最后更新时间 | |---------|---------|---------|---------|-------------| -| **PostgreSQL数据库** | ✅ 运行中 | PostgreSQL 15 + 插件 | RDS | 2026-03-10 | -| **前端Nginx服务** | ✅ 运行中 | **v2.8** | SAE | 2026-03-10 | +| **PostgreSQL数据库** | ✅ 运行中 | PostgreSQL 15 + 插件 | RDS | 2026-03-16 | +| **前端Nginx服务** | ✅ 运行中 | **v2.9** | SAE | 2026-03-16 | | **Python微服务** | ✅ 运行中 | **v1.2** | SAE | 2026-02-27 | -| **Node.js后端** | ✅ 运行中 | **v2.11** | SAE | 2026-03-10 | +| **Node.js后端** | ✅ 运行中 | **v2.12** | SAE | 2026-03-16 | | **R统计引擎** | ✅ 运行中 | **v1.0.5** | SAE | 2026-03-09 | | **Dify AI服务** | ⚠️ 已废弃 | - | - | 使用pgvector替代 | @@ -35,10 +35,10 @@ | 仓库名称 | 最新版本 | 镜像大小 | VPC地址 | |---------|---------|---------|---------| -| **python-extraction** | **v1.2** | ~1.1GB | `crpi-cd5ij4pjt65mweeo-vpc.cn-beijing.personal.cr.aliyuncs.com/ai-clinical/python-extraction:v1.2` | -| **ssa-r-statistics** | **v1.0.5** | ~2.1GB | `crpi-cd5ij4pjt65mweeo-vpc.cn-beijing.personal.cr.aliyuncs.com/ai-clinical/ssa-r-statistics:v1.0.5` | -| **ai-clinical_frontend-nginx** | **v2.8** | ~100MB | `crpi-cd5ij4pjt65mweeo-vpc.cn-beijing.personal.cr.aliyuncs.com/ai-clinical/ai-clinical_frontend-nginx:v2.8` | -| **backend-service** | **v2.11** | ~900MB | `crpi-cd5ij4pjt65mweeo-vpc.cn-beijing.personal.cr.aliyuncs.com/ai-clinical/backend-service:v2.11` | +| **python-extraction** | **v1.3** | ~1.1GB | `crpi-cd5ij4pjt65mweeo-vpc.cn-beijing.personal.cr.aliyuncs.com/ai-clinical/python-extraction:v1.3` | +| **ssa-r-statistics** | **v1.0.6** | ~2.1GB | `crpi-cd5ij4pjt65mweeo-vpc.cn-beijing.personal.cr.aliyuncs.com/ai-clinical/ssa-r-statistics:v1.0.6` | +| **ai-clinical_frontend-nginx** | **v2.9** | ~100MB | `crpi-cd5ij4pjt65mweeo-vpc.cn-beijing.personal.cr.aliyuncs.com/ai-clinical/ai-clinical_frontend-nginx:v2.9` | +| **backend-service** | **v2.12** | ~900MB | `crpi-cd5ij4pjt65mweeo-vpc.cn-beijing.personal.cr.aliyuncs.com/ai-clinical/backend-service:v2.12` | --- @@ -129,8 +129,8 @@ postgresql://airesearch:Xibahe%40fengzhibo117@pgm-2zex1m2y3r23hdn5.pg.rds.aliyun |---------|------|------|-------|------|---------|---------| | **r-statistics-test** | ✅ 运行中 | 1核2GB | 1 | 8080 | `http://172.17.197.26:8080` | **v1.0.5** | | **python-extraction-test** | ✅ 运行中 | **2核4GB** | 1 | 8000 | `http://172.17.173.102:8000` | **v1.2** | -| **nodejs-backend-test** | ✅ 运行中 | **2核4GB** | 1 | 3001 | `http://172.17.173.110:3001` | **v2.11** | -| **frontend-nginx-service** | ✅ 运行中 | 0.5核1GB | 1 | 80 | `http://172.17.197.28:80` | **v2.8** | +| **nodejs-backend-test** | ✅ 运行中 | **2核4GB** | 1 | 3001 | `http://172.17.197.30:3001` | **v2.12** | +| **frontend-nginx-service** | ✅ 运行中 | 0.5核1GB | 1 | 80 | `http://172.17.197.31:80` | **v2.9** | **环境变量配置**: @@ -191,7 +191,7 @@ LEGACY_MYSQL_DATABASE=xzyx_online **前端Nginx(frontend-nginx-service)**: ```bash -BACKEND_SERVICE_HOST=172.17.173.110 +BACKEND_SERVICE_HOST=172.17.197.30 BACKEND_SERVICE_PORT=3001 ``` @@ -259,19 +259,19 @@ TEMP_DIR=/tmp/extraction_service ### 3.2 前端Nginx服务 -**当前部署版本**:v2.8 +**当前部署版本**:v2.9 **镜像信息**: - **仓库名称**:`ai-clinical_frontend-nginx` -- **镜像版本**:`v2.8` ✅(当前部署版本) +- **镜像版本**:`v2.9` ✅(当前部署版本) - **镜像大小**:约50MB - **基础镜像**:`nginx:alpine` -- **构建时间**:2026-03-05 -- **镜像摘要**:sha256:6cb9e8be2bcd21fd8ccfe09dabdbb04d64c252fd9a5b5b3a55d5ba6fb52dcde1 +- **构建时间**:2026-03-16 +- **镜像摘要**:sha256:68ec5521b9322d3a99770f702ed604d5af58dfac7eaeb5e2bba763bd5dfe7a10 **部署状态**: - ✅ 已成功部署到SAE(2026-03-05) -- ✅ 服务运行正常(内网地址:http://172.17.197.28:80) +- ✅ 服务运行正常(内网地址:http://172.17.197.31:80) - ✅ 企业微信域名验证文件已部署(WW_verify_YnhsQBwI0ARnNoG0.txt) **v2.5版本更新内容**: @@ -293,16 +293,16 @@ AIclinicalresearch/frontend-v2/ ### 3.3 Node.js后端服务 -**当前部署版本**:v2.11 +**当前部署版本**:v2.12 **镜像信息**: - **仓库名称**:`backend-service` -- **镜像版本**:`v2.11` ✅(已部署) +- **镜像版本**:`v2.12` ✅(已部署) - **镜像大小**:~838MB - **基础镜像**:`node:alpine` -- **构建时间**:2026-03-05 +- **构建时间**:2026-03-16 - **构建策略**:改进版方案B(本地编译+Docker打包) -- **镜像摘要**:sha256:45886ffd90edbaf6b9a57c1938f14b076fdae175b5d8e53caebabdd8c7ef8b7c +- **镜像摘要**:sha256:2a0730dc1b54f82450a1b11881572430918621eef310c6647f55e7225b9c027d **技术架构**: - **Node.js版本**:22.x @@ -314,7 +314,7 @@ AIclinicalresearch/frontend-v2/ **部署状态**: - ✅ 已成功部署到SAE(2026-03-05) -- ✅ 服务运行正常(内网地址:http://172.17.173.110:3001) +- ✅ 服务运行正常(内网地址:http://172.17.197.30:3001) - ✅ 健康检查通过 **Git文件结构**: @@ -364,6 +364,36 @@ AIclinicalresearch/extraction_service/ ## 🔄 四、部署历史记录 +### 2026-03-16(0316部署 - RVW V4.0 全量发布 + JSON 稳态修复) + +#### 部署概览 +- **部署时间**:2026-03-16 +- **部署范围**:数据库(DB-1~DB-4)+ Node.js后端 + 前端Nginx + 镜像仓库更新 +- **主要变更**:RVW 租户化全链路上线、期刊配置中心 MVP、JSON 解析稳态增强、中英稿约语言任务级选择 + +#### 数据库更新 +- ✅ 迁移执行:`20260311_add_ssa_agent_step_seed_fields` +- ✅ 迁移执行:`20260314_add_tenant_rvw_configs` +- ✅ 迁移执行:`20260314_add_tenant_id_to_review_tasks` +- ✅ 迁移执行:`20260315_journal_config_center_mvp` +- ✅ 迁移状态:RDS 30/30,Schema Up To Date + +#### Node.js后端更新(v2.11 → v2.12) +- ✅ 镜像推送:`backend-service:v2.12` +- ✅ 内网地址变更:`172.17.197.29` → `172.17.197.30` +- ✅ 线上问题修复:稿约规范性 + 方法学 JSON 解析稳态加固(BE-10) + +#### 前端Nginx更新(v2.8 → v2.9) +- ✅ 镜像推送:`ai-clinical_frontend-nginx:v2.9` +- ✅ 内网地址变更:`172.17.173.114` → `172.17.197.31` +- ✅ 新增任务级“稿约基线语言(中文/英文)”选择(FE-9) + +#### 环境变量同步 +- ✅ `nodejs-backend-test`:`RVW_FORENSICS_RULES_ENABLED=false` +- ✅ `frontend-nginx-service`:`BACKEND_SERVICE_HOST=172.17.197.30` + +--- + ### 2026-03-10(0310部署 - 用户直授权限体系 + 运营看板增强) #### 部署概览 @@ -779,5 +809,5 @@ AIclinicalresearch/extraction_service/ --- > **提示**:本文档记录SAE服务器的最新真实状态,每次部署后必须更新! -> **最后更新**:2026-03-10 -> **当前版本**:前端v2.8 | 后端v2.11 | Python v1.2 | R统计v1.0.5 | PostgreSQL 15 +> **最后更新**:2026-03-16 +> **当前版本**:前端v2.9 | 后端v2.12 | Python v1.2 | R统计v1.0.5 | PostgreSQL 15 diff --git a/docs/05-部署文档/03-待部署变更清单.md b/docs/05-部署文档/03-待部署变更清单.md index dc9f8994..46499df0 100644 --- a/docs/05-部署文档/03-待部署变更清单.md +++ b/docs/05-部署文档/03-待部署变更清单.md @@ -3,8 +3,8 @@ > **用途**: 开发过程中实时记录所有待部署的变更,下次部署时按此清单逐项执行 > **维护规则**: 每次修改 Schema / 新增依赖 / 改配置时,**立即**在此文档追加记录 > **Cursor Rule**: `.cursor/rules/deployment-change-tracking.mdc` 会自动提醒 -> **最后清零**: 2026-03-10(0310 部署完成后清零) -> **本次变更**: 已新增待部署项(2026-03-11,含 Agent 严格分步执行模式) +> **最后清零**: 2026-03-16(0316 部署完成后清零) +> **本次变更**: 无(当前待部署清单已清零) --- @@ -16,64 +16,43 @@ | # | 变更内容 | 迁移文件 | 优先级 | 备注 | |---|---------|---------|--------|------| -| DB-1 | SSA Agent 执行记录新增分步执行与种子审计字段(`step_results/current_step/seed_audit`) | `20260311_add_ssa_agent_step_seed_fields` | 高 | 按数据库规范生成;Shadow DB 失败后采用降级流程产出 SQL,并已人工收敛为仅本次字段变更 | -| DB-2 | RVW V4.0:新增 `platform_schema.tenant_rvw_configs` 表(每期刊独立审稿配置,含4维提示词+Handlebars模板) | `20260314_add_tenant_rvw_configs` | 高 | ⚠️ 部署前无需前置条件;使用降级流程手动创建迁移SQL,需执行 `prisma migrate resolve --applied 20260314_add_tenant_rvw_configs` 标记为已应用 | -| DB-3 | RVW V4.0:`rvw_schema.review_tasks` 新增 `tenant_id` 字段 + 索引(历史数据平滑回填两步走) | `20260314_add_tenant_id_to_review_tasks` | 高 | ⚠️ **部署前必须先确认** `platform_schema.tenants` 中存在 `code='yanjiu'` 的主站默认租户;迁移会自动将历史记录回填为该租户ID;需执行 `prisma migrate resolve --applied 20260314_add_tenant_id_to_review_tasks` 标记为已应用 | -| DB-4 | RVW V4.0:期刊配置中心 MVP 一次性补齐字段(`tenants` 新增期刊字段;`tenant_rvw_configs` 升级为 4 维 Prompt+Template;`TenantType` 新增 `JOURNAL`) | `20260315_journal_config_center_mvp` | 高 | ⚠️ `prisma migrate dev` 因历史 shadow DB 迁移失败触发降级流程,已按规范收敛手工 SQL;部署后执行 `prisma migrate resolve --applied 20260315_journal_config_center_mvp` | +| — | *暂无* | | | | ### 后端变更 (Node.js) | # | 变更内容 | 涉及文件 | 需要操作 | 备注 | |---|---------|---------|---------|------| -| BE-1 | SSA Agent 执行链路增加确定性种子注入、错误分类、seed 审计透传 + 分步执行事件(step_*) | `backend/src/modules/ssa/services/ChatHandlerService.ts`, `backend/src/modules/ssa/services/CodeRunnerService.ts`, `backend/src/modules/ssa/services/AgentCoderService.ts` | 重新构建镜像 | 与 DB-1 配套上线,确保执行可复现与可追溯 | -| BE-2 | 新增 Agent 计划参数编辑接口 `PATCH /api/v1/ssa/agent-executions/:executionId/plan-params`(复用参数约束配置) | `backend/src/modules/ssa/routes/agent-execution.routes.ts`, `backend/src/modules/ssa/index.ts` | 重新构建镜像 | Phase 5A.5 后端入口,限制 `plan_pending` 状态可编辑 | -| BE-3 | Agent 切换为严格分步模式:`confirm_plan` 不生成整段代码,执行阶段统一按步骤生成 + 失败后依赖短路跳过后续步骤 | `backend/src/modules/ssa/services/ChatHandlerService.ts` | 重新构建镜像 | 修复“第3步失败仍尝试第4步”问题,降低无效重试与误导性结果 | -| BE-4 | R 代码语法修复器纠正 `} else` 处理策略,避免引入 `unexpected 'else'` | `backend/src/modules/ssa/services/CodeRunnerService.ts` | 重新构建镜像 | 修复线上语法错误噪声,减少重试失败 | -| BE-5 | RVW 审稿通道改造:4 通道 Prompt 动静分离(业务提示词可编辑 + 系统协议固化)+ 方法学/稿约 JSON 结构化修复兜底 + DataForensics 默认切换为 LLM-only(规则验证默认关闭) | `backend/src/modules/rvw/services/promptProtocols.ts`, `backend/src/modules/rvw/services/editorialService.ts`, `backend/src/modules/rvw/services/methodologyService.ts`, `backend/src/modules/rvw/services/clinicalService.ts`, `backend/src/modules/rvw/skills/library/DataForensicsSkill.ts`, `backend/src/modules/rvw/skills/core/types.ts`, `backend/src/common/document/ExtractionClient.ts`, `backend/src/common/prompt/prompt.fallbacks.ts` | 重新构建镜像 | 解决运营端改 Prompt 导致 JSON 解析失败;数据侦探默认仅“表格提取+LLM判断”,规则代码保留可回切 | -| BE-6 | RVW V4.0 Phase 1:Prisma Schema 新增 TenantRvwConfig 模型 + ReviewTask.tenantId 字段 + RVW租户中间件(rvwTenantMiddleware,slug到UUID解析+tenant_members校验+缓存)+ FastifyRequest 扩展 tenantId/tenant 字段 | `backend/prisma/schema.prisma`, `backend/src/modules/rvw/middleware/rvwTenantMiddleware.ts`, `backend/src/common/auth/auth.middleware.ts` | 重新构建镜像 + 执行 DB-2/DB-3 迁移 | 与 DB-2/DB-3 配套上线;Prisma Client 已重新生成 | -| BE-7 | RVW V4.0 Phase 2:RVW Config CRUD API(GET/PUT `/api/admin/tenants/:id/rvw-config`)+ Handlebars 渲染引擎(Zod 校验 + 默认模板)+ SkillExecutor 按租户装配 Hybrid Prompt | `backend/src/modules/admin/rvw-config/rvwConfigController.ts`, `backend/src/modules/admin/rvw-config/rvwConfigService.ts`, `backend/src/modules/admin/rvw-config/rvwConfigRoutes.ts`, `backend/src/modules/rvw/services/rvwReportRenderer.ts`, `backend/src/modules/rvw/workers/reviewWorker.ts`, `backend/src/index.ts` | 重新构建镜像 | 与 BE-6/DB-2/DB-3 配套上线 | -| BE-8 | RVW V4.0 Phase 3:后端 CORS 配置新增 x-tenant-id 白名单(多租户 Header 跨域支持) | `backend/src/index.ts` | 重新构建镜像 | ⚠️ 缺少此项会导致浏览器预检请求拦截 x-tenant-id,整个多租户功能失效 | -| BE-9 | RVW V4.0 Phase 4(期刊配置中心 MVP):新增 `/api/admin/journal-configs` 专用 API;租户模型扩展期刊字段;RVW 执行链路接入最小配置适配(`tenantCustom ?? systemDefault`),Editorial 支持 `zh/en` 基线路由;创建/更新为 `JOURNAL` 时自动兜底开通 `RVW` 模块 | `backend/src/modules/admin/journal-config/*`, `backend/src/modules/admin/services/tenantService.ts`, `backend/src/modules/admin/types/tenant.types.ts`, `backend/src/modules/admin/rvw-config/*`, `backend/src/modules/rvw/workers/reviewWorker.ts`, `backend/src/modules/rvw/skills/library/*`, `backend/src/modules/rvw/services/editorialService.ts`, `backend/src/modules/rvw/services/clinicalService.ts`, `backend/src/common/prompt/prompt.fallbacks.ts`, `backend/src/index.ts` | 重新构建镜像 + 执行 DB-4 迁移 | 与 FE-7 联动上线,满足「独立一级菜单 + 中英文基线 + 继承/覆盖」MVP 范围,并避免新期刊用户登录后无 RVW 模块权限 | +| — | *暂无* | | | | ### 前端变更 | # | 变更内容 | 涉及文件 | 需要操作 | 备注 | |---|---------|---------|---------|------| -| FE-1 | Agent 通道接入 step_* SSE 事件并展示分步执行状态(兼容旧 code_* 事件) | `frontend-v2/src/modules/ssa/hooks/useSSAChat.ts`, `frontend-v2/src/modules/ssa/components/AgentCodePanel.tsx`, `frontend-v2/src/modules/ssa/types/index.ts`, `frontend-v2/src/modules/ssa/stores/ssaStore.ts` | 重新构建镜像 | 右侧工作区可见每步状态/错误/耗时,便于排障 | -| FE-2 | Agent 计划阶段复用 QPER 变量编辑控件(单变量/多变量)并接入保存、确认前自动保存 | `frontend-v2/src/modules/ssa/components/AgentCodePanel.tsx`, `frontend-v2/src/modules/ssa/components/WorkflowTimeline.tsx`, `frontend-v2/src/modules/ssa/components/SSAWorkspacePane.tsx` | 重新构建镜像 | 对接 `PATCH /agent-executions/:executionId/plan-params`,实现 5A.5 前后端闭环 | -| FE-3 | Agent 工作区增强:在分步状态下可展开查看每步已生成结果(`reportBlocks`),并兼容严格分步模式下的 `code_pending` 空代码预览 | `frontend-v2/src/modules/ssa/components/AgentCodePanel.tsx`, `frontend-v2/src/modules/ssa/hooks/useSSAChat.ts` | 重新构建镜像 | 修复“有结果但代码下方不可见”与状态显示误导问题 | -| FE-4 | RVW V4.0 Phase 2:TenantDetailPage 新增「智能审稿配置」Tab(4 Panel:稿约规范占位 / 方法学Prompt+Handlebars模板 / 数据验证深度L1-L3 / 临床FINER权重)+ tenantApi 新增 fetchRvwConfig/saveRvwConfig | `frontend-v2/src/pages/admin/tenants/TenantDetailPage.tsx`, `frontend-v2/src/pages/admin/tenants/api/tenantApi.ts` | 重新构建镜像 | 与 BE-7/DB-2/DB-3 配套上线 | -| FE-5 | RVW V4.0 Phase 3:新增 TenantPortalLayout 极简期刊门户布局 + App.tsx 新增 /:tenantSlug/rvw/* 路由(期刊专属 URL)+ LoginPage.tsx 修复跳转逻辑(读取 ?redirect= 查询参数 + 租户默认落地页 /:tenantSlug/rvw) | `frontend-v2/src/framework/layout/TenantPortalLayout.tsx`, `frontend-v2/src/App.tsx`, `frontend-v2/src/pages/LoginPage.tsx` | 重新构建镜像 | 与 BE-6/BE-7/BE-8 配套上线;实现期刊租户完整访问路径 /jtim → /jtim/rvw | -| FE-6 | RVW V4.0 租户门户体验收敛:上传按钮稳定触发(原生文件选择器 API + input 回退)、执行审稿后跳转旧版过程页(复用 `TaskDetail`)、列表四维状态图标修复(完成态正确显示绿勾/警告) | `frontend-v2/src/modules/rvw/pages/TenantDashboard.tsx`, `frontend-v2/src/modules/rvw/pages/TenantTaskDetail.tsx` | 重新构建镜像 | 已本地联调通过;上线后重点回归 `/:tenant/login -> /:tenant/rvw` 主流程 | -| FE-7 | RVW V4.0 期刊配置中心 MVP:ADMIN 新增独立一级菜单“期刊配置中心”,新增列表页/详情页(基础信息 + 4 维审稿配置),并对齐新 API DTO(`journalLanguage`、`editorialBaseStandard`、Prompt/Handlebars 字段) | `frontend-v2/src/framework/layout/AdminLayout.tsx`, `frontend-v2/src/App.tsx`, `frontend-v2/src/pages/admin/journal-configs/*`, `frontend-v2/src/pages/admin/tenants/api/tenantApi.ts`, `frontend-v2/src/pages/admin/tenants/TenantDetailPage.tsx` | 重新构建镜像 | 与 BE-9/DB-4 配套上线;MVP 阶段先跑通配置闭环,品牌视觉字段先存储后逐步渲染 | -| FE-8 | 登录路径策略统一为单一路径:仅保留 `/:tenantCode/login`,未登录重定向与退出登录统一 `/:tenant/login`,开发/生产同路径仅域名不同 | `frontend-v2/src/App.tsx`, `frontend-v2/src/framework/router/RouteGuard.tsx`, `frontend-v2/src/framework/layout/TenantPortalLayout.tsx`, `frontend-v2/src/pages/TenantLoginPage.tsx`, `frontend-v2/src/pages/LoginPage.tsx` | 重新构建镜像 | 对齐生产目标链接 `review.xunzhengyixue.com/test-qikan01`;不再保留 `/t/:tenantCode/login` 路径,需同步更新测试文档/书签 | +| — | *暂无* | | | | ### Python 微服务变更 | # | 变更内容 | 涉及文件 | 需要操作 | 备注 | |---|---------|---------|---------|------| -| PY-1 | Forensics API 新增 `EXTRACT_ONLY` 模式并默认仅提取表格(不执行 L1/L2 规则校验) | `extraction_service/forensics/api.py`, `extraction_service/forensics/types.py` | 重新构建镜像 | 与后端 RVW LLM-only 路径配套,避免规则与 LLM 双轨冲突 | +| — | *暂无* | | | | ### R 统计引擎变更 | # | 变更内容 | 涉及文件 | 需要操作 | 备注 | |---|---------|---------|---------|------| -| R-1 | execute-code 端点升级为语法+安全双层预检,新增 E_SECURITY 与运行时高危函数拦截 | `r-statistics-service/plumber.R` | 重新构建镜像 | 阻断 system/eval/source/file.remove/setwd 等风险调用 | +| — | *暂无* | | | | ### 环境变量 / 配置变更 | # | 变更内容 | 服务 | 变量名 | 备注 | |---|---------|------|--------|------| -| ENV-1 | 新增 RVW 数据侦探规则引擎开关(默认关闭) | nodejs-backend-test / nodejs-backend-prod | `RVW_FORENSICS_RULES_ENABLED=false` | `false`=仅表格提取+LLM判断(推荐);如需恢复规则验证可设为 `true` | +| — | *暂无* | | | | ### 基础设施变更 | # | 变更内容 | 范围 | 备注 | |---|---------|------|------| -| INF-1 | 前端 Nginx 增加 SPA 深链回退(`try_files $uri /index.html`),并确保 `/api/` 路由优先反代后端(不能被 index.html 吞掉) | frontend-nginx-service / ingress | ⚠️ 否则直接访问 `review.xunzhengyixue.com/test-qikan01` 或 `.../test-qikan01/login` 会 404;部署后需在无缓存浏览器验证 | -| INF-2 | 前端 Docker 构建与发布注意:确认镜像内 Nginx 配置已包含深链规则、并清理旧静态资源缓存(CDN/浏览器)后再灰度 | frontend-v2 build/deploy pipeline | 建议发布后先执行硬刷新(Ctrl+F5)与隐身窗口验证,避免旧 bundle 缓存导致仍跳旧路径 | -| INF-3 | SAE 接入 RVW 多租户链路:`review.xunzhengyixue.com` 仅负责流量入口,实际租户/模块判定由应用完成(`/:tenantCode/login`、`/:tenantSlug/rvw` + `x-tenant-id` + `tenant_members` + `tenant_modules`) | frontend SAE + backend SAE + ingress/SLB | ⚠️ SAE 不会自动“识别审稿模块”;必须保证前端路由命中、`/api` 正确转发后端、后端开启 RVW 路由与租户中间件;并确认租户已开通 `RVW` 且用户在该租户下有成员关系 | -| INF-4 | 前端镜像构建发布标准流程(生产):安装依赖→构建 `frontend-v2/dist`→打包 Nginx 镜像→推送镜像仓库→SAE 更新版本→灰度验证→全量发布 | frontend-v2 build/deploy pipeline | 推荐命令:`npm ci && npm run build`(在 `frontend-v2` 目录);镜像发布后必须验证 `review.xunzhengyixue.com/{tenant}/login`、`/{tenant}/rvw`、`/api/v1/auth/verification-code` 三条链路可用 | +| — | *暂无* | | | --- @@ -99,6 +78,14 @@ - `验证码发送成功 + 倒计时启动 + 登录成功` 需完整通过 - 弱网场景若偶发失败,先排查网络/网关,再看后端日志;前端已增加“发送中”防重入保护 +5. **中英稿约语言链路验证(新增)** + - 在任务发起弹窗中确认“稿约基线语言”默认值与租户语言一致(中文期刊默认中文,英文期刊默认英文) + - 手动切换语言后执行审稿,确认稿约模块按所选语言输出,且任务状态不出现解析失败导致的 `partial_completed` + +6. **JSON 解析稳态验证(新增)** + - 分别用中文/英文稿件各执行至少 3 次“稿约规范性 + 方法学”组合,确认无 `无法从LLM响应中解析JSON` / `section repair invalid` + - 如出现兜底修复日志(warn)但任务完成,可判定为“已自动恢复”;若连续失败,再排查模型配额/网络超时/提示词内容 + --- ## 记录模板 @@ -129,6 +116,19 @@ ## 历史(已部署,仅供追溯) +### 0316 部署已清零项 + +| # | 变更内容 | 部署日期 | 结果 | +|---|---------|---------|------| +| DB | DB-1~DB-4 全量完成(SSA 分步审计字段 + RVW 租户配置 + review_tasks.tenant_id 回填 + JOURNAL/MVP 字段) | 2026-03-16 | ✅ | +| BE | 后端 v2.11 → v2.12(含 BE-10 JSON 解析稳态加固与观测日志) | 2026-03-16 | ✅ | +| FE | 前端 v2.8 → v2.9(含 FE-9 稿约基线语言任务级选择) | 2026-03-16 | ✅ | +| PY | Python 提取服务镜像构建并推送 v1.3(与 RVW LLM-only 路径配套) | 2026-03-16 | ✅ | +| R | R 统计引擎镜像构建并推送 v1.0.6(语法+安全预检) | 2026-03-16 | ✅ | +| ENV | nodejs-backend-test 新增 `RVW_FORENSICS_RULES_ENABLED=false` | 2026-03-16 | ✅ | +| ENV | frontend-nginx-service: `BACKEND_SERVICE_HOST` 更新为 `172.17.197.30` | 2026-03-16 | ✅ | +| INF | `review.xunzhengyixue.com` 链路发布:深链回退 + `/api` 反代优先级 + 租户路由可用 | 2026-03-16 | ✅ | + ### 0310 部署已清零项 | # | 变更内容 | 部署日期 | 结果 | diff --git a/docs/05-部署文档/0316部署/01-部署完成总结.md b/docs/05-部署文档/0316部署/01-部署完成总结.md new file mode 100644 index 00000000..89cc5407 --- /dev/null +++ b/docs/05-部署文档/0316部署/01-部署完成总结.md @@ -0,0 +1,73 @@ +# 0316 部署完成总结(v1.0) + +> **部署日期**:2026-03-16 +> **部署环境**:阿里云 SAE 测试环境(cn-beijing:test-airesearch) +> **部署主题**:RVW V4.0 全量发布 + JSON 解析稳态修复二次发布 + +--- + +## 一、部署范围 + +### 1) 数据库(RDS PostgreSQL) +- 已执行 DB-1~DB-4 迁移并完成状态对齐: + - `20260311_add_ssa_agent_step_seed_fields` + - `20260314_add_tenant_rvw_configs` + - `20260314_add_tenant_id_to_review_tasks` + - `20260315_journal_config_center_mvp` +- 已完成 `prisma migrate resolve --applied` 标记,迁移链路一致。 +- 已补齐默认租户:`code='yanjiu'`,并完成 `review_tasks.tenant_id` 历史回填(空值为 0)。 + +### 2) 镜像构建与推送(ACR) +- 后端:`backend-service:v2.12` +- 前端:`ai-clinical_frontend-nginx:v2.9` +- Python:`python-extraction:v1.3` +- R:`ssa-r-statistics:v1.0.6` + +### 3) SAE 发布结果(本轮重点) +- `nodejs-backend-test`:`172.17.197.30` +- `frontend-nginx-service`:`172.17.197.31` + +--- + +## 二、核心修复说明(本轮二次发布) + +### 后端 JSON 解析稳态(BE-10) +- 针对 RVW 稿约规范性与方法学输出,增强多策略 JSON 解析与二次严格修复。 +- 增加解析失败观测日志(长度、预览),便于线上快速定位。 +- 目标问题:`无法从LLM响应中解析JSON`、`section repair invalid`。 + +### 前端联动能力(FE-9) +- 任务发起弹窗新增“稿约基线语言(中文/英文)”任务级选择。 +- 优先级规则:任务级显式选择 > 租户默认语言。 +- 用于覆盖中英稿件混合审稿场景,降低提示词与输出格式错配风险。 + +--- + +## 三、环境变量与配置 + +- Node.js 服务新增: + - `RVW_FORENSICS_RULES_ENABLED=false` +- 前端 Nginx 服务更新: + - `BACKEND_SERVICE_HOST=172.17.197.30` + - `BACKEND_SERVICE_PORT=3001` +- `review.xunzhengyixue.com` 路由链路依赖: + - SPA 深链回退(`try_files ... /index.html`) + - `/api/` 反代优先级高于 SPA 回退 + +--- + +## 四、验收结论 + +- 数据库结构与迁移链路:✅ 完成 +- 后端与前端二次发布:✅ 完成 +- JSON 解析稳态修复版本已上线:✅ 完成 +- 待部署清单:✅ 已清零并归档到 0316 历史 + +--- + +## 五、回滚建议 + +若出现异常,建议按以下顺序回滚: +1. 前端回滚到 `v2.8` +2. 后端回滚到 `v2.11` +3. 数据库不建议直接反向 DDL;优先应用层回滚。如必须回退,使用部署前备份恢复并复核迁移基线。 diff --git a/frontend-v2/src/modules/rvw/api/index.ts b/frontend-v2/src/modules/rvw/api/index.ts index 8549ac6a..ab9232b3 100644 --- a/frontend-v2/src/modules/rvw/api/index.ts +++ b/frontend-v2/src/modules/rvw/api/index.ts @@ -2,7 +2,13 @@ * RVW模块API */ import apiClient from '../../../common/api/axios'; -import type { ReviewTask, ReviewReport, ApiResponse, AgentType } from '../types'; +import type { + ReviewTask, + ReviewReport, + ApiResponse, + AgentType, + EditorialBaseStandard, +} from '../types'; const API_BASE = '/api/v1/rvw'; @@ -45,8 +51,15 @@ export async function getTaskReport(taskId: string): Promise { } // 运行审查任务(返回jobId供轮询) -export async function runTask(taskId: string, agents: AgentType[]): Promise<{ taskId: string; jobId: string }> { - const response = await apiClient.post>(`${API_BASE}/tasks/${taskId}/run`, { agents }); +export async function runTask( + taskId: string, + agents: AgentType[], + editorialBaseStandard?: EditorialBaseStandard +): Promise<{ taskId: string; jobId: string }> { + const response = await apiClient.post>(`${API_BASE}/tasks/${taskId}/run`, { + agents, + editorialBaseStandard, + }); if (!response.data.success) { throw new Error(response.data.error || '运行失败'); } @@ -54,8 +67,16 @@ export async function runTask(taskId: string, agents: AgentType[]): Promise<{ ta } // 批量运行审查任务 -export async function batchRunTasks(taskIds: string[], agents: AgentType[]): Promise { - const response = await apiClient.post>(`${API_BASE}/tasks/batch/run`, { taskIds, agents }); +export async function batchRunTasks( + taskIds: string[], + agents: AgentType[], + editorialBaseStandard?: EditorialBaseStandard +): Promise { + const response = await apiClient.post>(`${API_BASE}/tasks/batch/run`, { + taskIds, + agents, + editorialBaseStandard, + }); if (!response.data.success) { throw new Error(response.data.error || '批量运行失败'); } diff --git a/frontend-v2/src/modules/rvw/components/AgentModal.tsx b/frontend-v2/src/modules/rvw/components/AgentModal.tsx index 590a9911..7fe7d7e7 100644 --- a/frontend-v2/src/modules/rvw/components/AgentModal.tsx +++ b/frontend-v2/src/modules/rvw/components/AgentModal.tsx @@ -1,20 +1,35 @@ /** * 智能体选择弹窗 */ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { PlayCircle, X } from 'lucide-react'; -import type { AgentType } from '../types'; +import type { AgentType, EditorialBaseStandard } from '../types'; interface AgentModalProps { visible: boolean; taskCount: number; onClose: () => void; - onConfirm: (agents: AgentType[]) => void; + onConfirm: (agents: AgentType[], editorialBaseStandard?: EditorialBaseStandard) => void; + defaultEditorialBaseStandard?: EditorialBaseStandard; isSubmitting?: boolean; // 🔒 防止重复提交 } -export default function AgentModal({ visible, taskCount, onClose, onConfirm, isSubmitting = false }: AgentModalProps) { +export default function AgentModal({ + visible, + taskCount, + onClose, + onConfirm, + defaultEditorialBaseStandard = 'zh', + isSubmitting = false, +}: AgentModalProps) { const [selectedAgents, setSelectedAgents] = useState(['editorial']); + const [editorialBaseStandard, setEditorialBaseStandard] = useState(defaultEditorialBaseStandard); + + useEffect(() => { + if (!visible) return; + setSelectedAgents(['editorial']); + setEditorialBaseStandard(defaultEditorialBaseStandard); + }, [visible, defaultEditorialBaseStandard]); const toggleAgent = (agent: AgentType) => { if (selectedAgents.includes(agent)) { @@ -29,14 +44,17 @@ export default function AgentModal({ visible, taskCount, onClose, onConfirm, isS const handleConfirm = () => { // 只调用onConfirm,让调用方控制关闭时机 - onConfirm(selectedAgents); + onConfirm( + selectedAgents, + selectedAgents.includes('editorial') ? editorialBaseStandard : undefined + ); }; if (!visible) return null; return (
-
+
{/* 头部 */}

@@ -76,6 +94,34 @@ export default function AgentModal({ visible, taskCount, onClose, onConfirm, isS

快速 + + {selectedAgents.includes('editorial') && ( +
+
稿约基线语言
+
+ + +
+
+ )} {/* 方法学智能体 */} +
{/* 底部按钮 */} diff --git a/frontend-v2/src/modules/rvw/pages/Dashboard.tsx b/frontend-v2/src/modules/rvw/pages/Dashboard.tsx index cd2fd0ab..ea0ee21d 100644 --- a/frontend-v2/src/modules/rvw/pages/Dashboard.tsx +++ b/frontend-v2/src/modules/rvw/pages/Dashboard.tsx @@ -14,10 +14,18 @@ import { TaskDetail, } from '../components'; import * as api from '../api'; -import type { ReviewTask, ReviewReport, TaskFilters, AgentType } from '../types'; +import type { + ReviewTask, + ReviewReport, + TaskFilters, + AgentType, + EditorialBaseStandard, +} from '../types'; +import { useAuth } from '../../../framework/auth'; import '../styles/index.css'; export default function Dashboard() { + const { user } = useAuth(); // ==================== State ==================== const [currentView, setCurrentView] = useState<'dashboard' | 'archive'>('dashboard'); const [tasks, setTasks] = useState([]); @@ -27,6 +35,8 @@ export default function Dashboard() { const [agentModalVisible, setAgentModalVisible] = useState(false); const [pendingTaskForRun, setPendingTaskForRun] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); // 🔒 防止重复提交 + const [defaultEditorialBaseStandard, setDefaultEditorialBaseStandard] = + useState('zh'); // 报告详情 const [reportDetail, setReportDetail] = useState(null); @@ -64,6 +74,18 @@ export default function Dashboard() { loadTasks(); }, [loadTasks]); + useEffect(() => { + const tenantCode = user?.tenantCode; + if (!tenantCode) return; + fetch(`/api/v1/public/tenant-config/${tenantCode}`) + .then((res) => res.json()) + .then((data) => { + const base = data?.data?.editorialDefaultStandard; + setDefaultEditorialBaseStandard(base === 'en' ? 'en' : 'zh'); + }) + .catch(() => {}); + }, [user?.tenantCode]); + // 轮询更新进行中的任务 useEffect(() => { const processingTasks = tasks.filter(t => @@ -119,7 +141,7 @@ export default function Dashboard() { setAgentModalVisible(true); }; - const handleConfirmRun = async (agents: AgentType[]) => { + const handleConfirmRun = async (agents: AgentType[], editorialBaseStandard?: EditorialBaseStandard) => { // 🔒 防止重复提交 if (isSubmitting) { console.warn('[Dashboard] 已在提交中,忽略重复请求'); @@ -138,7 +160,7 @@ export default function Dashboard() { if (taskToRun) { // 单个任务 - 启动后跳转到详情页显示进度 message.loading({ content: '正在启动审查...', key: 'run' }); - const { jobId } = await api.runTask(taskToRun.id, agents); + const { jobId } = await api.runTask(taskToRun.id, agents, editorialBaseStandard); message.success({ content: '审查已启动', key: 'run', duration: 2 }); // 更新任务状态后跳转到详情页(传递jobId) @@ -159,7 +181,7 @@ export default function Dashboard() { } message.loading({ content: `正在启动 ${pendingIds.length} 个任务...`, key: 'run' }); - await api.batchRunTasks(pendingIds, agents); + await api.batchRunTasks(pendingIds, agents, editorialBaseStandard); message.success({ content: `${pendingIds.length} 个任务已启动`, key: 'run', duration: 2 }); setSelectedIds([]); } @@ -282,6 +304,7 @@ export default function Dashboard() { { setAgentModalVisible(false); setPendingTaskForRun(null); diff --git a/frontend-v2/src/modules/rvw/pages/TenantDashboard.tsx b/frontend-v2/src/modules/rvw/pages/TenantDashboard.tsx index d8251de0..d2dd22b8 100644 --- a/frontend-v2/src/modules/rvw/pages/TenantDashboard.tsx +++ b/frontend-v2/src/modules/rvw/pages/TenantDashboard.tsx @@ -6,10 +6,10 @@ */ import { useState, useEffect, useCallback, useRef } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { message } from 'antd'; import * as api from '../api'; -import type { ReviewTask, AgentType } from '../types'; +import type { ReviewTask, AgentType, EditorialBaseStandard } from '../types'; import AgentModal from '../components/AgentModal'; // ── 内联 SVG 状态图标 ───────────────────────────────────────────── @@ -137,6 +137,7 @@ function StatusBadge({ status }: { status: ReviewTask['status'] }) { export default function TenantDashboard() { const navigate = useNavigate(); + const { tenantSlug } = useParams<{ tenantSlug: string }>(); const fileInputRef = useRef(null); const [tasks, setTasks] = useState([]); @@ -147,6 +148,19 @@ export default function TenantDashboard() { const [agentModalVisible, setAgentModalVisible] = useState(false); const [pendingTask, setPendingTask] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); + const [defaultEditorialBaseStandard, setDefaultEditorialBaseStandard] = + useState('zh'); + + useEffect(() => { + if (!tenantSlug) return; + fetch(`/api/v1/public/tenant-config/${tenantSlug}`) + .then((res) => res.json()) + .then((data) => { + const base = data?.data?.editorialDefaultStandard; + setDefaultEditorialBaseStandard(base === 'en' ? 'en' : 'zh'); + }) + .catch(() => {}); + }, [tenantSlug]); // ── 数据加载 ────────────────────────────────────────────────── const loadTasks = useCallback(async () => { @@ -265,7 +279,7 @@ export default function TenantDashboard() { setAgentModalVisible(true); }; - const handleConfirmRun = async (agents: AgentType[]) => { + const handleConfirmRun = async (agents: AgentType[], editorialBaseStandard?: EditorialBaseStandard) => { if (isSubmitting || !pendingTask) return; const task = pendingTask; setAgentModalVisible(false); @@ -273,7 +287,7 @@ export default function TenantDashboard() { setIsSubmitting(true); try { message.loading({ content: '正在启动审查…', key: 'run' }); - const { jobId } = await api.runTask(task.id, agents); + const { jobId } = await api.runTask(task.id, agents, editorialBaseStandard); message.success({ content: '审查已启动', key: 'run', duration: 2 }); // 对齐旧版体验:启动后立即进入“审稿过程页”,动态查看进度与分模块结果 navigate(`${task.id}?jobId=${encodeURIComponent(jobId)}`); @@ -502,6 +516,7 @@ export default function TenantDashboard() { { setAgentModalVisible(false); setPendingTask(null); }} onConfirm={handleConfirmRun} isSubmitting={isSubmitting} diff --git a/frontend-v2/src/modules/rvw/types/index.ts b/frontend-v2/src/modules/rvw/types/index.ts index 71fe1572..b1467a01 100644 --- a/frontend-v2/src/modules/rvw/types/index.ts +++ b/frontend-v2/src/modules/rvw/types/index.ts @@ -15,6 +15,7 @@ export type TaskStatus = // 智能体类型 export type AgentType = 'editorial' | 'methodology' | 'clinical'; +export type EditorialBaseStandard = 'zh' | 'en'; // 审查任务 export interface ReviewTask { @@ -23,6 +24,7 @@ export interface ReviewTask { fileSize: number; status: TaskStatus; selectedAgents: AgentType[]; + editorialBaseStandard?: EditorialBaseStandard; wordCount?: number; overallScore?: number; editorialScore?: number; diff --git a/frontend-v2/tsconfig.tsbuildinfo b/frontend-v2/tsconfig.tsbuildinfo index 03c315c8..812fba9f 100644 --- a/frontend-v2/tsconfig.tsbuildinfo +++ b/frontend-v2/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/common/api/axios.ts","./src/framework/auth/authcontext.tsx","./src/framework/auth/api.ts","./src/framework/auth/index.ts","./src/framework/auth/moduleapi.ts","./src/framework/auth/sessionguard.ts","./src/framework/auth/types.ts","./src/framework/auth/useauthheartbeat.ts","./src/framework/layout/adminlayout.tsx","./src/framework/layout/mainlayout.tsx","./src/framework/layout/orglayout.tsx","./src/framework/layout/topnavigation.tsx","./src/framework/modules/errorboundary.tsx","./src/framework/modules/moduleerrorfallback.tsx","./src/framework/modules/moduleregistry.ts","./src/framework/modules/types.ts","./src/framework/permission/permissioncontext.tsx","./src/framework/permission/index.ts","./src/framework/permission/types.ts","./src/framework/permission/usepermission.ts","./src/framework/router/permissiondenied.tsx","./src/framework/router/routeguard.tsx","./src/framework/router/index.ts","./src/modules/admin/index.tsx","./src/modules/admin/api/iitprojectapi.ts","./src/modules/admin/api/statsapi.ts","./src/modules/admin/api/systemkbapi.ts","./src/modules/admin/api/userapi.ts","./src/modules/admin/components/assigntenantmodal.tsx","./src/modules/admin/components/importusermodal.tsx","./src/modules/admin/components/modulepermissionmodal.tsx","./src/modules/admin/components/qc-cockpit/qcdetaildrawer.tsx","./src/modules/admin/components/qc-cockpit/qcreportdrawer.tsx","./src/modules/admin/components/qc-cockpit/qcstatcards.tsx","./src/modules/admin/components/qc-cockpit/riskheatmap.tsx","./src/modules/admin/components/qc-cockpit/index.ts","./src/modules/admin/pages/iitmembermanagepage.tsx","./src/modules/admin/pages/iitprojectdetailpage.tsx","./src/modules/admin/pages/iitprojectlistpage.tsx","./src/modules/admin/pages/iitqccockpitpage.tsx","./src/modules/admin/pages/statsdashboardpage.tsx","./src/modules/admin/pages/systemkbdetailpage.tsx","./src/modules/admin/pages/systemkblistpage.tsx","./src/modules/admin/pages/userdetailpage.tsx","./src/modules/admin/pages/userformpage.tsx","./src/modules/admin/pages/userlistpage.tsx","./src/modules/admin/types/iitproject.ts","./src/modules/admin/types/qccockpit.ts","./src/modules/admin/types/systemkb.ts","./src/modules/admin/types/user.ts","./src/modules/aia/constants.ts","./src/modules/aia/index.tsx","./src/modules/aia/types.ts","./src/modules/aia/components/agentcard.tsx","./src/modules/aia/components/agenthub.tsx","./src/modules/aia/components/chatworkspace.tsx","./src/modules/aia/components/index.ts","./src/modules/aia/protocol-agent/protocolagentpage.tsx","./src/modules/aia/protocol-agent/index.ts","./src/modules/aia/protocol-agent/types.ts","./src/modules/aia/protocol-agent/components/actioncard.tsx","./src/modules/aia/protocol-agent/components/chatarea.tsx","./src/modules/aia/protocol-agent/components/documentpanel.tsx","./src/modules/aia/protocol-agent/components/markdowncontent.tsx","./src/modules/aia/protocol-agent/components/reflexionmessage.tsx","./src/modules/aia/protocol-agent/components/resizablesplitpane.tsx","./src/modules/aia/protocol-agent/components/stagecard.tsx","./src/modules/aia/protocol-agent/components/stageeditmodal.tsx","./src/modules/aia/protocol-agent/components/statepanel.tsx","./src/modules/aia/protocol-agent/components/syncbutton.tsx","./src/modules/aia/protocol-agent/components/viewswitcher.tsx","./src/modules/aia/protocol-agent/components/index.ts","./src/modules/aia/protocol-agent/hooks/index.ts","./src/modules/aia/protocol-agent/hooks/useprotocolcontext.ts","./src/modules/aia/protocol-agent/hooks/useprotocolconversations.ts","./src/modules/aia/protocol-agent/hooks/useprotocolgeneration.ts","./src/modules/asl/index.tsx","./src/modules/asl/api/index.ts","./src/modules/asl/components/asllayout.tsx","./src/modules/asl/components/conclusiontag.tsx","./src/modules/asl/components/detailreviewdrawer.tsx","./src/modules/asl/components/fulltextdetaildrawer.tsx","./src/modules/asl/components/judgmentbadge.tsx","./src/modules/asl/components/charting/baselinetable.tsx","./src/modules/asl/components/charting/datasourceselector.tsx","./src/modules/asl/components/charting/prismaflowdiagram.tsx","./src/modules/asl/components/deep-research/agentterminal.tsx","./src/modules/asl/components/deep-research/landingview.tsx","./src/modules/asl/components/deep-research/resultsview.tsx","./src/modules/asl/components/deep-research/setuppanel.tsx","./src/modules/asl/components/deep-research/strategyconfirm.tsx","./src/modules/asl/components/extraction/extractiondrawer.tsx","./src/modules/asl/components/extraction/extractionstatusbadge.tsx","./src/modules/asl/components/extraction/fieldgroup.tsx","./src/modules/asl/components/extraction/processingterminal.tsx","./src/modules/asl/components/extraction/quoteblock.tsx","./src/modules/asl/components/meta/resultspanel.tsx","./src/modules/asl/hooks/usedeepresearchtask.ts","./src/modules/asl/hooks/useextractionlogs.ts","./src/modules/asl/hooks/usefulltextresults.ts","./src/modules/asl/hooks/usefulltexttask.ts","./src/modules/asl/hooks/usescreeningresults.ts","./src/modules/asl/hooks/usescreeningtask.ts","./src/modules/asl/pages/deepresearchpage.tsx","./src/modules/asl/pages/extractionpage.tsx","./src/modules/asl/pages/extractionprogress.tsx","./src/modules/asl/pages/extractionsetup.tsx","./src/modules/asl/pages/extractionworkbench.tsx","./src/modules/asl/pages/fulltextprogress.tsx","./src/modules/asl/pages/fulltextresults.tsx","./src/modules/asl/pages/fulltextsettings.tsx","./src/modules/asl/pages/fulltextworkbench.tsx","./src/modules/asl/pages/metaanalysisengine.tsx","./src/modules/asl/pages/researchsearch.tsx","./src/modules/asl/pages/srchartgenerator.tsx","./src/modules/asl/pages/screeningresults.tsx","./src/modules/asl/pages/screeningworkbench.tsx","./src/modules/asl/pages/titlescreeningsettings.tsx","./src/modules/asl/types/deepresearch.ts","./src/modules/asl/types/index.ts","./src/modules/asl/utils/chartingexcelutils.ts","./src/modules/asl/utils/excelexport.ts","./src/modules/asl/utils/excelutils.ts","./src/modules/asl/utils/metaexcelutils.ts","./src/modules/asl/utils/tabletransform.ts","./src/modules/dc/index.tsx","./src/modules/dc/api/toolb.ts","./src/modules/dc/api/toolc.ts","./src/modules/dc/components/assetlibrary.tsx","./src/modules/dc/components/tasklist.tsx","./src/modules/dc/components/toolcard.tsx","./src/modules/dc/hooks/useassets.ts","./src/modules/dc/hooks/userecenttasks.ts","./src/modules/dc/pages/portal.tsx","./src/modules/dc/pages/tool-b/step1upload.tsx","./src/modules/dc/pages/tool-b/step2schema.tsx","./src/modules/dc/pages/tool-b/step3processing.tsx","./src/modules/dc/pages/tool-b/step4verify.tsx","./src/modules/dc/pages/tool-b/step5result.tsx","./src/modules/dc/pages/tool-b/index.tsx","./src/modules/dc/pages/tool-b/components/stepindicator.tsx","./src/modules/dc/pages/tool-c/index.tsx","./src/modules/dc/pages/tool-c/components/binningdialog.tsx","./src/modules/dc/pages/tool-c/components/binningdialog_improved.tsx","./src/modules/dc/pages/tool-c/components/computedialog.tsx","./src/modules/dc/pages/tool-c/components/conditionaldialog.tsx","./src/modules/dc/pages/tool-c/components/datagrid.tsx","./src/modules/dc/pages/tool-c/components/dropnadialog.tsx","./src/modules/dc/pages/tool-c/components/filterdialog.tsx","./src/modules/dc/pages/tool-c/components/header.tsx","./src/modules/dc/pages/tool-c/components/metrictimepanel.tsx","./src/modules/dc/pages/tool-c/components/missingvaluedialog.tsx","./src/modules/dc/pages/tool-c/components/multimetricpanel.tsx","./src/modules/dc/pages/tool-c/components/pivotdialog.tsx","./src/modules/dc/pages/tool-c/components/pivotpanel.tsx","./src/modules/dc/pages/tool-c/components/recodedialog.tsx","./src/modules/dc/pages/tool-c/components/sidebar.tsx","./src/modules/dc/pages/tool-c/components/streamingsteps.tsx","./src/modules/dc/pages/tool-c/components/toolbar.tsx","./src/modules/dc/pages/tool-c/components/transformdialog.tsx","./src/modules/dc/pages/tool-c/components/unpivotpanel.tsx","./src/modules/dc/pages/tool-c/hooks/usesessionstatus.ts","./src/modules/dc/pages/tool-c/types/index.ts","./src/modules/dc/types/portal.ts","./src/modules/iit/iitlayout.tsx","./src/modules/iit/index.tsx","./src/modules/iit/api/iitprojectapi.ts","./src/modules/iit/components/ruletemplatebuilder.tsx","./src/modules/iit/components/variablepicker.tsx","./src/modules/iit/components/reports/completenesstable.tsx","./src/modules/iit/components/reports/deviationlogtable.tsx","./src/modules/iit/components/reports/eligibilitytable.tsx","./src/modules/iit/components/reports/equerylogtable.tsx","./src/modules/iit/config/projectdetailpage.tsx","./src/modules/iit/config/projectlistpage.tsx","./src/modules/iit/context/iitprojectcontext.tsx","./src/modules/iit/pages/aichatpage.tsx","./src/modules/iit/pages/aistreampage.tsx","./src/modules/iit/pages/dashboardpage.tsx","./src/modules/iit/pages/equerypage.tsx","./src/modules/iit/pages/reportspage.tsx","./src/modules/iit/pages/variablelistpage.tsx","./src/modules/iit/types/iitproject.ts","./src/modules/iit/types/qccockpit.ts","./src/modules/legacy/legacysystempage.tsx","./src/modules/legacy/researchmanagement.tsx","./src/modules/legacy/statisticaltools.tsx","./src/modules/pkb/index.tsx","./src/modules/pkb/api/knowledgebaseapi.ts","./src/modules/pkb/components/createkbdialog.tsx","./src/modules/pkb/components/documentlist.tsx","./src/modules/pkb/components/documentupload.tsx","./src/modules/pkb/components/editkbdialog.tsx","./src/modules/pkb/components/knowledgebaselist.tsx","./src/modules/pkb/components/workspace/batchmode.tsx","./src/modules/pkb/components/workspace/batchmodecomplete.tsx","./src/modules/pkb/components/workspace/deepreadmode.tsx","./src/modules/pkb/components/workspace/fulltextmode.tsx","./src/modules/pkb/components/workspace/workmodeselector.tsx","./src/modules/pkb/hooks/useworkmode.ts","./src/modules/pkb/pages/dashboardpage.tsx","./src/modules/pkb/pages/knowledgepage.tsx","./src/modules/pkb/pages/workspacepage.tsx","./src/modules/pkb/stores/useknowledgebasestore.ts","./src/modules/pkb/types/workspace.ts","./src/modules/rvw/index.tsx","./src/modules/rvw/api/index.ts","./src/modules/rvw/components/agentmodal.tsx","./src/modules/rvw/components/batchtoolbar.tsx","./src/modules/rvw/components/clinicalreport.tsx","./src/modules/rvw/components/editorialreport.tsx","./src/modules/rvw/components/filterchips.tsx","./src/modules/rvw/components/forensicsreport.tsx","./src/modules/rvw/components/header.tsx","./src/modules/rvw/components/methodologyreport.tsx","./src/modules/rvw/components/reportdetail.tsx","./src/modules/rvw/components/scorering.tsx","./src/modules/rvw/components/sidebar.tsx","./src/modules/rvw/components/taskdetail.tsx","./src/modules/rvw/components/tasktable.tsx","./src/modules/rvw/components/index.ts","./src/modules/rvw/pages/dashboard.tsx","./src/modules/rvw/types/index.ts","./src/modules/ssa/ssaworkspace.tsx","./src/modules/ssa/index.tsx","./src/modules/ssa/components/agentcodepanel.tsx","./src/modules/ssa/components/askusercard.tsx","./src/modules/ssa/components/clarificationcard.tsx","./src/modules/ssa/components/conclusionreport.tsx","./src/modules/ssa/components/datacontextcard.tsx","./src/modules/ssa/components/dataprofilecard.tsx","./src/modules/ssa/components/dataprofilemodal.tsx","./src/modules/ssa/components/dynamicreport.tsx","./src/modules/ssa/components/modetoggle.tsx","./src/modules/ssa/components/ssachatpane.tsx","./src/modules/ssa/components/ssacodemodal.tsx","./src/modules/ssa/components/ssasidebar.tsx","./src/modules/ssa/components/ssatoast.tsx","./src/modules/ssa/components/ssaworkspacepane.tsx","./src/modules/ssa/components/stepprogresscard.tsx","./src/modules/ssa/components/typewriter.tsx","./src/modules/ssa/components/variabledetailpanel.tsx","./src/modules/ssa/components/variabledictionarypanel.tsx","./src/modules/ssa/components/workflowtimeline.tsx","./src/modules/ssa/components/index.ts","./src/modules/ssa/hooks/index.ts","./src/modules/ssa/hooks/useanalysis.ts","./src/modules/ssa/hooks/useartifactparser.ts","./src/modules/ssa/hooks/usessachat.ts","./src/modules/ssa/hooks/useworkflow.ts","./src/modules/ssa/stores/ssastore.ts","./src/modules/ssa/types/index.ts","./src/modules/ssa/utils/exportblockstoword.ts","./src/modules/st/index.tsx","./src/pages/homepage.tsx","./src/pages/loginpage.tsx","./src/pages/admin/activitylogspage.tsx","./src/pages/admin/admindashboard.tsx","./src/pages/admin/prompteditorpage.tsx","./src/pages/admin/promptlistpage.tsx","./src/pages/admin/api/activityapi.ts","./src/pages/admin/api/promptapi.ts","./src/pages/admin/components/prompteditor.tsx","./src/pages/admin/tenants/tenantdetailpage.tsx","./src/pages/admin/tenants/tenantlistpage.tsx","./src/pages/admin/tenants/api/tenantapi.ts","./src/pages/org/orgdashboard.tsx","./src/pages/user/profilepage.tsx","./src/shared/components/placeholder.tsx","./src/shared/components/index.ts","./src/shared/components/chat/aistreamchat.tsx","./src/shared/components/chat/chatcontainer.tsx","./src/shared/components/chat/codeblockrenderer.tsx","./src/shared/components/chat/conversationlist.tsx","./src/shared/components/chat/messagerenderer.tsx","./src/shared/components/chat/thinkingblock.tsx","./src/shared/components/chat/index.ts","./src/shared/components/chat/types.ts","./src/shared/components/chat/hooks/index.ts","./src/shared/components/chat/hooks/useaistream.ts","./src/shared/components/chat/hooks/useconversations.ts","./src/shared/components/layout/resizablesplitpane.tsx","./src/shared/components/layout/index.ts"],"errors":true,"version":"5.9.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/common/api/axios.ts","./src/framework/auth/authcontext.tsx","./src/framework/auth/api.ts","./src/framework/auth/index.ts","./src/framework/auth/moduleapi.ts","./src/framework/auth/sessionguard.ts","./src/framework/auth/types.ts","./src/framework/auth/useauthheartbeat.ts","./src/framework/layout/adminlayout.tsx","./src/framework/layout/mainlayout.tsx","./src/framework/layout/orglayout.tsx","./src/framework/layout/tenantportallayout.tsx","./src/framework/layout/topnavigation.tsx","./src/framework/modules/errorboundary.tsx","./src/framework/modules/moduleerrorfallback.tsx","./src/framework/modules/moduleregistry.ts","./src/framework/modules/types.ts","./src/framework/permission/permissioncontext.tsx","./src/framework/permission/index.ts","./src/framework/permission/types.ts","./src/framework/permission/usepermission.ts","./src/framework/router/permissiondenied.tsx","./src/framework/router/routeguard.tsx","./src/framework/router/index.ts","./src/framework/tenant/index.ts","./src/framework/tenant/usetenantobserver.ts","./src/framework/tenant/usetenantstore.ts","./src/modules/admin/index.tsx","./src/modules/admin/api/iitprojectapi.ts","./src/modules/admin/api/statsapi.ts","./src/modules/admin/api/systemkbapi.ts","./src/modules/admin/api/userapi.ts","./src/modules/admin/components/assigntenantmodal.tsx","./src/modules/admin/components/importusermodal.tsx","./src/modules/admin/components/modulepermissionmodal.tsx","./src/modules/admin/components/qc-cockpit/qcdetaildrawer.tsx","./src/modules/admin/components/qc-cockpit/qcreportdrawer.tsx","./src/modules/admin/components/qc-cockpit/qcstatcards.tsx","./src/modules/admin/components/qc-cockpit/riskheatmap.tsx","./src/modules/admin/components/qc-cockpit/index.ts","./src/modules/admin/pages/iitmembermanagepage.tsx","./src/modules/admin/pages/iitprojectdetailpage.tsx","./src/modules/admin/pages/iitprojectlistpage.tsx","./src/modules/admin/pages/iitqccockpitpage.tsx","./src/modules/admin/pages/statsdashboardpage.tsx","./src/modules/admin/pages/systemkbdetailpage.tsx","./src/modules/admin/pages/systemkblistpage.tsx","./src/modules/admin/pages/userdetailpage.tsx","./src/modules/admin/pages/userformpage.tsx","./src/modules/admin/pages/userlistpage.tsx","./src/modules/admin/types/iitproject.ts","./src/modules/admin/types/qccockpit.ts","./src/modules/admin/types/systemkb.ts","./src/modules/admin/types/user.ts","./src/modules/aia/constants.ts","./src/modules/aia/index.tsx","./src/modules/aia/types.ts","./src/modules/aia/components/agentcard.tsx","./src/modules/aia/components/agenthub.tsx","./src/modules/aia/components/chatworkspace.tsx","./src/modules/aia/components/index.ts","./src/modules/aia/protocol-agent/protocolagentpage.tsx","./src/modules/aia/protocol-agent/index.ts","./src/modules/aia/protocol-agent/types.ts","./src/modules/aia/protocol-agent/components/actioncard.tsx","./src/modules/aia/protocol-agent/components/chatarea.tsx","./src/modules/aia/protocol-agent/components/documentpanel.tsx","./src/modules/aia/protocol-agent/components/markdowncontent.tsx","./src/modules/aia/protocol-agent/components/reflexionmessage.tsx","./src/modules/aia/protocol-agent/components/resizablesplitpane.tsx","./src/modules/aia/protocol-agent/components/stagecard.tsx","./src/modules/aia/protocol-agent/components/stageeditmodal.tsx","./src/modules/aia/protocol-agent/components/statepanel.tsx","./src/modules/aia/protocol-agent/components/syncbutton.tsx","./src/modules/aia/protocol-agent/components/viewswitcher.tsx","./src/modules/aia/protocol-agent/components/index.ts","./src/modules/aia/protocol-agent/hooks/index.ts","./src/modules/aia/protocol-agent/hooks/useprotocolcontext.ts","./src/modules/aia/protocol-agent/hooks/useprotocolconversations.ts","./src/modules/aia/protocol-agent/hooks/useprotocolgeneration.ts","./src/modules/asl/index.tsx","./src/modules/asl/api/index.ts","./src/modules/asl/components/asllayout.tsx","./src/modules/asl/components/conclusiontag.tsx","./src/modules/asl/components/detailreviewdrawer.tsx","./src/modules/asl/components/fulltextdetaildrawer.tsx","./src/modules/asl/components/judgmentbadge.tsx","./src/modules/asl/components/charting/baselinetable.tsx","./src/modules/asl/components/charting/datasourceselector.tsx","./src/modules/asl/components/charting/prismaflowdiagram.tsx","./src/modules/asl/components/deep-research/agentterminal.tsx","./src/modules/asl/components/deep-research/landingview.tsx","./src/modules/asl/components/deep-research/resultsview.tsx","./src/modules/asl/components/deep-research/setuppanel.tsx","./src/modules/asl/components/deep-research/strategyconfirm.tsx","./src/modules/asl/components/extraction/extractiondrawer.tsx","./src/modules/asl/components/extraction/extractionstatusbadge.tsx","./src/modules/asl/components/extraction/fieldgroup.tsx","./src/modules/asl/components/extraction/processingterminal.tsx","./src/modules/asl/components/extraction/quoteblock.tsx","./src/modules/asl/components/meta/resultspanel.tsx","./src/modules/asl/hooks/usedeepresearchtask.ts","./src/modules/asl/hooks/useextractionlogs.ts","./src/modules/asl/hooks/usefulltextresults.ts","./src/modules/asl/hooks/usefulltexttask.ts","./src/modules/asl/hooks/usescreeningresults.ts","./src/modules/asl/hooks/usescreeningtask.ts","./src/modules/asl/pages/deepresearchpage.tsx","./src/modules/asl/pages/extractionpage.tsx","./src/modules/asl/pages/extractionprogress.tsx","./src/modules/asl/pages/extractionsetup.tsx","./src/modules/asl/pages/extractionworkbench.tsx","./src/modules/asl/pages/fulltextprogress.tsx","./src/modules/asl/pages/fulltextresults.tsx","./src/modules/asl/pages/fulltextsettings.tsx","./src/modules/asl/pages/fulltextworkbench.tsx","./src/modules/asl/pages/metaanalysisengine.tsx","./src/modules/asl/pages/researchsearch.tsx","./src/modules/asl/pages/srchartgenerator.tsx","./src/modules/asl/pages/screeningresults.tsx","./src/modules/asl/pages/screeningworkbench.tsx","./src/modules/asl/pages/titlescreeningsettings.tsx","./src/modules/asl/types/deepresearch.ts","./src/modules/asl/types/index.ts","./src/modules/asl/utils/chartingexcelutils.ts","./src/modules/asl/utils/excelexport.ts","./src/modules/asl/utils/excelutils.ts","./src/modules/asl/utils/metaexcelutils.ts","./src/modules/asl/utils/tabletransform.ts","./src/modules/dc/index.tsx","./src/modules/dc/api/toolb.ts","./src/modules/dc/api/toolc.ts","./src/modules/dc/components/assetlibrary.tsx","./src/modules/dc/components/tasklist.tsx","./src/modules/dc/components/toolcard.tsx","./src/modules/dc/hooks/useassets.ts","./src/modules/dc/hooks/userecenttasks.ts","./src/modules/dc/pages/portal.tsx","./src/modules/dc/pages/tool-b/step1upload.tsx","./src/modules/dc/pages/tool-b/step2schema.tsx","./src/modules/dc/pages/tool-b/step3processing.tsx","./src/modules/dc/pages/tool-b/step4verify.tsx","./src/modules/dc/pages/tool-b/step5result.tsx","./src/modules/dc/pages/tool-b/index.tsx","./src/modules/dc/pages/tool-b/components/stepindicator.tsx","./src/modules/dc/pages/tool-c/index.tsx","./src/modules/dc/pages/tool-c/components/binningdialog.tsx","./src/modules/dc/pages/tool-c/components/binningdialog_improved.tsx","./src/modules/dc/pages/tool-c/components/computedialog.tsx","./src/modules/dc/pages/tool-c/components/conditionaldialog.tsx","./src/modules/dc/pages/tool-c/components/datagrid.tsx","./src/modules/dc/pages/tool-c/components/dropnadialog.tsx","./src/modules/dc/pages/tool-c/components/filterdialog.tsx","./src/modules/dc/pages/tool-c/components/header.tsx","./src/modules/dc/pages/tool-c/components/metrictimepanel.tsx","./src/modules/dc/pages/tool-c/components/missingvaluedialog.tsx","./src/modules/dc/pages/tool-c/components/multimetricpanel.tsx","./src/modules/dc/pages/tool-c/components/pivotdialog.tsx","./src/modules/dc/pages/tool-c/components/pivotpanel.tsx","./src/modules/dc/pages/tool-c/components/recodedialog.tsx","./src/modules/dc/pages/tool-c/components/sidebar.tsx","./src/modules/dc/pages/tool-c/components/streamingsteps.tsx","./src/modules/dc/pages/tool-c/components/toolbar.tsx","./src/modules/dc/pages/tool-c/components/transformdialog.tsx","./src/modules/dc/pages/tool-c/components/unpivotpanel.tsx","./src/modules/dc/pages/tool-c/hooks/usesessionstatus.ts","./src/modules/dc/pages/tool-c/types/index.ts","./src/modules/dc/types/portal.ts","./src/modules/iit/iitlayout.tsx","./src/modules/iit/index.tsx","./src/modules/iit/api/iitprojectapi.ts","./src/modules/iit/components/ruletemplatebuilder.tsx","./src/modules/iit/components/variablepicker.tsx","./src/modules/iit/components/reports/completenesstable.tsx","./src/modules/iit/components/reports/deviationlogtable.tsx","./src/modules/iit/components/reports/eligibilitytable.tsx","./src/modules/iit/components/reports/equerylogtable.tsx","./src/modules/iit/config/projectdetailpage.tsx","./src/modules/iit/config/projectlistpage.tsx","./src/modules/iit/context/iitprojectcontext.tsx","./src/modules/iit/pages/aichatpage.tsx","./src/modules/iit/pages/aistreampage.tsx","./src/modules/iit/pages/dashboardpage.tsx","./src/modules/iit/pages/equerypage.tsx","./src/modules/iit/pages/reportspage.tsx","./src/modules/iit/pages/variablelistpage.tsx","./src/modules/iit/types/iitproject.ts","./src/modules/iit/types/qccockpit.ts","./src/modules/legacy/legacysystempage.tsx","./src/modules/legacy/researchmanagement.tsx","./src/modules/legacy/statisticaltools.tsx","./src/modules/pkb/index.tsx","./src/modules/pkb/api/knowledgebaseapi.ts","./src/modules/pkb/components/createkbdialog.tsx","./src/modules/pkb/components/documentlist.tsx","./src/modules/pkb/components/documentupload.tsx","./src/modules/pkb/components/editkbdialog.tsx","./src/modules/pkb/components/knowledgebaselist.tsx","./src/modules/pkb/components/workspace/batchmode.tsx","./src/modules/pkb/components/workspace/batchmodecomplete.tsx","./src/modules/pkb/components/workspace/deepreadmode.tsx","./src/modules/pkb/components/workspace/fulltextmode.tsx","./src/modules/pkb/components/workspace/workmodeselector.tsx","./src/modules/pkb/hooks/useworkmode.ts","./src/modules/pkb/pages/dashboardpage.tsx","./src/modules/pkb/pages/knowledgepage.tsx","./src/modules/pkb/pages/workspacepage.tsx","./src/modules/pkb/stores/useknowledgebasestore.ts","./src/modules/pkb/types/workspace.ts","./src/modules/rvw/tenantmodule.tsx","./src/modules/rvw/index.tsx","./src/modules/rvw/api/index.ts","./src/modules/rvw/components/agentmodal.tsx","./src/modules/rvw/components/batchtoolbar.tsx","./src/modules/rvw/components/clinicalreport.tsx","./src/modules/rvw/components/editorialreport.tsx","./src/modules/rvw/components/filterchips.tsx","./src/modules/rvw/components/forensicsreport.tsx","./src/modules/rvw/components/header.tsx","./src/modules/rvw/components/methodologyreport.tsx","./src/modules/rvw/components/reportdetail.tsx","./src/modules/rvw/components/scorering.tsx","./src/modules/rvw/components/sidebar.tsx","./src/modules/rvw/components/taskdetail.tsx","./src/modules/rvw/components/tasktable.tsx","./src/modules/rvw/components/index.ts","./src/modules/rvw/pages/dashboard.tsx","./src/modules/rvw/pages/tenantdashboard.tsx","./src/modules/rvw/pages/tenanttaskdetail.tsx","./src/modules/rvw/types/index.ts","./src/modules/ssa/ssaworkspace.tsx","./src/modules/ssa/index.tsx","./src/modules/ssa/components/agentcodepanel.tsx","./src/modules/ssa/components/askusercard.tsx","./src/modules/ssa/components/clarificationcard.tsx","./src/modules/ssa/components/conclusionreport.tsx","./src/modules/ssa/components/datacontextcard.tsx","./src/modules/ssa/components/dataprofilecard.tsx","./src/modules/ssa/components/dataprofilemodal.tsx","./src/modules/ssa/components/dynamicreport.tsx","./src/modules/ssa/components/modetoggle.tsx","./src/modules/ssa/components/ssachatpane.tsx","./src/modules/ssa/components/ssacodemodal.tsx","./src/modules/ssa/components/ssasidebar.tsx","./src/modules/ssa/components/ssatoast.tsx","./src/modules/ssa/components/ssaworkspacepane.tsx","./src/modules/ssa/components/stepprogresscard.tsx","./src/modules/ssa/components/typewriter.tsx","./src/modules/ssa/components/variabledetailpanel.tsx","./src/modules/ssa/components/variabledictionarypanel.tsx","./src/modules/ssa/components/workflowtimeline.tsx","./src/modules/ssa/components/index.ts","./src/modules/ssa/hooks/index.ts","./src/modules/ssa/hooks/useanalysis.ts","./src/modules/ssa/hooks/useartifactparser.ts","./src/modules/ssa/hooks/usessachat.ts","./src/modules/ssa/hooks/useworkflow.ts","./src/modules/ssa/stores/ssastore.ts","./src/modules/ssa/types/index.ts","./src/modules/ssa/utils/exportblockstoword.ts","./src/modules/ssa/utils/id.ts","./src/modules/st/index.tsx","./src/pages/homepage.tsx","./src/pages/loginpage.tsx","./src/pages/tenantloginpage.tsx","./src/pages/admin/activitylogspage.tsx","./src/pages/admin/admindashboard.tsx","./src/pages/admin/prompteditorpage.tsx","./src/pages/admin/promptlistpage.tsx","./src/pages/admin/api/activityapi.ts","./src/pages/admin/api/promptapi.ts","./src/pages/admin/components/prompteditor.tsx","./src/pages/admin/journal-configs/journalconfigdetailpage.tsx","./src/pages/admin/journal-configs/journalconfiglistpage.tsx","./src/pages/admin/journal-configs/api/journalconfigapi.ts","./src/pages/admin/tenants/tenantdetailpage.tsx","./src/pages/admin/tenants/tenantlistpage.tsx","./src/pages/admin/tenants/api/tenantapi.ts","./src/pages/org/orgdashboard.tsx","./src/pages/tenant/tenantprofilepage.tsx","./src/pages/user/profilepage.tsx","./src/shared/components/placeholder.tsx","./src/shared/components/index.ts","./src/shared/components/chat/aistreamchat.tsx","./src/shared/components/chat/chatcontainer.tsx","./src/shared/components/chat/codeblockrenderer.tsx","./src/shared/components/chat/conversationlist.tsx","./src/shared/components/chat/messagerenderer.tsx","./src/shared/components/chat/thinkingblock.tsx","./src/shared/components/chat/index.ts","./src/shared/components/chat/types.ts","./src/shared/components/chat/hooks/index.ts","./src/shared/components/chat/hooks/useaistream.ts","./src/shared/components/chat/hooks/useconversations.ts","./src/shared/components/layout/resizablesplitpane.tsx","./src/shared/components/layout/index.ts"],"errors":true,"version":"5.9.3"} \ No newline at end of file