数字我 Digital Me:全量入库(大文件走 Git LFS)
nginx serve /uploads direct (audio speed) / patch-nginx (push) Failing after 1m34s

This commit is contained in:
2026-10-10 16:24:43 +08:00
commit 0593ff7e62
1893 changed files with 361723 additions and 0 deletions
+166
View File
@@ -0,0 +1,166 @@
// 天赋树 = Prompt + 模型行为的「控制平面」(不是游戏成长系统)。
// 主天赋(唯一) = 身份 + 知识边界 + 合规等级 + 默认模型;副天赋(≤N) = 风格/软技能,后置改写,永不改事实。
// 运行时: 主天赋 System Prompt → (可选)RAG → LLM 主回答 → 副天赋后置改写(合并 1 次) → Guardrail → 输出。
// 注册表按「数据表形状」写在代码里,留 DB 化的缝;主天赋 = profession(不另建一套身份)。
import { getProfession } from './professions.js';
// ── 职业类别 → 原型(决定主天赋的行为基线 + 默认模型)──
const ARCH_BY_CAT = { content: 'creator', pro: 'expert', health: 'expert', edu: 'teacher', art: 'artist', biz: 'expert', life: 'expert', sport: 'sport', craft: 'craft', beauty: 'craft', heritage: 'expert', aec: 'expert', other: 'expert' };
// 原型 → 主天赋行为基线(把旧的主树各节点效果,收敛成主天赋的固定行为)
const ARCH_BEHAVIOR = {
expert: '在你的专业领域内保持专业、准确、有深度;尽量基于知识库作答,资料不足就明说、绝不编造;复杂问题先简要拆解再给结论;主动提示适用边界与风险;结尾给一句最关键、可执行的结论。',
creator: '敏锐抓住受众关心的选题与新鲜角度;开头一两句就抛钩子抓注意力;信息密度高、少水话,给能直接用的干货;表达有节奏、有起伏;自然引导到关注/行动,不硬广。',
teacher: '由浅入深、拆成小步讲解;多用例子把抽象讲具体;判断对方水平并调整深度;发现错误温和纠正并讲清原因;讲完适时给一道小题检验;多鼓励、建立信心。',
artist: '聊到相关话题时展示并讲解你的作品;有鲜明的审美主张;把技法/工艺讲清楚、可上手;讲清作品背后的理念与"为什么";强化你独特的风格辨识度;自然引导到合作/约稿/收藏。',
sport: '把比赛/操作拆开复盘、找到关键;精准点出关键技术要点;给出可执行的训练/上分计划;帮对方调节心态、抗压;有感染力、点燃斗志。',
craft: '一步步手把手教、清晰可跟做;给出精确的参数/配方/规格;主动提醒常见坑与注意事项;按对方具体情况给定制方案;给出从入门到进阶的成长路线。',
};
// 原型 → 模型偏好(当前仅 qwen;留路由缝,将来按天赋/成本/返佣切换)
const ARCH_MODEL = { expert: ['qwen'], creator: ['qwen'], teacher: ['qwen'], artist: ['qwen'], sport: ['qwen'], craft: ['qwen'] };
// ── 合规等级 → 安全配置(safety_level 越高越严;接 Guardrail)──
const COMPLIANCE = {
none: { safety_level: 2, audit_required: false, block_keywords: [], disclaimer: '' },
soft: { safety_level: 5, audit_required: false, block_keywords: ['包治', '绝对有效', '100%有效', '保证治好'], disclaimer: '(以上为一般性建议,具体情况请结合你的实际或线下专业意见判断。)' },
strict: { safety_level: 9, audit_required: true, block_keywords: ['包赢', '一定胜诉', '保证治愈', '必胜', '稳赚', '包好', '绝对没问题'], disclaimer: '⚠️ 以上内容仅供参考,不构成正式的法律/医疗/心理诊疗意见;重要决策请咨询线下持牌专业人士。' },
};
// ── 主天赋(由 profession 派生,主天赋 = 身份单元)──
export function mainTalent(profId) {
const p = getProfession(profId);
if (!p) return { id: 'general', name: '通用分身', emoji: '🙂', type: 'main', level: 1, arch: 'expert', entity: '数字分身',
system_prompt: '像本人一样自然、真诚地聊天;不确定就如实说,不编造具体事实。',
rag: { required: false, top_k: 5, scope: [] }, model_preference: ['qwen'],
safety_level: 2, block_keywords: [], disclaimer: '', audit_required: false, compliance: 'none' };
const arch = ARCH_BY_CAT[p.cat] || 'expert';
const c = COMPLIANCE[p.compliance] || COMPLIANCE.none;
return {
id: p.id, name: p.name, emoji: p.emoji, type: 'main', level: 1, arch, entity: p.entity,
system_prompt: `你的职业身份:${p.name}。${ARCH_BEHAVIOR[arch]}`,
rag: { required: arch === 'expert' || p.compliance !== 'none', top_k: 5, scope: [] },
model_preference: ARCH_MODEL[arch] || ['qwen'],
safety_level: c.safety_level, block_keywords: c.block_keywords, disclaimer: c.disclaimer,
audit_required: c.audit_required, compliance: p.compliance || 'none',
};
}
// ── 副天赋注册表(通用、全部后置改写型 = 只碰成稿、绝不改事实)──
// subtype: style(语气) | constraint(结构约束) | hobby(前置注入,机制预留,MVP 未铺货)
// 副天赋 = 说话风格软技能。可多选(叠加);仅「对立性格」互斥(conflicts 双向声明)。
export const SUB_REGISTRY = [
// —— 语气/性格 ——
{ id: 'warm', name: '亲和温度', subtype: 'style', desc: '语气温和、有共情与人情味', rewrite_prompt: '语气更温和、有共情与人情味', conflicts: ['blunt'] },
{ id: 'blunt', name: '直率坦诚', subtype: 'style', desc: '有话直说、不绕弯子', rewrite_prompt: '更直率坦诚、有话直说、不绕弯子(但不失礼貌)', conflicts: ['warm'] },
{ id: 'humor', name: '幽默风趣', subtype: 'style', desc: '适度玩梗、轻松风趣', rewrite_prompt: '加入适度的幽默感与轻松的梗' },
{ id: 'playful', name: '俏皮活泼', subtype: 'style', desc: '语气俏皮、活泼跳脱', rewrite_prompt: '语气更俏皮活泼、跳脱一点', conflicts: ['calm', 'rigorous'] },
{ id: 'calm', name: '沉稳克制', subtype: 'style', desc: '沉稳、不煽情、稳重可靠', rewrite_prompt: '更沉稳克制、稳重可靠,不夸张不煽情', conflicts: ['playful', 'motivate'] },
{ id: 'motivate', name: '激励感染', subtype: 'style', desc: '有鼓励与感染力、让人想行动', rewrite_prompt: '语气更有鼓励和感染力,让人更想行动', conflicts: ['calm'] },
{ id: 'poetic', name: '文艺细腻', subtype: 'style', desc: '文艺、有画面感、细腻', rewrite_prompt: '表达更文艺细腻、有画面感' },
// —— 表达方式 ——
{ id: 'story', name: '会讲故事', subtype: 'style', desc: '用故事或比喻讲得更生动', rewrite_prompt: '善用故事与比喻,让表达更生动(不虚构关键事实)', conflicts: ['concise'] },
{ id: 'vivid', name: '口语生动', subtype: 'style', desc: '像真人说话、去书面腔', rewrite_prompt: '更口语、更像真人说话,去掉书面腔和 AI 味' },
{ id: 'socratic', name: '启发提问', subtype: 'style', desc: '多用反问、引导对方思考', rewrite_prompt: '适当用反问和启发式提问,引导对方自己想明白' },
{ id: 'signature', name: '人设强化', subtype: 'style', desc: '强化口头禅与说话腔调', rewrite_prompt: '强化你独特的口头禅与说话腔调,突出个人风格' },
// —— 结构/严谨(约束型)——
{ id: 'concise', name: '简洁利落', subtype: 'constraint', desc: '先给要点、去掉铺垫废话', rewrite_prompt: '更精简,先给要点、去掉铺垫与废话', conflicts: ['story', 'patient'] },
{ id: 'shortfirst', name: '结论先行', subtype: 'constraint', desc: '结论在前、理由在后', rewrite_prompt: '重排为结论先行:先给结论,再给理由' },
{ id: 'patient', name: '耐心细致', subtype: 'constraint', desc: '循循善诱、拆解得更细', rewrite_prompt: '更耐心细致、循循善诱,把步骤和道理拆解清楚', conflicts: ['concise'] },
{ id: 'rigorous', name: '严谨专业', subtype: 'constraint', desc: '措辞严谨、给依据不夸大', rewrite_prompt: '措辞更严谨专业、给出依据、不夸大不臆断', conflicts: ['playful'] },
{ id: 'datadriven', name: '有理有据', subtype: 'constraint', desc: '爱用数据、例证、引用', rewrite_prompt: '多用数据、具体例证和引用来支撑观点' },
// —— ★擅长深化(老板 2026-07-31「擅长再多点·现在就深化」):给三条【才轴】各补一项副天赋,让顶尖之才够得更高。
// ★向后兼容:老号没这三项 sub → 轴值不变 → 照命完全不变;只有【新点这三项】的人能把对应才轴顶得更高(hit+1)。
// ★gate 不受影响(全表 gate 都是「至少点1项」·加 sub 不改此语义)。挂进 dynasties.js AXES 的 wen/fa/wu。
{ id: 'eloquent', name: '辞采雄辩', subtype: 'style', desc: '出口成章、辞采华美、善用排比铺陈', rewrite_prompt: '辞采更华美讲究、善用排比铺陈与凝练警句,出口成章而不堆砌' },
{ id: 'analytical', name: '洞察缜密', subtype: 'constraint', desc: '洞察本质、条分缕析、逻辑缜密', rewrite_prompt: '更善于洞察本质、条分缕析地拆解问题,逻辑缜密、层层推进' },
{ id: 'bold', name: '果敢担当', subtype: 'style', desc: '敢为敢当、有决断魄力', rewrite_prompt: '更果敢有担当、有决断魄力,遇事敢拍板、扛得起', conflicts: ['patient'] },
// —— 治学之才(hobby 型:前置注入,不进说话风格改写)——
// ★ 第 17 项。★★ 它是【治学之才,不是说话风格】:量的是「你读通了什么」,不是「你怎么说」。
// · subtype:'hobby' ⇒ 走 injectBlock(前置注入 inject_prompt),【不进】 restyleInstruction
// (后者显式 `subtype !== 'hobby'` 排它)⇒ 它永远不改分身的说话风格、不改事实。
// · 没有 rewrite_prompt、没有 conflicts —— 它不与任何「说话风格」软技能互斥(正交)。
// ★★★ 它是 dynasties.js 第 7 轴「经学」的唯一 sub:jingxue 只此一项 ⇒ computeAxes 只吐两个值
// {40, 63}(binary:通经 63 / 不通经 40)。够跑西汉博士弟子的「通一藝」gate(63≥55 开、40<55 关),
// 撑不起卷88 四档(那要多 sub/分级阈值,见 dynasties.js 西汉 xuan.notDone)。
// ★ 画像依据(xihan-zheng.js JINGXUE_PORTRAITS,双证):董仲舒「下帷…三年不窺園」、
// 兒寬「帶經而鉏…以射策爲掌故」——「读通经义、持一家之说」正是这一类人。
{ id: 'jingxue_tong', name: '通经', subtype: 'hobby',
desc: '读通经义、能持一家之说(治学之才,非说话风格)',
inject_prompt: '你读通经义、持一家之说,可据经立论' },
];
const SUB_BY_ID = Object.fromEntries(SUB_REGISTRY.map((s) => [s.id, s]));
// 兼容保留(部分调用点仍引用):不再用作槽位上限,副天赋可多选。
export function maxSubs() { return SUB_REGISTRY.length; }
// 清洗用户提交的副天赋:只留合法 id、去重;对立性格互斥(保留先选,丢后来的冲突项)。不限数量。
export function validateSubs(ids) {
const seen = new Set(); const out = [];
for (const id of Array.isArray(ids) ? ids : []) {
const s = SUB_BY_ID[id];
if (!s || seen.has(id)) continue;
if ((s.conflicts || []).some((c) => seen.has(c))) continue; // 与已选对立 → 跳过
seen.add(id); out.push(id);
}
return out;
}
// User Talent Instance → 解析出 { main, subs[] }
export function resolveTalents(twin, level = 0) {
const main = mainTalent(twin.profession);
const ids = validateSubs(twin.talents?.sub_talents, level);
return { main, subs: ids.map((id) => SUB_BY_ID[id]) };
}
// ── 管线助手 ──
// 主天赋 → System Prompt 片段(身份+行为+强合规时的免责指令)
export function mainPromptBlock(main) {
let s = main.system_prompt;
if (main.disclaimer) s += `\n【合规】你处于${main.compliance === 'strict' ? '强' : '软'}合规身份:不做绝对化承诺,涉及专业判断时说明这只是参考;必要时在结尾附免责说明。`;
return s;
}
// 前置注入(hobby 型副天赋;MVP 未铺货 → 通常为空)
export function injectBlock(subs) {
const inj = subs.filter((s) => s.subtype === 'hobby' && s.inject_prompt).map((s) => '· ' + s.inject_prompt);
return inj.length ? '【兴趣/偏好】' + inj.join(';') : '';
}
// 后置改写指令(把所有 style/constraint 副天赋合并成 1 次改写;不改事实)
export function restyleInstruction(subs) {
const rw = subs.filter((s) => s.rewrite_prompt && s.subtype !== 'hobby').map((s) => '· ' + s.rewrite_prompt);
if (!rw.length) return '';
return `请在【绝对不改变任何事实、数字、结论、引用与专业判断】的前提下,只调整表达风格,重写下面这段回答:\n${rw.join('\n')}\n\n直接输出重写后的回答本身,不要加任何解释或前后缀。`;
}
// Guardrail:屏蔽词软化(总是)+ 按 safety 附免责(非破坏式)。
// includeDisclaimer=false 时跳过免责——用于「会话级一次性」:同一段对话只在第一句附,
// 别每句尾挂,否则把「另一个我」的身份/闲聊体验压成「法律咨询工具」(P0-1)。屏蔽词软化不受影响、始终生效。
export function guardrail(text, main, includeDisclaimer = true) {
let out = String(text || '');
for (const kw of main.block_keywords || []) {
if (out.includes(kw)) out = out.split(kw).join('(依法/依专业不宜绝对化)');
}
if (includeDisclaimer && main.disclaimer && main.safety_level >= 5) {
const head = main.disclaimer.replace(/^[⚠️\s]+/, '').slice(0, 10);
if (!out.includes(head)) out = out.trimEnd() + '\n\n' + main.disclaimer;
}
return out;
}
// GET /talents 的响应(主身份 + 副选择 + 成长解锁)
export function describeTalents(twin, level = 0, verified = false) {
const main = mainTalent(twin.profession);
const selected = validateSubs(twin.talents?.sub_talents, level);
return {
main: {
id: main.id, name: main.name, emoji: main.emoji, entity: main.entity, arch: main.arch,
compliance: main.compliance, safety_level: main.safety_level, rag_required: main.rag.required,
audit_required: main.audit_required, audit_ok: !main.audit_required || verified,
behavior: main.system_prompt,
},
subs: {
selected,
// 副天赋可多选;仅 conflicts 里的对立项互斥(前端据此置灰)。
options: SUB_REGISTRY.map((s) => ({ id: s.id, name: s.name, subtype: s.subtype, desc: s.desc, conflicts: s.conflicts || [] })),
},
growth: { level },
};
}