200 lines
5.5 KiB
TypeScript
200 lines
5.5 KiB
TypeScript
/**
|
|
* Collabora Online Python 脚本调用工具
|
|
*
|
|
* 职责: 统一封装所有 Python 脚本的调用逻辑和响应处理
|
|
* @encoding UTF-8
|
|
*/
|
|
|
|
export interface ScriptResult {
|
|
success: boolean;
|
|
message: string;
|
|
data?: {
|
|
count?: number;
|
|
action?: string;
|
|
unit?: string;
|
|
[key: string]: unknown;
|
|
};
|
|
timestamp: number;
|
|
}
|
|
|
|
export interface CallScriptOptions {
|
|
timeout?: number;
|
|
verbose?: boolean;
|
|
}
|
|
|
|
interface PostMessageResponse {
|
|
MessageId?: string;
|
|
Values?: {
|
|
commandName?: string;
|
|
success?: boolean;
|
|
result?: string | { type?: string; value?: string };
|
|
};
|
|
}
|
|
|
|
export async function callPythonScript(
|
|
iframeWindow: Window,
|
|
scriptFile: string,
|
|
functionName: string,
|
|
args?: Record<string, unknown>,
|
|
options?: CallScriptOptions
|
|
): Promise<ScriptResult> {
|
|
const timeout = options?.timeout || 10000;
|
|
const verbose = options?.verbose ?? true;
|
|
|
|
if (verbose) {
|
|
console.log('[CallCustomScript] 调用 Python 脚本:', { scriptFile, functionName, args });
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const cleanup = () => {
|
|
clearTimeout(timeoutId);
|
|
window.removeEventListener('message', handleMessage);
|
|
};
|
|
|
|
const timeoutId: NodeJS.Timeout = setTimeout(() => {
|
|
cleanup();
|
|
reject(new Error(`Python 脚本调用超时 (${timeout}ms): ${scriptFile}.${functionName}`));
|
|
}, timeout);
|
|
|
|
const handleMessage = (event: MessageEvent) => {
|
|
try {
|
|
if (event.source !== iframeWindow) return;
|
|
|
|
const data: PostMessageResponse = typeof event.data === 'string'
|
|
? JSON.parse(event.data) : event.data;
|
|
|
|
// 兼容两种 MessageId 格式:
|
|
// - 'CallPythonScript_Resp' (预期格式)
|
|
// - 'CallPythonScript-Result' (bundle.js 实际发送的格式)
|
|
if (data.MessageId !== 'CallPythonScript-Result') return;
|
|
|
|
cleanup();
|
|
|
|
if (verbose) {
|
|
console.log('[CallCustomScript] 收到 Python 脚本响应:', data);
|
|
}
|
|
|
|
const result = parseScriptResponse(data, verbose);
|
|
resolve(result);
|
|
} catch (error) {
|
|
cleanup();
|
|
reject(error);
|
|
}
|
|
};
|
|
|
|
window.addEventListener('message', handleMessage);
|
|
|
|
const message = {
|
|
MessageId: 'CallPythonScript',
|
|
ScriptFile: scriptFile,
|
|
Function: functionName,
|
|
Values: args || {},
|
|
};
|
|
|
|
if (verbose) {
|
|
console.log('[CallCustomScript] 发送 PostMessage:', message);
|
|
}
|
|
|
|
iframeWindow.postMessage(JSON.stringify(message), '*');
|
|
});
|
|
}
|
|
|
|
function parseScriptResponse(data: PostMessageResponse, verbose: boolean): ScriptResult {
|
|
const values = data.Values;
|
|
|
|
if (!values || typeof values !== 'object') {
|
|
throw new Error('响应格式错误: Values 字段缺失或格式不正确');
|
|
}
|
|
|
|
let resultValue: string | undefined;
|
|
|
|
if (typeof values.result === 'string') {
|
|
resultValue = values.result;
|
|
} else if (values.result && typeof values.result === 'object') {
|
|
resultValue = (values.result as { value?: string }).value;
|
|
}
|
|
|
|
if (verbose) {
|
|
console.log('[CallCustomScript] 解析结果:', {
|
|
commandName: values.commandName,
|
|
unoSuccess: values.success,
|
|
resultRaw: values.result,
|
|
resultExtracted: resultValue,
|
|
});
|
|
}
|
|
|
|
if (values.success === false) {
|
|
return {
|
|
success: false,
|
|
message: resultValue || 'UNO 命令执行失败',
|
|
timestamp: Date.now(),
|
|
};
|
|
}
|
|
|
|
if (typeof resultValue !== 'string') {
|
|
throw new Error('Python 脚本返回值格式错误: result 不是字符串');
|
|
}
|
|
|
|
return parseStandardResponse(resultValue);
|
|
}
|
|
|
|
function parseStandardResponse(message: string): ScriptResult {
|
|
const timestamp = Date.now();
|
|
|
|
if (message.includes('Error:') || message.toLowerCase().includes('error')) {
|
|
return { success: false, message, timestamp };
|
|
}
|
|
|
|
const countMatch = message.match(/(\w+)\s+(\d+)\s+(regions?|instances?|items?)/i);
|
|
|
|
if (countMatch) {
|
|
const count = parseInt(countMatch[2], 10);
|
|
return {
|
|
success: true,
|
|
message,
|
|
data: {
|
|
count,
|
|
action: countMatch[1].toLowerCase(),
|
|
unit: countMatch[3].toLowerCase(),
|
|
},
|
|
timestamp,
|
|
};
|
|
}
|
|
|
|
return { success: true, message, timestamp };
|
|
}
|
|
|
|
export async function callPythonScriptBatch(
|
|
iframeWindow: Window,
|
|
calls: Array<{
|
|
scriptFile: string;
|
|
functionName: string;
|
|
args?: Record<string, unknown>;
|
|
}>,
|
|
options?: CallScriptOptions
|
|
): Promise<ScriptResult[]> {
|
|
const results: ScriptResult[] = [];
|
|
|
|
for (const call of calls) {
|
|
try {
|
|
const result = await callPythonScript(
|
|
iframeWindow,
|
|
call.scriptFile,
|
|
call.functionName,
|
|
call.args,
|
|
options
|
|
);
|
|
results.push(result);
|
|
} catch (error) {
|
|
results.push({
|
|
success: false,
|
|
message: error instanceof Error ? error.message : String(error),
|
|
timestamp: Date.now(),
|
|
});
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|