-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Implemented long term memory #1828
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| module.exports = { | ||
| preset: 'ts-jest', | ||
| testEnvironment: 'node', | ||
| transform: { | ||
| '^.+\\.tsx?$': 'ts-jest', | ||
| }, | ||
| moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| { | ||
| "name": "@onlook/memory", | ||
| "version": "0.1.0", | ||
| "private": true, | ||
| "main": "dist/index.js", | ||
| "module": "dist/index.mjs", | ||
| "types": "dist/index.d.ts", | ||
| "scripts": { | ||
| "build": "tsup", | ||
| "dev": "tsup --watch", | ||
| "test": "jest --config jest.config.js" | ||
| }, | ||
| "dependencies": { | ||
| "@onlook/types": "*", | ||
| "fs-extra": "^11.0.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/fs-extra": "^11.0.4", | ||
| "@types/jest": "^29.5.14", | ||
| "jest": "^29.7.0", | ||
| "ts-jest": "^29.3.2", | ||
| "tsup": "^8.0.2", | ||
| "typescript": "^5.3.3" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| import { LongTermMemory, Rule } from './index'; | ||
| import fs from 'fs-extra'; | ||
| import path from 'path'; | ||
|
|
||
| describe('LongTermMemory', () => { | ||
| let memory: LongTermMemory; | ||
| const testRulesDir = path.join(process.cwd(), 'test-rules'); | ||
|
|
||
| beforeEach(async () => { | ||
| await fs.remove(testRulesDir); | ||
| memory = new LongTermMemory(testRulesDir); | ||
| }); | ||
|
|
||
| afterEach(async () => { | ||
| await fs.remove(testRulesDir); | ||
| }); | ||
|
|
||
| it('should initialize with empty rules', () => { | ||
| expect(memory.getAllRules()).toEqual([]); | ||
| }); | ||
|
|
||
| it('should add a new rule', async () => { | ||
| const rule: Omit<Rule, 'id' | 'createdAt' | 'updatedAt'> = { | ||
| content: 'Test rule content', | ||
| tags: ['test'] | ||
| }; | ||
|
|
||
| const addedRule = await memory.addRule(rule); | ||
| expect(addedRule).toMatchObject({ | ||
| content: rule.content, | ||
| tags: rule.tags | ||
| }); | ||
| expect(addedRule.id).toBeDefined(); | ||
| expect(addedRule.createdAt).toBeInstanceOf(Date); | ||
| expect(addedRule.updatedAt).toBeInstanceOf(Date); | ||
|
|
||
| const filePath = path.join(testRulesDir, `${addedRule.id}.json`); | ||
| expect(await fs.pathExists(filePath)).toBe(true); | ||
| }); | ||
|
|
||
| it('should update an existing rule', async () => { | ||
| const rule = await memory.addRule({ | ||
| content: 'Original content', | ||
| tags: ['test'] | ||
| }); | ||
|
|
||
| const updatedRule = await memory.updateRule(rule.id, { | ||
| content: 'Updated content' | ||
| }); | ||
|
|
||
| expect(updatedRule).not.toBeNull(); | ||
| expect(updatedRule?.content).toBe('Updated content'); | ||
| expect(updatedRule?.tags).toEqual(['test']); | ||
| }); | ||
|
|
||
| it('should delete a rule', async () => { | ||
| const rule = await memory.addRule({ | ||
| content: 'Test rule', | ||
| tags: ['test'] | ||
| }); | ||
|
|
||
| const deleted = await memory.deleteRule(rule.id); | ||
| expect(deleted).toBe(true); | ||
| expect(memory.getRule(rule.id)).toBeNull(); | ||
|
|
||
| const filePath = path.join(testRulesDir, `${rule.id}.json`); | ||
| expect(await fs.pathExists(filePath)).toBe(false); | ||
| }); | ||
|
|
||
| it('should get rules by tag', async () => { | ||
| await memory.addRule({ | ||
| content: 'Rule 1', | ||
| tags: ['test'] | ||
| }); | ||
|
|
||
| await memory.addRule({ | ||
| content: 'Rule 2', | ||
| tags: ['test'] | ||
| }); | ||
|
|
||
| await memory.addRule({ | ||
| content: 'Rule 3', | ||
| tags: ['other'] | ||
| }); | ||
|
|
||
| const testRules = memory.getRulesByTag('test'); | ||
| expect(testRules).toHaveLength(2); | ||
| expect(testRules.every(rule => rule.tags?.includes('test'))).toBe(true); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| import fs from 'fs-extra'; | ||
| import path from 'path'; | ||
|
|
||
| export interface Rule { | ||
| id: string; | ||
| content: string; | ||
| createdAt: Date; | ||
| updatedAt: Date; | ||
| tags?: string[]; | ||
| } | ||
|
|
||
| export class LongTermMemory { | ||
| private rulesDir: string; | ||
| private rules: Map<string, Rule>; | ||
|
|
||
| constructor(rulesDir: string = path.join(process.cwd(), 'rules')) { | ||
| this.rulesDir = rulesDir; | ||
| this.rules = new Map(); | ||
| this.initialize(); | ||
| } | ||
|
|
||
| private async initialize() { | ||
| try { | ||
| await fs.ensureDir(this.rulesDir); | ||
| await this.loadRules(); | ||
| } catch (error) { | ||
| console.error('Failed to initialize long-term memory:', error); | ||
| } | ||
| } | ||
|
|
||
| private async loadRules() { | ||
| try { | ||
| const files = await fs.readdir(this.rulesDir); | ||
| for (const file of files) { | ||
| if (file.endsWith('.json')) { | ||
| const rulePath = path.join(this.rulesDir, file); | ||
| const ruleData = await fs.readJson(rulePath); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider validating the structure of |
||
| this.rules.set(ruleData.id, { | ||
| ...ruleData, | ||
| createdAt: new Date(ruleData.createdAt), | ||
| updatedAt: new Date(ruleData.updatedAt) | ||
| }); | ||
| } | ||
| } | ||
| } catch (error) { | ||
| console.error('Failed to load rules:', error); | ||
| } | ||
| } | ||
|
|
||
| async addRule(rule: Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>): Promise<Rule> { | ||
| const newRule: Rule = { | ||
| ...rule, | ||
| id: crypto.randomUUID(), | ||
SoloDevAbu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| createdAt: new Date(), | ||
| updatedAt: new Date() | ||
| }; | ||
|
|
||
| const rulePath = path.join(this.rulesDir, `${newRule.id}.json`); | ||
| await fs.writeJson(rulePath, newRule); | ||
| this.rules.set(newRule.id, newRule); | ||
| return newRule; | ||
| } | ||
|
|
||
| async updateRule(id: string, updates: Partial<Omit<Rule, 'id' | 'createdAt'>>): Promise<Rule | null> { | ||
| const existingRule = this.rules.get(id); | ||
| if (!existingRule) return null; | ||
|
|
||
| const updatedRule: Rule = { | ||
| ...existingRule, | ||
| ...updates, | ||
| updatedAt: new Date() | ||
| }; | ||
|
|
||
| const rulePath = path.join(this.rulesDir, `${id}.json`); | ||
| await fs.writeJson(rulePath, updatedRule); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrap |
||
| this.rules.set(id, updatedRule); | ||
| return updatedRule; | ||
| } | ||
|
|
||
| async deleteRule(id: string): Promise<boolean> { | ||
| const rulePath = path.join(this.rulesDir, `${id}.json`); | ||
| try { | ||
| await fs.remove(rulePath); | ||
| this.rules.delete(id); | ||
| return true; | ||
| } catch (error) { | ||
| console.error('Failed to delete rule:', error); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| getRule(id: string): Rule | null { | ||
| return this.rules.get(id) || null; | ||
| } | ||
|
|
||
| getAllRules(): Rule[] { | ||
| return Array.from(this.rules.values()); | ||
| } | ||
|
|
||
| getRulesByTag(tag: string): Rule[] { | ||
| return Array.from(this.rules.values()).filter(rule => | ||
| rule.tags?.includes(tag) | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "outDir": "dist", | ||
| "rootDir": "src", | ||
| "esModuleInterop": true | ||
| }, | ||
| "include": ["src/**/*"], | ||
| "exclude": ["node_modules", "dist", "**/*.test.ts"] | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.