diff --git a/apps/web/vue3/index_ml.html b/apps/web/vue3/index_ml.html
index 8936729a6..ef98e8dcd 100644
--- a/apps/web/vue3/index_ml.html
+++ b/apps/web/vue3/index_ml.html
@@ -999,6 +999,10 @@
padding: 10px;
}
+ .execution-log-item.assistant .log-content.structured-content {
+ font-size: 13px;
+ }
+
.execution-log-item.assistant .log-content.short-content {
font-size: 13px;
}
@@ -1366,11 +1370,13 @@
-
+
{{ t('拓展分析阶段') }}
+ {{ t('运行结果') }}
{{ t('已写阶段') }}
@@ -1431,7 +1437,6 @@
{{ t('运行已发布') }}
- {{ t('运行结果') }}
@@ -1780,6 +1785,20 @@
📂 Select
+
+
+
拖入固定结构拓展包
+
要求包含 raw_code/run.sh 与 raw_test_data/,系统会自动创建 Job、解析环境并开始 lightweight upload。
+
+
+ {{ lightweightExtensionTestStatus }}
+ 选择文件夹
+
+
+
@@ -7825,6 +7844,7 @@ RNA-seq KEGG/fgsea 会优先使用物种级 KEGG gene set;没有物种级缓
});
const activeRnaseqScopeId = ref('');
const activeGenotypeScopeId = ref('');
+ const lightweightExtensionTestStatus = ref('拖入固定结构文件夹,系统将自动创建 Job、解析环境并开始 upload。');
// 对话消息
const messages = ref([]);
@@ -19304,6 +19324,10 @@ compare: [[flesh, peel]]`;
};
const extensionFileKey = (file) => `${file?.name || ''}::${file?.size || 0}::${file?.lastModified || 0}`;
+ const extensionUploadPath = (file) => {
+ const relative = String(file?.lightweightRelativePath || '').trim().replace(/^\/+/, '');
+ return relative || String(file?.name || '').trim();
+ };
const appendExtensionFiles = (target, files) => {
const current = Array.isArray(target) ? target : [];
const merged = [...current];
@@ -19329,6 +19353,53 @@ compare: [[flesh, peel]]`;
if (selected.length) ElMessage.success(`已选择 ${selected.length} 个测试数据文件`);
event.target.value = '';
};
+ const handleLightweightExtensionFolderDrop = (event) => {
+ const droppedFiles = Array.from(event?.dataTransfer?.files || []);
+ if (!droppedFiles.length) {
+ ElMessage.warning('未检测到可用文件');
+ return;
+ }
+
+ const codeExtPattern = /\.(py|r|sh|bash|pl|js|ts|java|c|cc|cpp|go|rs|jl|rb|m|smk|nf|ipynb)$/i;
+ const dataExtPattern = /\.(fastq|fq|gz|fa|fasta|fna|faa|pep|cds|gff|gff3|gtf|csv|tsv|txt|xlsx|xls|vcf|sam|bam)$/i;
+
+ const normalized = droppedFiles.map((file) => {
+ const relativePath = file.webkitRelativePath || file.name;
+ if (!file.lightweightRelativePath) {
+ Object.defineProperty(file, 'lightweightRelativePath', {
+ value: relativePath,
+ configurable: true
+ });
+ }
+ return file;
+ });
+
+ const codeFiles = [];
+ const dataFiles = [];
+ normalized.forEach((file) => {
+ const filePath = extensionUploadPath(file).toLowerCase();
+ if (codeExtPattern.test(filePath)) {
+ codeFiles.push(file);
+ } else if (dataExtPattern.test(filePath)) {
+ dataFiles.push(file);
+ } else {
+ codeFiles.push(file);
+ }
+ });
+
+ extensionBuild.codeFiles = appendExtensionFiles(extensionBuild.codeFiles, codeFiles);
+ extensionBuild.dataFiles = appendExtensionFiles(extensionBuild.dataFiles, dataFiles);
+
+ if (!extensionBuild.displayName) {
+ const firstPath = normalized[0]?.webkitRelativePath || normalized[0]?.name || '';
+ const folder = firstPath.includes('/') ? firstPath.split('/')[0] : '';
+ if (folder) {
+ extensionBuild.displayName = folder.replace(/[^a-zA-Z0-9_-]+/g, '_');
+ }
+ }
+
+ ElMessage.success(`已拖入 ${normalized.length} 个文件,已自动填充拓展包`);
+ };
const removeExtensionFile = (kind, file) => {
const key = extensionFileKey(file);
if (kind === 'code') {
@@ -19363,7 +19434,7 @@ compare: [[flesh, peel]]`;
const value = String(path || '');
if (!value.startsWith('/api/extension-stages') && !value.startsWith('/api/extension-stage-runs')) return value;
const host = window.location.hostname || '127.0.0.1';
- return `${window.location.protocol}//${host}:7880${value}`;
+ return `${window.location.protocol}//${host}:${window.location.port}${value}`;
};
const extensionApiFetch = async (path, options = {}) => fetch(extensionApiUrl(path), options);
const pushExtensionStatus = (message, type = 'info', showDialog = false) => {
@@ -19724,8 +19795,8 @@ compare: [[flesh, peel]]`;
form.append('extension_id', extensionId);
if (hasCommand) form.append('original_command', extensionBuild.command);
form.append('operation_token', token);
- extensionBuild.codeFiles.forEach(file => form.append('raw_code_files', file));
- extensionBuild.dataFiles.forEach(file => form.append('raw_data_files', file));
+ extensionBuild.codeFiles.forEach(file => form.append('raw_code_files', file, extensionUploadPath(file)));
+ extensionBuild.dataFiles.forEach(file => form.append('raw_data_files', file, extensionUploadPath(file)));
if (hasDataPaths) form.append('raw_data_paths', extensionBuild.dataPaths);
const response = await extensionApiFetch('/api/lightweight-extension-stages/upload', { method: 'POST', body: form });
if (!response.ok) throw new Error(await extensionApiError(response, '原始包可复现检查提交失败'));
@@ -22289,7 +22360,7 @@ compare: [[flesh, peel]]`;
addMessage('user', userInput);
ragHistory.value.push({ role: 'user', content: userInput, time: new Date().toLocaleTimeString() });
- // 调用当前 bbav5 后端 RAG 代理,随页面端口走同源地址(例如 7880)
+ // 调用当前 bbav5 后端 RAG 代理,随页面端口走同源地址
const data = await fetchJsonWithTimeout('/api/agent/rag', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -23523,6 +23594,25 @@ compare: [[flesh, peel]]`;
runExtensionStageFromIntent,
extensionOutputIcon,
openExtensionOutput,
+ // lightWeightExtensionTest
+ lightweightExtensionTestStatus,
+ handleLightweightExtensionFolderDrop: (event) => window.LightWeightExtensionTest && window.LightWeightExtensionTest.handleDrop({
+ jobInfo,
+ extensionBuild,
+ createNewJob,
+ appendExtensionFiles,
+ buildLightweightExtensionStage,
+ lightweightExtensionTestStatus,
+ }, event),
+ handleLightweightExtensionFolderPick: (event) => window.LightWeightExtensionTest && window.LightWeightExtensionTest.handlePick({
+ jobInfo,
+ extensionBuild,
+ createNewJob,
+ appendExtensionFiles,
+ buildLightweightExtensionStage,
+ lightweightExtensionTestStatus,
+ }, event),
+ openLightweightExtensionFolderPicker: () => window.LightWeightExtensionTest && window.LightWeightExtensionTest.openPicker(),
methodologyPaperId,
methodologyFamily,
methodologyBatchDir,
@@ -23761,6 +23851,9 @@ compare: [[flesh, peel]]`;
document.getElementById('app').style.display = 'block';
console.log('App mounted successfully');
+
+
+