function stripRainSkillPrefix(text) { let next = String(text ?? '').trim(); next = next.replace(/^【Rain[^】]*】\s*/u, ''); next = next.replace(/^请使用\s+rain\s+技能[::]\s*/iu, ''); if (/^请描述要分析的时间/u.test(next) || /^在下方选择时间范围/u.test(next)) { const marker = '我的问题是:'; const idx = next.indexOf(marker); if (idx >= 0) next = next.slice(idx + marker.length); } return next.trim(); } function parseRainLlmJson(raw) { const text = String(raw ?? '').trim(); const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i); const candidate = fenced?.[1]?.trim() || text; try { return JSON.parse(candidate); } catch { return null; } } /** * @param {{ llmProviderService: object, userQuery: string, meinputBlock: string, timeRangeLabel: string, recordCount: number }} input */ export async function runRainLlmAnalysis(input) { const userQuery = stripRainSkillPrefix(input.userQuery); const system = [ '你是 TKMind Rain 分析层。只能依据【MeInput 原始输入】块中的内容做归纳,禁止引用或编造长期记忆、聊天历史、日程等外部信息。', '输出必须是单个 JSON 对象,不要 markdown,不要代码围栏,字段如下:', '{"needs_clarification":boolean,"clarification_question":string|null,"user_goal":string,"meinput_analysis":string,"suggested_next_steps":string[],"user_reply":string}', '- needs_clarification=true 时:clarification_question 必填,user_reply 用自然语言向用户追问;meinput_analysis 可为空。', '- needs_clarification=false 时:meinput_analysis 按时间线归纳用户在各 App 的输入活动;user_reply 是可直接展示给用户的中文回复(含区间说明);suggested_next_steps 供下游 Agent 参考(如生成报告页、继续追问)。', '- 时间区间已在输入中给定,禁止追问「这几天指哪几天」或让用户再次确认区间;若查询范围内实际有记录的时段更短,在 user_reply 中说明实际覆盖时段即可。', '- 用户提到「做成页面」「生成报告」时,默认理解为 MindSpace HTML 可视化报告页;suggested_next_steps 应包含生成 HTML 报告页,禁止追问是要卡片还是页面。', '- needs_clarification 仅用于用户诉求本身完全无法理解(与时间区间、页面形式无关)。', '- 不要把内部排序分数、source 字段名暴露给用户。', ].join('\n'); const user = [ `时间区间:${input.timeRangeLabel}`, `记录条数:${input.recordCount}`, '', input.meinputBlock, '', `用户诉求:${userQuery || '请总结我在上述区间的输入活动'}`, ].join('\n'); const completion = await input.llmProviderService.createChatCompletion({ messages: [ { role: 'system', content: system }, { role: 'user', content: user }, ], }); if (!completion?.ok) { const err = new Error(completion?.message ?? 'Rain LLM 分析失败'); err.code = 'RAIN_LLM_FAILED'; throw err; } const parsed = parseRainLlmJson(completion.reply); if (!parsed || typeof parsed !== 'object') { return { needs_clarification: false, clarification_question: null, user_goal: userQuery || '回顾 MeInput 输入', meinput_analysis: String(completion.reply ?? '').trim(), suggested_next_steps: [], user_reply: String(completion.reply ?? '').trim(), raw: completion.reply, }; } return { needs_clarification: Boolean(parsed.needs_clarification), clarification_question: parsed.clarification_question ?? null, user_goal: String(parsed.user_goal ?? userQuery ?? '').trim(), meinput_analysis: String(parsed.meinput_analysis ?? '').trim(), suggested_next_steps: Array.isArray(parsed.suggested_next_steps) ? parsed.suggested_next_steps.map((s) => String(s).trim()).filter(Boolean) : [], user_reply: String(parsed.user_reply ?? '').trim(), raw: completion.reply, }; } export function buildRainGooseHandoffText({ userQuery, timeRangeLabel, recordCount, analysis, userGoal, suggestedNextSteps = [], }) { const steps = suggestedNextSteps.length > 0 ? suggestedNextSteps.map((s) => `- ${s}`).join('\n') : '- (无明确工具动作,先给用户文字总结)'; return [ '[Rain · MeInput 分析简报]', '以下简报由 Rain 分析层基于 MeInput 全量原始输入生成。请据此决定如何回复用户、是否调用工具或 skill;不要重复询问时间区间。', '若用户诉求含「做成页面」或「生成报告」,直接生成 MindSpace HTML 可视化报告页,勿再追问形式。', '', `时间区间:${timeRangeLabel}`, `原始记录条数:${recordCount}`, `用户诉求:${userGoal || stripRainSkillPrefix(userQuery)}`, '', '【分析归纳】', analysis || '(无)', '', '【建议下一步】', steps, '', '【用户原始问题】', stripRainSkillPrefix(userQuery), ].join('\n'); } export { stripRainSkillPrefix };