集成 OpenHub 与 WeKnora Page AI
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const repoRoot = path.resolve(__dirname, "..");
|
||||
const openHubRoot =
|
||||
process.env.OPENHUB_RESEARCH_ROOT || "/mnt/Data1T/tmp/mnote-openhub-research/OpenHub";
|
||||
|
||||
const files = {
|
||||
chatInput: path.join(openHubRoot, "smart-query-frontend/src/components/ChatInput.jsx"),
|
||||
smartQueryPage: path.join(openHubRoot, "smart-query-frontend/src/pages/SmartQueryPage.jsx"),
|
||||
api: path.join(openHubRoot, "smart-query-frontend/src/services/api.js"),
|
||||
openHubFrontendSrc: path.join(openHubRoot, "smart-query-frontend/src"),
|
||||
mnoteOpenHubRoute: path.join(repoRoot, "rust/crates/mnote-web/src/routes/page_ai_openhub.rs"),
|
||||
};
|
||||
|
||||
function read(file) {
|
||||
return fs.readFileSync(file, "utf8");
|
||||
}
|
||||
|
||||
function walkFiles(dir, result = []) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
walkFiles(fullPath, result);
|
||||
} else {
|
||||
result.push(fullPath);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function extractBetween(source, startNeedle, endNeedle) {
|
||||
const start = source.indexOf(startNeedle);
|
||||
if (start < 0) return "";
|
||||
const end = source.indexOf(endNeedle, start);
|
||||
return source.slice(start, end < 0 ? undefined : end);
|
||||
}
|
||||
|
||||
function check(failures, name, passed, detail = "") {
|
||||
if (!passed) {
|
||||
failures.push(detail ? `${name}: ${detail}` : name);
|
||||
}
|
||||
}
|
||||
|
||||
const chatInput = read(files.chatInput);
|
||||
const smartQueryPage = read(files.smartQueryPage);
|
||||
const api = read(files.api);
|
||||
const controlRow = extractBetween(chatInput, "<Segmented", "<TextArea");
|
||||
const handleMNoteSend = extractBetween(chatInput, "const handleSendWithMNoteContext", "useEffect(() =>");
|
||||
const handleSend = extractBetween(smartQueryPage, "const handleSend = async", "const handleKeyPress");
|
||||
|
||||
const legacyFloatingSelectors = [
|
||||
"mnote-openhub-context-bar",
|
||||
"mnote-openhub-native-context",
|
||||
"data-mnote-openhub-ai-quick-actions",
|
||||
"mnote_openhub_bridge_markup",
|
||||
"insertAddress(",
|
||||
];
|
||||
|
||||
const frontendLegacyHits = [];
|
||||
for (const file of walkFiles(files.openHubFrontendSrc)) {
|
||||
const rel = path.relative(openHubRoot, file);
|
||||
const source = read(file);
|
||||
for (const selector of legacyFloatingSelectors) {
|
||||
if (source.includes(selector)) {
|
||||
frontendLegacyHits.push(`${rel} -> ${selector}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mnoteRoute = fs.existsSync(files.mnoteOpenHubRoute) ? read(files.mnoteOpenHubRoute) : "";
|
||||
const mnoteRouteLegacyHits = legacyFloatingSelectors.filter((selector) =>
|
||||
mnoteRoute.includes(selector)
|
||||
);
|
||||
|
||||
const failures = [];
|
||||
|
||||
check(
|
||||
failures,
|
||||
"ChatInput owns two native MNote context buttons",
|
||||
chatInput.includes("isMNoteOpenHubEmbed()") &&
|
||||
chatInput.includes("requestMNoteActiveTabAddress") &&
|
||||
chatInput.includes("data-mnote-openhub-current-tab-toggle") &&
|
||||
chatInput.includes("data-mnote-openhub-current-folder-toggle"),
|
||||
"missing embed gate, context request helper, or native button data attributes"
|
||||
);
|
||||
|
||||
check(
|
||||
failures,
|
||||
"MNote context buttons are in the controls row beside agent/model controls",
|
||||
controlRow.includes("<Segmented") &&
|
||||
controlRow.includes("<ModelSelect") &&
|
||||
controlRow.includes("data-mnote-openhub-current-tab-toggle") &&
|
||||
controlRow.includes("data-mnote-openhub-current-folder-toggle") &&
|
||||
controlRow.indexOf("<Segmented") < controlRow.indexOf("data-mnote-openhub-current-tab-toggle") &&
|
||||
controlRow.indexOf("<ModelSelect") < controlRow.indexOf("data-mnote-openhub-current-folder-toggle"),
|
||||
"native buttons must be before TextArea and in the same row as agent segmented/model select"
|
||||
);
|
||||
|
||||
check(
|
||||
failures,
|
||||
"Icon-only buttons use tooltips and aria labels for current page/tab and folder",
|
||||
/Tooltip\s+title=["{][^"'}]*(当前(?:页面|激活\s*Tab|Tab)|current\s*(?:page|tab))/i.test(controlRow) &&
|
||||
/Tooltip\s+title=["{][^"'}]*(当前(?:文件夹|激活\s*Tab\s*的文件夹)|current\s*folder|folder)/i.test(controlRow) &&
|
||||
/aria-label="当前页面"/.test(controlRow) &&
|
||||
/aria-label="文件夹"/.test(controlRow) &&
|
||||
/icon=\{<FileTextOutlined\s*\/>\}/.test(controlRow) &&
|
||||
/icon=\{<FolderOpenOutlined\s*\/>\}/.test(controlRow) &&
|
||||
!/>当前页面<\/Button>/.test(controlRow) &&
|
||||
!/>文件夹<\/Button>/.test(controlRow),
|
||||
"expected two icon-only buttons with distinct tooltip and aria labels"
|
||||
);
|
||||
|
||||
check(
|
||||
failures,
|
||||
"ChatInput sends mnote_context as hidden metadata instead of writing URL into textarea question",
|
||||
/handleSend\(undefined,\s*context\s*\?\s*\{/.test(handleMNoteSend) &&
|
||||
handleMNoteSend.includes("kind: mnoteContextMode") &&
|
||||
handleMNoteSend.includes("value: context.value") &&
|
||||
!/setQuestion\s*\(\s*context\./.test(handleMNoteSend) &&
|
||||
!/setQuestion\s*\(\s*mnoteContext/.test(handleMNoteSend) &&
|
||||
!/question\s*=\s*context\.value/.test(handleMNoteSend),
|
||||
"context must flow through handleSend second argument and must not mutate the visible question value"
|
||||
);
|
||||
|
||||
check(
|
||||
failures,
|
||||
"SmartQueryPage accepts mnote_context and forwards it to queryDataService.queryDataStream",
|
||||
/const\s+handleSend\s*=\s*async\s*\(\s*overrideQuestion\s*,\s*mnoteContext\s*=\s*null\s*\)/.test(
|
||||
smartQueryPage
|
||||
) &&
|
||||
handleSend.includes("queryDataService.queryDataStream(") &&
|
||||
/abortControllerRef\.current\.signal\s*,\s*mnoteContext/.test(handleSend),
|
||||
"handleSend must accept mnoteContext and pass it through the stream send call"
|
||||
);
|
||||
|
||||
check(
|
||||
failures,
|
||||
"OpenHub send body carries hidden mnote_context",
|
||||
/queryDataStream:\s*async\s*\([^)]*mnoteContext\s*=\s*null/.test(api) &&
|
||||
api.includes("requestBody.mnote_context = mnoteContext") &&
|
||||
!/question\s*:\s*.*mnoteContext/.test(api),
|
||||
"api.js must put mnoteContext into requestBody.mnote_context, not append it to question"
|
||||
);
|
||||
|
||||
check(
|
||||
failures,
|
||||
"OpenHub frontend no longer depends on legacy MNote floating button selectors",
|
||||
frontendLegacyHits.length === 0,
|
||||
frontendLegacyHits.join("; ")
|
||||
);
|
||||
|
||||
check(
|
||||
failures,
|
||||
"MNote OpenHub route no longer injects legacy floating context buttons",
|
||||
mnoteRouteLegacyHits.length === 0,
|
||||
mnoteRouteLegacyHits.join("; ")
|
||||
);
|
||||
|
||||
if (failures.length) {
|
||||
console.error("OpenHub native MNote context source smoke failed:");
|
||||
for (const failure of failures) console.error(`- ${failure}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("OpenHub native MNote context source smoke passed.");
|
||||
Reference in New Issue
Block a user