feat(core): finalize rvw stability updates and pending module changes
Summary: - Harden RVW prompt protocol handling and methodology review flow with 20-checkpoint coverage, divide-and-conquer execution, and timeout tuning - Update RVW frontend methodology report rendering to show real structured outputs and grouped checkpoint sections - Include pending backend/frontend updates across IIT admin, SSA, extraction forensics, and related integration files - Sync system and RVW status documentation, deployment checklist, and RVW architecture/plan docs Validation: - Verified lint diagnostics for touched RVW backend/frontend files show no new errors - Kept backup dump files and local test artifacts untracked Made-with: Cursor
This commit is contained in:
@@ -278,12 +278,12 @@ export interface PlatformUserOption {
|
||||
role?: string;
|
||||
}
|
||||
|
||||
export async function searchPlatformUsers(search: string): Promise<PlatformUserOption[]> {
|
||||
export async function searchPlatformUsers(projectId: string, search: string): Promise<PlatformUserOption[]> {
|
||||
if (!search || search.length < 2) return [];
|
||||
const response = await apiClient.get('/api/admin/users', {
|
||||
params: { search, pageSize: 20 },
|
||||
const response = await apiClient.get(`${BASE_URL}/${projectId}/users/search`, {
|
||||
params: { search, limit: 20 },
|
||||
});
|
||||
const items = response.data.data?.data || response.data.data?.items || response.data.data?.list || [];
|
||||
const items = response.data.data || [];
|
||||
return items.map((u: any) => ({
|
||||
id: u.id,
|
||||
name: u.name || '',
|
||||
|
||||
@@ -105,7 +105,11 @@ const IitMemberManagePage: React.FC = () => {
|
||||
setUserSearching(true);
|
||||
searchTimerRef.current = setTimeout(async () => {
|
||||
try {
|
||||
const users = await iitProjectApi.searchPlatformUsers(value);
|
||||
if (!selectedProjectId) {
|
||||
setUserSearchOptions([]);
|
||||
return;
|
||||
}
|
||||
const users = await iitProjectApi.searchPlatformUsers(selectedProjectId, value);
|
||||
setUserSearchOptions(users);
|
||||
} catch {
|
||||
setUserSearchOptions([]);
|
||||
|
||||
@@ -951,7 +951,7 @@ const UserMappingTab: React.FC<UserMappingTabProps> = ({ projectId }) => {
|
||||
setUserSearching(true);
|
||||
searchTimerRef.current = setTimeout(async () => {
|
||||
try {
|
||||
const users = await iitProjectApi.searchPlatformUsers(value);
|
||||
const users = await iitProjectApi.searchPlatformUsers(projectId, value);
|
||||
setUserSearchOptions(users);
|
||||
} catch {
|
||||
setUserSearchOptions([]);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 方法学评估报告组件 - 专业版
|
||||
*/
|
||||
import { XCircle, AlertTriangle, CheckCircle, Microscope, Lightbulb, MapPin, TrendingUp } from 'lucide-react';
|
||||
import { XCircle, AlertTriangle, CheckCircle, Microscope } from 'lucide-react';
|
||||
import type { MethodologyReviewResult } from '../types';
|
||||
|
||||
interface MethodologyReportProps {
|
||||
@@ -9,16 +9,32 @@ interface MethodologyReportProps {
|
||||
}
|
||||
|
||||
export default function MethodologyReport({ data }: MethodologyReportProps) {
|
||||
const totalIssues = data.parts.reduce((sum, part) => sum + part.issues.length, 0);
|
||||
const majorIssues = data.parts.reduce((sum, part) => sum + part.issues.filter(i => i.severity === 'major').length, 0);
|
||||
const conclusion = data.conclusion || '未给出';
|
||||
const checkpoints = data.checkpoints || [];
|
||||
const majorCheckpointIssues = checkpoints.filter(cp => cp.status === 'major_issue');
|
||||
const minorCheckpointIssues = checkpoints.filter(cp => cp.status === 'minor_issue');
|
||||
const totalIssues = checkpoints.length > 0
|
||||
? checkpoints.filter(cp => cp.status === 'major_issue' || cp.status === 'minor_issue').length
|
||||
: data.parts.reduce((sum, part) => sum + part.issues.length, 0);
|
||||
const majorIssues = checkpoints.length > 0
|
||||
? checkpoints.filter(cp => cp.status === 'major_issue').length
|
||||
: data.parts.reduce((sum, part) => sum + part.issues.filter(i => i.severity === 'major').length, 0);
|
||||
const minorIssues = totalIssues - majorIssues;
|
||||
const uncoveredCount = checkpoints.filter(cp => cp.status === 'not_mentioned').length;
|
||||
|
||||
const getSeverityStyle = (severity: 'major' | 'minor') => {
|
||||
return severity === 'major'
|
||||
? { icon: <XCircle className="w-4 h-4 text-red-500" />, label: '严重', badge: 'bg-red-100 text-red-700 border-red-200' }
|
||||
: { icon: <AlertTriangle className="w-4 h-4 text-amber-500" />, label: '轻微', badge: 'bg-amber-100 text-amber-700 border-amber-200' };
|
||||
const getCheckpointStyle = (status: 'pass' | 'minor_issue' | 'major_issue' | 'not_mentioned') => {
|
||||
if (status === 'pass') return { text: '通过', cls: 'bg-green-100 text-green-700' };
|
||||
if (status === 'minor_issue') return { text: '一般问题', cls: 'bg-amber-100 text-amber-700' };
|
||||
if (status === 'major_issue') return { text: '严重问题', cls: 'bg-red-100 text-red-700' };
|
||||
return { text: '未覆盖', cls: 'bg-slate-100 text-slate-600' };
|
||||
};
|
||||
|
||||
const checkpointSections = [
|
||||
{ title: '一、科研设计评估 (Scientific Design)', start: 1, end: 9 },
|
||||
{ title: '二、统计学方法描述评估 (Statistical Methodology)', start: 10, end: 14 },
|
||||
{ title: '三、统计分析与结果评估 (Analysis & Results)', start: 15, end: 20 },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-6 fade-in">
|
||||
{/* 总览卡片 */}
|
||||
@@ -32,7 +48,13 @@ export default function MethodologyReport({ data }: MethodologyReportProps) {
|
||||
|
||||
<div className="flex gap-4">
|
||||
<div className="flex items-center gap-2 px-3 py-1.5 bg-slate-50 rounded-lg border border-slate-200">
|
||||
<span className="text-sm text-slate-600">共检测 <span className="font-bold text-slate-800">{data.parts.length}</span> 个方面</span>
|
||||
<span className="text-sm text-slate-600">
|
||||
共覆盖 <span className="font-bold text-slate-800">{checkpoints.length || 20}</span> 个检查点
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 px-3 py-1.5 bg-indigo-50 rounded-lg border border-indigo-100">
|
||||
<span className="text-sm text-indigo-600">审稿结论</span>
|
||||
<span className="text-sm font-semibold text-indigo-700">{conclusion}</span>
|
||||
</div>
|
||||
{totalIssues === 0 ? (
|
||||
<div className="flex items-center gap-2 px-3 py-1.5 bg-green-50 rounded-lg border border-green-100">
|
||||
@@ -59,100 +81,97 @@ export default function MethodologyReport({ data }: MethodologyReportProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 分项详情标题 */}
|
||||
<div className="flex items-center gap-3">
|
||||
<TrendingUp className="w-5 h-5 text-purple-500" />
|
||||
<h3 className="font-bold text-base text-slate-800">分项评估</h3>
|
||||
<span className="text-xs text-slate-400 bg-slate-100 px-2 py-0.5 rounded">共 {data.parts.length} 项</span>
|
||||
</div>
|
||||
{/* 按专家要求的报告结构快速呈现 */}
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-sm font-semibold text-slate-800">1. 总体评价</h4>
|
||||
<p className="text-sm text-slate-600 leading-relaxed">{data.summary || '未生成总体评价。'}</p>
|
||||
</div>
|
||||
|
||||
{/* 分项详情 */}
|
||||
<div className="space-y-4">
|
||||
{data.parts.map((part, partIndex) => (
|
||||
<div key={partIndex} className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
{/* 分项头部 */}
|
||||
<div className={`px-5 py-4 border-b ${part.issues.length === 0 ? 'bg-green-50/50 border-green-100' : 'bg-slate-50 border-slate-100'}`}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
{part.issues.length === 0 ? (
|
||||
<CheckCircle className="w-5 h-5 text-green-500" />
|
||||
) : (
|
||||
<AlertTriangle className="w-5 h-5 text-amber-500" />
|
||||
)}
|
||||
<h4 className="font-semibold text-slate-800">{part.part}</h4>
|
||||
</div>
|
||||
{part.issues.length === 0 ? (
|
||||
<span className="px-2.5 py-1 rounded-md text-xs font-medium bg-green-100 text-green-700 flex items-center gap-1">
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
无问题
|
||||
</span>
|
||||
) : (
|
||||
<span className="px-2.5 py-1 rounded-md text-xs font-medium bg-amber-100 text-amber-700">
|
||||
{part.issues.length} 个问题
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 问题列表 */}
|
||||
{part.issues.length > 0 && (
|
||||
<div className="divide-y divide-gray-50">
|
||||
{part.issues.map((issue, issueIndex) => {
|
||||
const severity = getSeverityStyle(issue.severity);
|
||||
return (
|
||||
<div key={issueIndex} className="px-5 py-4">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="mt-0.5">{severity.icon}</div>
|
||||
<div className="flex-1 space-y-3">
|
||||
{/* 问题标题和严重程度 */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="font-semibold text-slate-800">{issue.type}</span>
|
||||
<span className={`px-2 py-0.5 rounded text-xs font-medium border ${severity.badge}`}>
|
||||
{severity.label}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 问题描述 */}
|
||||
<p className="text-sm text-slate-600 leading-relaxed">{issue.description}</p>
|
||||
|
||||
{/* 位置信息 */}
|
||||
{issue.location && (
|
||||
<div className="flex items-center gap-2 text-xs text-slate-400">
|
||||
<MapPin className="w-3.5 h-3.5" />
|
||||
<span>位置:{issue.location}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 改进建议 */}
|
||||
{issue.suggestion && (
|
||||
<div className="bg-indigo-50/50 rounded-lg p-3 border border-indigo-100">
|
||||
<div className="flex items-start gap-2">
|
||||
<Lightbulb className="w-4 h-4 text-indigo-500 mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="text-xs font-semibold text-indigo-600 mb-1">改进建议</p>
|
||||
<p className="text-sm text-slate-700">{issue.suggestion}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-sm font-semibold text-slate-800">2. 详细问题清单与建议</h4>
|
||||
{totalIssues === 0 ? (
|
||||
<p className="text-sm text-green-700">未发现需要修订的问题。</p>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{majorCheckpointIssues.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs font-semibold text-red-600">严重问题</p>
|
||||
<div className="space-y-2">
|
||||
{majorCheckpointIssues.map((cp) => (
|
||||
<div key={`major-${cp.id}`} className="rounded-lg border border-red-100 bg-red-50/50 p-3">
|
||||
<p className="text-sm font-medium text-slate-800">{cp.id}. {cp.item}</p>
|
||||
<p className="mt-1 text-sm text-slate-600">{cp.finding}</p>
|
||||
{cp.suggestion && <p className="mt-1 text-sm text-slate-700">建议:{cp.suggestion}</p>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 无问题时的简洁显示 */}
|
||||
{part.issues.length === 0 && (
|
||||
<div className="px-5 py-4 text-sm text-green-600 flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
<span>该部分未发现方法学问题,符合规范要求</span>
|
||||
</div>
|
||||
)}
|
||||
{minorCheckpointIssues.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs font-semibold text-amber-600">一般问题</p>
|
||||
<div className="space-y-2">
|
||||
{minorCheckpointIssues.map((cp) => (
|
||||
<div key={`minor-${cp.id}`} className="rounded-lg border border-amber-100 bg-amber-50/50 p-3">
|
||||
<p className="text-sm font-medium text-slate-800">{cp.id}. {cp.item}</p>
|
||||
<p className="mt-1 text-sm text-slate-600">{cp.finding}</p>
|
||||
{cp.suggestion && <p className="mt-1 text-sm text-slate-700">建议:{cp.suggestion}</p>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-sm font-semibold text-slate-800">3. 审稿结论</h4>
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1.5 bg-indigo-50 rounded-lg border border-indigo-100">
|
||||
<span className="text-sm text-indigo-600">当前结论</span>
|
||||
<span className="text-sm font-semibold text-indigo-700">{conclusion}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{checkpoints.length > 0 && (
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div className="p-6 space-y-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-sm font-semibold text-slate-800">20项检查点评估</h4>
|
||||
<span className={`text-xs px-2 py-1 rounded ${uncoveredCount > 0 ? 'bg-amber-100 text-amber-700' : 'bg-green-100 text-green-700'}`}>
|
||||
{uncoveredCount > 0 ? `仍有 ${uncoveredCount} 项未覆盖` : '20项已覆盖'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{checkpointSections.map(section => {
|
||||
const sectionItems = checkpoints.filter(cp => cp.id >= section.start && cp.id <= section.end);
|
||||
return (
|
||||
<div key={section.title} className="space-y-3">
|
||||
<h5 className="text-sm font-semibold text-slate-800">{section.title}</h5>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{sectionItems.map(cp => {
|
||||
const style = getCheckpointStyle(cp.status);
|
||||
return (
|
||||
<div key={cp.id} className="border border-slate-100 rounded-lg p-3 space-y-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className="text-sm font-medium text-slate-800">{cp.id}. {cp.item}</p>
|
||||
<span className={`text-xs px-2 py-0.5 rounded ${style.cls}`}>{style.text}</span>
|
||||
</div>
|
||||
<p className="text-xs text-slate-600">{cp.finding}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,10 +89,20 @@ export interface MethodologyPart {
|
||||
issues: MethodologyIssue[];
|
||||
}
|
||||
|
||||
export interface MethodologyCheckpoint {
|
||||
id: number;
|
||||
item: string;
|
||||
status: 'pass' | 'minor_issue' | 'major_issue' | 'not_mentioned';
|
||||
finding: string;
|
||||
suggestion?: string;
|
||||
}
|
||||
|
||||
// 方法学评估结果
|
||||
export interface MethodologyReviewResult {
|
||||
overall_score: number;
|
||||
summary: string;
|
||||
conclusion?: '直接接收' | '小修' | '大修' | '拒稿';
|
||||
checkpoints?: MethodologyCheckpoint[];
|
||||
parts: MethodologyPart[];
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import { AskUserCard } from './AskUserCard';
|
||||
import type { AskUserResponseData } from './AskUserCard';
|
||||
import { ThinkingBlock } from '@/shared/components/Chat';
|
||||
import type { ClarificationCardData, IntentResult } from '../types';
|
||||
import { safeRandomUUID } from '../utils/id';
|
||||
|
||||
export const SSAChatPane: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -228,7 +229,7 @@ export const SSAChatPane: React.FC = () => {
|
||||
} else {
|
||||
// 没有 session(未上传数据):走老的 generatePlan 流程
|
||||
addMessage({
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'user',
|
||||
content: query,
|
||||
createdAt: new Date().toISOString(),
|
||||
@@ -248,7 +249,7 @@ export const SSAChatPane: React.FC = () => {
|
||||
|
||||
const selectedLabel = Object.values(selections).join(', ');
|
||||
addMessage({
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'user',
|
||||
content: selectedLabel,
|
||||
createdAt: new Date().toISOString(),
|
||||
@@ -263,7 +264,7 @@ export const SSAChatPane: React.FC = () => {
|
||||
|
||||
if (resp.needsClarification && resp.clarificationCards?.length > 0) {
|
||||
addMessage({
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'assistant',
|
||||
content: '还需要确认一下:',
|
||||
createdAt: new Date().toISOString(),
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useCallback, useState } from 'react';
|
||||
import apiClient from '@/common/api/axios';
|
||||
import { getAccessToken } from '@/framework/auth/api';
|
||||
import { useSSAStore } from '../stores/ssaStore';
|
||||
import { safeRandomUUID } from '../utils/id';
|
||||
import type { AnalysisPlan, ExecutionResult, SSAMessage } from '../types';
|
||||
import {
|
||||
Document,
|
||||
@@ -119,7 +120,7 @@ export function useAnalysis(): UseAnalysisReturn {
|
||||
|
||||
try {
|
||||
const userMessage: SSAMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'user',
|
||||
content: query,
|
||||
createdAt: new Date().toISOString(),
|
||||
@@ -146,7 +147,7 @@ export function useAnalysis(): UseAnalysisReturn {
|
||||
} as any);
|
||||
|
||||
const planMessage: SSAMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'assistant',
|
||||
content: `已生成分析方案:${plan.toolName}\n\n${plan.description}`,
|
||||
artifactType: 'sap',
|
||||
@@ -156,7 +157,7 @@ export function useAnalysis(): UseAnalysisReturn {
|
||||
addMessage(planMessage);
|
||||
|
||||
addMessage({
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'assistant',
|
||||
content: '请确认数据映射并执行分析。',
|
||||
artifactType: 'confirm',
|
||||
@@ -221,7 +222,7 @@ export function useAnalysis(): UseAnalysisReturn {
|
||||
});
|
||||
|
||||
addMessage({
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'assistant',
|
||||
content: result.interpretation || '分析完成,请查看右侧结果面板。',
|
||||
artifactType: 'result',
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import { useState, useCallback, useRef } from 'react';
|
||||
import { getAccessToken, isTokenExpired, refreshAccessToken } from '@/framework/auth/api';
|
||||
import { useSSAStore } from '../stores/ssaStore';
|
||||
import { safeRandomUUID } from '../utils/id';
|
||||
import type { AskUserEventData, AskUserResponseData } from '../components/AskUserCard';
|
||||
import type { WorkflowPlan, AgentStepResult } from '../types';
|
||||
|
||||
@@ -243,7 +244,7 @@ export function useSSAChat(): UseSSAChatReturn {
|
||||
|
||||
const isAgentAction = !!finalMetadata?.agentAction || isAgentInlineInstruction;
|
||||
|
||||
const assistantMsgId = crypto.randomUUID();
|
||||
const assistantMsgId = safeRandomUUID();
|
||||
const assistantPlaceholder: ChatMessage = {
|
||||
id: assistantMsgId,
|
||||
role: 'assistant',
|
||||
@@ -257,7 +258,7 @@ export function useSSAChat(): UseSSAChatReturn {
|
||||
setChatMessages(prev => [...prev, assistantPlaceholder]);
|
||||
} else {
|
||||
const userMsg: ChatMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'user',
|
||||
content,
|
||||
status: 'complete',
|
||||
@@ -359,7 +360,7 @@ export function useSSAChat(): UseSSAChatReturn {
|
||||
if (parsed.type === 'agent_planning') {
|
||||
const { pushAgentExecution, setWorkspaceOpen, setActivePane } = useSSAStore.getState();
|
||||
pushAgentExecution({
|
||||
id: parsed.executionId || crypto.randomUUID(),
|
||||
id: parsed.executionId || safeRandomUUID(),
|
||||
sessionId: sessionId,
|
||||
query: content,
|
||||
retryCount: 0,
|
||||
@@ -524,7 +525,7 @@ export function useSSAChat(): UseSSAChatReturn {
|
||||
const execId = curExec?.id;
|
||||
const execQuery = curExec?.query || content;
|
||||
setChatMessages(prev => [...prev, {
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'assistant' as const,
|
||||
content: `✅ 分析完成:${execQuery}`,
|
||||
status: 'complete' as const,
|
||||
@@ -664,7 +665,7 @@ export function useSSAChat(): UseSSAChatReturn {
|
||||
const auditContent = AUDIT_MESSAGES[action];
|
||||
|
||||
// 1. 追加审计轨迹消息(系统风格,不是用户消息)
|
||||
const auditMsgId = crypto.randomUUID();
|
||||
const auditMsgId = safeRandomUUID();
|
||||
setChatMessages(prev => [...prev, {
|
||||
id: auditMsgId,
|
||||
role: 'assistant' as const,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useCallback, useRef } from 'react';
|
||||
import apiClient from '@/common/api/axios';
|
||||
import { getAccessToken } from '@/framework/auth/api';
|
||||
import { useSSAStore } from '../stores/ssaStore';
|
||||
import { safeRandomUUID } from '../utils/id';
|
||||
import type { AnalysisRecord } from '../stores/ssaStore';
|
||||
import type {
|
||||
DataProfile,
|
||||
@@ -72,7 +73,7 @@ export function useWorkflow(): UseWorkflowReturn {
|
||||
const profile: DataProfile = response.data.profile;
|
||||
setDataProfile(profile);
|
||||
addMessage({
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'assistant',
|
||||
content: `数据质量核查完成:${profile.quality_grade}级 (${profile.quality_score}分)`,
|
||||
artifactType: 'profile',
|
||||
@@ -112,7 +113,7 @@ export function useWorkflow(): UseWorkflowReturn {
|
||||
setWorkspaceOpen(true);
|
||||
|
||||
addMessage({
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'assistant',
|
||||
content: `已生成分析方案:${plan.title}\n共 ${plan.total_steps} 个分析步骤`,
|
||||
artifactType: 'sap',
|
||||
@@ -120,7 +121,7 @@ export function useWorkflow(): UseWorkflowReturn {
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
addMessage({
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'assistant',
|
||||
content: '请确认分析计划并开始执行。',
|
||||
artifactType: 'confirm',
|
||||
@@ -183,7 +184,7 @@ export function useWorkflow(): UseWorkflowReturn {
|
||||
setActivePane('sap');
|
||||
setWorkspaceOpen(true);
|
||||
addMessage({
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'assistant',
|
||||
content: `已生成分析方案:${data.plan.title}\n共 ${data.plan.total_steps} 个分析步骤`,
|
||||
artifactType: 'sap',
|
||||
@@ -346,7 +347,7 @@ export function useWorkflow(): UseWorkflowReturn {
|
||||
: String(firstErr);
|
||||
}
|
||||
addMessage({
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'assistant',
|
||||
content: `分析执行失败:${errText}`,
|
||||
artifactType: 'execution',
|
||||
@@ -368,7 +369,7 @@ export function useWorkflow(): UseWorkflowReturn {
|
||||
if (conclusion) {
|
||||
setActivePane('result');
|
||||
addMessage({
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'assistant',
|
||||
content: `分析完成!${conclusion.executive_summary?.slice(0, 100) || '查看右侧结果面板获取详细信息'}`,
|
||||
artifactType: 'result',
|
||||
@@ -377,7 +378,7 @@ export function useWorkflow(): UseWorkflowReturn {
|
||||
});
|
||||
} else if (hasAnySuccess) {
|
||||
addMessage({
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'assistant',
|
||||
content: '分析执行完成!',
|
||||
artifactType: 'result',
|
||||
@@ -386,7 +387,7 @@ export function useWorkflow(): UseWorkflowReturn {
|
||||
});
|
||||
} else {
|
||||
addMessage({
|
||||
id: crypto.randomUUID(),
|
||||
id: safeRandomUUID(),
|
||||
role: 'assistant',
|
||||
content: '分析执行完成,但未生成结论报告。',
|
||||
createdAt: new Date().toISOString(),
|
||||
|
||||
19
frontend-v2/src/modules/ssa/utils/id.ts
Normal file
19
frontend-v2/src/modules/ssa/utils/id.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 兼容性 ID 生成器:
|
||||
* - 安全上下文(HTTPS / localhost)优先使用 crypto.randomUUID()
|
||||
* - 非安全上下文(如内网 HTTP)自动降级,避免 runtime 报错
|
||||
*/
|
||||
export function safeRandomUUID(): string {
|
||||
try {
|
||||
if (typeof globalThis !== 'undefined' && globalThis.crypto?.randomUUID) {
|
||||
return globalThis.crypto.randomUUID();
|
||||
}
|
||||
} catch {
|
||||
// ignore and fallback
|
||||
}
|
||||
|
||||
const time = Date.now().toString(36);
|
||||
const rand = Math.random().toString(36).slice(2, 10);
|
||||
return `ssa-${time}-${rand}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user