| | class UserFeedbackStore { |
| | constructor() { |
| | this.dbName = 'ClipTaggerDB'; |
| | this.version = 1; |
| | this.db = null; |
| | } |
| |
|
| | async initialize() { |
| | return new Promise((resolve, reject) => { |
| | const request = indexedDB.open(this.dbName, this.version); |
| |
|
| | request.onerror = () => reject(request.error); |
| | request.onsuccess = () => { |
| | this.db = request.result; |
| | resolve(); |
| | }; |
| |
|
| | request.onupgradeneeded = (event) => { |
| | const db = event.target.result; |
| |
|
| | |
| | if (!db.objectStoreNames.contains('audioFeedback')) { |
| | const audioStore = db.createObjectStore('audioFeedback', { |
| | keyPath: 'id', |
| | autoIncrement: true |
| | }); |
| | audioStore.createIndex('timestamp', 'timestamp', { unique: false }); |
| | } |
| |
|
| | if (!db.objectStoreNames.contains('tagFeedback')) { |
| | const tagStore = db.createObjectStore('tagFeedback', { |
| | keyPath: 'id', |
| | autoIncrement: true |
| | }); |
| | tagStore.createIndex('tag', 'tag', { unique: false }); |
| | tagStore.createIndex('timestamp', 'timestamp', { unique: false }); |
| | } |
| |
|
| | if (!db.objectStoreNames.contains('customTags')) { |
| | const customTagStore = db.createObjectStore('customTags', { |
| | keyPath: 'tag' |
| | }); |
| | customTagStore.createIndex('usage', 'usage', { unique: false }); |
| | } |
| | }; |
| | }); |
| | } |
| |
|
| | async saveAudioFeedback(audioHash, originalTags, correctedTags, audioFeatures) { |
| | if (!this.db) await this.initialize(); |
| |
|
| | const transaction = this.db.transaction(['audioFeedback'], 'readwrite'); |
| | const store = transaction.objectStore('audioFeedback'); |
| |
|
| | const feedback = { |
| | audioHash, |
| | originalTags, |
| | correctedTags, |
| | audioFeatures, |
| | timestamp: Date.now() |
| | }; |
| |
|
| | return new Promise((resolve, reject) => { |
| | const request = store.add(feedback); |
| | request.onsuccess = () => resolve(request.result); |
| | request.onerror = () => reject(request.error); |
| | }); |
| | } |
| |
|
| | async saveTagFeedback(tag, feedback, audioHash) { |
| | if (!this.db) await this.initialize(); |
| |
|
| | const transaction = this.db.transaction(['tagFeedback'], 'readwrite'); |
| | const store = transaction.objectStore('tagFeedback'); |
| |
|
| | const tagFeedback = { |
| | tag, |
| | feedback, |
| | audioHash, |
| | timestamp: Date.now() |
| | }; |
| |
|
| | return new Promise((resolve, reject) => { |
| | const request = store.add(tagFeedback); |
| | request.onsuccess = () => resolve(request.result); |
| | request.onerror = () => reject(request.error); |
| | }); |
| | } |
| |
|
| | async saveCustomTag(tag) { |
| | if (!this.db) await this.initialize(); |
| |
|
| | const transaction = this.db.transaction(['customTags'], 'readwrite'); |
| | const store = transaction.objectStore('customTags'); |
| |
|
| | return new Promise((resolve, reject) => { |
| | const getRequest = store.get(tag); |
| | getRequest.onsuccess = () => { |
| | const existing = getRequest.result; |
| | const tagData = existing ? |
| | { ...existing, usage: existing.usage + 1 } : |
| | { tag, usage: 1, timestamp: Date.now() }; |
| |
|
| | const putRequest = store.put(tagData); |
| | putRequest.onsuccess = () => resolve(putRequest.result); |
| | putRequest.onerror = () => reject(putRequest.error); |
| | }; |
| | getRequest.onerror = () => reject(getRequest.error); |
| | }); |
| | } |
| |
|
| | async getCustomTags(limit = 20) { |
| | if (!this.db) await this.initialize(); |
| |
|
| | const transaction = this.db.transaction(['customTags'], 'readonly'); |
| | const store = transaction.objectStore('customTags'); |
| | const index = store.index('usage'); |
| |
|
| | return new Promise((resolve, reject) => { |
| | const request = index.openCursor(null, 'prev'); |
| | const results = []; |
| | |
| | request.onsuccess = (event) => { |
| | const cursor = event.target.result; |
| | if (cursor && results.length < limit) { |
| | results.push(cursor.value); |
| | cursor.continue(); |
| | } else { |
| | resolve(results); |
| | } |
| | }; |
| | request.onerror = () => reject(request.error); |
| | }); |
| | } |
| |
|
| | async getTagFeedback(tag = null) { |
| | if (!this.db) await this.initialize(); |
| |
|
| | const transaction = this.db.transaction(['tagFeedback'], 'readonly'); |
| | const store = transaction.objectStore('tagFeedback'); |
| |
|
| | return new Promise((resolve, reject) => { |
| | let request; |
| | if (tag) { |
| | const index = store.index('tag'); |
| | request = index.getAll(tag); |
| | } else { |
| | request = store.getAll(); |
| | } |
| |
|
| | request.onsuccess = () => resolve(request.result); |
| | request.onerror = () => reject(request.error); |
| | }); |
| | } |
| |
|
| | async getAudioFeedback(limit = 100) { |
| | if (!this.db) await this.initialize(); |
| |
|
| | const transaction = this.db.transaction(['audioFeedback'], 'readonly'); |
| | const store = transaction.objectStore('audioFeedback'); |
| | const index = store.index('timestamp'); |
| |
|
| | return new Promise((resolve, reject) => { |
| | const request = index.openCursor(null, 'prev'); |
| | const results = []; |
| | |
| | request.onsuccess = (event) => { |
| | const cursor = event.target.result; |
| | if (cursor && results.length < limit) { |
| | results.push(cursor.value); |
| | cursor.continue(); |
| | } else { |
| | resolve(results); |
| | } |
| | }; |
| | request.onerror = () => reject(request.error); |
| | }); |
| | } |
| |
|
| | |
| | async hashAudioFile(file) { |
| | const arrayBuffer = await file.arrayBuffer(); |
| | const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer); |
| | const hashArray = Array.from(new Uint8Array(hashBuffer)); |
| | return hashArray.map(b => b.toString(16).padStart(2, '0')).join('').substring(0, 16); |
| | } |
| |
|
| | async clearAllData() { |
| | if (!this.db) await this.initialize(); |
| |
|
| | const transaction = this.db.transaction(['audioFeedback', 'tagFeedback', 'customTags'], 'readwrite'); |
| | |
| | await Promise.all([ |
| | new Promise((resolve, reject) => { |
| | const request = transaction.objectStore('audioFeedback').clear(); |
| | request.onsuccess = () => resolve(); |
| | request.onerror = () => reject(request.error); |
| | }), |
| | new Promise((resolve, reject) => { |
| | const request = transaction.objectStore('tagFeedback').clear(); |
| | request.onsuccess = () => resolve(); |
| | request.onerror = () => reject(request.error); |
| | }), |
| | new Promise((resolve, reject) => { |
| | const request = transaction.objectStore('customTags').clear(); |
| | request.onsuccess = () => resolve(); |
| | request.onerror = () => reject(request.error); |
| | }) |
| | ]); |
| | } |
| | } |
| |
|
| | export default UserFeedbackStore; |