memos/apps/memos-local-openclaw/scripts/seed-test-data.ts

381 lines
15 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Seed script: inserts test data for Task-related features into the live database.
*
* Usage: npx tsx scripts/seed-test-data.ts
*
* After running, open Memory Viewer (http://127.0.0.1:18799/) and check:
* - Tasks page: 4 tasks (3 completed, 1 active)
* - Memories page: chunks associated with each task
* - Analytics: totals reflect new data
*/
import Database from "better-sqlite3";
import { v4 as uuid } from "uuid";
import * as path from "path";
import * as os from "os";
const DB_PATH = path.join(os.homedir(), ".openclaw", "memos-local", "memos.db");
const db = new Database(DB_PATH);
db.pragma("journal_mode = WAL");
db.pragma("foreign_keys = ON");
const now = Date.now();
const HOUR = 3600_000;
const MIN = 60_000;
// ─── Task 1: TypeScript 防抖节流3小时前完成 ───
const task1Id = uuid();
const session1 = "test-session-debounce";
const task1Start = now - 3 * HOUR;
const task1Chunks = [
{ role: "user", content: "用 TypeScript 写一个防抖函数 debounce支持 leading 和 trailing 选项。", summary: "用户请求:用 TS 写防抖函数 debounce支持 leading/trailing", offset: 0 },
{ role: "assistant", content: `好的,下面是一个支持 leading 和 trailing 选项的 TypeScript 防抖函数:
\`\`\`typescript
type DebounceOptions = {
leading?: boolean;
trailing?: boolean;
};
function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number,
options: DebounceOptions = { leading: false, trailing: true }
): (...args: Parameters<T>) => void {
let timer: ReturnType<typeof setTimeout> | null = null;
let lastArgs: Parameters<T> | null = null;
return function (this: any, ...args: Parameters<T>) {
const isFirstCall = timer === null;
lastArgs = args;
if (options.leading && isFirstCall) {
fn.apply(this, args);
}
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
if (options.trailing && lastArgs) {
fn.apply(this, lastArgs);
}
timer = null;
lastArgs = null;
}, delay);
};
}
\`\`\`
这个实现支持 \`leading\`(首次立即执行)和 \`trailing\`(延迟后执行)两种模式。默认只开启 trailing。`, summary: "AI 提供了完整的 TypeScript debounce 实现,支持 leading/trailing", offset: 1 },
{ role: "user", content: "再写一个节流 throttle和 debounce 一起导出。", summary: "用户请求:写 throttle 函数,和 debounce 一起导出", offset: 3 },
{ role: "assistant", content: `好的,下面是 throttle 实现,和 debounce 一起导出:
\`\`\`typescript
function throttle<T extends (...args: any[]) => any>(
fn: T,
interval: number
): (...args: Parameters<T>) => void {
let lastTime = 0;
let timer: ReturnType<typeof setTimeout> | null = null;
return function (this: any, ...args: Parameters<T>) {
const now = Date.now();
const remaining = interval - (now - lastTime);
if (remaining <= 0) {
if (timer) { clearTimeout(timer); timer = null; }
lastTime = now;
fn.apply(this, args);
} else if (!timer) {
timer = setTimeout(() => {
lastTime = Date.now();
timer = null;
fn.apply(this, args);
}, remaining);
}
};
}
export { debounce, throttle };
\`\`\``, summary: "AI 提供了 throttle 实现,并将 debounce/throttle 一起导出", offset: 4 },
{ role: "user", content: "给这两个函数各写一个单元测试。", summary: "用户请求:为 debounce 和 throttle 写单元测试", offset: 6 },
{ role: "assistant", content: `以下是使用 vitest 的测试:
\`\`\`typescript
import { describe, it, expect, vi } from 'vitest';
import { debounce, throttle } from './utils';
describe('debounce', () => {
it('should delay execution', async () => {
const fn = vi.fn();
const debounced = debounce(fn, 100);
debounced(); debounced(); debounced();
expect(fn).not.toHaveBeenCalled();
await new Promise(r => setTimeout(r, 150));
expect(fn).toHaveBeenCalledTimes(1);
});
});
describe('throttle', () => {
it('should limit call frequency', async () => {
const fn = vi.fn();
const throttled = throttle(fn, 100);
throttled(); throttled(); throttled();
expect(fn).toHaveBeenCalledTimes(1);
await new Promise(r => setTimeout(r, 150));
expect(fn).toHaveBeenCalledTimes(2);
});
});
\`\`\``, summary: "AI 提供了 debounce 和 throttle 的 vitest 单元测试", offset: 7 },
];
// ─── Task 2: 红烧肉菜谱2小时前完成同一 session换话题 ───
const task2Id = uuid();
const task2Start = now - 2 * HOUR;
const task2Chunks = [
{ role: "user", content: "我想学做红烧肉,需要准备哪些食材和步骤?", summary: "用户请求:红烧肉的食材和做法步骤", offset: 0 },
{ role: "assistant", content: `红烧肉的做法如下:
**食材**
- 五花肉 500g选三层肉最佳
- 冰糖 30g
- 生抽 2勺、老抽 1勺
- 料酒 2勺
- 葱段、姜片、八角 2个、桂皮 1小段
**步骤**
1. 五花肉切 3cm 方块,冷水下锅焯水,撇去浮沫,捞出洗净。
2. 锅中放少量油,小火放入冰糖,炒至枣红色糖色。
3. 放入五花肉翻炒上色,加入葱姜、八角、桂皮。
4. 加入料酒、生抽、老抽翻炒均匀。
5. 加入没过肉的热水,大火烧开后转小火炖 60 分钟。
6. 最后大火收汁至浓稠即可。
小贴士:全程不要加盐,生抽和老抽的咸度已经足够。`, summary: "AI 提供了红烧肉完整做法:食材清单 + 6 步骤 + 小贴士", offset: 0 },
{ role: "user", content: "做的时候火候要注意什么?炖多久最好?", summary: "用户追问:红烧肉的火候和炖煮时长", offset: 2 },
{ role: "assistant", content: `火候是红烧肉好吃的关键:
1. **炒糖色**:全程小火,冰糖变枣红色冒小泡时立刻下肉,大火会焦苦。
2. **炖煮**:大火烧开后务必转**小火**(微沸、表面冒小泡),炖 **6090 分钟**。小火慢炖才能让肉软烂入味又不柴。
3. **收汁**:最后 5 分钟转大火收浓,一边翻动防止粘底。
时长参考:普通锅 6090 分钟;压力锅 2530 分钟。肉能用筷子轻松戳透就是炖好了。`, summary: "AI 讲解红烧肉火候要点炒糖色小火、炖煮小火60-90分钟、最后大火收汁", offset: 3 },
];
// ─── Task 3: React 性能优化1小时前完成新 session ───
const task3Id = uuid();
const session2 = "test-session-react-perf";
const task3Start = now - 1 * HOUR;
const task3Chunks = [
{ role: "user", content: "我的 React 应用渲染很慢,列表有几千条数据,怎么优化?", summary: "用户请求React 列表几千条数据渲染慢,求优化方案", offset: 0 },
{ role: "assistant", content: `几千条数据的列表优化,按优先级推荐:
**1. 虚拟滚动(最有效)**
使用 \`react-window\`\`react-virtuoso\`,只渲染可视区域内的 DOM 节点。
\`\`\`tsx
import { FixedSizeList } from 'react-window';
<FixedSizeList height={600} itemCount={items.length} itemSize={50} width="100%">
{({ index, style }) => <div style={style}>{items[index].name}</div>}
</FixedSizeList>
\`\`\`
**2. React.memo + useMemo**
避免父组件重渲导致所有列表项重新渲染。
**3. key 优化**
确保 key 是稳定唯一值(如 id不要用 index。
**4. 分页 / 无限滚动**
如果数据来自 API不要一次拉全量用分页或 cursor-based pagination。
**5. 状态下沉**
把频繁变化的状态(如 hover/selected放到子组件不要提升到列表层。`, summary: "AI 提供 React 列表性能优化方案虚拟滚动、React.memo、key 优化、分页、状态下沉", offset: 0 },
{ role: "user", content: "react-window 和 react-virtuoso 哪个更好用?有什么区别?", summary: "用户追问react-window vs react-virtuoso 的区别和选择", offset: 2 },
{ role: "assistant", content: `两者对比:
| | react-window | react-virtuoso |
|---|---|---|
| 包大小 | ~6KB | ~30KB |
| 动态行高 | 需用 VariableSizeList + 手动测量 | 内建支持,自动测量 |
| 分组/表头 | 不支持 | 支持 GroupedVirtuoso |
| 无限滚动 | 需额外组合 react-window-infinite-loader | 内建 endReached |
| TypeScript | 需装 @types | 原生 TS |
| API 简洁度 | 较底层 | 较高层 |
**选择建议**
- 如果行高固定且追求极致性能 → **react-window**
- 如果行高不固定、需要分组、或想快速集成 → **react-virtuoso**
大多数业务场景推荐 **react-virtuoso**,开箱即用更省心。`, summary: "AI 对比 react-window 和 react-virtuoso大小、动态行高、分组、无限滚动等推荐大多数场景用 react-virtuoso", offset: 3 },
];
// ─── Task 4: 正在进行的任务 — Git 工作流10分钟前开始active ───
const task4Id = uuid();
const session3 = "test-session-git-workflow";
const task4Start = now - 10 * MIN;
const task4Chunks = [
{ role: "user", content: "帮我梳理一下 Git 常用工作流,特别是 feature branch 和 trunk-based 的区别。", summary: "用户请求:梳理 Git 工作流feature branch vs trunk-based", offset: 0 },
{ role: "assistant", content: `两种主流 Git 工作流对比:
## Feature Branch 工作流
- 每个功能/修复从 main 拉出独立分支
- 开发完成后通过 PR/MR 合并回 main
- **优点**隔离性强、Code Review 自然、适合大团队
- **缺点**:长期分支容易 merge conflict、集成延迟
\`\`\`
main ──────●──────●──────●──────
\\ \\ /
feature-A ●──●──●──────○
\\ /
feature-B ●──●──○
\`\`\`
## Trunk-Based 工作流
- 所有人直接向 maintrunk提交或用极短生命周期分支<1天
- 依赖 Feature Flag 控制未完成功能的可见性
- **优点**:持续集成、冲突少、部署频率高
- **缺点**:需要完善的 CI/CD 和 Feature Flag 基础设施
\`\`\`
main ──●──●──●──●──●──●──●──
\`\`\`
**选择建议**
- 小团队(<5人、持续部署 → Trunk-Based
- 大团队、发布周期长、需要严格审查 → Feature Branch`, summary: "AI 对比了 Feature Branch 和 Trunk-Based 两种 Git 工作流,含优缺点和选择建议", offset: 0 },
];
// ─── Insert ───
const insertTask = db.prepare(`
INSERT OR REPLACE INTO tasks (id, session_key, title, summary, status, started_at, ended_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`);
const insertChunk = db.prepare(`
INSERT OR REPLACE INTO chunks (id, session_key, turn_id, seq, role, content, kind, summary, task_id, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
function seedTask(
taskId: string,
sessionKey: string,
title: string,
summary: string,
status: "active" | "completed",
startedAt: number,
endedAt: number | null,
chunks: Array<{ role: string; content: string; summary: string; offset: number }>,
) {
insertTask.run(taskId, sessionKey, title, summary, status, startedAt, endedAt, now);
for (const c of chunks) {
const chunkId = uuid();
const turnId = `turn-${c.offset}-${Math.random().toString(36).slice(2, 6)}`;
const chunkTs = startedAt + c.offset * MIN;
insertChunk.run(
chunkId,
sessionKey,
turnId,
c.offset,
c.role,
c.content,
c.content.includes("```") ? "code_block" : "paragraph",
c.summary,
taskId,
chunkTs,
chunkTs,
);
}
}
const insertAll = db.transaction(() => {
seedTask(
task1Id, session1,
"TypeScript 防抖 debounce 与节流 throttle 实现",
`🎯 Goal
用 TypeScript 实现防抖 debounce 和节流 throttle 函数,并编写单元测试。
📋 Key Steps
- 实现 debounce 函数:支持 leading首次立即执行和 trailing延迟后执行两种模式通过 DebounceOptions 配置
- 实现 throttle 函数:通过时间戳间隔限制调用频率,支持尾调用
- 两个函数通过 export { debounce, throttle } 一起导出
- 使用 vitest 编写单元测试:测试 debounce 的延迟执行、测试 throttle 的频率限制
✅ Result
两个函数均已实现并通过测试,支持泛型类型推断,可直接导入使用。`,
"completed", task1Start, task1Start + 30 * MIN,
task1Chunks,
);
seedTask(
task2Id, session1,
"红烧肉做法与火候技巧",
`🎯 Goal
学做红烧肉,了解食材、步骤和火候要点。
📋 Key Steps
- 食材准备:五花肉 500g、冰糖 30g、生抽 2 勺、老抽 1 勺、料酒 2 勺、葱姜八角桂皮
- 制作流程:冷水焯水 → 小火炒冰糖至枣红色 → 五花肉翻炒上色 → 加调料和热水 → 小火炖 60-90 分钟 → 大火收汁
- 火候要点:炒糖色全程小火(大火会焦苦);炖煮保持小火微沸;最后 5 分钟大火收汁翻动防粘底
✅ Result
掌握了完整红烧肉做法。全程不加盐(生抽老抽已够)。压力锅可缩短至 25-30 分钟。`,
"completed", task2Start, task2Start + 15 * MIN,
task2Chunks,
);
seedTask(
task3Id, session2,
"React 长列表性能优化方案",
`🎯 Goal
优化 React 应用中几千条数据的列表渲染性能。
📋 Key Steps
- 方案 1最有效虚拟滚动使用 react-window 或 react-virtuoso只渲染可视区域 DOM
- 方案 2React.memo + useMemo 避免父组件重渲导致列表项全部重新渲染
- 方案 3key 使用稳定唯一值(如 id不用 index
- 方案 4分页或 cursor-based pagination不一次拉全量数据
- 方案 5状态下沉把 hover/selected 等频繁变化的状态放到子组件
- 对比 react-window6KB、底层、适合固定行高vs react-virtuoso30KB、高层、支持动态行高和分组
✅ Result
推荐大多数业务场景使用 react-virtuoso开箱即用追求极致性能且行高固定时用 react-window。`,
"completed", task3Start, task3Start + 20 * MIN,
task3Chunks,
);
seedTask(
task4Id, session3,
"Git 工作流Feature Branch vs Trunk-Based",
"",
"active", task4Start, null,
task4Chunks,
);
});
insertAll();
const taskCount = (db.prepare("SELECT COUNT(*) as c FROM tasks WHERE id IN (?,?,?,?)").get(task1Id, task2Id, task3Id, task4Id) as { c: number }).c;
const chunkCount = (db.prepare("SELECT COUNT(*) as c FROM chunks WHERE task_id IN (?,?,?,?)").get(task1Id, task2Id, task3Id, task4Id) as { c: number }).c;
console.log(`✅ 插入完成!`);
console.log(` Tasks: ${taskCount}3 completed + 1 active`);
console.log(` Chunks: ${chunkCount} 条记忆`);
console.log(``);
console.log(`📋 测试数据概览:`);
console.log(` Task 1: "TypeScript 防抖 debounce 与节流 throttle 实现" — completed, session=${session1}`);
console.log(` Task 2: "红烧肉做法与火候技巧" — completed, session=${session1}(同 session 换话题)`);
console.log(` Task 3: "React 长列表性能优化方案" — completed, session=${session2}(新 session`);
console.log(` Task 4: "Git 工作流Feature Branch vs Trunk-Based" — active, session=${session3}(进行中)`);
console.log(``);
console.log(`🌐 打开 Memory Viewer 查看: http://127.0.0.1:18799/`);
db.close();