4e21ca937a
Deploy Documentation / deploy (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
CI / H5 Plaza Tests and Build (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Fork goose with custom MCP widgets, platform extensions (aider, git, web, search), MindSpace H5 backend/frontend, Plaza/Ops UIs, and deploy scripts for tkmind.cn. Co-authored-by: Cursor <cursoragent@cursor.com>
143 lines
4.3 KiB
JavaScript
143 lines
4.3 KiB
JavaScript
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { PUBLISH_ROOT_DIR } from './user-publish.mjs';
|
|
|
|
const URL_PATTERN =
|
|
/https?:\/\/[^\s<>"')\]]+\/(?:MindSpace|temp)\/([a-z0-9._-]+)\/([^\s<>"')\]]+\.html)/gi;
|
|
|
|
function decodePathSegment(segment) {
|
|
try {
|
|
return decodeURIComponent(segment);
|
|
} catch {
|
|
return segment;
|
|
}
|
|
}
|
|
|
|
export function extractStaticPageLinks(content, { username } = {}) {
|
|
const text = String(content ?? '');
|
|
const links = [];
|
|
const seen = new Set();
|
|
for (const match of text.matchAll(URL_PATTERN)) {
|
|
const owner = decodePathSegment(match[1]).toLowerCase();
|
|
const relativePath = decodePathSegment(match[2]);
|
|
if (username && owner !== String(username).trim().toLowerCase()) continue;
|
|
const key = `${owner}/${relativePath}`;
|
|
if (seen.has(key)) continue;
|
|
seen.add(key);
|
|
links.push({
|
|
publicUrl: match[0],
|
|
owner,
|
|
relativePath,
|
|
filename: path.basename(relativePath),
|
|
});
|
|
}
|
|
return links;
|
|
}
|
|
|
|
export function resolvePublishHtmlAbsolutePath(h5Root, username, relativePath) {
|
|
const slug = String(username ?? '').trim().toLowerCase();
|
|
const clean = String(relativePath ?? '')
|
|
.replace(/^\/+/, '')
|
|
.split('/')
|
|
.filter((part) => part && part !== '.' && part !== '..')
|
|
.join('/');
|
|
if (!slug || !clean || !clean.toLowerCase().endsWith('.html')) {
|
|
throw Object.assign(new Error('无效的页面路径'), { code: 'invalid_page_path' });
|
|
}
|
|
const publishRoot = path.resolve(h5Root, PUBLISH_ROOT_DIR, slug);
|
|
const absolute = path.resolve(publishRoot, clean);
|
|
if (absolute !== publishRoot && !absolute.startsWith(`${publishRoot}${path.sep}`)) {
|
|
throw Object.assign(new Error('页面路径越界'), { code: 'invalid_page_path' });
|
|
}
|
|
return absolute;
|
|
}
|
|
|
|
export async function readPublishHtml(h5Root, username, relativePath) {
|
|
const absolute = resolvePublishHtmlAbsolutePath(h5Root, username, relativePath);
|
|
const content = await fs.readFile(absolute, 'utf8');
|
|
if (!content.trim()) {
|
|
throw Object.assign(new Error('页面内容为空'), { code: 'empty_page_content' });
|
|
}
|
|
return { absolute, content, relativePath, filename: path.basename(relativePath) };
|
|
}
|
|
|
|
function titleFromHtml(html) {
|
|
const match = String(html).match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
return match?.[1]?.trim() ?? '';
|
|
}
|
|
|
|
function summaryFromHtml(html) {
|
|
const stripped = String(html)
|
|
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
|
|
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
|
.replace(/<[^>]+>/g, ' ')
|
|
.replace(/\s+/g, ' ')
|
|
.trim();
|
|
return stripped.slice(0, 180);
|
|
}
|
|
|
|
export function analyzeChatMessageForSave({
|
|
content,
|
|
username,
|
|
h5Root,
|
|
selectedLinkIndex = 0,
|
|
}) {
|
|
const links = extractStaticPageLinks(content, { username });
|
|
const text = String(content ?? '').replace(/\s+/g, ' ').trim();
|
|
const suggestedTitleFromText = text
|
|
.replace(/^#{1,6}\s*/, '')
|
|
.replace(/[*_`~[\]]/g, '')
|
|
.trim()
|
|
.slice(0, 48);
|
|
|
|
if (links.length === 0) {
|
|
return {
|
|
contentMode: 'markdown',
|
|
links: [],
|
|
selectedLink: null,
|
|
suggestedTitle: suggestedTitleFromText || 'AI 创作页面',
|
|
suggestedSummary: text.slice(0, 160),
|
|
previewUrl: null,
|
|
relativePath: null,
|
|
filename: null,
|
|
};
|
|
}
|
|
|
|
const index = Math.min(Math.max(0, selectedLinkIndex), links.length - 1);
|
|
const selectedLink = links[index];
|
|
return {
|
|
contentMode: 'static_html',
|
|
links,
|
|
selectedLink,
|
|
suggestedTitle:
|
|
selectedLink.filename.replace(/\.html$/i, '').replace(/[-_]/g, ' ') ||
|
|
suggestedTitleFromText ||
|
|
'AI 生成页面',
|
|
suggestedSummary: text.slice(0, 160),
|
|
previewUrl: selectedLink.publicUrl,
|
|
relativePath: selectedLink.relativePath,
|
|
filename: selectedLink.filename,
|
|
h5Root,
|
|
username,
|
|
};
|
|
}
|
|
|
|
export async function resolveStaticHtmlContent(analysis) {
|
|
if (analysis.contentMode !== 'static_html' || !analysis.selectedLink) {
|
|
return null;
|
|
}
|
|
const loaded = await readPublishHtml(
|
|
analysis.h5Root,
|
|
analysis.username,
|
|
analysis.selectedLink.relativePath,
|
|
);
|
|
const title = titleFromHtml(loaded.content);
|
|
const summary = summaryFromHtml(loaded.content);
|
|
return {
|
|
...loaded,
|
|
suggestedTitle: title || analysis.suggestedTitle,
|
|
suggestedSummary: summary || analysis.suggestedSummary,
|
|
publicUrl: analysis.selectedLink.publicUrl,
|
|
};
|
|
}
|