fix(miniapp): add missing uuid and message-display utils

Commit the utility modules required by api.js, messages.js, and chat page
so WeChat DevTools can resolve require('./uuid') at runtime.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-11 13:01:05 +08:00
parent 04ef1c9105
commit c842119929
16 changed files with 829 additions and 72 deletions
+336
View File
@@ -0,0 +1,336 @@
const TASK_ROUTING_HINT_RE = /^【TKMind 路由提示】[\s\S]*?\n\n/;
const MEMIND_TASK_ORCHESTRATION_RE = /^【Memind 任务编排】[\s\S]*?(?:用户任务:|用户任务:)\s*/u;
const MINDSPACE_CONTEXT_RE = /^\[MindSpace 上下文\][\s\S]*?\n\n/;
const USER_IDENTITY_BLOCK_RE = /^\[用户身份\][\s\S]*?\n\n/;
const IMAGE_URL_LINES_RE = /\n*\[图片\d+]: [^\n]+/g;
const FILE_ATTACHMENT_LINES_RE = /\n*\[文件\d+: [^\]]+\]: [^\n]+/g;
const ASSISTANT_DELIVERABLE_RE =
/\[[^\]]+\]\(https?:\/\/[^)]+\)|https?:\/\/(?:m\.)?[^/\s]*tkmind\.(?:cn|ai)\/(?:MindSpace|u)\/|\bpublic\/[A-Za-z0-9._-]+\.html\b/i;
const INTERNAL_ASSISTANT_MARKERS = [
/data-mindspace-page-tag/i,
/mindspace-cover/i,
/\bload_skill\b/i,
/static-page-publish/i,
/\.agents\/skills/i,
/SKILL\.md/i,
/注意到技能/u,
/技能更新/u,
/页脚要用.*platform-brand/u,
];
const URL_PATTERN =
/https?:\/\/[^\s<>"')\]]+\/(?:MindSpace|temp)\/([0-9a-f-]{36}|[a-z0-9._-]+)\/([^\s<>"')\]]+\.html)/gi;
const PUBLICATION_URL_PATTERN =
/https?:\/\/[^\s<>"')\]]+\/u\/([a-z0-9._-]+)\/pages\/([^\s<>"')\]]+)/gi;
const MARKDOWN_LINK_RE = /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g;
const COMBINED_LINK_RE =
/\[([^\]]+)\]\((https?:\/\/[^)]+)\)|(https?:\/\/[^\s<>"')\]]+)|`?(public\/[A-Za-z0-9._-]+\.html)`?|(\/?MindSpace\/([0-9a-f-]{36}|[a-z0-9._-]+)\/(public\/[A-Za-z0-9._-]+\.html))/gi;
const RELATIVE_PUBLIC_HTML_RE = /\b(public\/[A-Za-z0-9._-]+\.html)\b/gi;
function stripImageUrlLines(text) {
return String(text ?? '')
.replace(IMAGE_URL_LINES_RE, '')
.replace(FILE_ATTACHMENT_LINES_RE, '')
.trim();
}
function stripUserFacingPrefixes(text) {
let next = String(text ?? '');
next = next.replace(USER_IDENTITY_BLOCK_RE, '').trimStart();
next = next.replace(TASK_ROUTING_HINT_RE, '').trimStart();
next = next.replace(MEMIND_TASK_ORCHESTRATION_RE, '').trimStart();
while (MINDSPACE_CONTEXT_RE.test(next)) {
next = next.replace(MINDSPACE_CONTEXT_RE, '').trimStart();
}
return next.trim();
}
function deriveAssistantFacingText(text) {
const trimmed = String(text ?? '').trim();
if (!trimmed) return '';
if (ASSISTANT_DELIVERABLE_RE.test(trimmed)) return trimmed;
if (INTERNAL_ASSISTANT_MARKERS.some((pattern) => pattern.test(trimmed))) return '';
return trimmed;
}
function decodeSegment(segment) {
try {
return decodeURIComponent(segment);
} catch {
return segment;
}
}
function normalizeWorkspaceRelativePath(relativePath) {
const parts = String(relativePath ?? '')
.replace(/^\/+/, '')
.split('/')
.filter((part) => part && part !== '.' && part !== '..');
if (parts.length === 0) return '';
if (parts[0]?.toLowerCase() === 'public') return parts.join('/');
if (parts.length === 1 && parts[0].toLowerCase().endsWith('.html')) return `public/${parts[0]}`;
return parts.join('/');
}
function resolveWorkspacePublicUrl(relativePath, publishKey, apiBaseUrl) {
const normalized = normalizeWorkspaceRelativePath(relativePath);
if (!publishKey || !normalized.startsWith('public/') || !/\.html$/i.test(normalized)) {
return null;
}
const base = String(apiBaseUrl || '').replace(/\/$/, '');
if (!base) return null;
const encodedPath = normalized.split('/').map((part) => encodeURIComponent(part)).join('/');
return `${base}/MindSpace/${encodeURIComponent(publishKey)}/${encodedPath}`;
}
function buildPageLinkFromRelativePath(relativePath, publishKey, apiBaseUrl) {
const normalized = normalizeWorkspaceRelativePath(relativePath);
const publicUrl = resolveWorkspacePublicUrl(normalized, publishKey, apiBaseUrl);
if (!publicUrl) return null;
const filename = normalized.split('/').pop() || normalized;
return {
publicUrl,
filename,
title: filename.replace(/\.html$/i, '').replace(/[-_]+/g, ' '),
relativePath: normalized,
};
}
function extractRelativePublicPageLinks(content, publishKey, apiBaseUrl) {
if (!publishKey || !apiBaseUrl) return [];
const links = [];
const seen = new Set();
for (const match of String(content ?? '').matchAll(RELATIVE_PUBLIC_HTML_RE)) {
const link = buildPageLinkFromRelativePath(match[1], publishKey, apiBaseUrl);
if (!link || seen.has(link.publicUrl)) continue;
seen.add(link.publicUrl);
links.push(link);
}
return links;
}
function mergePageLinks(...groups) {
const links = [];
const seen = new Set();
for (const group of groups) {
for (const link of group || []) {
if (!link?.publicUrl || seen.has(link.publicUrl)) continue;
seen.add(link.publicUrl);
links.push(link);
}
}
return links;
}
function normalizeStaticHtmlRelativePath(relativePath) {
const parts = String(relativePath ?? '')
.replace(/^\/+/, '')
.split('/')
.filter((part) => part && part !== '.' && part !== '..');
if (parts.length === 0) return '';
if (parts[0]?.toLowerCase() === 'public') return ['public', ...parts.slice(1)].join('/');
if (parts.length === 1 && parts[0].toLowerCase().endsWith('.html')) return `public/${parts[0]}`;
return parts.join('/');
}
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function encodeUrlPath(relativePath) {
return relativePath
.split('/')
.filter(Boolean)
.map((part) => encodeURIComponent(part))
.join('/');
}
function canonicalizeStaticPageUrl(publicUrl, originalRelativePath) {
const canonicalRelativePath = normalizeStaticHtmlRelativePath(originalRelativePath);
const originalClean = originalRelativePath.replace(/^\/+/, '');
if (!canonicalRelativePath || canonicalRelativePath === originalClean) return publicUrl;
const suffix = escapeRegExp(encodeUrlPath(originalClean));
return publicUrl.replace(new RegExp(`${suffix}$`), encodeUrlPath(canonicalRelativePath));
}
function extractStaticPageLinks(content) {
const links = [];
const seen = new Set();
const source = String(content ?? '');
for (const match of source.matchAll(URL_PATTERN)) {
const relativePath = decodeSegment(match[2]);
const filename = relativePath.split('/').pop() || relativePath;
const publicUrl = canonicalizeStaticPageUrl(match[0], relativePath);
if (seen.has(publicUrl)) continue;
seen.add(publicUrl);
links.push({
publicUrl,
filename,
title: filename.replace(/\.html$/i, '').replace(/[-_]+/g, ' '),
});
}
for (const match of source.matchAll(PUBLICATION_URL_PATTERN)) {
const slug = decodeSegment(match[2]).replace(/\/$/, '');
const publicUrl = match[0].replace(/\/$/, '');
if (seen.has(publicUrl)) continue;
seen.add(publicUrl);
const filename = slug.split('/').pop() || slug;
links.push({
publicUrl,
filename,
title: filename.replace(/\.html$/i, '').replace(/[-_]+/g, ' '),
});
}
return links;
}
function normalizeUrlForMatch(url) {
return String(url ?? '').replace(/\/$/, '').trim();
}
function cleanupOrphanMarkdownDecorations(text) {
return String(text ?? '')
.replace(/(?:\*\*){1,}/g, '')
.replace(/^[👉🔗\s\-•*:]+$/gm, '')
.replace(/\n{3,}/g, '\n\n')
.trim();
}
function stripPageDeliverableLinks(text, pageLinks) {
if (!pageLinks?.length) return String(text ?? '').trim();
const urlSet = new Set(pageLinks.map((link) => normalizeUrlForMatch(link.publicUrl)));
let next = String(text ?? '');
const markdownWithDecorationRe =
/[👉🔗\s]*(?:\*\*)?\[([^\]]+)\]\((https?:\/\/[^)]+)\)(?:\*\*)?/g;
next = next.replace(markdownWithDecorationRe, (full, _label, url) =>
urlSet.has(normalizeUrlForMatch(url)) ? '' : full,
);
for (const url of urlSet) {
next = next.replace(new RegExp(escapeRegExp(url) + '\\/?', 'gi'), '');
}
return cleanupOrphanMarkdownDecorations(next);
}
function parseMessageParts(text, options = {}) {
const { publishKey = '', apiBaseUrl = '' } = options;
const input = String(text ?? '');
if (!input) return [];
const parts = [];
let lastIndex = 0;
let match;
COMBINED_LINK_RE.lastIndex = 0;
while ((match = COMBINED_LINK_RE.exec(input)) !== null) {
if (match.index > lastIndex) {
parts.push({ type: 'text', text: input.slice(lastIndex, match.index) });
}
if (match[1] && match[2]) {
parts.push({ type: 'link', text: match[1], url: match[2] });
} else if (match[3]) {
parts.push({ type: 'link', text: match[3], url: match[3] });
} else if (match[4]) {
const relativePath = match[4].replace(/`/g, '');
const link = buildPageLinkFromRelativePath(relativePath, publishKey, apiBaseUrl);
parts.push({
type: 'link',
text: link?.title || relativePath,
url: link?.publicUrl || relativePath,
});
} else if (match[5] && match[6]) {
const encodedPath = match[5].replace(/^\/+/, '');
const publicUrl = resolveWorkspacePublicUrl(
encodedPath.slice(encodedPath.indexOf('public/')),
match[6],
apiBaseUrl,
) || (apiBaseUrl ? `${String(apiBaseUrl).replace(/\/$/, '')}/${encodedPath}` : match[5]);
parts.push({
type: 'link',
text: buildPageLinkFromRelativePath(
encodedPath.slice(encodedPath.indexOf('public/')),
match[6],
apiBaseUrl,
)?.title || encodedPath.split('/').pop(),
url: publicUrl,
});
}
lastIndex = match.index + match[0].length;
}
if (lastIndex < input.length) {
parts.push({ type: 'text', text: input.slice(lastIndex) });
}
return parts.length ? parts : [{ type: 'text', text: input }];
}
function getRawMessageText(message) {
if (!message) return '';
if (typeof message.text === 'string') return message.text;
if (Array.isArray(message.content)) {
return message.content
.filter((part) => part && part.type === 'text')
.map((part) => part.text || '')
.join('\n');
}
return '';
}
function getDisplayText(message) {
const rawText = getRawMessageText(message);
if (message?.role === 'user') {
if (message.metadata?.displayText != null) {
return stripImageUrlLines(message.metadata.displayText);
}
return stripImageUrlLines(stripUserFacingPrefixes(rawText));
}
if (message?.metadata?.displayText != null) {
return stripImageUrlLines(deriveAssistantFacingText(message.metadata.displayText));
}
return stripImageUrlLines(deriveAssistantFacingText(rawText));
}
function buildMessageView(message, options = {}) {
const { publishKey = '', apiBaseUrl = '' } = options;
const rawText = getRawMessageText(message);
const displayText = getDisplayText(message);
const sourceText = displayText || rawText;
const pageLinks = mergePageLinks(
extractStaticPageLinks(sourceText),
extractRelativePublicPageLinks(sourceText, publishKey, apiBaseUrl),
);
let text = displayText;
if (!text && pageLinks.length > 0) {
text = '页面已生成,可点击下方链接查看。';
}
if (!text.trim()) return null;
const markdownTitleByUrl = {};
for (const match of String(sourceText).matchAll(MARKDOWN_LINK_RE)) {
markdownTitleByUrl[match[2]] = match[1];
}
const enrichedPageLinks = pageLinks.map((link) => ({
...link,
title: markdownTitleByUrl[link.publicUrl] || link.title,
}));
const renderText = stripPageDeliverableLinks(text, enrichedPageLinks);
const pageLinkUrls = new Set(enrichedPageLinks.map((link) => normalizeUrlForMatch(link.publicUrl)));
const parts = parseMessageParts(renderText, { publishKey, apiBaseUrl }).filter(
(part) =>
part.type !== 'link' || !pageLinkUrls.has(normalizeUrlForMatch(part.url)),
);
return {
id: message.id || `${message.role}_${message.created || Date.now()}`,
role: message.role || 'assistant',
text: renderText,
copyText: renderText,
parts,
pageLinks: enrichedPageLinks,
created: message.created || 0,
};
}
module.exports = {
buildMessageView,
extractStaticPageLinks,
extractRelativePublicPageLinks,
getDisplayText,
parseMessageParts,
resolveWorkspacePublicUrl,
};