feat: update desktop workflows and app center
This commit is contained in:
41
electron/utils/local-preferences.ts
Normal file
41
electron/utils/local-preferences.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Application-level local preferences.
|
||||
*
|
||||
* Renderer localStorage is origin-scoped, so dev server port changes can make
|
||||
* user preferences appear to disappear. Keep business-local preferences here
|
||||
* as the stable source shared by all renderer origins.
|
||||
*/
|
||||
|
||||
export interface AppLocalPreferences {
|
||||
quickTasks?: unknown[];
|
||||
channelAccountRemarks?: Record<string, string>;
|
||||
desktopUserName?: string;
|
||||
workspaceDisplayName?: string;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let localPreferencesStoreInstance: any = null;
|
||||
|
||||
async function getLocalPreferencesStore() {
|
||||
if (!localPreferencesStoreInstance) {
|
||||
const Store = (await import('electron-store')).default;
|
||||
localPreferencesStoreInstance = new Store<AppLocalPreferences>({
|
||||
name: 'local-preferences',
|
||||
});
|
||||
}
|
||||
return localPreferencesStoreInstance;
|
||||
}
|
||||
|
||||
export async function getAllLocalPreferences(): Promise<AppLocalPreferences> {
|
||||
const store = await getLocalPreferencesStore();
|
||||
return { ...(store.store ?? {}) };
|
||||
}
|
||||
|
||||
export async function patchLocalPreferences(patch: AppLocalPreferences): Promise<AppLocalPreferences> {
|
||||
const store = await getLocalPreferencesStore();
|
||||
for (const [key, value] of Object.entries(patch) as Array<[keyof AppLocalPreferences, unknown]>) {
|
||||
if (value === undefined) continue;
|
||||
store.set(key, value);
|
||||
}
|
||||
return { ...(store.store ?? {}) };
|
||||
}
|
||||
@@ -12,6 +12,11 @@ import {
|
||||
import { logger } from './logger';
|
||||
import { getOpenClawConfigDir, needsWinShell, quoteForCmd } from './paths';
|
||||
import { buildDotnetEnv, resolveDotnetExecutable } from './dotnet-runtime';
|
||||
import {
|
||||
buildPlaywrightRuntimeEnv,
|
||||
ensureYinianPlaywrightRuntimeDirs,
|
||||
resolveYinianPlaywrightBrowsersPath,
|
||||
} from './playwright-runtime';
|
||||
|
||||
export type OfficeRuntimeStatus = 'ok' | 'warning' | 'error';
|
||||
|
||||
@@ -45,6 +50,15 @@ export interface OfficeSkillRuntimeDiagnostics {
|
||||
available: boolean;
|
||||
version: string | null;
|
||||
};
|
||||
playwright: {
|
||||
moduleInstalled: boolean;
|
||||
moduleName: string | null;
|
||||
moduleResolvedPath: string | null;
|
||||
browsersPath: string;
|
||||
chromiumExecutablePath: string | null;
|
||||
chromiumInstalled: boolean;
|
||||
error: string | null;
|
||||
};
|
||||
checks: OfficeRuntimeCheck[];
|
||||
}
|
||||
|
||||
@@ -74,6 +88,8 @@ const NODE_MODULES = [
|
||||
'sharp',
|
||||
] as const;
|
||||
|
||||
const PLAYWRIGHT_MODULE_CANDIDATES = ['playwright', 'playwright-core'] as const;
|
||||
|
||||
const PYTHON_INSTALL_TIMEOUT_MS = 10 * 60_000;
|
||||
const COMMAND_TIMEOUT_MS = 20_000;
|
||||
|
||||
@@ -194,6 +210,66 @@ function resolveNodeModule(name: string): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function checkPlaywrightRuntime(): OfficeSkillRuntimeDiagnostics['playwright'] {
|
||||
const runtimeEnv = buildPlaywrightRuntimeEnv();
|
||||
const browsersPath = runtimeEnv.PLAYWRIGHT_BROWSERS_PATH || resolveYinianPlaywrightBrowsersPath();
|
||||
const previousBrowsersPath = process.env.PLAYWRIGHT_BROWSERS_PATH;
|
||||
process.env.PLAYWRIGHT_BROWSERS_PATH = browsersPath;
|
||||
|
||||
try {
|
||||
for (const req of buildRequireCandidates()) {
|
||||
for (const moduleName of PLAYWRIGHT_MODULE_CANDIDATES) {
|
||||
try {
|
||||
const moduleResolvedPath = req.resolve(moduleName);
|
||||
const loaded = req(moduleName) as {
|
||||
chromium?: {
|
||||
executablePath?: () => string;
|
||||
};
|
||||
};
|
||||
const chromiumExecutablePath = loaded.chromium?.executablePath?.() ?? null;
|
||||
return {
|
||||
moduleInstalled: true,
|
||||
moduleName,
|
||||
moduleResolvedPath,
|
||||
browsersPath,
|
||||
chromiumExecutablePath,
|
||||
chromiumInstalled: Boolean(chromiumExecutablePath && existsSync(chromiumExecutablePath)),
|
||||
error: null,
|
||||
};
|
||||
} catch {
|
||||
// Try the next module/root.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
moduleInstalled: false,
|
||||
moduleName: null,
|
||||
moduleResolvedPath: null,
|
||||
browsersPath,
|
||||
chromiumExecutablePath: null,
|
||||
chromiumInstalled: false,
|
||||
error: 'Playwright runtime module not found',
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
moduleInstalled: false,
|
||||
moduleName: null,
|
||||
moduleResolvedPath: null,
|
||||
browsersPath,
|
||||
chromiumExecutablePath: null,
|
||||
chromiumInstalled: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
} finally {
|
||||
if (previousBrowsersPath === undefined) {
|
||||
delete process.env.PLAYWRIGHT_BROWSERS_PATH;
|
||||
} else {
|
||||
process.env.PLAYWRIGHT_BROWSERS_PATH = previousBrowsersPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function checkPythonPackage(pythonPath: string | null, importName: string): Promise<boolean> {
|
||||
if (!pythonPath) return false;
|
||||
const result = await runCommand(pythonPath, ['-c', `__import__(${JSON.stringify(importName)})`]);
|
||||
@@ -245,6 +321,7 @@ export async function buildOfficeSkillRuntimeDiagnostics(options: {
|
||||
|
||||
if (repairAttempted) {
|
||||
await setupManagedPython();
|
||||
ensureYinianPlaywrightRuntimeDirs();
|
||||
}
|
||||
|
||||
let pythonPath = await findManagedPythonPath();
|
||||
@@ -281,6 +358,7 @@ export async function buildOfficeSkillRuntimeDiagnostics(options: {
|
||||
};
|
||||
});
|
||||
const dotnet = await checkDotnet();
|
||||
const playwright = checkPlaywrightRuntime();
|
||||
|
||||
const missingPythonPackages = packageResults.filter((pkg) => !pkg.installed);
|
||||
const missingNodeModules = nodeModules.filter((mod) => !mod.installed);
|
||||
@@ -315,6 +393,16 @@ export async function buildOfficeSkillRuntimeDiagnostics(options: {
|
||||
? `.NET ${dotnet.version}`
|
||||
: '未找到 .NET,docx 高级 OpenXML 生成可能受限',
|
||||
},
|
||||
{
|
||||
id: 'playwright-chromium',
|
||||
label: '幻灯片浏览器预览',
|
||||
status: playwright.moduleInstalled && playwright.chromiumInstalled ? 'ok' : 'warning',
|
||||
detail: playwright.moduleInstalled
|
||||
? (playwright.chromiumInstalled
|
||||
? `Chromium 可用:${playwright.chromiumExecutablePath}`
|
||||
: `未找到 Chromium 二进制文件;浏览器预览应跳过,不要在任务中运行 npx playwright install chromium。缓存路径:${playwright.browsersPath}`)
|
||||
: `未找到 Playwright 运行时;浏览器预览应跳过。${playwright.error ?? ''}`,
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
@@ -329,6 +417,7 @@ export async function buildOfficeSkillRuntimeDiagnostics(options: {
|
||||
modules: nodeModules,
|
||||
},
|
||||
dotnet,
|
||||
playwright,
|
||||
checks,
|
||||
};
|
||||
}
|
||||
|
||||
74
electron/utils/playwright-runtime.ts
Normal file
74
electron/utils/playwright-runtime.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { existsSync, mkdirSync, readdirSync, symlinkSync } from 'node:fs';
|
||||
import { homedir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { getOpenClawConfigDir } from './paths';
|
||||
|
||||
const PLAYWRIGHT_BROWSERS_DIR = 'ms-playwright';
|
||||
const PLAYWRIGHT_LOCKS_DIR = 'locks';
|
||||
const PLAYWRIGHT_COMPLETE_MARKER = 'INSTALLATION_COMPLETE';
|
||||
|
||||
function resolveDefaultPlaywrightBrowsersPath(): string | null {
|
||||
if (process.platform === 'darwin') {
|
||||
return path.join(homedir(), 'Library', 'Caches', 'ms-playwright');
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
const localAppData = process.env.LOCALAPPDATA?.trim();
|
||||
return localAppData ? path.join(localAppData, 'ms-playwright') : null;
|
||||
}
|
||||
return path.join(homedir(), '.cache', 'ms-playwright');
|
||||
}
|
||||
|
||||
export function resolveYinianPlaywrightBrowsersPath(): string {
|
||||
return path.join(getOpenClawConfigDir(), 'runtime', PLAYWRIGHT_BROWSERS_DIR);
|
||||
}
|
||||
|
||||
export function resolveYinianPlaywrightInstallLockPath(): string {
|
||||
return path.join(getOpenClawConfigDir(), 'runtime', PLAYWRIGHT_LOCKS_DIR, 'playwright-chromium-install.lock');
|
||||
}
|
||||
|
||||
function mirrorCompletedDefaultBrowsers(targetRoot: string): void {
|
||||
const defaultRoot = resolveDefaultPlaywrightBrowsersPath();
|
||||
if (!defaultRoot || defaultRoot === targetRoot || !existsSync(defaultRoot)) return;
|
||||
|
||||
let entries;
|
||||
try {
|
||||
entries = readdirSync(defaultRoot, { withFileTypes: true });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory() || !entry.name.startsWith('chromium')) continue;
|
||||
|
||||
const sourceDir = path.join(defaultRoot, entry.name);
|
||||
if (!existsSync(path.join(sourceDir, PLAYWRIGHT_COMPLETE_MARKER))) continue;
|
||||
|
||||
const targetDir = path.join(targetRoot, entry.name);
|
||||
if (existsSync(targetDir)) continue;
|
||||
|
||||
try {
|
||||
symlinkSync(sourceDir, targetDir, process.platform === 'win32' ? 'junction' : 'dir');
|
||||
} catch {
|
||||
// Best effort. A missing browser remains a warning-level diagnostic.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function ensureYinianPlaywrightRuntimeDirs(): void {
|
||||
const browsersPath = resolveYinianPlaywrightBrowsersPath();
|
||||
mkdirSync(browsersPath, { recursive: true });
|
||||
mkdirSync(path.dirname(resolveYinianPlaywrightInstallLockPath()), { recursive: true });
|
||||
mirrorCompletedDefaultBrowsers(browsersPath);
|
||||
}
|
||||
|
||||
export function buildPlaywrightRuntimeEnv(
|
||||
baseEnv: Record<string, string | undefined> = process.env as Record<string, string | undefined>,
|
||||
): Record<string, string> {
|
||||
const explicitBrowsersPath = baseEnv.PLAYWRIGHT_BROWSERS_PATH?.trim();
|
||||
return {
|
||||
PLAYWRIGHT_BROWSERS_PATH: explicitBrowsersPath || resolveYinianPlaywrightBrowsersPath(),
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: baseEnv.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD ?? '1',
|
||||
YINIAN_PLAYWRIGHT_INSTALL_LOCK_PATH: baseEnv.YINIAN_PLAYWRIGHT_INSTALL_LOCK_PATH
|
||||
?? resolveYinianPlaywrightInstallLockPath(),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user