feat: 离线部署模式改进和 SystemVerilog 支持
- 添加离线模式仿真模拟:识别代码生成完成消息后自动模拟仿真流程 - 启用 iverilog SystemVerilog 2012 标准支持(-g2012) - 优化进度条显示逻辑 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -128,6 +128,8 @@ export class DialogSession {
|
|||||||
private currentTextSegment: MessageSegment | null = null;
|
private currentTextSegment: MessageSegment | null = null;
|
||||||
private completeCallback: ((segments: MessageSegment[]) => void) | null =
|
private completeCallback: ((segments: MessageSegment[]) => void) | null =
|
||||||
null; // 保存完成回调,用于 abort 时触发
|
null; // 保存完成回调,用于 abort 时触发
|
||||||
|
private consecutiveToolErrors = 0; // 连续工具错误计数
|
||||||
|
private readonly MAX_CONSECUTIVE_ERRORS = 5; // 最大连续错误次数
|
||||||
|
|
||||||
constructor(extensionPath: string, existingTaskId?: string) {
|
constructor(extensionPath: string, existingTaskId?: string) {
|
||||||
// 支持复用现有 taskId(用于 Plan 模式确认后继续执行)
|
// 支持复用现有 taskId(用于 Plan 模式确认后继续执行)
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import * as vscode from "vscode";
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as os from "os";
|
import * as os from "os";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { readFileContent, readDirectory } from "../utils/readFiles";
|
import { readFileContent, readDirectory, listDirectory } from "../utils/readFiles";
|
||||||
import { createOrOverwriteFile } from "../utils/createFiles";
|
import { createOrOverwriteFile } from "../utils/createFiles";
|
||||||
import { resolveWorkspaceFilePath, showFileDiff } from "../utils/fileDiff";
|
import { resolveWorkspaceFilePath, showFileDiff } from "../utils/fileDiff";
|
||||||
import { changeTracker } from "./changeTracker";
|
import { changeTracker } from "./changeTracker";
|
||||||
@ -126,8 +126,9 @@ export async function executeToolCall(
|
|||||||
|
|
||||||
// 提交成功结果
|
// 提交成功结果
|
||||||
const result = createSuccessResult(callId, resultText);
|
const result = createSuccessResult(callId, resultText);
|
||||||
|
console.log(`[ToolExecutor] 准备提交结果: ${toolName}, callId=${callId}`);
|
||||||
await submitToolResult(result);
|
await submitToolResult(result);
|
||||||
console.log(`[ToolExecutor] 工具执行成功: ${toolName}, callId=${callId}`);
|
console.log(`[ToolExecutor] 结果提交成功: ${toolName}, callId=${callId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : "未知错误";
|
const errorMessage = error instanceof Error ? error.message : "未知错误";
|
||||||
console.error(
|
console.error(
|
||||||
@ -136,8 +137,20 @@ export async function executeToolCall(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 提交错误结果
|
// 提交错误结果
|
||||||
const result = createBusinessErrorResult(callId, errorMessage);
|
try {
|
||||||
await submitToolResult(result);
|
const result = createBusinessErrorResult(callId, errorMessage);
|
||||||
|
console.log(`[ToolExecutor] 准备提交错误结果: ${toolName}, callId=${callId}`);
|
||||||
|
await submitToolResult(result);
|
||||||
|
console.log(`[ToolExecutor] 错误结果提交成功: ${toolName}, callId=${callId}`);
|
||||||
|
} catch (submitError) {
|
||||||
|
console.error(
|
||||||
|
`[ToolExecutor] 提交错误结果失败: ${toolName}, callId=${callId}`,
|
||||||
|
submitError,
|
||||||
|
);
|
||||||
|
throw submitError;
|
||||||
|
}
|
||||||
|
// 重新抛出原始错误,让调用方知道工具执行失败
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,10 +287,8 @@ async function executeFileList(args: FileListArgs): Promise<string> {
|
|||||||
const dirPath = args.path || ".";
|
const dirPath = args.path || ".";
|
||||||
const extensions = args.extension ? [args.extension] : undefined;
|
const extensions = args.extension ? [args.extension] : undefined;
|
||||||
|
|
||||||
const files = await readDirectory(dirPath, extensions);
|
const files = await listDirectory(dirPath, extensions);
|
||||||
const fileList = files.map((f) => f.path).join("\n");
|
return files.join("\n") || "(目录为空)";
|
||||||
|
|
||||||
return fileList || "(目录为空)";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -201,6 +201,33 @@ async function findVerilogFiles(dir: string): Promise<string[]> {
|
|||||||
return verilogFiles;
|
return verilogFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归查找目录下所有 VCD 文件
|
||||||
|
*/
|
||||||
|
async function findVcdFilesRecursive(dir: string): Promise<string[]> {
|
||||||
|
const vcdFiles: string[] = [];
|
||||||
|
|
||||||
|
async function searchDir(currentDir: string) {
|
||||||
|
const dirUri = vscode.Uri.file(currentDir);
|
||||||
|
const entries = await vscode.workspace.fs.readDirectory(dirUri);
|
||||||
|
|
||||||
|
for (const [fileName, fileType] of entries) {
|
||||||
|
const filePath = path.join(currentDir, fileName);
|
||||||
|
|
||||||
|
if (fileType === vscode.FileType.Directory) {
|
||||||
|
if (!fileName.startsWith(".") && fileName !== "node_modules") {
|
||||||
|
await searchDir(filePath);
|
||||||
|
}
|
||||||
|
} else if (fileType === vscode.FileType.File && fileName.endsWith(".vcd")) {
|
||||||
|
vcdFiles.push(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await searchDir(dir);
|
||||||
|
return vcdFiles;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 iverilog 可执行文件路径
|
* 获取 iverilog 可执行文件路径
|
||||||
*/
|
*/
|
||||||
@ -304,8 +331,8 @@ export async function generateVCD(
|
|||||||
IVERILOG_ROOT: path.join(extensionPath, "tools", "iverilog"),
|
IVERILOG_ROOT: path.join(extensionPath, "tools", "iverilog"),
|
||||||
};
|
};
|
||||||
|
|
||||||
// 5. 构建 iverilog 编译参数
|
// 5. 构建 iverilog 编译参数(启用 SystemVerilog 2012 标准)
|
||||||
const compileArgs = ["-o", outputFile, ...projectCheck.allVerilogFiles];
|
const compileArgs = ["-g2012", "-o", outputFile, ...projectCheck.allVerilogFiles];
|
||||||
|
|
||||||
console.log("执行编译命令:", iverilogPath, compileArgs.join(" "));
|
console.log("执行编译命令:", iverilogPath, compileArgs.join(" "));
|
||||||
console.log("IVERILOG_ROOT:", env.IVERILOG_ROOT);
|
console.log("IVERILOG_ROOT:", env.IVERILOG_ROOT);
|
||||||
@ -317,7 +344,10 @@ export async function generateVCD(
|
|||||||
cwd: projectPath,
|
cwd: projectPath,
|
||||||
env: env,
|
env: env,
|
||||||
});
|
});
|
||||||
|
console.log("编译成功,stdout:", compileResult.stdout);
|
||||||
|
console.log("编译成功,stderr:", compileResult.stderr);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
console.error("编译失败:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: `IC Coder编译器编译失败:\n${error.message}`,
|
message: `IC Coder编译器编译失败:\n${error.message}`,
|
||||||
@ -326,9 +356,20 @@ export async function generateVCD(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 6.1 检查 .vvp 文件是否生成
|
||||||
|
const fs = require("fs");
|
||||||
|
if (!fs.existsSync(outputFile)) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `编译未生成 .vvp 文件: ${outputFile}`,
|
||||||
|
stderr: compileResult.stderr,
|
||||||
|
stdout: compileResult.stdout,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
console.log("已生成 .vvp 文件:", outputFile);
|
||||||
|
|
||||||
// 6.5. 删除 shebang 行(修复 Windows 上的 vvp 解析错误)
|
// 6.5. 删除 shebang 行(修复 Windows 上的 vvp 解析错误)
|
||||||
try {
|
try {
|
||||||
const fs = require("fs");
|
|
||||||
const vvpContent = fs.readFileSync(outputFile, "utf8");
|
const vvpContent = fs.readFileSync(outputFile, "utf8");
|
||||||
const lines = vvpContent.split("\n");
|
const lines = vvpContent.split("\n");
|
||||||
|
|
||||||
@ -336,9 +377,62 @@ export async function generateVCD(
|
|||||||
const cleanedContent = lines.slice(1).join("\n");
|
const cleanedContent = lines.slice(1).join("\n");
|
||||||
fs.writeFileSync(outputFile, cleanedContent, "utf8");
|
fs.writeFileSync(outputFile, cleanedContent, "utf8");
|
||||||
console.log("已删除 .vvp 文件的 shebang 行");
|
console.log("已删除 .vvp 文件的 shebang 行");
|
||||||
|
} else {
|
||||||
|
console.log(".vvp 文件无 shebang 行,跳过");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("删除 shebang 失败,继续执行:", error);
|
console.error("删除 shebang 失败:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `处理 .vvp 文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6.6. 检查并创建 VCD 输出目录,并处理 Windows 路径问题
|
||||||
|
try {
|
||||||
|
const tbPath = projectCheck.testbenchFile;
|
||||||
|
if (tbPath && fs.existsSync(tbPath)) {
|
||||||
|
const tbContent = fs.readFileSync(tbPath, "utf8");
|
||||||
|
const dumpfileMatch = tbContent.match(/\$dumpfile\s*\(\s*["']([^"']+)["']\s*\)/);
|
||||||
|
if (dumpfileMatch) {
|
||||||
|
const vcdPath = dumpfileMatch[1];
|
||||||
|
const vcdDir = path.dirname(vcdPath);
|
||||||
|
console.log(`testbench 中的 VCD 路径: ${vcdPath}`);
|
||||||
|
|
||||||
|
if (vcdDir && vcdDir !== "." && vcdDir !== "") {
|
||||||
|
const vcdDirPath = path.join(projectPath, vcdDir);
|
||||||
|
console.log(`检查 VCD 目录: ${vcdDirPath}`);
|
||||||
|
if (!fs.existsSync(vcdDirPath)) {
|
||||||
|
fs.mkdirSync(vcdDirPath, { recursive: true });
|
||||||
|
console.log(`已创建 VCD 输出目录: ${vcdDirPath}`);
|
||||||
|
} else {
|
||||||
|
console.log(`VCD 目录已存在: ${vcdDirPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows 兼容性:修改 .vvp 文件中的路径,将正斜杠替换为反斜杠
|
||||||
|
if (process.platform === "win32" && vcdPath.includes("/")) {
|
||||||
|
const vvpContent = fs.readFileSync(outputFile, "utf8");
|
||||||
|
const windowsPath = vcdPath.replace(/\//g, "\\\\");
|
||||||
|
const modifiedContent = vvpContent.replace(
|
||||||
|
new RegExp(`"${vcdPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`, 'g'),
|
||||||
|
`"${windowsPath}"`
|
||||||
|
);
|
||||||
|
fs.writeFileSync(outputFile, modifiedContent, "utf8");
|
||||||
|
console.log(`已修正 VCD 路径: ${vcdPath} -> ${windowsPath}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("VCD 文件在根目录,无需创建子目录");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("testbench 中未找到 $dumpfile 语句");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("处理 VCD 路径失败:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `处理 VCD 路径失败: ${error instanceof Error ? error.message : "未知错误"}`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. 执行仿真生成 VCD
|
// 7. 执行仿真生成 VCD
|
||||||
@ -351,7 +445,11 @@ export async function generateVCD(
|
|||||||
cwd: projectPath,
|
cwd: projectPath,
|
||||||
env: env,
|
env: env,
|
||||||
});
|
});
|
||||||
|
console.log("仿真执行完成");
|
||||||
|
console.log("仿真 stdout:", simResult.stdout);
|
||||||
|
console.log("仿真 stderr:", simResult.stderr);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
console.error("仿真失败:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: `VVP 仿真失败:\n${error.message}`,
|
message: `VVP 仿真失败:\n${error.message}`,
|
||||||
@ -361,16 +459,38 @@ export async function generateVCD(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 8. 查找生成的 VCD 文件
|
// 8. 查找生成的 VCD 文件
|
||||||
const projectUri = vscode.Uri.file(projectPath);
|
let vcdFile: string | null = null;
|
||||||
const entries = await vscode.workspace.fs.readDirectory(projectUri);
|
|
||||||
const vcdFiles = entries
|
|
||||||
.filter(
|
|
||||||
([fileName, fileType]) =>
|
|
||||||
fileType === vscode.FileType.File && fileName.endsWith(".vcd"),
|
|
||||||
)
|
|
||||||
.map(([fileName]) => fileName);
|
|
||||||
|
|
||||||
if (vcdFiles.length === 0) {
|
// 8.1 尝试从 testbench 中提取 VCD 路径
|
||||||
|
try {
|
||||||
|
const fs = require("fs");
|
||||||
|
const tbPath = projectCheck.testbenchFile;
|
||||||
|
if (tbPath && fs.existsSync(tbPath)) {
|
||||||
|
const tbContent = fs.readFileSync(tbPath, "utf8");
|
||||||
|
const dumpfileMatch = tbContent.match(/\$dumpfile\s*\(\s*["']([^"']+)["']\s*\)/);
|
||||||
|
if (dumpfileMatch) {
|
||||||
|
const vcdPath = dumpfileMatch[1];
|
||||||
|
const absoluteVcdPath = path.join(projectPath, vcdPath);
|
||||||
|
if (fs.existsSync(absoluteVcdPath)) {
|
||||||
|
vcdFile = absoluteVcdPath;
|
||||||
|
console.log(`找到 VCD 文件(从 testbench): ${vcdFile}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("从 testbench 提取 VCD 路径失败:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8.2 如果未找到,递归搜索项目目录
|
||||||
|
if (!vcdFile) {
|
||||||
|
const foundFiles = await findVcdFilesRecursive(projectPath);
|
||||||
|
if (foundFiles.length > 0) {
|
||||||
|
vcdFile = foundFiles[0];
|
||||||
|
console.log(`找到 VCD 文件(递归搜索): ${vcdFile}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vcdFile) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message:
|
message:
|
||||||
@ -379,9 +499,6 @@ export async function generateVCD(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用找到的第一个 VCD 文件
|
|
||||||
const vcdFile = path.join(projectPath, vcdFiles[0]);
|
|
||||||
|
|
||||||
// 9. 清理中间文件
|
// 9. 清理中间文件
|
||||||
try {
|
try {
|
||||||
const outputUri = vscode.Uri.file(outputFile);
|
const outputUri = vscode.Uri.file(outputFile);
|
||||||
@ -594,8 +711,9 @@ export async function generateMultiVCD(
|
|||||||
console.log(`[generateMultiVCD] 仿真模块: ${module.name} (${macroName})`);
|
console.log(`[generateMultiVCD] 仿真模块: ${module.name} (${macroName})`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 编译(带宏定义)
|
// 编译(带宏定义,启用 SystemVerilog 2012 标准)
|
||||||
const compileArgs = [
|
const compileArgs = [
|
||||||
|
"-g2012",
|
||||||
`-D${macroName}`,
|
`-D${macroName}`,
|
||||||
"-o",
|
"-o",
|
||||||
outputFile,
|
outputFile,
|
||||||
|
|||||||
@ -37,6 +37,9 @@ let currentSession: DialogSession | null = null;
|
|||||||
/** 最后一个活跃的 taskId(用于压缩等操作) */
|
/** 最后一个活跃的 taskId(用于压缩等操作) */
|
||||||
let lastTaskId: string | null = null;
|
let lastTaskId: string | null = null;
|
||||||
|
|
||||||
|
/** 离线模式仿真模拟标志(防止重复触发) */
|
||||||
|
let offlineSimulationTriggered = false;
|
||||||
|
|
||||||
async function trackFileChange(
|
async function trackFileChange(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
oldContent: string,
|
oldContent: string,
|
||||||
@ -258,6 +261,8 @@ async function handleUserMessageWithBackend(
|
|||||||
);
|
);
|
||||||
// 保存 taskId 用于后续操作(如压缩)
|
// 保存 taskId 用于后续操作(如压缩)
|
||||||
lastTaskId = currentSession.getTaskId();
|
lastTaskId = currentSession.getTaskId();
|
||||||
|
// 重置离线模式仿真标志(新会话开始)
|
||||||
|
offlineSimulationTriggered = false;
|
||||||
console.log(
|
console.log(
|
||||||
"[MessageHandler] 创建会话: taskId=",
|
"[MessageHandler] 创建会话: taskId=",
|
||||||
lastTaskId,
|
lastTaskId,
|
||||||
@ -286,6 +291,45 @@ async function handleUserMessageWithBackend(
|
|||||||
command: "updateSegments",
|
command: "updateSegments",
|
||||||
segments: segments,
|
segments: segments,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 【离线部署模式】检测代码生成完成消息,模拟仿真流程
|
||||||
|
if (!offlineSimulationTriggered) {
|
||||||
|
const hasCompletionMessage = segments.some(seg =>
|
||||||
|
seg.type === 'text' &&
|
||||||
|
seg.content?.includes('【代码生成完成】') &&
|
||||||
|
seg.content?.includes('语法检查:已通过')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasCompletionMessage) {
|
||||||
|
offlineSimulationTriggered = true;
|
||||||
|
console.log('[离线模式] 检测到代码生成完成,开始模拟仿真流程');
|
||||||
|
|
||||||
|
// 立即点亮 Simulation 阶段
|
||||||
|
panel.webview.postMessage({
|
||||||
|
type: "updateProgress",
|
||||||
|
step: "simulation"
|
||||||
|
});
|
||||||
|
|
||||||
|
// 随机延时 8-13 秒后完成仿真
|
||||||
|
const simulationDelay = 8000 + Math.random() * 5000;
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('[离线模式] 模拟仿真完成,进入 Done 阶段');
|
||||||
|
// Simulation 完成,进入 Done
|
||||||
|
panel.webview.postMessage({
|
||||||
|
type: "updateProgress",
|
||||||
|
step: "done"
|
||||||
|
});
|
||||||
|
|
||||||
|
// 再延时 1 秒完成所有步骤
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('[离线模式] 所有阶段完成');
|
||||||
|
panel.webview.postMessage({
|
||||||
|
type: "completeProgress"
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
}, simulationDelay);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onToolStart: (toolName) => {
|
onToolStart: (toolName) => {
|
||||||
|
|||||||
@ -129,6 +129,62 @@ export async function readDirectory(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列出目录下的文件和文件夹(不读取内容,仅返回路径)
|
||||||
|
*/
|
||||||
|
export async function listDirectory(
|
||||||
|
dirPath: string,
|
||||||
|
extensions?: string[]
|
||||||
|
): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
// 如果是相对路径,转换为绝对路径
|
||||||
|
let absolutePath = dirPath;
|
||||||
|
if (!path.isAbsolute(dirPath)) {
|
||||||
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||||
|
if (workspaceFolders && workspaceFolders.length > 0) {
|
||||||
|
absolutePath = path.join(workspaceFolders[0].uri.fsPath, dirPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dirUri = vscode.Uri.file(absolutePath);
|
||||||
|
|
||||||
|
// 检查目录是否存在
|
||||||
|
try {
|
||||||
|
const stat = await vscode.workspace.fs.stat(dirUri);
|
||||||
|
if (stat.type !== vscode.FileType.Directory) {
|
||||||
|
throw new Error(`路径不是目录: ${absolutePath}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`目录不存在: ${absolutePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取目录内容
|
||||||
|
const entries = await vscode.workspace.fs.readDirectory(dirUri);
|
||||||
|
const results: string[] = [];
|
||||||
|
|
||||||
|
for (const [fileName, fileType] of entries) {
|
||||||
|
if (fileType === vscode.FileType.Directory) {
|
||||||
|
results.push(fileName + '/');
|
||||||
|
} else if (fileType === vscode.FileType.File) {
|
||||||
|
// 扩展名过滤
|
||||||
|
if (extensions && extensions.length > 0) {
|
||||||
|
const ext = path.extname(fileName);
|
||||||
|
// 规范化扩展名(支持 "v" 和 ".v" 两种格式)
|
||||||
|
const normalizedExts = extensions.map(e => e.startsWith('.') ? e : '.' + e);
|
||||||
|
if (!normalizedExts.includes(ext)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results.push(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取文件信息
|
* 获取文件信息
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user