feat(ssa): Complete V11 UI development and frontend-backend integration - Pixel-perfect V11 UI, multi-task support, Word export, input overlay fix, code cleanup. MVP Phase 1 core 95% complete.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -30,10 +30,14 @@ export class RClientService {
|
||||
async execute(sessionId: string, plan: any, session: any): Promise<any> {
|
||||
const startTime = Date.now();
|
||||
|
||||
// 从 OSS Key 或 session title 提取原始文件名
|
||||
const originalFilename = this.extractFilename(session);
|
||||
|
||||
// 构建请求体(使用统一存储服务)
|
||||
const requestBody = {
|
||||
data_source: await this.buildDataSource(session),
|
||||
params: plan.params,
|
||||
original_filename: originalFilename,
|
||||
guardrails: plan.guardrails || {
|
||||
check_normality: true,
|
||||
auto_fix: true
|
||||
@@ -127,6 +131,28 @@ export class RClientService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 session 提取原始文件名
|
||||
*/
|
||||
private extractFilename(session: any): string {
|
||||
// 优先从 title 获取(上传时会设置为文件名)
|
||||
if (session.title) {
|
||||
// 如果 title 不包含扩展名,添加 .csv
|
||||
if (!session.title.match(/\.(csv|xlsx|xls)$/i)) {
|
||||
return `${session.title}.csv`;
|
||||
}
|
||||
return session.title;
|
||||
}
|
||||
|
||||
// 从 OSS Key 提取
|
||||
if (session.dataOssKey) {
|
||||
const parts = session.dataOssKey.split('/');
|
||||
return parts[parts.length - 1] || 'data.csv';
|
||||
}
|
||||
|
||||
return 'data.csv';
|
||||
}
|
||||
|
||||
async healthCheck(): Promise<boolean> {
|
||||
try {
|
||||
const res = await this.client.get('/health');
|
||||
|
||||
@@ -155,8 +155,9 @@ export default async function analysisRoutes(app: FastifyInstance) {
|
||||
});
|
||||
|
||||
// 返回前端期望的 AnalysisPlan 格式(camelCase)
|
||||
const planId = `plan_${Date.now()}`;
|
||||
const mockPlan = {
|
||||
id: `plan_${Date.now()}`,
|
||||
id: planId,
|
||||
toolCode: 'ST_T_TEST_IND',
|
||||
toolName: '独立样本 T 检验',
|
||||
description: `根据您的数据特征和分析需求"${query}",推荐使用独立样本 T 检验,比较 ${groupVar} 分组下 ${valueVar} 的差异。`,
|
||||
@@ -171,7 +172,27 @@ export default async function analysisRoutes(app: FastifyInstance) {
|
||||
confidence: 0.85
|
||||
};
|
||||
|
||||
logger.info('[SSA:Analysis] Plan generated', { sessionId: id, query, toolCode: mockPlan.toolCode, params: mockPlan.parameters });
|
||||
// 保存用户查询消息
|
||||
await prisma.ssaMessage.create({
|
||||
data: {
|
||||
sessionId: id,
|
||||
role: 'user',
|
||||
contentType: 'text',
|
||||
content: { text: query }
|
||||
}
|
||||
});
|
||||
|
||||
// 保存 plan 消息(支持多任务历史)
|
||||
await prisma.ssaMessage.create({
|
||||
data: {
|
||||
sessionId: id,
|
||||
role: 'assistant',
|
||||
contentType: 'plan',
|
||||
content: { ...mockPlan, query } // 保存原始查询以便后续关联
|
||||
}
|
||||
});
|
||||
|
||||
logger.info('[SSA:Analysis] Plan generated and saved', { sessionId: id, planId, query, toolCode: mockPlan.toolCode, params: mockPlan.parameters });
|
||||
|
||||
return reply.send(mockPlan);
|
||||
});
|
||||
@@ -265,7 +286,34 @@ export default async function analysisRoutes(app: FastifyInstance) {
|
||||
}
|
||||
});
|
||||
|
||||
return reply.send(result);
|
||||
// 转换 R 服务返回的 snake_case 为前端期望的 camelCase
|
||||
const transformedResult = {
|
||||
status: result.status,
|
||||
message: result.message,
|
||||
warnings: result.warnings,
|
||||
results: result.results ? {
|
||||
method: result.results.method,
|
||||
statistic: result.results.statistic,
|
||||
df: result.results.df,
|
||||
pValue: result.results.p_value,
|
||||
pValueFmt: result.results.p_value_fmt,
|
||||
confInt: result.results.conf_int,
|
||||
estimate: result.results.estimate,
|
||||
groupStats: result.results.group_stats,
|
||||
effectSize: result.results.effect_size,
|
||||
} : null,
|
||||
plots: result.plots?.map((p: any) =>
|
||||
typeof p === 'string'
|
||||
? { type: 'chart', title: '统计图表', imageBase64: p }
|
||||
: p
|
||||
),
|
||||
traceLog: result.trace_log,
|
||||
reproducibleCode: result.reproducible_code,
|
||||
guardrailResults: result.guardrail_results,
|
||||
executionMs: result.executionMs,
|
||||
};
|
||||
|
||||
return reply.send(transformedResult);
|
||||
|
||||
} catch (error: any) {
|
||||
logger.error('[SSA:Analysis] Execute failed', {
|
||||
|
||||
Reference in New Issue
Block a user