集成 OpenHub 与 WeKnora Page AI
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const ROOT = process.cwd();
|
||||
const BASE_URL = (process.env.MNOTE_WEB_SMOKE_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, "");
|
||||
const OUTPUT_DIR = path.join(ROOT, "tmp", "task787-weknora-default-lightrag-legacy-smoke");
|
||||
const RESULT_PATH = path.join(OUTPUT_DIR, "result.json");
|
||||
const FAILURE_PATH = path.join(OUTPUT_DIR, "failure.json");
|
||||
const REQUEST_TIMEOUT_MS = Number(process.env.MNOTE_TASK787_STATUS_TIMEOUT_MS || 15_000);
|
||||
|
||||
const REQUIRED_STATIC_FILES = [
|
||||
"rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js",
|
||||
"rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js",
|
||||
"scripts/TESTING_REFERENCE.md",
|
||||
];
|
||||
|
||||
const TASK_SMOKE_PATTERN = /^task(?:53|54|76|78)\d.*\.js$/;
|
||||
const SELF_PATH = "scripts/task787-weknora-default-lightrag-legacy-smoke.js";
|
||||
|
||||
function writeJson(filePath, payload) {
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
function listTaskSmokeFiles() {
|
||||
return fs.readdirSync(path.join(ROOT, "scripts"), { withFileTypes: true })
|
||||
.filter((entry) => entry.isFile() && TASK_SMOKE_PATTERN.test(entry.name))
|
||||
.map((entry) => `scripts/${entry.name}`)
|
||||
.sort((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function uniqueSorted(values) {
|
||||
return Array.from(new Set(values)).sort((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function extractWindow(line, start, length) {
|
||||
const from = Math.max(0, start - 90);
|
||||
const to = Math.min(line.length, start + length + 90);
|
||||
return line.slice(from, to).trim();
|
||||
}
|
||||
|
||||
function allowedLegacyContext(contextBlock, occurrenceWindow) {
|
||||
const lowerBlock = contextBlock.toLowerCase();
|
||||
const lowerWindow = occurrenceWindow.toLowerCase();
|
||||
const legacyMarkers = [
|
||||
"legacy",
|
||||
"fallback",
|
||||
"history",
|
||||
"historical",
|
||||
"debug",
|
||||
"compat",
|
||||
"retired",
|
||||
"assertnotcontains",
|
||||
"assertnotincludes",
|
||||
"forbidden",
|
||||
"notcontains",
|
||||
"notincludes",
|
||||
"not contain",
|
||||
"does not include",
|
||||
"no lightrag",
|
||||
"nolightrag",
|
||||
"nodefaultlightrag",
|
||||
"without lightrag",
|
||||
"bypass",
|
||||
"bypasses",
|
||||
"skip",
|
||||
"旧",
|
||||
"历史",
|
||||
"兼容",
|
||||
"调试",
|
||||
"退役",
|
||||
"软归档",
|
||||
"不应包含",
|
||||
"不作为",
|
||||
"不再",
|
||||
"不直接",
|
||||
"不删除",
|
||||
"不走",
|
||||
"禁止",
|
||||
"绕过",
|
||||
"跳过",
|
||||
"移除",
|
||||
"缺少",
|
||||
];
|
||||
return legacyMarkers.some((marker) => lowerWindow.includes(marker) || lowerBlock.includes(marker));
|
||||
}
|
||||
|
||||
function ignoredTechnicalField(line) {
|
||||
return /\blightRag(?:DocId|FilePath|Status)\b/.test(line)
|
||||
|| /lightrag-source-registry\.json/.test(line);
|
||||
}
|
||||
|
||||
function ignoredLegacyAssetName(line) {
|
||||
return /task\d+-lightrag-[\w-]+/.test(line);
|
||||
}
|
||||
|
||||
function ignoredNegativeAssertionName(line) {
|
||||
return /\b(?:uiNoDefault|ingestNo|deleteNo|sectionContextNo|noDefault)LightRag[A-Za-z0-9_]*\b/.test(line);
|
||||
}
|
||||
|
||||
function classifyActiveLightRagContext(contextBlock, occurrenceWindow) {
|
||||
const normalized = `${contextBlock}\n${occurrenceWindow}`.toLowerCase();
|
||||
const reasons = [];
|
||||
const activeDefaultPatterns = [
|
||||
/默认\s*provider/i,
|
||||
/唯一默认/i,
|
||||
/主线\s*provider/i,
|
||||
/default\s*provider/i,
|
||||
/primary\s*provider/i,
|
||||
/mainline\s*provider/i,
|
||||
/only\s*default/i,
|
||||
];
|
||||
if (activeDefaultPatterns.some((pattern) => pattern.test(contextBlock) || pattern.test(occurrenceWindow))) {
|
||||
reasons.push("forbidden_default_or_mainline_provider_copy");
|
||||
}
|
||||
const activeSurfacePatterns = [
|
||||
/资料库/,
|
||||
/知识库/,
|
||||
/问答/,
|
||||
/检索/,
|
||||
/搜索/,
|
||||
/引用/,
|
||||
/入库/,
|
||||
/dashboard/,
|
||||
/health/,
|
||||
/status/,
|
||||
/source[-_ ]?registry/,
|
||||
/search\s*provider/,
|
||||
/references?/,
|
||||
/retrieval/,
|
||||
/smoke/,
|
||||
];
|
||||
if (activeSurfacePatterns.some((pattern) => pattern.test(normalized))) {
|
||||
reasons.push("active_lightrag_surface_without_legacy_marker");
|
||||
}
|
||||
if (reasons.length === 0) {
|
||||
reasons.push("unqualified_lightrag_reference_without_legacy_marker");
|
||||
}
|
||||
return reasons;
|
||||
}
|
||||
|
||||
function scanStaticFiles(files) {
|
||||
const violations = [];
|
||||
const scannedFiles = [];
|
||||
for (const relativePath of files) {
|
||||
const absolutePath = path.join(ROOT, relativePath);
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
violations.push({
|
||||
file: relativePath,
|
||||
line: 0,
|
||||
reason: "static_file_missing",
|
||||
context: "required static/smoke baseline file is missing",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
scannedFiles.push(relativePath);
|
||||
const content = fs.readFileSync(absolutePath, "utf8");
|
||||
const lines = content.split(/\r?\n/);
|
||||
lines.forEach((line, index) => {
|
||||
const matches = Array.from(line.matchAll(/lightrag/gi));
|
||||
for (const match of matches) {
|
||||
if (ignoredTechnicalField(line) || ignoredLegacyAssetName(line) || ignoredNegativeAssertionName(line)) {
|
||||
continue;
|
||||
}
|
||||
const occurrenceWindow = extractWindow(line, match.index || 0, match[0].length);
|
||||
const contextBlock = lines.slice(Math.max(0, index - 8), Math.min(lines.length, index + 5)).join("\n");
|
||||
if (allowedLegacyContext(contextBlock, occurrenceWindow)) {
|
||||
continue;
|
||||
}
|
||||
violations.push({
|
||||
file: relativePath,
|
||||
line: index + 1,
|
||||
reason: classifyActiveLightRagContext(contextBlock, occurrenceWindow),
|
||||
context: occurrenceWindow,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return { scannedFiles, violations };
|
||||
}
|
||||
|
||||
async function fetchStatusProviderConfig() {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
||||
const url = `${BASE_URL}/api/knowledge-rag/status`;
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: { accept: "application/json" },
|
||||
signal: controller.signal,
|
||||
});
|
||||
const text = await response.text();
|
||||
let payload = null;
|
||||
try {
|
||||
payload = text ? JSON.parse(text) : null;
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
layer: "status_api_invalid_json",
|
||||
url,
|
||||
status: response.status,
|
||||
error: error.message,
|
||||
rawText: text.slice(0, 1000),
|
||||
providerConfig: null,
|
||||
};
|
||||
}
|
||||
const providerConfig = payload && typeof payload.providerConfig === "object" ? payload.providerConfig : null;
|
||||
const active = providerConfig && providerConfig.active;
|
||||
const defaultProvider = providerConfig && providerConfig.default;
|
||||
const provider = payload && payload.provider;
|
||||
const ok = response.ok && providerConfig && active === "weknora" && defaultProvider === "weknora";
|
||||
return {
|
||||
ok,
|
||||
layer: response.ok ? (ok ? "status_provider_config_ok" : "status_provider_config_mismatch") : "status_api_http_error",
|
||||
url,
|
||||
status: response.status,
|
||||
provider,
|
||||
providerConfig,
|
||||
responseSample: ok ? undefined : payload,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
layer: error && error.name === "AbortError" ? "status_api_timeout" : "status_api_unavailable",
|
||||
url,
|
||||
error: error && (error.stack || error.message) ? (error.stack || error.message) : String(error),
|
||||
providerConfig: null,
|
||||
};
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||
const staticFiles = uniqueSorted([...REQUIRED_STATIC_FILES, ...listTaskSmokeFiles()]);
|
||||
const staticScan = scanStaticFiles(staticFiles.filter((relativePath) => relativePath !== SELF_PATH));
|
||||
const statusCheck = await fetchStatusProviderConfig();
|
||||
const ok = staticScan.violations.length === 0 && statusCheck.ok;
|
||||
const payload = {
|
||||
ok,
|
||||
task: "task787-weknora-default-lightrag-legacy-smoke",
|
||||
baseUrl: BASE_URL,
|
||||
checkedAt: new Date().toISOString(),
|
||||
scannedFiles: staticScan.scannedFiles,
|
||||
violations: staticScan.violations,
|
||||
statusProviderConfig: statusCheck,
|
||||
};
|
||||
if (ok) {
|
||||
writeJson(RESULT_PATH, payload);
|
||||
console.log(JSON.stringify(payload, null, 2));
|
||||
return;
|
||||
}
|
||||
writeJson(FAILURE_PATH, payload);
|
||||
console.error(JSON.stringify(payload, null, 2));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
const payload = {
|
||||
ok: false,
|
||||
task: "task787-weknora-default-lightrag-legacy-smoke",
|
||||
layer: "unexpected",
|
||||
error: error && (error.stack || error.message) ? (error.stack || error.message) : String(error),
|
||||
baseUrl: BASE_URL,
|
||||
};
|
||||
writeJson(FAILURE_PATH, payload);
|
||||
console.error(JSON.stringify(payload, null, 2));
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user