kevlar-4u 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +357 -0
- package/config/mcp-config.json +9 -0
- package/dist/__tests__/configureWizard.test.d.ts +2 -0
- package/dist/__tests__/configureWizard.test.d.ts.map +1 -0
- package/dist/__tests__/configureWizard.test.js +89 -0
- package/dist/__tests__/configureWizard.test.js.map +1 -0
- package/dist/__tests__/createPersonaTool.test.d.ts +2 -0
- package/dist/__tests__/createPersonaTool.test.d.ts.map +1 -0
- package/dist/__tests__/createPersonaTool.test.js +292 -0
- package/dist/__tests__/createPersonaTool.test.js.map +1 -0
- package/dist/__tests__/createPersonaWizard.test.d.ts +2 -0
- package/dist/__tests__/createPersonaWizard.test.d.ts.map +1 -0
- package/dist/__tests__/createPersonaWizard.test.js +138 -0
- package/dist/__tests__/createPersonaWizard.test.js.map +1 -0
- package/dist/__tests__/deletePersonaWizard.test.d.ts +2 -0
- package/dist/__tests__/deletePersonaWizard.test.d.ts.map +1 -0
- package/dist/__tests__/deletePersonaWizard.test.js +78 -0
- package/dist/__tests__/deletePersonaWizard.test.js.map +1 -0
- package/dist/__tests__/e2e.test.d.ts +2 -0
- package/dist/__tests__/e2e.test.d.ts.map +1 -0
- package/dist/__tests__/e2e.test.js +121 -0
- package/dist/__tests__/e2e.test.js.map +1 -0
- package/dist/__tests__/errors.test.d.ts +2 -0
- package/dist/__tests__/errors.test.d.ts.map +1 -0
- package/dist/__tests__/errors.test.js +86 -0
- package/dist/__tests__/errors.test.js.map +1 -0
- package/dist/__tests__/execution.test.d.ts +2 -0
- package/dist/__tests__/execution.test.d.ts.map +1 -0
- package/dist/__tests__/execution.test.js +792 -0
- package/dist/__tests__/execution.test.js.map +1 -0
- package/dist/__tests__/getModesTool.test.d.ts +2 -0
- package/dist/__tests__/getModesTool.test.d.ts.map +1 -0
- package/dist/__tests__/getModesTool.test.js +47 -0
- package/dist/__tests__/getModesTool.test.js.map +1 -0
- package/dist/__tests__/helpTool.test.d.ts +2 -0
- package/dist/__tests__/helpTool.test.d.ts.map +1 -0
- package/dist/__tests__/helpTool.test.js +18 -0
- package/dist/__tests__/helpTool.test.js.map +1 -0
- package/dist/__tests__/listPersonasTool.test.d.ts +2 -0
- package/dist/__tests__/listPersonasTool.test.d.ts.map +1 -0
- package/dist/__tests__/listPersonasTool.test.js +110 -0
- package/dist/__tests__/listPersonasTool.test.js.map +1 -0
- package/dist/__tests__/logger.test.d.ts +2 -0
- package/dist/__tests__/logger.test.d.ts.map +1 -0
- package/dist/__tests__/logger.test.js +56 -0
- package/dist/__tests__/logger.test.js.map +1 -0
- package/dist/__tests__/observability.test.d.ts +2 -0
- package/dist/__tests__/observability.test.d.ts.map +1 -0
- package/dist/__tests__/observability.test.js +60 -0
- package/dist/__tests__/observability.test.js.map +1 -0
- package/dist/__tests__/parser.test.d.ts +2 -0
- package/dist/__tests__/parser.test.d.ts.map +1 -0
- package/dist/__tests__/parser.test.js +259 -0
- package/dist/__tests__/parser.test.js.map +1 -0
- package/dist/__tests__/persona_creation_debug.test.d.ts +2 -0
- package/dist/__tests__/persona_creation_debug.test.d.ts.map +1 -0
- package/dist/__tests__/persona_creation_debug.test.js +56 -0
- package/dist/__tests__/persona_creation_debug.test.js.map +1 -0
- package/dist/__tests__/resetPersonasWizard.test.d.ts +2 -0
- package/dist/__tests__/resetPersonasWizard.test.d.ts.map +1 -0
- package/dist/__tests__/resetPersonasWizard.test.js +74 -0
- package/dist/__tests__/resetPersonasWizard.test.js.map +1 -0
- package/dist/__tests__/reviewContentWizard.test.d.ts +2 -0
- package/dist/__tests__/reviewContentWizard.test.d.ts.map +1 -0
- package/dist/__tests__/reviewContentWizard.test.js +148 -0
- package/dist/__tests__/reviewContentWizard.test.js.map +1 -0
- package/dist/__tests__/sanitize.test.d.ts +2 -0
- package/dist/__tests__/sanitize.test.d.ts.map +1 -0
- package/dist/__tests__/sanitize.test.js +138 -0
- package/dist/__tests__/sanitize.test.js.map +1 -0
- package/dist/__tests__/server.test.d.ts +2 -0
- package/dist/__tests__/server.test.d.ts.map +1 -0
- package/dist/__tests__/server.test.js +49 -0
- package/dist/__tests__/server.test.js.map +1 -0
- package/dist/execution/aggregator.d.ts +43 -0
- package/dist/execution/aggregator.d.ts.map +1 -0
- package/dist/execution/aggregator.js +132 -0
- package/dist/execution/aggregator.js.map +1 -0
- package/dist/execution/base.d.ts +62 -0
- package/dist/execution/base.d.ts.map +1 -0
- package/dist/execution/base.js +5 -0
- package/dist/execution/base.js.map +1 -0
- package/dist/execution/client.d.ts +9 -0
- package/dist/execution/client.d.ts.map +1 -0
- package/dist/execution/client.js +30 -0
- package/dist/execution/client.js.map +1 -0
- package/dist/execution/config.d.ts +30 -0
- package/dist/execution/config.d.ts.map +1 -0
- package/dist/execution/config.js +95 -0
- package/dist/execution/config.js.map +1 -0
- package/dist/execution/index.d.ts +19 -0
- package/dist/execution/index.d.ts.map +1 -0
- package/dist/execution/index.js +151 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/execution/limiter.d.ts +32 -0
- package/dist/execution/limiter.d.ts.map +1 -0
- package/dist/execution/limiter.js +147 -0
- package/dist/execution/limiter.js.map +1 -0
- package/dist/execution/lock.d.ts +17 -0
- package/dist/execution/lock.d.ts.map +1 -0
- package/dist/execution/lock.js +37 -0
- package/dist/execution/lock.js.map +1 -0
- package/dist/execution/modes/direct_api.d.ts +11 -0
- package/dist/execution/modes/direct_api.d.ts.map +1 -0
- package/dist/execution/modes/direct_api.js +213 -0
- package/dist/execution/modes/direct_api.js.map +1 -0
- package/dist/execution/modes/index.d.ts +7 -0
- package/dist/execution/modes/index.d.ts.map +1 -0
- package/dist/execution/modes/index.js +7 -0
- package/dist/execution/modes/index.js.map +1 -0
- package/dist/execution/modes/orchestration.d.ts +11 -0
- package/dist/execution/modes/orchestration.d.ts.map +1 -0
- package/dist/execution/modes/orchestration.js +110 -0
- package/dist/execution/modes/orchestration.js.map +1 -0
- package/dist/execution/modes/sampling.d.ts +9 -0
- package/dist/execution/modes/sampling.d.ts.map +1 -0
- package/dist/execution/modes/sampling.js +66 -0
- package/dist/execution/modes/sampling.js.map +1 -0
- package/dist/execution/parallel.d.ts +16 -0
- package/dist/execution/parallel.d.ts.map +1 -0
- package/dist/execution/parallel.js +90 -0
- package/dist/execution/parallel.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/reviewDispatcherPrompt.d.ts +2 -0
- package/dist/prompts/reviewDispatcherPrompt.d.ts.map +1 -0
- package/dist/prompts/reviewDispatcherPrompt.js +67 -0
- package/dist/prompts/reviewDispatcherPrompt.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +156 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/configureTool.d.ts +17 -0
- package/dist/tools/configureTool.d.ts.map +1 -0
- package/dist/tools/configureTool.js +104 -0
- package/dist/tools/configureTool.js.map +1 -0
- package/dist/tools/configureWizardTool.d.ts +11 -0
- package/dist/tools/configureWizardTool.d.ts.map +1 -0
- package/dist/tools/configureWizardTool.js +205 -0
- package/dist/tools/configureWizardTool.js.map +1 -0
- package/dist/tools/createPersonaTool.d.ts +37 -0
- package/dist/tools/createPersonaTool.d.ts.map +1 -0
- package/dist/tools/createPersonaTool.js +353 -0
- package/dist/tools/createPersonaTool.js.map +1 -0
- package/dist/tools/createPersonaWizardTool.d.ts +13 -0
- package/dist/tools/createPersonaWizardTool.d.ts.map +1 -0
- package/dist/tools/createPersonaWizardTool.js +713 -0
- package/dist/tools/createPersonaWizardTool.js.map +1 -0
- package/dist/tools/deletePersonaTool.d.ts +10 -0
- package/dist/tools/deletePersonaTool.d.ts.map +1 -0
- package/dist/tools/deletePersonaTool.js +75 -0
- package/dist/tools/deletePersonaTool.js.map +1 -0
- package/dist/tools/deletePersonaWizardTool.d.ts +11 -0
- package/dist/tools/deletePersonaWizardTool.d.ts.map +1 -0
- package/dist/tools/deletePersonaWizardTool.js +184 -0
- package/dist/tools/deletePersonaWizardTool.js.map +1 -0
- package/dist/tools/getModesTool.d.ts +12 -0
- package/dist/tools/getModesTool.d.ts.map +1 -0
- package/dist/tools/getModesTool.js +78 -0
- package/dist/tools/getModesTool.js.map +1 -0
- package/dist/tools/helpTool.d.ts +7 -0
- package/dist/tools/helpTool.d.ts.map +1 -0
- package/dist/tools/helpTool.js +65 -0
- package/dist/tools/helpTool.js.map +1 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +30 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/listPersonasTool.d.ts +7 -0
- package/dist/tools/listPersonasTool.d.ts.map +1 -0
- package/dist/tools/listPersonasTool.js +118 -0
- package/dist/tools/listPersonasTool.js.map +1 -0
- package/dist/tools/resetPersonasTool.d.ts +7 -0
- package/dist/tools/resetPersonasTool.d.ts.map +1 -0
- package/dist/tools/resetPersonasTool.js +447 -0
- package/dist/tools/resetPersonasTool.js.map +1 -0
- package/dist/tools/resetPersonasWizardTool.d.ts +9 -0
- package/dist/tools/resetPersonasWizardTool.d.ts.map +1 -0
- package/dist/tools/resetPersonasWizardTool.js +125 -0
- package/dist/tools/resetPersonasWizardTool.js.map +1 -0
- package/dist/tools/reviewContentWizardTool.d.ts +13 -0
- package/dist/tools/reviewContentWizardTool.d.ts.map +1 -0
- package/dist/tools/reviewContentWizardTool.js +411 -0
- package/dist/tools/reviewContentWizardTool.js.map +1 -0
- package/dist/tools/reviewTool.d.ts +10 -0
- package/dist/tools/reviewTool.d.ts.map +1 -0
- package/dist/tools/reviewTool.js +133 -0
- package/dist/tools/reviewTool.js.map +1 -0
- package/dist/tools/types.d.ts +14 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/utils/errors.d.ts +30 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +47 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +18 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +52 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/observability.d.ts +61 -0
- package/dist/utils/observability.d.ts.map +1 -0
- package/dist/utils/observability.js +69 -0
- package/dist/utils/observability.js.map +1 -0
- package/dist/utils/parser.d.ts +27 -0
- package/dist/utils/parser.d.ts.map +1 -0
- package/dist/utils/parser.js +178 -0
- package/dist/utils/parser.js.map +1 -0
- package/dist/utils/personaIdMaps.d.ts +7 -0
- package/dist/utils/personaIdMaps.d.ts.map +1 -0
- package/dist/utils/personaIdMaps.js +51 -0
- package/dist/utils/personaIdMaps.js.map +1 -0
- package/dist/utils/sanitize.d.ts +4 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +49 -0
- package/dist/utils/sanitize.js.map +1 -0
- package/dist/utils/types.d.ts +8 -0
- package/dist/utils/types.d.ts.map +1 -0
- package/dist/utils/types.js +2 -0
- package/dist/utils/types.js.map +1 -0
- package/package.json +42 -0
- package/skills/_template.md +66 -0
- package/skills/tmp/wizard-create-cwzrrpim_draft.json +26 -0
- package/skills/tmp/wizard-create-cwzrrpim_wizard.json +26 -0
- package/skills/tmp/wizard-create-d81intme_draft.json +26 -0
- package/skills/tmp/wizard-create-d81intme_wizard.json +26 -0
- package/skills/tmp/wizard-create-g50jqzmh_draft.json +8 -0
- package/skills/tmp/wizard-create-g50jqzmh_wizard.json +8 -0
- package/skills/tmp/wizard-create-onupu9wb_draft.json +8 -0
- package/skills/tmp/wizard-create-onupu9wb_wizard.json +8 -0
- package/skills/tmp/wizard-review-fwbxe3d2_review_wizard.json +9 -0
- package/skills/tmp/wizard-review-sypg5e9d_review_wizard.json +9 -0
- package/skills/wechat_official/wechat_official.md +26 -0
- package/skills/wechat_official/wechat_official_1.md +31 -0
- package/skills/xiaohongshu/xiaohongshu.md +26 -0
- package/skills/xiaohongshu/xiaohongshu_1.md +29 -0
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import { handleCreatePersona, generateIdFromDraft, getSubDirFromDraft, applyDedup, } from "./createPersonaTool.js";
|
|
4
|
+
import { logger, getErrorInfo } from "../utils/observability.js";
|
|
5
|
+
const AGE_RANGE_OPTIONS = [
|
|
6
|
+
{ value: "18岁以下", label: "18岁以下" },
|
|
7
|
+
{ value: "18-24岁", label: "18-24岁" },
|
|
8
|
+
{ value: "25-30岁", label: "25-30岁" },
|
|
9
|
+
{ value: "30-35岁", label: "30-35岁" },
|
|
10
|
+
{ value: "35-40岁", label: "35-40岁" },
|
|
11
|
+
{ value: "40岁以上", label: "40岁以上" },
|
|
12
|
+
];
|
|
13
|
+
export const createPersonaWizardToolDefinition = {
|
|
14
|
+
name: "create_persona_wizard",
|
|
15
|
+
description: "当用户说「创建/新建/自定义评论员/人设/角色」时,调用此工具。首次调用不带 sessionId,将 userMessage 设为用户的原话;工具会引导用户逐步完成年龄段、兴趣方向、性格特质、讲话语气、常用平台、与作者关系等信息收集。完全独立,不涉及内容评测流程。",
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
sessionId: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "人设创建向导的会话标识。首次调用请留空,工具会自动生成并返回一个 sessionId。后续调用必须传入此值以继续上一次的创建会话。",
|
|
22
|
+
},
|
|
23
|
+
userMessage: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "用户在当前步骤的回复内容。首次调用时直接传入用户原话(例如「帮我创建一个时尚类评论员」),工具开始分步引导。后续步骤传入用户对工具提问的回复。",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ["userMessage"],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export const createPersonaWizardModule = {
|
|
32
|
+
definition: createPersonaWizardToolDefinition,
|
|
33
|
+
handler: (deps) => async (args) => {
|
|
34
|
+
if (!args)
|
|
35
|
+
throw new Error("向导需要提供参数");
|
|
36
|
+
const input = args;
|
|
37
|
+
if (deps.updateClientSamplingSupport()) {
|
|
38
|
+
input.samplingFn = deps.createMultiTurnSamplingFn();
|
|
39
|
+
}
|
|
40
|
+
return await handleCreatePersonaWizard(deps.skillsDir, deps.tmpDir, input);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
export async function handleCreatePersonaWizard(skillsDir, tmpDir, input) {
|
|
44
|
+
const { userMessage, samplingFn } = input;
|
|
45
|
+
if (!userMessage || typeof userMessage !== "string") {
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: "text", text: "❌ 请提供当前步骤的用户回复。" }],
|
|
48
|
+
isError: true,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
await fs.promises.mkdir(tmpDir, { recursive: true });
|
|
53
|
+
const state = await loadOrCreateState(tmpDir, input.sessionId);
|
|
54
|
+
if (!input.sessionId) {
|
|
55
|
+
await saveState(tmpDir, state);
|
|
56
|
+
return toolResponse(state, [
|
|
57
|
+
"请选择这个角色的年龄段(回复编号或文字):",
|
|
58
|
+
"",
|
|
59
|
+
"1. 18岁以下",
|
|
60
|
+
"2. 18-24岁",
|
|
61
|
+
"3. 25-30岁",
|
|
62
|
+
"4. 30-35岁",
|
|
63
|
+
"5. 35-40岁",
|
|
64
|
+
"6. 40岁以上",
|
|
65
|
+
].join("\n"));
|
|
66
|
+
}
|
|
67
|
+
const result = await advanceWizard(skillsDir, tmpDir, state, userMessage, samplingFn);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
const info = getErrorInfo(err);
|
|
72
|
+
logger.error("Create persona wizard failed", { event: "wizard_error", error: info.code, message: info.message });
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: "text", text: `❌ 人设创建向导失败:${info.message}` }],
|
|
75
|
+
isError: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function advanceWizard(skillsDir, tmpDir, state, userMessage, samplingFn) {
|
|
80
|
+
switch (state.step) {
|
|
81
|
+
case "ageRange": {
|
|
82
|
+
const resolved = resolveAgeRange(userMessage);
|
|
83
|
+
if (!resolved) {
|
|
84
|
+
return toolResponse(state, [
|
|
85
|
+
"请从以下选项中选择(回复编号或文字):",
|
|
86
|
+
"",
|
|
87
|
+
"1. 18岁以下",
|
|
88
|
+
"2. 18-24岁",
|
|
89
|
+
"3. 25-30岁",
|
|
90
|
+
"4. 30-35岁",
|
|
91
|
+
"5. 35-40岁",
|
|
92
|
+
"6. 40岁以上",
|
|
93
|
+
].join("\n"));
|
|
94
|
+
}
|
|
95
|
+
state.fields.ageRange = resolved;
|
|
96
|
+
state.step = "interests";
|
|
97
|
+
await saveState(tmpDir, state);
|
|
98
|
+
await saveDraft(tmpDir, state);
|
|
99
|
+
return toolResponse(state, [
|
|
100
|
+
`已记录年龄段:${state.fields.ageRange}`,
|
|
101
|
+
"",
|
|
102
|
+
"第二步:告诉我这个角色的日常兴趣与关注焦点?",
|
|
103
|
+
].join("\n"));
|
|
104
|
+
}
|
|
105
|
+
case "interests": {
|
|
106
|
+
const extracted = await extractInterests(userMessage, samplingFn);
|
|
107
|
+
state.fields.interests = normalizeStringArray(extracted.value);
|
|
108
|
+
state.step = "traits";
|
|
109
|
+
await saveState(tmpDir, state);
|
|
110
|
+
await saveDraft(tmpDir, state);
|
|
111
|
+
return toolResponse(state, [
|
|
112
|
+
extracted.assistantMessage,
|
|
113
|
+
"",
|
|
114
|
+
"第三步:请描述这个角色的性格特质",
|
|
115
|
+
"自由描述即可,例如:容易跟风、对价格敏感、喜欢对比评测……",
|
|
116
|
+
].join("\n"));
|
|
117
|
+
}
|
|
118
|
+
case "traits": {
|
|
119
|
+
const extracted = await extractTraits(userMessage, samplingFn);
|
|
120
|
+
state.fields.traits = normalizeStringArray(extracted.value);
|
|
121
|
+
state.step = "tone";
|
|
122
|
+
await saveState(tmpDir, state);
|
|
123
|
+
await saveDraft(tmpDir, state);
|
|
124
|
+
return toolResponse(state, [
|
|
125
|
+
extracted.assistantMessage,
|
|
126
|
+
"",
|
|
127
|
+
"第四步:请描述这个角色的讲话语气",
|
|
128
|
+
"自由描述即可,例如:毒舌犀利、温柔耐心、幽默风趣、一本正经……",
|
|
129
|
+
].join("\n"));
|
|
130
|
+
}
|
|
131
|
+
case "tone": {
|
|
132
|
+
const toneExtracted = await extractTone(userMessage, samplingFn);
|
|
133
|
+
state.fields.tone = normalizeStringArray(toneExtracted.value);
|
|
134
|
+
state.step = "platform";
|
|
135
|
+
await saveState(tmpDir, state);
|
|
136
|
+
await saveDraft(tmpDir, state);
|
|
137
|
+
return toolResponse(state, [
|
|
138
|
+
toneExtracted.assistantMessage,
|
|
139
|
+
"",
|
|
140
|
+
"第五步:内容主要投放平台",
|
|
141
|
+
"如果多平台投放,建议创建更多针对某一平台的虚拟评论员。",
|
|
142
|
+
].join("\n"));
|
|
143
|
+
}
|
|
144
|
+
case "platform": {
|
|
145
|
+
const raw = userMessage.trim();
|
|
146
|
+
if (/[和、/,]/.test(raw)) {
|
|
147
|
+
const first = raw.split(/[和、/,]/)[0].trim();
|
|
148
|
+
if (first) {
|
|
149
|
+
state.fields.platform = first;
|
|
150
|
+
state.fields.platformNote = `⚠️ 你输入了多个平台,将围绕「${first}」创建此评论员。如需覆盖其他平台,请另行创建。`;
|
|
151
|
+
state.step = "authorRelation";
|
|
152
|
+
await saveState(tmpDir, state);
|
|
153
|
+
await saveDraft(tmpDir, state);
|
|
154
|
+
return toolResponse(state, [
|
|
155
|
+
`已记录常用平台:${state.fields.platform}`,
|
|
156
|
+
state.fields.platformNote,
|
|
157
|
+
"",
|
|
158
|
+
"请选择这个角色与作者的关系(回复编号或文字):",
|
|
159
|
+
"",
|
|
160
|
+
"1. 已关注(信任阈值较高,但期望值也更高)",
|
|
161
|
+
"2. 未关注(信任阈值较低,更容易因细节问题流失注意力)",
|
|
162
|
+
].join("\n"));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
state.fields.platform = raw;
|
|
166
|
+
state.step = "authorRelation";
|
|
167
|
+
await saveState(tmpDir, state);
|
|
168
|
+
await saveDraft(tmpDir, state);
|
|
169
|
+
return toolResponse(state, [
|
|
170
|
+
`已记录常用平台:${state.fields.platform}`,
|
|
171
|
+
"",
|
|
172
|
+
"请选择这个角色与作者的关系(回复编号或文字):",
|
|
173
|
+
"",
|
|
174
|
+
"1. 已关注(信任阈值较高,但期望值也更高)",
|
|
175
|
+
"2. 未关注(信任阈值较低,更容易因细节问题流失注意力)",
|
|
176
|
+
].join("\n"));
|
|
177
|
+
}
|
|
178
|
+
case "authorRelation": {
|
|
179
|
+
const resolved = resolveAuthorRelation(userMessage);
|
|
180
|
+
if (!resolved) {
|
|
181
|
+
return toolResponse(state, [
|
|
182
|
+
"请从以下选项中选择(回复编号或文字):",
|
|
183
|
+
"",
|
|
184
|
+
"1. 已关注",
|
|
185
|
+
"2. 未关注",
|
|
186
|
+
].join("\n"));
|
|
187
|
+
}
|
|
188
|
+
state.fields.authorRelation = resolved;
|
|
189
|
+
// Infer name, gender, culturalContext, stance, blindSpot before showing preview
|
|
190
|
+
const inferred = await inferFinalFields(state, skillsDir, samplingFn);
|
|
191
|
+
state.fields = { ...state.fields, ...inferred };
|
|
192
|
+
state.step = "finalConfirm";
|
|
193
|
+
await saveState(tmpDir, state);
|
|
194
|
+
await saveDraft(tmpDir, state);
|
|
195
|
+
return toolResponse(state, buildFinalConfirmationMessage(state, skillsDir));
|
|
196
|
+
}
|
|
197
|
+
case "finalConfirm":
|
|
198
|
+
if (isAffirmative(userMessage)) {
|
|
199
|
+
return completeWizard(skillsDir, tmpDir, state, samplingFn);
|
|
200
|
+
}
|
|
201
|
+
{
|
|
202
|
+
const modified = await applyFinalModification(state, userMessage, samplingFn);
|
|
203
|
+
if (!modified) {
|
|
204
|
+
return toolResponse(state, "请说明要修改哪个字段:名字、性别、年龄段、兴趣方向、性格特质或常用平台。");
|
|
205
|
+
}
|
|
206
|
+
await saveState(tmpDir, state);
|
|
207
|
+
await saveDraft(tmpDir, state);
|
|
208
|
+
return toolResponse(state, [`已更新${fieldLabel(modified)}。`, "", buildFinalConfirmationMessage(state, skillsDir)].join("\n"));
|
|
209
|
+
}
|
|
210
|
+
case "completed":
|
|
211
|
+
return toolResponse(state, "这个人设创建流程已经完成。需要创建新角色时,请重新开始一个会话。");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async function applyFinalModification(state, userMessage, samplingFn) {
|
|
215
|
+
const field = detectModifiedField(userMessage);
|
|
216
|
+
if (!field)
|
|
217
|
+
return undefined;
|
|
218
|
+
const valueText = extractModificationValue(userMessage, field);
|
|
219
|
+
if (!valueText)
|
|
220
|
+
return undefined;
|
|
221
|
+
switch (field) {
|
|
222
|
+
case "name":
|
|
223
|
+
state.fields.personaName = valueText;
|
|
224
|
+
break;
|
|
225
|
+
case "gender":
|
|
226
|
+
state.fields.gender = valueText === "男" || valueText === "女" ? valueText : "未指定";
|
|
227
|
+
break;
|
|
228
|
+
case "ageRange":
|
|
229
|
+
state.fields.ageRange = valueText;
|
|
230
|
+
break;
|
|
231
|
+
case "interests": {
|
|
232
|
+
const extracted = await extractInterests(valueText, samplingFn);
|
|
233
|
+
state.fields.interests = normalizeStringArray(extracted.value);
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
case "traits": {
|
|
237
|
+
const extracted = await extractTraits(valueText, samplingFn);
|
|
238
|
+
state.fields.traits = normalizeStringArray(extracted.value);
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case "tone": {
|
|
242
|
+
const extracted = await extractTone(valueText, samplingFn);
|
|
243
|
+
state.fields.tone = normalizeStringArray(extracted.value);
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
case "platform":
|
|
247
|
+
state.fields.platform = valueText;
|
|
248
|
+
break;
|
|
249
|
+
case "authorRelation":
|
|
250
|
+
state.fields.authorRelation = valueText;
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
state.step = "finalConfirm";
|
|
254
|
+
return field;
|
|
255
|
+
}
|
|
256
|
+
async function completeWizard(skillsDir, tmpDir, state, _samplingFn) {
|
|
257
|
+
state.step = "completed";
|
|
258
|
+
await saveState(tmpDir, state);
|
|
259
|
+
await saveDraft(tmpDir, state);
|
|
260
|
+
const createResult = await handleCreatePersona(skillsDir, tmpDir, {
|
|
261
|
+
name: state.fields.personaName || inferPersonaName(state),
|
|
262
|
+
sessionId: state.sessionId,
|
|
263
|
+
culturalContext: state.fields.culturalContext,
|
|
264
|
+
authorRelation: state.fields.authorRelation,
|
|
265
|
+
stance: state.fields.stance,
|
|
266
|
+
blindSpot: state.fields.blindSpot,
|
|
267
|
+
gender: state.fields.gender,
|
|
268
|
+
});
|
|
269
|
+
if (!createResult.isError) {
|
|
270
|
+
await cleanupState(tmpDir, state.sessionId);
|
|
271
|
+
}
|
|
272
|
+
return createResult;
|
|
273
|
+
}
|
|
274
|
+
async function extractInterests(userMessage, samplingFn) {
|
|
275
|
+
if (samplingFn) {
|
|
276
|
+
try {
|
|
277
|
+
const json = await runJsonExtraction(samplingFn, {
|
|
278
|
+
systemPrompt: "你是字段提炼器。请把用户对兴趣方向的自然语言描述提炼为最多 3 个中文短标签,并严格输出 JSON:{\"interests\":[\"标签\"],\"assistantMessage\":\"整理说明\"}。assistantMessage 可自由给出贴合场景的引导性举例辅助说明,再总结标签。不要要求用户确认。不要输出 markdown。",
|
|
279
|
+
userMessage,
|
|
280
|
+
});
|
|
281
|
+
const interests = normalizeStringArray(json.interests).slice(0, 3);
|
|
282
|
+
if (interests.length > 0) {
|
|
283
|
+
return {
|
|
284
|
+
value: interests,
|
|
285
|
+
assistantMessage: typeof json.assistantMessage === "string"
|
|
286
|
+
? sanitizeStepAssistantMessage(json.assistantMessage)
|
|
287
|
+
: `我帮你总结为以下标签:${interests.join("、")}。`,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
const info = getErrorInfo(err);
|
|
293
|
+
logger.warn("Sampling extraction failed for interests, falling back to heuristic", {
|
|
294
|
+
event: "sampling_interests_fallback",
|
|
295
|
+
error: info.code,
|
|
296
|
+
message: info.message,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const interests = splitUserText(userMessage, 3);
|
|
301
|
+
return {
|
|
302
|
+
value: interests,
|
|
303
|
+
assistantMessage: `我帮你总结为以下标签:${interests.join("、")}。`,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
async function extractTraits(userMessage, samplingFn) {
|
|
307
|
+
if (samplingFn) {
|
|
308
|
+
try {
|
|
309
|
+
const json = await runJsonExtraction(samplingFn, {
|
|
310
|
+
systemPrompt: "你是字段提炼器。请把用户对性格特质的自然语言描述提炼为最多 4 条「特质 → 行为描述」字符串,并严格输出 JSON:{\"traits\":[\"特质 → 因此当 X 时,会 Y\"],\"assistantMessage\":\"整理说明\"}。assistantMessage 只说明已总结的性格特质,不要要求用户确认。不要输出 markdown。",
|
|
311
|
+
userMessage,
|
|
312
|
+
});
|
|
313
|
+
const traits = normalizeStringArray(json.traits).slice(0, 4).map(normalizeTrait);
|
|
314
|
+
if (traits.length > 0) {
|
|
315
|
+
return {
|
|
316
|
+
value: traits,
|
|
317
|
+
assistantMessage: typeof json.assistantMessage === "string"
|
|
318
|
+
? sanitizeStepAssistantMessage(json.assistantMessage)
|
|
319
|
+
: `我帮你总结为以下性格特质:\n${traits.map((t) => `- ${t}`).join("\n")}`,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
const info = getErrorInfo(err);
|
|
325
|
+
logger.warn("Sampling extraction failed for traits, falling back to heuristic", {
|
|
326
|
+
event: "sampling_traits_fallback",
|
|
327
|
+
error: info.code,
|
|
328
|
+
message: info.message,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const traits = splitUserText(userMessage, 4).map(normalizeTrait);
|
|
333
|
+
return {
|
|
334
|
+
value: traits,
|
|
335
|
+
assistantMessage: `我帮你总结为以下性格特质:\n${traits.map((t) => `- ${t}`).join("\n")}`,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
async function extractTone(userMessage, samplingFn) {
|
|
339
|
+
if (samplingFn) {
|
|
340
|
+
try {
|
|
341
|
+
const json = await runJsonExtraction(samplingFn, {
|
|
342
|
+
systemPrompt: "你是字段提炼器。请把用户对讲话语气的自然语言描述提炼为最多 4 个中文短标签(每个标签是独立描述,简洁自然),并严格输出 JSON:{\"tone\":[\"标签\"],\"assistantMessage\":\"整理说明\"}。assistantMessage 只说明已总结的语气特点,不要要求用户确认。不要输出 markdown。",
|
|
343
|
+
userMessage,
|
|
344
|
+
});
|
|
345
|
+
const tone = normalizeStringArray(json.tone).slice(0, 4);
|
|
346
|
+
if (tone.length > 0) {
|
|
347
|
+
return {
|
|
348
|
+
value: tone,
|
|
349
|
+
assistantMessage: typeof json.assistantMessage === "string"
|
|
350
|
+
? sanitizeStepAssistantMessage(json.assistantMessage)
|
|
351
|
+
: `我帮你总结为以下讲话特点:\n${tone.map((t) => `- ${t}`).join("\n")}`,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
const info = getErrorInfo(err);
|
|
357
|
+
logger.warn("Sampling extraction failed for tone, falling back to heuristic", {
|
|
358
|
+
event: "sampling_tone_fallback",
|
|
359
|
+
error: info.code,
|
|
360
|
+
message: info.message,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const tone = splitUserText(userMessage, 4);
|
|
365
|
+
return {
|
|
366
|
+
value: tone,
|
|
367
|
+
assistantMessage: `我帮你总结为以下讲话特点:\n${tone.map((t) => `- ${t}`).join("\n")}`,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
async function inferFinalFields(state, skillsDir, samplingFn) {
|
|
371
|
+
const interest = state.fields.interests?.[0] || "内容";
|
|
372
|
+
const fallback = {
|
|
373
|
+
culturalContext: inferCulturalContext(state),
|
|
374
|
+
authorRelation: state.fields.authorRelation || "未关注",
|
|
375
|
+
stance: "默认质疑",
|
|
376
|
+
blindSpot: "无特定盲区",
|
|
377
|
+
personaName: inferPersonaName(state),
|
|
378
|
+
gender: "未指定",
|
|
379
|
+
};
|
|
380
|
+
const platformRefs = loadPlatformStanceBlindSpotRefs(state, skillsDir);
|
|
381
|
+
if (!samplingFn)
|
|
382
|
+
return fallback;
|
|
383
|
+
try {
|
|
384
|
+
const json = await runJsonExtraction(samplingFn, {
|
|
385
|
+
systemPrompt: [
|
|
386
|
+
"你是人设属性推断器。根据已确认字段推断以下字段,必须全部填写,不能为空:",
|
|
387
|
+
"- personaName:有创意、像真实互联网网名,不要带「评论员」后缀,也不要带平台名",
|
|
388
|
+
"- gender:男 / 女 / 未指定",
|
|
389
|
+
"- culturalContext",
|
|
390
|
+
"- stance:该角色看问题的基本立场(参考同平台已有角色的风格,但不要照搬)",
|
|
391
|
+
"- blindSpot:该角色因自身局限可能忽略的视角(参考同平台已有角色的风格,但不要照搬)",
|
|
392
|
+
platformRefs ? `\n同平台已有人设参考:\n${platformRefs}` : "",
|
|
393
|
+
`\n严格输出 JSON:{"personaName":"...","gender":"...","culturalContext":"...","stance":"...","blindSpot":"..."}`,
|
|
394
|
+
"authorRelation 已由用户明确选择,不要覆盖。不要输出 markdown。",
|
|
395
|
+
].filter(Boolean).join("\n"),
|
|
396
|
+
userMessage: JSON.stringify(state.fields),
|
|
397
|
+
});
|
|
398
|
+
return {
|
|
399
|
+
personaName: typeof json.personaName === "string" && json.personaName.trim().length > 0
|
|
400
|
+
? json.personaName.trim()
|
|
401
|
+
: fallback.personaName,
|
|
402
|
+
gender: typeof json.gender === "string" && (json.gender === "男" || json.gender === "女")
|
|
403
|
+
? json.gender
|
|
404
|
+
: "未指定",
|
|
405
|
+
culturalContext: typeof json.culturalContext === "string" ? json.culturalContext : fallback.culturalContext,
|
|
406
|
+
authorRelation: fallback.authorRelation,
|
|
407
|
+
stance: typeof json.stance === "string" && json.stance.trim().length > 0 ? json.stance.trim() : fallback.stance,
|
|
408
|
+
blindSpot: typeof json.blindSpot === "string" && json.blindSpot.trim().length > 0 ? json.blindSpot.trim() : fallback.blindSpot,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
return fallback;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function loadPlatformStanceBlindSpotRefs(state, skillsDir) {
|
|
416
|
+
const subDir = getSubDirFromDraft({ fields: state.fields });
|
|
417
|
+
if (!subDir)
|
|
418
|
+
return "";
|
|
419
|
+
const dir = path.join(skillsDir, subDir);
|
|
420
|
+
try {
|
|
421
|
+
if (!fs.statSync(dir).isDirectory())
|
|
422
|
+
return "";
|
|
423
|
+
const files = fs.readdirSync(dir).filter(f => f.endsWith(".md") && f !== "_template.md");
|
|
424
|
+
if (files.length === 0)
|
|
425
|
+
return "";
|
|
426
|
+
const refs = [];
|
|
427
|
+
for (const file of files) {
|
|
428
|
+
const content = fs.readFileSync(path.join(dir, file), "utf-8");
|
|
429
|
+
const nameMatch = content.match(/^name:\s*(.+)/m);
|
|
430
|
+
const stanceMatch = content.match(/^stance:\s*(.+)/m);
|
|
431
|
+
const blindSpotMatch = content.match(/^blindSpot:\s*(.+)/m);
|
|
432
|
+
const name = nameMatch ? nameMatch[1].trim() : file.replace(".md", "");
|
|
433
|
+
const stance = stanceMatch ? stanceMatch[1].trim() : "(未设置)";
|
|
434
|
+
const blindSpot = blindSpotMatch ? blindSpotMatch[1].trim() : "(未设置)";
|
|
435
|
+
refs.push(`- ${name}:立场="${stance}",盲区="${blindSpot}"`);
|
|
436
|
+
}
|
|
437
|
+
return refs.join("\n");
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
return "";
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
async function runJsonExtraction(samplingFn, params) {
|
|
444
|
+
const response = await samplingFn({
|
|
445
|
+
systemPrompt: params.systemPrompt,
|
|
446
|
+
messages: [{ role: "user", content: params.userMessage }],
|
|
447
|
+
maxTokens: 1024,
|
|
448
|
+
});
|
|
449
|
+
const jsonText = stripCodeFence(response.content.trim());
|
|
450
|
+
return JSON.parse(jsonText);
|
|
451
|
+
}
|
|
452
|
+
function stripCodeFence(text) {
|
|
453
|
+
if (!text.startsWith("```"))
|
|
454
|
+
return text;
|
|
455
|
+
return text.replace(/^```json\s*/, "").replace(/^```\s*/, "").replace(/\s*```$/, "").trim();
|
|
456
|
+
}
|
|
457
|
+
async function loadOrCreateState(tmpDir, inputSessionId) {
|
|
458
|
+
if (inputSessionId && !/^[a-z0-9-]+$/.test(inputSessionId)) {
|
|
459
|
+
throw new Error("sessionId 格式不合法。");
|
|
460
|
+
}
|
|
461
|
+
const sessionId = inputSessionId || `wizard-create-${Math.random().toString(36).substring(2, 10)}`;
|
|
462
|
+
const statePath = getStatePath(tmpDir, sessionId);
|
|
463
|
+
if (inputSessionId && fs.existsSync(statePath)) {
|
|
464
|
+
const raw = await fs.promises.readFile(statePath, "utf-8");
|
|
465
|
+
return JSON.parse(raw);
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
sessionId,
|
|
469
|
+
createdAt: Date.now(),
|
|
470
|
+
step: "ageRange",
|
|
471
|
+
fields: {},
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
async function saveState(tmpDir, state) {
|
|
475
|
+
await fs.promises.mkdir(tmpDir, { recursive: true });
|
|
476
|
+
const statePath = getStatePath(tmpDir, state.sessionId);
|
|
477
|
+
const tmpPath = statePath + ".tmp";
|
|
478
|
+
await fs.promises.writeFile(tmpPath, JSON.stringify(state, null, 2), "utf-8");
|
|
479
|
+
await fs.promises.rename(tmpPath, statePath);
|
|
480
|
+
}
|
|
481
|
+
async function saveDraft(tmpDir, state) {
|
|
482
|
+
const draft = {
|
|
483
|
+
sessionId: state.sessionId,
|
|
484
|
+
createdAt: state.createdAt,
|
|
485
|
+
step: stepNumber(state),
|
|
486
|
+
fields: state.fields,
|
|
487
|
+
};
|
|
488
|
+
const draftPath = getDraftPath(tmpDir, state.sessionId);
|
|
489
|
+
const tmpPath = draftPath + ".tmp";
|
|
490
|
+
await fs.promises.writeFile(tmpPath, JSON.stringify(draft, null, 2), "utf-8");
|
|
491
|
+
await fs.promises.rename(tmpPath, draftPath);
|
|
492
|
+
}
|
|
493
|
+
async function cleanupState(tmpDir, sessionId) {
|
|
494
|
+
for (const filePath of [getStatePath(tmpDir, sessionId), getDraftPath(tmpDir, sessionId)]) {
|
|
495
|
+
try {
|
|
496
|
+
if (fs.existsSync(filePath))
|
|
497
|
+
await fs.promises.unlink(filePath);
|
|
498
|
+
}
|
|
499
|
+
catch (err) {
|
|
500
|
+
const info = getErrorInfo(err);
|
|
501
|
+
logger.warn("Failed to clean wizard file", { event: "wizard_cleanup_error", path: filePath, error: info.code, message: info.message });
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function getStatePath(tmpDir, sessionId) {
|
|
506
|
+
return path.join(tmpDir, `${sessionId}_wizard.json`);
|
|
507
|
+
}
|
|
508
|
+
function getDraftPath(tmpDir, sessionId) {
|
|
509
|
+
return path.join(tmpDir, `${sessionId}_draft.json`);
|
|
510
|
+
}
|
|
511
|
+
function toolResponse(state, assistantMessage) {
|
|
512
|
+
return {
|
|
513
|
+
content: [
|
|
514
|
+
{
|
|
515
|
+
type: "text",
|
|
516
|
+
text: [
|
|
517
|
+
assistantMessage,
|
|
518
|
+
"",
|
|
519
|
+
"```kevlar-state",
|
|
520
|
+
`sessionId: ${state.sessionId}`,
|
|
521
|
+
"workflow: create_persona",
|
|
522
|
+
`currentStep: ${state.step}`,
|
|
523
|
+
`completedFields: ${Object.keys(state.fields).join(", ") || "none"}`,
|
|
524
|
+
"```",
|
|
525
|
+
]
|
|
526
|
+
.filter(Boolean)
|
|
527
|
+
.join("\n"),
|
|
528
|
+
},
|
|
529
|
+
],
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
function buildFinalConfirmationMessage(state, skillsDir) {
|
|
533
|
+
const fields = state.fields;
|
|
534
|
+
// Build a preview of the persona file that will be created
|
|
535
|
+
const previewDraft = { fields };
|
|
536
|
+
const subDir = getSubDirFromDraft(previewDraft);
|
|
537
|
+
const baseId = generateIdFromDraft(previewDraft) ||
|
|
538
|
+
inferPersonaName(state).replace(/[^a-zA-Z0-9_]/g, "").toLowerCase() ||
|
|
539
|
+
`persona_${Math.random().toString(36).substring(2, 8)}`;
|
|
540
|
+
const id = applyDedup(skillsDir, baseId, subDir);
|
|
541
|
+
const personaName = fields.personaName || inferPersonaName(state);
|
|
542
|
+
const culturalContext = fields.culturalContext || "未提供";
|
|
543
|
+
const authorRelation = fields.authorRelation || "未关注";
|
|
544
|
+
const stance = fields.stance || "默认质疑";
|
|
545
|
+
const blindSpot = fields.blindSpot || "无特定盲区";
|
|
546
|
+
const gender = fields.gender || undefined;
|
|
547
|
+
const lines = [
|
|
548
|
+
"基本信息:",
|
|
549
|
+
`角色名:${personaName}`,
|
|
550
|
+
`ID:${id}`,
|
|
551
|
+
`年龄段:${fields.ageRange || ""}`,
|
|
552
|
+
];
|
|
553
|
+
if (gender)
|
|
554
|
+
lines.push(`性别:${gender}`);
|
|
555
|
+
lines.push(`兴趣方向:${Array.isArray(fields.interests) ? fields.interests.join("、") : ""}`, `常用平台:${fields.platform || ""}`, `文化背景:${culturalContext}`, `立场:${stance}`, `盲区:${blindSpot}`, "性格特质:");
|
|
556
|
+
if (Array.isArray(fields.traits)) {
|
|
557
|
+
fields.traits.forEach((t) => lines.push(` ${t}`));
|
|
558
|
+
}
|
|
559
|
+
lines.push("讲话语气:");
|
|
560
|
+
if (Array.isArray(fields.tone)) {
|
|
561
|
+
fields.tone.forEach((t) => lines.push(` ${t}`));
|
|
562
|
+
}
|
|
563
|
+
lines.push(`与作者关系:${authorRelation}`);
|
|
564
|
+
if (fields.platformNote) {
|
|
565
|
+
lines.push("", fields.platformNote);
|
|
566
|
+
}
|
|
567
|
+
lines.push("", "如需修改,请直接说:名字改成... / 性别改成... / 年龄段改成... / 兴趣方向改成... / 性格特质改成... / 讲话语气改成... / 平台改成... / 关系改成...", "确认无误请回复:确认创建");
|
|
568
|
+
return lines.join("\n");
|
|
569
|
+
}
|
|
570
|
+
function detectModifiedField(input) {
|
|
571
|
+
if (/名字|名|名称|角色名|称呼/.test(input))
|
|
572
|
+
return "name";
|
|
573
|
+
if (/性别|男|女/.test(input))
|
|
574
|
+
return "gender";
|
|
575
|
+
if (/年龄|年龄段|\d+\s*[-到至]\s*\d+\s*岁|岁/.test(input))
|
|
576
|
+
return "ageRange";
|
|
577
|
+
if (/兴趣|方向|标签/.test(input))
|
|
578
|
+
return "interests";
|
|
579
|
+
if (/性格|特质|脾气|行为/.test(input))
|
|
580
|
+
return "traits";
|
|
581
|
+
if (/语气|讲话|说话|口吻/.test(input))
|
|
582
|
+
return "tone";
|
|
583
|
+
if (/平台|渠道|小红书|知乎|B站|公众号|微信|微博|Instagram|Reddit|YouTube|Twitter|X\b/i.test(input)) {
|
|
584
|
+
return "platform";
|
|
585
|
+
}
|
|
586
|
+
if (/关系|关注|作者/.test(input))
|
|
587
|
+
return "authorRelation";
|
|
588
|
+
return undefined;
|
|
589
|
+
}
|
|
590
|
+
function extractModificationValue(input, field) {
|
|
591
|
+
const trimmed = input.trim();
|
|
592
|
+
const explicitMatch = trimmed.match(/(?:改成|改为|修改为|换成|变成|调整为|设置为|[::])\s*(.+)$/);
|
|
593
|
+
if (explicitMatch?.[1])
|
|
594
|
+
return explicitMatch[1].trim();
|
|
595
|
+
const fieldWords = {
|
|
596
|
+
name: /名字|名|名称|角色名|称呼/g,
|
|
597
|
+
gender: /性别/g,
|
|
598
|
+
ageRange: /年龄段?|岁数?/g,
|
|
599
|
+
interests: /兴趣方向|兴趣|方向|标签/g,
|
|
600
|
+
traits: /性格特质|性格|特质|脾气|行为/g,
|
|
601
|
+
tone: /讲话语气|语气|讲话|说话|口吻/g,
|
|
602
|
+
platform: /常用平台|平台|渠道/g,
|
|
603
|
+
authorRelation: /与作者的关系|关系|关注/g,
|
|
604
|
+
};
|
|
605
|
+
const cleaned = trimmed
|
|
606
|
+
.replace(fieldWords[field], "")
|
|
607
|
+
.replace(/^(请|帮我|把|将|给我|重新)?\s*/, "")
|
|
608
|
+
.replace(/(改一下|修改一下|调整一下|改|修改|调整|换)\s*/g, "")
|
|
609
|
+
.trim();
|
|
610
|
+
return cleaned || trimmed;
|
|
611
|
+
}
|
|
612
|
+
function fieldLabel(field) {
|
|
613
|
+
const labels = {
|
|
614
|
+
name: "名字",
|
|
615
|
+
gender: "性别",
|
|
616
|
+
ageRange: "年龄段",
|
|
617
|
+
interests: "兴趣方向",
|
|
618
|
+
traits: "性格特质",
|
|
619
|
+
tone: "讲话语气",
|
|
620
|
+
platform: "常用平台",
|
|
621
|
+
authorRelation: "与作者的关系",
|
|
622
|
+
};
|
|
623
|
+
return labels[field];
|
|
624
|
+
}
|
|
625
|
+
function sanitizeStepAssistantMessage(input) {
|
|
626
|
+
return input
|
|
627
|
+
.replace(/确认没问题吗??如需调整请直接告诉我。?/g, "")
|
|
628
|
+
.replace(/确认没问题吗??/g, "")
|
|
629
|
+
.replace(/如需调整请直接告诉我。?/g, "")
|
|
630
|
+
.trim();
|
|
631
|
+
}
|
|
632
|
+
function isAffirmative(input) {
|
|
633
|
+
const normalized = input.trim().toLowerCase();
|
|
634
|
+
// Short/ambiguous words require exact match to avoid false positives.
|
|
635
|
+
// e.g. "确认没问题" should match via containsMatchWords; "这是什么" should NOT match.
|
|
636
|
+
const exactMatchWords = ["是", "对", "好", "y"];
|
|
637
|
+
const containsMatchWords = ["确认", "可以", "没问题", "ok", "yes"];
|
|
638
|
+
return (exactMatchWords.some((w) => normalized === w) ||
|
|
639
|
+
containsMatchWords.some((w) => normalized.includes(w)));
|
|
640
|
+
}
|
|
641
|
+
function normalizeStringArray(value) {
|
|
642
|
+
if (!Array.isArray(value))
|
|
643
|
+
return [];
|
|
644
|
+
return value.map((item) => String(item).trim()).filter(Boolean);
|
|
645
|
+
}
|
|
646
|
+
function splitUserText(input, maxItems) {
|
|
647
|
+
const parts = input
|
|
648
|
+
.split(/[,,、;;\n]/)
|
|
649
|
+
.map((part) => part.trim())
|
|
650
|
+
.filter(Boolean);
|
|
651
|
+
return (parts.length > 0 ? parts : [input.trim()]).slice(0, maxItems);
|
|
652
|
+
}
|
|
653
|
+
function normalizeTrait(input) {
|
|
654
|
+
const text = input.trim();
|
|
655
|
+
if (text.includes("→"))
|
|
656
|
+
return text;
|
|
657
|
+
return `${text} → 因此在相关内容判断中会表现出这一倾向`;
|
|
658
|
+
}
|
|
659
|
+
function resolveAgeRange(input) {
|
|
660
|
+
const trimmed = input.trim();
|
|
661
|
+
const num = parseInt(trimmed, 10);
|
|
662
|
+
if (num >= 1 && num <= AGE_RANGE_OPTIONS.length) {
|
|
663
|
+
return AGE_RANGE_OPTIONS[num - 1].value;
|
|
664
|
+
}
|
|
665
|
+
const exact = AGE_RANGE_OPTIONS.find((o) => o.value === trimmed);
|
|
666
|
+
if (exact)
|
|
667
|
+
return exact.value;
|
|
668
|
+
const partial = AGE_RANGE_OPTIONS.find((o) => o.value.replace("岁", "") === trimmed);
|
|
669
|
+
if (partial)
|
|
670
|
+
return partial.value;
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
function resolveAuthorRelation(input) {
|
|
674
|
+
const trimmed = input.trim();
|
|
675
|
+
const num = parseInt(trimmed, 10);
|
|
676
|
+
if (num === 1)
|
|
677
|
+
return "已关注";
|
|
678
|
+
if (num === 2)
|
|
679
|
+
return "未关注";
|
|
680
|
+
if (/已关注|关注了|已关/.test(trimmed))
|
|
681
|
+
return "已关注";
|
|
682
|
+
if (/未关注|没关注|未关/.test(trimmed))
|
|
683
|
+
return "未关注";
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
function inferCulturalContext(state) {
|
|
687
|
+
const platform = state.fields.platform || "";
|
|
688
|
+
if (/小红书|微信|公众号|知乎|B站|抖音/.test(platform)) {
|
|
689
|
+
return "中国大陆互联网文化语境";
|
|
690
|
+
}
|
|
691
|
+
if (/Instagram|Reddit|YouTube|Twitter|X/.test(platform)) {
|
|
692
|
+
return "海外互联网文化语境";
|
|
693
|
+
}
|
|
694
|
+
return "未提供";
|
|
695
|
+
}
|
|
696
|
+
function inferPersonaName(state) {
|
|
697
|
+
const interest = state.fields.interests?.[0] || "内容";
|
|
698
|
+
return interest;
|
|
699
|
+
}
|
|
700
|
+
function stepNumber(state) {
|
|
701
|
+
const order = [
|
|
702
|
+
"ageRange",
|
|
703
|
+
"interests",
|
|
704
|
+
"traits",
|
|
705
|
+
"tone",
|
|
706
|
+
"platform",
|
|
707
|
+
"authorRelation",
|
|
708
|
+
"finalConfirm",
|
|
709
|
+
"completed",
|
|
710
|
+
];
|
|
711
|
+
return Math.max(1, order.indexOf(state.step) + 1);
|
|
712
|
+
}
|
|
713
|
+
//# sourceMappingURL=createPersonaWizardTool.js.map
|