File size: 8,786 Bytes
494c9e4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | /**
* 分析流程模块
* 负责处理 Analyze 和 Analyze & Upload 流程
*/
import * as d3 from 'd3';
import type { TextAnalysisAPI, AnalyzeResponse } from '../api/GLTR_API';
import type { TextInputController } from '../controllers/textInputController';
import type { DemoManager } from '../ui/demoManager';
import type { AppStateManager } from './appStateManager';
import type { VisualizationUpdater } from './visualizationUpdater';
import type { DemoBusinessLogic } from './demoBusinessLogic';
import type { ServerStorage } from '../storage/demoStorage';
import type { GLTR_Text_Box } from '../vis/GLTR_Text_Box';
import { showAlertDialog } from '../ui/dialog';
import { handleServerDemoSave } from '../controllers/serverDemoController';
// 国际化
import { tr } from '../lang/i18n-lite';
import { playAnalysisCompleteSound } from './soundNotification';
/**
* 分析进度回调
*/
export type AnalyzeProgressCallback = (step: number, totalSteps: number, stage: string, percentage?: number) => void;
/**
* 分析流程依赖
*/
export interface AnalyzeFlowDependencies {
api: TextAnalysisAPI;
textInputController: TextInputController;
demoManager: DemoManager | null;
appStateManager: AppStateManager;
visualizationUpdater: VisualizationUpdater;
demoBusinessLogic: DemoBusinessLogic;
serverStorage: ServerStorage;
lmf: GLTR_Text_Box;
modelName: string;
enableDemo: boolean;
showToast: (message: string, type: 'success' | 'error') => void;
updateFileNameDisplay: (filename: string | null) => void;
}
/**
* Analyze & Upload 任务
*/
export interface AnalyzeUploadTask {
name: string;
path: string;
text: string;
}
/**
* 分析流程管理器
*/
export class AnalyzeFlowManager {
private deps: AnalyzeFlowDependencies;
constructor(deps: AnalyzeFlowDependencies) {
this.deps = deps;
}
/**
* 更新 demoManager 引用
*/
setDemoManager(demoManager: DemoManager | null): void {
this.deps.demoManager = demoManager;
}
/**
* 更新分析进度显示
*/
updateAnalyzeProgress(step: number, totalSteps: number, stage: string, percentage?: number): void {
// 根据是否有百分比来决定显示格式
const progressText = percentage !== undefined && percentage !== null
? `Step ${step}/${totalSteps}:\t ${stage} ${percentage}%`
: `Step ${step}/${totalSteps}:\t ${stage}`;
d3.select('#analyze_progress')
.text(progressText)
.style('display', 'inline-block');
}
/**
* 滚动到顶部
*/
private scrollToTop(): void {
requestAnimationFrame(() => {
const rightPanel = document.querySelector('.right_panel') as HTMLElement;
if (rightPanel) {
rightPanel.scrollTop = 0;
}
});
}
/**
* 执行单次 Analyze
*
* @param text 要分析的文本
* @param enableAnimation 是否启用动画
* @returns 分析结果
*/
async runAnalyze(text: string, enableAnimation: boolean = true): Promise<AnalyzeResponse | null> {
// 仅当文本与输入框不同时写入(如 Analyze & Upload 弹窗中的文本),保证输入框与分析内容一致。
// 相同则跳过,避免触发 input 导致 clearDataOnTextChange,从而保留语义直方图(仅语义时点击 Analyze 的 bug 修复)
const currentText = this.deps.textInputController.getTextValue();
if (currentText !== text) {
this.deps.textInputController.setTextValue(text);
}
// 分析前滚动到顶部
this.scrollToTop();
// 重置为新分析状态(数据来源为null,保存标志为false,清除文件名)
this.deps.appStateManager.updateState({
dataSource: null,
isSavedToLocal: false,
isSavedToServer: false,
currentFileName: null
});
this.deps.appStateManager.setIsAnalyzing(true);
this.deps.appStateManager.setGlobalLoading(true);
this.deps.demoManager?.highlightDemo(null);
// 清除URL中的demo参数(手动分析时)
this.deps.demoBusinessLogic.clearDemoUrlParam();
// 清除文件名显示(手动分析时,与远程demo行为一致)
this.deps.updateFileNameDisplay(null);
// 立即显示文本内容
d3.select('#all_result').style('opacity', 1).style('display', null);
this.deps.lmf.setTextOnly(text);
this.deps.visualizationUpdater.updateHistogramVisibilityForPending('infoDensity', text);
try {
const data = await this.deps.api.analyze(
this.deps.modelName,
text,
null,
true, // 启用流式响应
(step: number, totalSteps: number, stage: string, percentage?: number) => {
// 更新UI进度显示
this.updateAnalyzeProgress(step, totalSteps, stage, percentage);
}
);
this.deps.visualizationUpdater.updateFromRequest(data, !enableAnimation, { enableSave: true });
// 分析完成,播放提示音
playAnalysisCompleteSound();
return data;
} catch (error) {
console.error('Analyze failed:', error);
this.deps.appStateManager.setIsAnalyzing(false);
this.deps.appStateManager.setGlobalLoading(false);
this.deps.appStateManager.updateState({ hasValidData: false });
this.deps.visualizationUpdater.rerenderHistograms();
const message = error instanceof Error ? error.message : tr('Analysis failed');
showAlertDialog(tr('Error'), `${tr('Analysis failed')}: ${message}`);
return null;
}
}
/**
* 执行 Analyze -> Upload 串行流程
*
* @param task 任务信息
*/
async runAnalyzeAndUpload(task: AnalyzeUploadTask): Promise<void> {
const textValue = task.text || '';
// 执行分析
const analyzeResult = await this.runAnalyze(textValue, true);
if (!analyzeResult) {
// 分析失败,已经在 runAnalyze 中处理了错误
return;
}
// 分析成功,执行上传
try {
await handleServerDemoSave({
api: this.deps.api,
currentData: this.deps.visualizationUpdater.getCurrentData(),
rawApiResponse: this.deps.visualizationUpdater.getRawApiResponse(),
textFieldValue: textValue,
enableDemo: this.deps.enableDemo,
demoManager: this.deps.demoManager,
presetSaveInfo: {
name: task.name,
path: task.path
},
serverStorage: this.deps.serverStorage,
showSuccessToast: false, // 由调用者自行处理成功提示
onSaveStart: () => {
this.deps.appStateManager.updateState({ isSaving: true });
},
onSaveSuccess: () => {
this.deps.appStateManager.updateState({
isSaving: false,
isSavedToServer: true
});
this.deps.showToast(tr('Demo "{name}" analyzed and uploaded successfully!').replace('{name}', task.name), 'success');
},
onSaveComplete: () => {
this.deps.appStateManager.updateState({ isSavedToServer: true });
},
onSaveError: () => {
// 保存失败后,恢复按钮状态(数据来源仍然是server)
this.deps.appStateManager.updateState({ isSaving: false });
this.deps.appStateManager.updateButtonStates();
},
setGlobalLoading: (loading: boolean) => this.deps.appStateManager.setGlobalLoading(loading),
showToast: this.deps.showToast
});
} catch (error) {
// handleServerDemoSave 已处理提示,这里确保所有状态恢复
this.deps.appStateManager.setGlobalLoading(false);
// 保存失败,但数据来源仍然是server(因为分析已经成功)
this.deps.appStateManager.updateState({ isSaving: false });
this.deps.appStateManager.updateButtonStates();
console.error('Upload failed in runAnalyzeAndUpload:', error);
}
}
}
// 重新导出 buildFolderOptions 以便向后兼容
export { buildFolderOptions } from './demoPathUtils';
|