岁月札记:给鸿蒙「智能笔记」装上一个会回望的脑子
·
手头有一套微电子王阔同学写的 HarmonyOS 应用「智能笔记」(ArkTS + Stage 模型),功能很朴素:写笔记、配模型、看作者页。几个月之后我发现一个问题——笔记是越写越多了,但我从来没有回头看。
笔记的价值不在「记需求听起来简单,真正动手时会发现它分属两个完全不同的层面:一层是工程实现,一层是平台能力的边界。下面逐个说。」的那一刻,而在「回看」的那一刻。可人在忙起来的时候,是不会主动去翻三个月前写了什么的。
在原有基础上,增加了模块名叫「岁月札记」,集成后底部导航从 3 项扩到 4 项(笔记 / 模型 / 感悟 / 作者),感悟的可信度取决于素材的丰富度。只靠站内笔记,视角是单一的;把这段时间拍下的照片、随手记下的句子一起放进来,回顾才有质感。这类生活素材分散在各个应用里,硬去打通某一个平台既不现实也不必要——只要素材能进到应用里,分析效果完全一致。
总体架构:一条可以局部失败的流水线,整个架构只有一条原则,但它是这个模块能稳的根本:
每一层都可以单独失败而不影响其它层。
数据模型:从「记录」到「结构化感悟」
模块新增三类数据,用独立的 preferences 文件 pencil_memoir 存放,与原有笔记数据完全隔离。模块出任何问题,都不会影响「智能笔记」的主体功能。
核心代码:
export async function generateInsight(context: Context, options: GenerateOptions,
onProgress: ProgressCallback | null): Promise<GenerateOutcome> {
/* --- 1. 初始化数据层 --- */
report(onProgress, 4, '正在读取数据…');
await initStore(context);
await initMemoirStore(context);
const settings = loadSettings();
/* --- 2. 计算时间窗 --- */
report(onProgress, 10, '正在计算时间区间…');
const range: DateRange = computeRange(options.cycle, Date.now(), options.offset,
options.customStart, options.customEnd);
const notes: Note[] = filterNotes(getRawNotes(), range);
const moments: Moment[] = options.includeMoments ? filterMoments(loadMoments(), range) : [];
if (notes.length === 0 && moments.length === 0) {
return failOutcome(`${range.startDate} 至 ${range.endDate} 这段时间还没有可复盘的素材。`);
}
/* --- 3. 取模型配置 --- */
report(onProgress, 16, '正在加载模型配置…');
const endpoint = pickEndpoint(await loadModelEndpoints(context), settings.modelId);
if (endpoint === null) {
return failOutcome('还没有可用的模型。请先到「模型」页添加一个 OpenAI 兼容的模型。');
}
/* --- 4. 拼接提示词并调用 --- */
report(onProgress, 22, `正在把 ${notes.length} 篇笔记交给模型…`);
const totalChars = countChars(notes);
const system: LlmMessage = { role: 'system', content: buildSystemPrompt() };
const userText = buildUserPrompt(options.cycle, range.startDate, range.endDate,
notes, moments, totalChars) + buildFocusSuffix(options.focus);
const messages: LlmMessage[] = [system, { role: 'user', content: userText }];
report(onProgress, 34, '模型正在阅读与归纳,这一步通常需要 10-60 秒…');
const result = await chatWithRetry(endpoint, messages, 0.75, 4096, 2);
if (!result.ok) {
return failOutcome(`模型调用失败:${result.error}`);
}
/* --- 5. 结构化解析 --- */
report(onProgress, 62, '正在整理模型的输出…');
const insight = parseInsight(result.text, options.cycle, range.startDate, range.endDate,
range.startTs, notes.length, moments.length);
if (insight === null) {
return failOutcome('模型没有按约定格式返回 JSON。原始输出片段:\n'
+ result.text.substring(0, 300));
}
/* --- 6. 落库(先落库,保证文本不丢) --- */
report(onProgress, 70, '感悟已生成,正在保存…');
upsertInsight(insight);
/* --- 7. 出图 --- */
if (options.autoImage) {
report(onProgress, 78, '正在绘制分享卡片…');
const cover = await renderInsightCard(context, insight);
if (cover !== '') {
insight.coverPath = cover;
updateInsightMedia(insight.id, cover, '');
}
}
/* --- 8. 出片 --- */
if (options.autoVideo) {
report(onProgress, 90, '正在合成动图…');
const motion = await composeMotion(context, insight);
if (motion !== '') {
updateInsightMedia(insight.id, '', motion);
}
}
report(onP
更多推荐




所有评论(0)