feat(mindspace): add SEO/GEO delivery, page template catalog, and admin hooks
Memind CI / Test, build, and release guards (push) Has been cancelled
Memind CI / Test, build, and release guards (push) Has been cancelled
Enable optional SEO/GEO injection and discovery routes for confirmed public pages while keeping private pages noindex. Add premium page template skills, portal catalog API, template shop UI, and Baidu push gated by memind_adm config. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+264
-2
@@ -1,12 +1,31 @@
|
||||
// Keep browser-safe: do not import user-publish.mjs (uses node:fs/path/url).
|
||||
import { shouldUseScheduledTaskAutomation } from './scheduled-task-intent.mjs';
|
||||
|
||||
const PAGE_TEMPLATE_PROMPT_SUFFIX =
|
||||
'【硬性要求】必须先 load_skill 并 read_file 官方 templates/*.html,仅替换 {{PLACEHOLDER}};' +
|
||||
'禁止修改 <style> 结构或新增模板外 CSS 类;正文内容只能填入占位符 HTML 片段。' +
|
||||
'交付链接必须含 /MindSpace/<用户ID>/public/...,禁止 http://127.0.0.1:5173/public/...。';
|
||||
|
||||
const PUBLISH_SKILL_NAME = 'static-page-publish';
|
||||
export const PAGE_DATA_COLLECT_SKILL_NAME = 'page-data-collect';
|
||||
export const EXCEL_ANALYST_SKILL_NAME = 'excel-analyst';
|
||||
export const SCHEDULED_TASK_AUTOMATION_SKILL_NAME = 'scheduled-task-automation';
|
||||
export const AIDER_DEVELOPMENT_SKILL_NAME = 'aider-development';
|
||||
export const SKILL_ROUTER_V2_ENV = 'TKMIND_SKILL_ROUTER_V2';
|
||||
const PAGE_TEMPLATE_SKILL_PREFIX = 'page-template-';
|
||||
|
||||
function isPageTemplatePromptKey(promptKey) {
|
||||
return String(promptKey ?? '').startsWith(PAGE_TEMPLATE_SKILL_PREFIX);
|
||||
}
|
||||
|
||||
function buildPageTemplateSkillPrompt(skillName) {
|
||||
return (
|
||||
`请使用 ${skillName} 技能:先 load_skill,再 read_file 读取 templates/ 下 HTML 模板,` +
|
||||
'替换全部占位符后 write_file 到 public/页面.html,并遵守 static-page-publish 的 mindspace-cover 与 Markdown 链接格式。' +
|
||||
PAGE_TEMPLATE_PROMPT_SUFFIX +
|
||||
'页面主题:'
|
||||
);
|
||||
}
|
||||
|
||||
const EXCEL_ANALYSIS_INTENT_PATTERNS = [
|
||||
/\.xlsx(?:\b|$)/iu,
|
||||
@@ -223,6 +242,186 @@ export const CHAT_SKILL_DEFINITIONS = [
|
||||
requiresSkill: 'product-campaign-page',
|
||||
promptKey: 'product-campaign-page',
|
||||
},
|
||||
{
|
||||
id: 'page-template-travel',
|
||||
label: '旅游模板',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-travel',
|
||||
requiresSkill: 'page-template-travel',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-travel',
|
||||
},
|
||||
{
|
||||
id: 'page-template-campaign',
|
||||
label: '促销模板',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-campaign',
|
||||
requiresSkill: 'page-template-campaign',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-campaign',
|
||||
},
|
||||
{
|
||||
id: 'page-template-survey',
|
||||
label: '问卷模板',
|
||||
icon: 'form',
|
||||
skillName: 'page-template-survey',
|
||||
requiresSkill: 'page-template-survey',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-survey',
|
||||
},
|
||||
{
|
||||
id: 'page-template-restaurant',
|
||||
label: '餐饮模板',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-restaurant',
|
||||
requiresSkill: 'page-template-restaurant',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-restaurant',
|
||||
},
|
||||
{
|
||||
id: 'page-template-portfolio',
|
||||
label: '作品集',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-portfolio',
|
||||
requiresSkill: 'page-template-portfolio',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-portfolio',
|
||||
},
|
||||
{
|
||||
id: 'page-template-event',
|
||||
label: '活动邀请',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-event',
|
||||
requiresSkill: 'page-template-event',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-event',
|
||||
},
|
||||
{
|
||||
id: 'page-template-course',
|
||||
label: '课程页',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-course',
|
||||
requiresSkill: 'page-template-course',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-course',
|
||||
},
|
||||
{
|
||||
id: 'page-template-realestate',
|
||||
label: '房产展示',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-realestate',
|
||||
requiresSkill: 'page-template-realestate',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-realestate',
|
||||
},
|
||||
{
|
||||
id: 'page-template-blog',
|
||||
label: '博客长文',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-blog',
|
||||
requiresSkill: 'page-template-blog',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-blog',
|
||||
},
|
||||
{
|
||||
id: 'page-template-resume',
|
||||
label: '个人简历',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-resume',
|
||||
requiresSkill: 'page-template-resume',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-resume',
|
||||
},
|
||||
{
|
||||
id: 'page-template-wedding',
|
||||
label: '婚礼邀请',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-wedding',
|
||||
requiresSkill: 'page-template-wedding',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-wedding',
|
||||
},
|
||||
{
|
||||
id: 'page-template-fitness',
|
||||
label: '健身课程',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-fitness',
|
||||
requiresSkill: 'page-template-fitness',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-fitness',
|
||||
},
|
||||
{
|
||||
id: 'page-template-product-grid',
|
||||
label: '产品网格',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-product-grid',
|
||||
requiresSkill: 'page-template-product-grid',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-product-grid',
|
||||
},
|
||||
{
|
||||
id: 'page-template-saas',
|
||||
label: 'SaaS 产品页',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-saas',
|
||||
requiresSkill: 'page-template-saas',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-saas',
|
||||
},
|
||||
{
|
||||
id: 'page-template-hotel',
|
||||
label: '酒店民宿',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-hotel',
|
||||
requiresSkill: 'page-template-hotel',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-hotel',
|
||||
},
|
||||
{
|
||||
id: 'page-template-agency',
|
||||
label: '创意机构',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-agency',
|
||||
requiresSkill: 'page-template-agency',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-agency',
|
||||
},
|
||||
{
|
||||
id: 'page-template-photography',
|
||||
label: '摄影画廊',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-photography',
|
||||
requiresSkill: 'page-template-photography',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-photography',
|
||||
},
|
||||
{
|
||||
id: 'page-template-podcast',
|
||||
label: '播客节目',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-podcast',
|
||||
requiresSkill: 'page-template-podcast',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-podcast',
|
||||
},
|
||||
{
|
||||
id: 'page-template-charity',
|
||||
label: '公益募捐',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-charity',
|
||||
requiresSkill: 'page-template-charity',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-charity',
|
||||
},
|
||||
{
|
||||
id: 'page-template-startup',
|
||||
label: '创业融资',
|
||||
icon: 'page',
|
||||
skillName: 'page-template-startup',
|
||||
requiresSkill: 'page-template-startup',
|
||||
requiresPublish: true,
|
||||
promptKey: 'page-template-startup',
|
||||
},
|
||||
{
|
||||
id: 'summarize',
|
||||
label: '总结内容',
|
||||
@@ -282,6 +481,23 @@ export function buildChatSkillPrompt(promptKey, skillName) {
|
||||
'请先询问商品链接、页面主题、是否有商品图片或品牌素材;信息足够后,生成包含购买跳转按钮的页面方案。' +
|
||||
'如果我确认要发布成 H5 页面,请继续使用 static-page-publish 技能生成可访问链接。'
|
||||
);
|
||||
case 'page-template-travel':
|
||||
return (
|
||||
`请使用 page-template-travel 技能(templateId=travel-hero):先 load_skill,再 read_file 读取 templates/travel-hero.html,` +
|
||||
'替换全部占位符后 write_file 到 public/页面.html,并遵守 static-page-publish 的 mindspace-cover 与 Markdown 链接格式。' +
|
||||
'交付链接必须含 /MindSpace/<用户ID>/public/...,禁止 5173/public/...。页面主题:'
|
||||
);
|
||||
case 'page-template-campaign':
|
||||
return (
|
||||
`请使用 page-template-campaign 技能(templateId=campaign-cta):先 load_skill,再 read_file 读取 templates/campaign-cta.html,` +
|
||||
'填入商品名、卖点与真实购买链接,write_file 到 public/页面.html,遵守 product-campaign-page 与 static-page-publish。' +
|
||||
'交付链接必须含 /MindSpace/<用户ID>/public/...。商品/活动:'
|
||||
);
|
||||
case 'page-template-survey':
|
||||
return (
|
||||
`请使用 page-template-survey 技能:必须先 load_skill → page-data-collect 完成建表/register,再 read_file templates/survey-basic.html,` +
|
||||
'填好 DATASET_ID 与表单字段,write_file 并完成 private_data_bind_workspace_page。问卷需求:'
|
||||
);
|
||||
case 'image-generation':
|
||||
return (
|
||||
`请使用 ${skillName ?? 'image-generation'} 技能:把我的简短视觉需求自动扩展成完整 prompt 与独立 negative_prompt,实际调用 generate_image 生成并校验新位图。` +
|
||||
@@ -307,18 +523,64 @@ export function buildChatSkillPrompt(promptKey, skillName) {
|
||||
'禁止在当前对话立即执行长任务;工具成功返回后才能确认已设置。一次性任务用 runAtLocal,循环任务用 hour/minute/weekday。我的需求是:'
|
||||
);
|
||||
default:
|
||||
if (isPageTemplatePromptKey(promptKey)) {
|
||||
return buildPageTemplateSkillPrompt(promptKey);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function mergeChatSkillPromptWithInput(prompt, currentInput) {
|
||||
export function mergeChatSkillPromptWithInput(prompt, currentInput, options = {}) {
|
||||
const normalizedPrompt = String(prompt ?? '');
|
||||
const existing = String(currentInput ?? '').trim();
|
||||
const replacePrefixes = Array.isArray(options.replacePrefixes)
|
||||
? options.replacePrefixes.filter(Boolean)
|
||||
: [];
|
||||
let existing = stripLeadingPrefills(currentInput, replacePrefixes);
|
||||
if (!existing) return normalizedPrompt;
|
||||
if (existing.startsWith(normalizedPrompt)) return existing;
|
||||
return `${normalizedPrompt}${existing}`;
|
||||
}
|
||||
|
||||
function stripLeadingPrefills(text, prefixes) {
|
||||
let result = String(text ?? '').trim();
|
||||
if (!result || !prefixes.length) return result;
|
||||
const sorted = [...prefixes].sort((a, b) => b.length - a.length);
|
||||
let changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (const prefix of sorted) {
|
||||
if (prefix && result.startsWith(prefix)) {
|
||||
result = result.slice(prefix.length).trimStart();
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function listPageTemplatePrefillPrompts() {
|
||||
return CHAT_SKILL_DEFINITIONS.filter(
|
||||
(def) =>
|
||||
String(def.skillName ?? '').startsWith(PAGE_TEMPLATE_SKILL_PREFIX) ||
|
||||
String(def.id ?? '').startsWith(PAGE_TEMPLATE_SKILL_PREFIX),
|
||||
).map((def) => buildChatSkillPrompt(def.promptKey, def.skillName));
|
||||
}
|
||||
|
||||
export function applyPageTemplatePrefill(prompt, currentInput) {
|
||||
const normalizedPrompt = String(prompt ?? '');
|
||||
const prefixes = listPageTemplatePrefillPrompts();
|
||||
const userSuffix = stripLeadingPrefills(currentInput, prefixes);
|
||||
if (!normalizedPrompt) return userSuffix;
|
||||
if (!userSuffix) return normalizedPrompt;
|
||||
if (userSuffix.startsWith(normalizedPrompt)) return userSuffix;
|
||||
return `${normalizedPrompt}${userSuffix}`;
|
||||
}
|
||||
|
||||
export function isPageTemplateChatSkillId(skillId) {
|
||||
return String(skillId ?? '').startsWith(PAGE_TEMPLATE_SKILL_PREFIX);
|
||||
}
|
||||
|
||||
export function extractAiderDevelopmentTask(userMessage) {
|
||||
const metadata = userMessage?.metadata;
|
||||
const displayText = String(metadata?.displayText ?? '').trim();
|
||||
|
||||
Reference in New Issue
Block a user