feat: OpenMesh 基础平台与 MD/PDF 转换技能
- 后端: coworker 智能体框架, WS API, 文件上传, 附件处理 - 前端: Open WebUI, 文件全量走 upload API (含 MD/TXT/JSON 等文本类) - 技能: md-to-office (pandoc + wkhtmltopdf) - 修复: 上传文件路径丢失, Agent 搜索浪费, 输出文件跑到 uploads/ - 打包: PyInstaller one-dir, 预打包 pandoc/wkhtmltopdf/chromium
This commit is contained in:
85
surfaces/gui/e2e-live/helpers.ts
Normal file
85
surfaces/gui/e2e-live/helpers.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { readFileSync, readdirSync, statSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { join } from "path";
|
||||
import type { Page } from "@playwright/test";
|
||||
|
||||
// Shared helpers for the LIVE smoke specs (real backend + real model). Kept out of the hermetic
|
||||
// suite (separate dir/config); see e2e/README.md.
|
||||
|
||||
export const BACKEND = "http://127.0.0.1:8765";
|
||||
|
||||
function sidecarToken(): string {
|
||||
const state =
|
||||
process.env.COWORKER_STATE_DIR ||
|
||||
(process.platform === "win32"
|
||||
? join(process.env.APPDATA || homedir(), "coworker")
|
||||
: join(homedir(), ".config", "coworker"));
|
||||
try {
|
||||
return readFileSync(join(state, "sidecar-8765.token"), "utf8").trim();
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetch from the live sidecar with its per-launch authentication token. */
|
||||
export function backendFetch(path: string, init: RequestInit = {}): Promise<Response> {
|
||||
const headers = new Headers(init.headers);
|
||||
const token = sidecarToken();
|
||||
if (token) headers.set("X-OpenWorker-Token", token);
|
||||
return fetch(`${BACKEND}${path}`, { ...init, headers });
|
||||
}
|
||||
|
||||
/** The expanded scratch base if the backend is up and a model is ready — else null (→ skip). */
|
||||
export async function scratchBaseIfReady(): Promise<string | null> {
|
||||
try {
|
||||
const res = await backendFetch("/v1/settings");
|
||||
const s = await res.json();
|
||||
if (res.ok && s.model_ready) {
|
||||
return String(s.scratch_base || "~/OpenWorker").replace(/^~(?=\/|$)/, homedir());
|
||||
}
|
||||
} catch {
|
||||
/* backend unreachable */
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Newest `name` file across the per-session scratch dirs (each live session gets its own). */
|
||||
export function newestFile(scratchBase: string, name: string): string | null {
|
||||
let best: { path: string; mtime: number } | null = null;
|
||||
let dirs: string[];
|
||||
try {
|
||||
dirs = readdirSync(scratchBase);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
for (const d of dirs) {
|
||||
const f = join(scratchBase, d, name);
|
||||
try {
|
||||
const st = statSync(f);
|
||||
if (!best || st.mtimeMs > best.mtime) best = { path: f, mtime: st.mtimeMs };
|
||||
} catch {
|
||||
/* not in this session dir */
|
||||
}
|
||||
}
|
||||
return best?.path ?? null;
|
||||
}
|
||||
|
||||
/** Open a fresh Cowork session via the split button's persona menu. */
|
||||
export async function startCoworkSession(page: Page) {
|
||||
await page.goto("/");
|
||||
await page.getByRole("button", { name: "Choose a persona" }).click();
|
||||
await page.getByText(/Produce a deliverable/).click();
|
||||
}
|
||||
|
||||
/** Switch the composer's permission mode from the default "Ask for approval". */
|
||||
export async function selectMode(page: Page, label: "Full access" | "Plan" | "Discuss") {
|
||||
await page.getByText("Ask for approval").click();
|
||||
await page.getByText(label, { exact: true }).click();
|
||||
}
|
||||
|
||||
/** Type a task and send it. */
|
||||
export async function sendTask(page: Page, text: string) {
|
||||
await page.getByPlaceholder(/Ask the coworker/).fill(text);
|
||||
// exact — "Send" is a substring of the Inbox control's "Sending approvals…" title when unattended.
|
||||
await page.getByRole("button", { name: "Send", exact: true }).click();
|
||||
}
|
||||
Reference in New Issue
Block a user