fix dependency
This commit is contained in:
parent
86e903ec4b
commit
427be2a293
1
.npmrc
1
.npmrc
@ -1,3 +1,2 @@
|
||||
better-sqlite3:ignore-scripts=true
|
||||
electron-deeplink:ignore-scripts=true
|
||||
sharp:ignore-scripts=true
|
@ -10,6 +10,7 @@
|
||||
"package": "npm run build:renderer && electron-forge package",
|
||||
"make": "npm run build:renderer && electron-forge make",
|
||||
"build": "npm run build:renderer && electron-builder --config electron-builder.yml --publish never",
|
||||
"build:win": "npm run build:renderer && electron-builder --win --x64 --publish never",
|
||||
"publish": "npm run build:renderer && electron-builder --config electron-builder.yml --publish always",
|
||||
"lint": "eslint --ext .ts,.tsx,.js .",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
@ -35,7 +36,7 @@
|
||||
"better-sqlite3": "^9.4.3",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.0.0",
|
||||
"electron-deeplink": "^1.0.10",
|
||||
|
||||
"electron-squirrel-startup": "^1.0.1",
|
||||
"electron-store": "^8.2.0",
|
||||
"electron-updater": "^6.6.2",
|
||||
@ -47,7 +48,6 @@
|
||||
"openai": "^4.70.0",
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"sharp": "^0.34.2",
|
||||
"sqlite3": "^5.1.7",
|
||||
"validator": "^13.11.0",
|
||||
"wait-on": "^8.0.3",
|
||||
"ws": "^8.18.0"
|
||||
@ -69,8 +69,6 @@
|
||||
"esbuild": "^0.25.5"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-x64": "^0.34.2",
|
||||
"@img/sharp-libvips-darwin-x64": "^1.1.0",
|
||||
"electron-liquid-glass": "^1.0.1"
|
||||
}
|
||||
}
|
@ -2,40 +2,34 @@ const sqliteClient = require('../../services/sqliteClient');
|
||||
|
||||
function getPresets(uid) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = `
|
||||
SELECT * FROM prompt_presets
|
||||
WHERE uid = ? OR is_default = 1
|
||||
ORDER BY is_default DESC, title ASC
|
||||
`;
|
||||
db.all(query, [uid], (err, rows) => {
|
||||
if (err) {
|
||||
console.error('SQLite: Failed to get presets:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows);
|
||||
}
|
||||
});
|
||||
});
|
||||
const query = `
|
||||
SELECT * FROM prompt_presets
|
||||
WHERE uid = ? OR is_default = 1
|
||||
ORDER BY is_default DESC, title ASC
|
||||
`;
|
||||
|
||||
try {
|
||||
return db.prepare(query).all(uid);
|
||||
} catch (err) {
|
||||
console.error('SQLite: Failed to get presets:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function getPresetTemplates() {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = `
|
||||
SELECT * FROM prompt_presets
|
||||
WHERE is_default = 1
|
||||
ORDER BY title ASC
|
||||
`;
|
||||
db.all(query, [], (err, rows) => {
|
||||
if (err) {
|
||||
console.error('SQLite: Failed to get preset templates:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows);
|
||||
}
|
||||
});
|
||||
});
|
||||
const query = `
|
||||
SELECT * FROM prompt_presets
|
||||
WHERE is_default = 1
|
||||
ORDER BY title ASC
|
||||
`;
|
||||
|
||||
try {
|
||||
return db.prepare(query).all();
|
||||
} catch (err) {
|
||||
console.error('SQLite: Failed to get preset templates:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function create({ uid, title, prompt }) {
|
||||
@ -44,38 +38,42 @@ function create({ uid, title, prompt }) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `INSERT INTO prompt_presets (id, uid, title, prompt, is_default, created_at, sync_state) VALUES (?, ?, ?, ?, 0, ?, 'dirty')`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(query, [presetId, uid, title, prompt, now], function(err) {
|
||||
if (err) reject(err);
|
||||
else resolve({ id: presetId });
|
||||
});
|
||||
});
|
||||
try {
|
||||
db.prepare(query).run(presetId, uid, title, prompt, now);
|
||||
return { id: presetId };
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function update(id, { title, prompt }, uid) {
|
||||
const db = sqliteClient.getDb();
|
||||
const query = `UPDATE prompt_presets SET title = ?, prompt = ?, sync_state = 'dirty' WHERE id = ? AND uid = ? AND is_default = 0`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(query, [title, prompt, id, uid], function(err) {
|
||||
if (err) reject(err);
|
||||
else if (this.changes === 0) reject(new Error("Preset not found or permission denied."));
|
||||
else resolve({ changes: this.changes });
|
||||
});
|
||||
});
|
||||
try {
|
||||
const result = db.prepare(query).run(title, prompt, id, uid);
|
||||
if (result.changes === 0) {
|
||||
throw new Error("Preset not found or permission denied.");
|
||||
}
|
||||
return { changes: result.changes };
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function del(id, uid) {
|
||||
const db = sqliteClient.getDb();
|
||||
const query = `DELETE FROM prompt_presets WHERE id = ? AND uid = ? AND is_default = 0`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(query, [id, uid], function(err) {
|
||||
if (err) reject(err);
|
||||
else if (this.changes === 0) reject(new Error("Preset not found or permission denied."));
|
||||
else resolve({ changes: this.changes });
|
||||
});
|
||||
});
|
||||
try {
|
||||
const result = db.prepare(query).run(id, uid);
|
||||
if (result.changes === 0) {
|
||||
throw new Error("Preset not found or permission denied.");
|
||||
}
|
||||
return { changes: result.changes };
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -2,124 +2,79 @@ const sqliteClient = require('../../services/sqliteClient');
|
||||
|
||||
function getById(id) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get('SELECT * FROM sessions WHERE id = ?', [id], (err, row) => {
|
||||
if (err) reject(err);
|
||||
else resolve(row);
|
||||
});
|
||||
});
|
||||
return db.prepare('SELECT * FROM sessions WHERE id = ?').get(id);
|
||||
}
|
||||
|
||||
function create(uid, type = 'ask') {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const sessionId = require('crypto').randomUUID();
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `INSERT INTO sessions (id, uid, title, session_type, started_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`;
|
||||
const sessionId = require('crypto').randomUUID();
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `INSERT INTO sessions (id, uid, title, session_type, started_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`;
|
||||
|
||||
db.run(query, [sessionId, uid, `Session @ ${new Date().toLocaleTimeString()}`, type, now, now], function(err) {
|
||||
if (err) {
|
||||
console.error('SQLite: Failed to create session:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
console.log(`SQLite: Created session ${sessionId} for user ${uid} (type: ${type})`);
|
||||
resolve(sessionId);
|
||||
}
|
||||
});
|
||||
});
|
||||
try {
|
||||
db.prepare(query).run(sessionId, uid, `Session @ ${new Date().toLocaleTimeString()}`, type, now, now);
|
||||
console.log(`SQLite: Created session ${sessionId} for user ${uid} (type: ${type})`);
|
||||
return sessionId;
|
||||
} catch (err) {
|
||||
console.error('SQLite: Failed to create session:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function getAllByUserId(uid) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = "SELECT id, uid, title, session_type, started_at, ended_at, sync_state, updated_at FROM sessions WHERE uid = ? ORDER BY started_at DESC";
|
||||
db.all(query, [uid], (err, rows) => {
|
||||
if (err) reject(err);
|
||||
else resolve(rows);
|
||||
});
|
||||
});
|
||||
const query = "SELECT id, uid, title, session_type, started_at, ended_at, sync_state, updated_at FROM sessions WHERE uid = ? ORDER BY started_at DESC";
|
||||
return db.prepare(query).all(uid);
|
||||
}
|
||||
|
||||
function updateTitle(id, title) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run('UPDATE sessions SET title = ? WHERE id = ?', [title, id], function(err) {
|
||||
if (err) reject(err);
|
||||
else resolve({ changes: this.changes });
|
||||
});
|
||||
});
|
||||
const result = db.prepare('UPDATE sessions SET title = ? WHERE id = ?').run(title, id);
|
||||
return { changes: result.changes };
|
||||
}
|
||||
|
||||
function deleteWithRelatedData(id) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.serialize(() => {
|
||||
db.run("BEGIN TRANSACTION;");
|
||||
const queries = [
|
||||
"DELETE FROM transcripts WHERE session_id = ?",
|
||||
"DELETE FROM ai_messages WHERE session_id = ?",
|
||||
"DELETE FROM summaries WHERE session_id = ?",
|
||||
"DELETE FROM sessions WHERE id = ?"
|
||||
];
|
||||
queries.forEach(query => {
|
||||
db.run(query, [id], (err) => {
|
||||
if (err) {
|
||||
db.run("ROLLBACK;");
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
db.run("COMMIT;", (err) => {
|
||||
if (err) {
|
||||
db.run("ROLLBACK;");
|
||||
return reject(err);
|
||||
}
|
||||
resolve({ success: true });
|
||||
});
|
||||
});
|
||||
const transaction = db.transaction(() => {
|
||||
db.prepare("DELETE FROM transcripts WHERE session_id = ?").run(id);
|
||||
db.prepare("DELETE FROM ai_messages WHERE session_id = ?").run(id);
|
||||
db.prepare("DELETE FROM summaries WHERE session_id = ?").run(id);
|
||||
db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
|
||||
});
|
||||
|
||||
try {
|
||||
transaction();
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function end(id) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `UPDATE sessions SET ended_at = ?, updated_at = ? WHERE id = ?`;
|
||||
db.run(query, [now, now, id], function(err) {
|
||||
if (err) reject(err);
|
||||
else resolve({ changes: this.changes });
|
||||
});
|
||||
});
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `UPDATE sessions SET ended_at = ?, updated_at = ? WHERE id = ?`;
|
||||
const result = db.prepare(query).run(now, now, id);
|
||||
return { changes: result.changes };
|
||||
}
|
||||
|
||||
function updateType(id, type) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = 'UPDATE sessions SET session_type = ?, updated_at = ? WHERE id = ?';
|
||||
db.run(query, [type, now, id], function(err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({ changes: this.changes });
|
||||
}
|
||||
});
|
||||
});
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = 'UPDATE sessions SET session_type = ?, updated_at = ? WHERE id = ?';
|
||||
const result = db.prepare(query).run(type, now, id);
|
||||
return { changes: result.changes };
|
||||
}
|
||||
|
||||
function touch(id) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = 'UPDATE sessions SET updated_at = ? WHERE id = ?';
|
||||
db.run(query, [now, id], function(err) {
|
||||
if (err) reject(err);
|
||||
else resolve({ changes: this.changes });
|
||||
});
|
||||
});
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = 'UPDATE sessions SET updated_at = ? WHERE id = ?';
|
||||
const result = db.prepare(query).run(now, id);
|
||||
return { changes: result.changes };
|
||||
}
|
||||
|
||||
async function getOrCreateActive(uid, requestedType = 'ask') {
|
||||
function getOrCreateActive(uid, requestedType = 'ask') {
|
||||
const db = sqliteClient.getDb();
|
||||
|
||||
// 1. Look for ANY active session for the user (ended_at IS NULL).
|
||||
@ -131,12 +86,7 @@ async function getOrCreateActive(uid, requestedType = 'ask') {
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
const activeSession = await new Promise((resolve, reject) => {
|
||||
db.get(findQuery, [uid], (err, row) => {
|
||||
if (err) reject(err);
|
||||
else resolve(row);
|
||||
});
|
||||
});
|
||||
const activeSession = db.prepare(findQuery).get(uid);
|
||||
|
||||
if (activeSession) {
|
||||
// An active session exists.
|
||||
@ -144,12 +94,12 @@ async function getOrCreateActive(uid, requestedType = 'ask') {
|
||||
|
||||
// 2. Promotion Logic: If it's an 'ask' session and we need 'listen', promote it.
|
||||
if (activeSession.session_type === 'ask' && requestedType === 'listen') {
|
||||
await updateType(activeSession.id, 'listen');
|
||||
updateType(activeSession.id, 'listen');
|
||||
console.log(`[Repo] Promoted session ${activeSession.id} to 'listen' type.`);
|
||||
}
|
||||
|
||||
// 3. Touch the session and return its ID.
|
||||
await touch(activeSession.id);
|
||||
touch(activeSession.id);
|
||||
return activeSession.id;
|
||||
} else {
|
||||
// 4. No active session found, create a new one.
|
||||
@ -160,19 +110,17 @@ async function getOrCreateActive(uid, requestedType = 'ask') {
|
||||
|
||||
function endAllActiveSessions() {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `UPDATE sessions SET ended_at = ?, updated_at = ? WHERE ended_at IS NULL`;
|
||||
db.run(query, [now, now], function(err) {
|
||||
if (err) {
|
||||
console.error('SQLite: Failed to end all active sessions:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
console.log(`[Repo] Ended ${this.changes} active session(s).`);
|
||||
resolve({ changes: this.changes });
|
||||
}
|
||||
});
|
||||
});
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `UPDATE sessions SET ended_at = ?, updated_at = ? WHERE ended_at IS NULL`;
|
||||
|
||||
try {
|
||||
const result = db.prepare(query).run(now, now);
|
||||
console.log(`[Repo] Ended ${result.changes} active session(s).`);
|
||||
return { changes: result.changes };
|
||||
} catch (err) {
|
||||
console.error('SQLite: Failed to end all active sessions:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -13,89 +13,61 @@ function findOrCreate(user) {
|
||||
email=excluded.email
|
||||
`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(query, [uid, displayName, email, now], (err) => {
|
||||
if (err) {
|
||||
console.error('SQLite: Failed to find or create user:', err);
|
||||
return reject(err);
|
||||
}
|
||||
getById(uid).then(resolve).catch(reject);
|
||||
});
|
||||
});
|
||||
try {
|
||||
db.prepare(query).run(uid, displayName, email, now);
|
||||
return getById(uid);
|
||||
} catch (err) {
|
||||
console.error('SQLite: Failed to find or create user:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function getById(uid) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get('SELECT * FROM users WHERE uid = ?', [uid], (err, row) => {
|
||||
if (err) reject(err);
|
||||
else resolve(row);
|
||||
});
|
||||
});
|
||||
return db.prepare('SELECT * FROM users WHERE uid = ?').get(uid);
|
||||
}
|
||||
|
||||
function saveApiKey(apiKey, uid, provider = 'openai') {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
'UPDATE users SET api_key = ?, provider = ? WHERE uid = ?',
|
||||
[apiKey, provider, uid],
|
||||
function(err) {
|
||||
if (err) {
|
||||
console.error('SQLite: Failed to save API key:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
console.log(`SQLite: API key saved for user ${uid} with provider ${provider}.`);
|
||||
resolve({ changes: this.changes });
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
try {
|
||||
const result = db.prepare('UPDATE users SET api_key = ?, provider = ? WHERE uid = ?').run(apiKey, provider, uid);
|
||||
console.log(`SQLite: API key saved for user ${uid} with provider ${provider}.`);
|
||||
return { changes: result.changes };
|
||||
} catch (err) {
|
||||
console.error('SQLite: Failed to save API key:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function update({ uid, displayName }) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run('UPDATE users SET display_name = ? WHERE uid = ?', [displayName, uid], function(err) {
|
||||
if (err) reject(err);
|
||||
else resolve({ changes: this.changes });
|
||||
});
|
||||
});
|
||||
const result = db.prepare('UPDATE users SET display_name = ? WHERE uid = ?').run(displayName, uid);
|
||||
return { changes: result.changes };
|
||||
}
|
||||
|
||||
function deleteById(uid) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const userSessions = db.prepare('SELECT id FROM sessions WHERE uid = ?').all(uid);
|
||||
const sessionIds = userSessions.map(s => s.id);
|
||||
const userSessions = db.prepare('SELECT id FROM sessions WHERE uid = ?').all(uid);
|
||||
const sessionIds = userSessions.map(s => s.id);
|
||||
|
||||
db.serialize(() => {
|
||||
db.run("BEGIN TRANSACTION;");
|
||||
|
||||
try {
|
||||
if (sessionIds.length > 0) {
|
||||
const placeholders = sessionIds.map(() => '?').join(',');
|
||||
db.prepare(`DELETE FROM transcripts WHERE session_id IN (${placeholders})`).run(...sessionIds);
|
||||
db.prepare(`DELETE FROM ai_messages WHERE session_id IN (${placeholders})`).run(...sessionIds);
|
||||
db.prepare(`DELETE FROM summaries WHERE session_id IN (${placeholders})`).run(...sessionIds);
|
||||
db.prepare(`DELETE FROM sessions WHERE uid = ?`).run(uid);
|
||||
}
|
||||
db.prepare('DELETE FROM prompt_presets WHERE uid = ? AND is_default = 0').run(uid);
|
||||
db.prepare('DELETE FROM users WHERE uid = ?').run(uid);
|
||||
|
||||
db.run("COMMIT;", (err) => {
|
||||
if (err) {
|
||||
db.run("ROLLBACK;");
|
||||
return reject(err);
|
||||
}
|
||||
resolve({ success: true });
|
||||
});
|
||||
} catch (err) {
|
||||
db.run("ROLLBACK;");
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
const transaction = db.transaction(() => {
|
||||
if (sessionIds.length > 0) {
|
||||
const placeholders = sessionIds.map(() => '?').join(',');
|
||||
db.prepare(`DELETE FROM transcripts WHERE session_id IN (${placeholders})`).run(...sessionIds);
|
||||
db.prepare(`DELETE FROM ai_messages WHERE session_id IN (${placeholders})`).run(...sessionIds);
|
||||
db.prepare(`DELETE FROM summaries WHERE session_id IN (${placeholders})`).run(...sessionIds);
|
||||
db.prepare(`DELETE FROM sessions WHERE uid = ?`).run(uid);
|
||||
}
|
||||
db.prepare('DELETE FROM prompt_presets WHERE uid = ? AND is_default = 0').run(uid);
|
||||
db.prepare('DELETE FROM users WHERE uid = ?').run(uid);
|
||||
});
|
||||
|
||||
try {
|
||||
transaction();
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -123,7 +123,7 @@ class AuthService {
|
||||
const userState = this.getCurrentUser();
|
||||
console.log('[AuthService] Broadcasting user state change:', userState);
|
||||
BrowserWindow.getAllWindows().forEach(win => {
|
||||
if (win && !win.isDestroyed()) {
|
||||
if (win && !win.isDestroyed() && win.webContents && !win.webContents.isDestroyed()) {
|
||||
win.webContents.send('user-state-changed', userState);
|
||||
}
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
const LATEST_SCHEMA = require('../config/schema');
|
||||
|
||||
@ -10,28 +10,20 @@ class SQLiteClient {
|
||||
}
|
||||
|
||||
connect(dbPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.db) {
|
||||
console.log('[SQLiteClient] Already connected.');
|
||||
return resolve();
|
||||
}
|
||||
if (this.db) {
|
||||
console.log('[SQLiteClient] Already connected.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.dbPath = dbPath;
|
||||
this.db = new sqlite3.Database(this.dbPath, (err) => {
|
||||
if (err) {
|
||||
console.error('[SQLiteClient] Could not connect to database', err);
|
||||
return reject(err);
|
||||
}
|
||||
console.log('[SQLiteClient] Connected successfully to:', this.dbPath);
|
||||
|
||||
this.db.run('PRAGMA journal_mode = WAL;', (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
this.db = new Database(this.dbPath);
|
||||
this.db.pragma('journal_mode = WAL');
|
||||
console.log('[SQLiteClient] Connected successfully to:', this.dbPath);
|
||||
} catch (err) {
|
||||
console.error('[SQLiteClient] Could not connect to database', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
getDb() {
|
||||
@ -41,80 +33,56 @@ class SQLiteClient {
|
||||
return this.db;
|
||||
}
|
||||
|
||||
async synchronizeSchema() {
|
||||
synchronizeSchema() {
|
||||
console.log('[DB Sync] Starting schema synchronization...');
|
||||
const tablesInDb = await this.getTablesFromDb();
|
||||
const tablesInDb = this.getTablesFromDb();
|
||||
|
||||
for (const tableName of Object.keys(LATEST_SCHEMA)) {
|
||||
const tableSchema = LATEST_SCHEMA[tableName];
|
||||
|
||||
if (!tablesInDb.includes(tableName)) {
|
||||
// Table doesn't exist, create it
|
||||
await this.createTable(tableName, tableSchema);
|
||||
this.createTable(tableName, tableSchema);
|
||||
} else {
|
||||
// Table exists, check for missing columns
|
||||
await this.updateTable(tableName, tableSchema);
|
||||
this.updateTable(tableName, tableSchema);
|
||||
}
|
||||
}
|
||||
console.log('[DB Sync] Schema synchronization finished.');
|
||||
}
|
||||
|
||||
async getTablesFromDb() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.all("SELECT name FROM sqlite_master WHERE type='table'", (err, tables) => {
|
||||
if (err) return reject(err);
|
||||
resolve(tables.map(t => t.name));
|
||||
});
|
||||
});
|
||||
getTablesFromDb() {
|
||||
const tables = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();
|
||||
return tables.map(t => t.name);
|
||||
}
|
||||
|
||||
async createTable(tableName, tableSchema) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const columnDefs = tableSchema.columns.map(col => `"${col.name}" ${col.type}`).join(', ');
|
||||
const query = `CREATE TABLE IF NOT EXISTS "${tableName}" (${columnDefs})`;
|
||||
createTable(tableName, tableSchema) {
|
||||
const columnDefs = tableSchema.columns.map(col => `"${col.name}" ${col.type}`).join(', ');
|
||||
const query = `CREATE TABLE IF NOT EXISTS "${tableName}" (${columnDefs})`;
|
||||
|
||||
console.log(`[DB Sync] Creating table: ${tableName}`);
|
||||
this.db.run(query, (err) => {
|
||||
if (err) return reject(err);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
console.log(`[DB Sync] Creating table: ${tableName}`);
|
||||
this.db.prepare(query).run();
|
||||
}
|
||||
|
||||
async updateTable(tableName, tableSchema) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.all(`PRAGMA table_info("${tableName}")`, async (err, existingColumns) => {
|
||||
if (err) return reject(err);
|
||||
updateTable(tableName, tableSchema) {
|
||||
const existingColumns = this.db.prepare(`PRAGMA table_info("${tableName}")`).all();
|
||||
const existingColumnNames = existingColumns.map(c => c.name);
|
||||
const columnsToAdd = tableSchema.columns.filter(col => !existingColumnNames.includes(col.name));
|
||||
|
||||
const existingColumnNames = existingColumns.map(c => c.name);
|
||||
const columnsToAdd = tableSchema.columns.filter(col => !existingColumnNames.includes(col.name));
|
||||
|
||||
if (columnsToAdd.length > 0) {
|
||||
console.log(`[DB Sync] Updating table: ${tableName}. Adding columns: ${columnsToAdd.map(c=>c.name).join(', ')}`);
|
||||
for (const column of columnsToAdd) {
|
||||
const addColumnQuery = `ALTER TABLE "${tableName}" ADD COLUMN "${column.name}" ${column.type}`;
|
||||
try {
|
||||
await this.runQuery(addColumnQuery);
|
||||
} catch (alterErr) {
|
||||
return reject(alterErr);
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
if (columnsToAdd.length > 0) {
|
||||
console.log(`[DB Sync] Updating table: ${tableName}. Adding columns: ${columnsToAdd.map(c=>c.name).join(', ')}`);
|
||||
for (const column of columnsToAdd) {
|
||||
const addColumnQuery = `ALTER TABLE "${tableName}" ADD COLUMN "${column.name}" ${column.type}`;
|
||||
this.db.prepare(addColumnQuery).run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async runQuery(query, params = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.run(query, params, function(err) {
|
||||
if (err) return reject(err);
|
||||
resolve(this);
|
||||
});
|
||||
});
|
||||
runQuery(query, params = []) {
|
||||
return this.db.prepare(query).run(params);
|
||||
}
|
||||
|
||||
async cleanupEmptySessions() {
|
||||
cleanupEmptySessions() {
|
||||
console.log('[DB Cleanup] Checking for empty sessions...');
|
||||
const query = `
|
||||
SELECT s.id FROM sessions s
|
||||
@ -124,92 +92,65 @@ class SQLiteClient {
|
||||
WHERE t.id IS NULL AND a.id IS NULL AND su.session_id IS NULL
|
||||
`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.all(query, [], (err, rows) => {
|
||||
if (err) {
|
||||
console.error('[DB Cleanup] Error finding empty sessions:', err);
|
||||
return reject(err);
|
||||
}
|
||||
const rows = this.db.prepare(query).all();
|
||||
|
||||
if (rows.length === 0) {
|
||||
console.log('[DB Cleanup] No empty sessions found.');
|
||||
return resolve();
|
||||
}
|
||||
if (rows.length === 0) {
|
||||
console.log('[DB Cleanup] No empty sessions found.');
|
||||
return;
|
||||
}
|
||||
|
||||
const idsToDelete = rows.map(r => r.id);
|
||||
const placeholders = idsToDelete.map(() => '?').join(',');
|
||||
const deleteQuery = `DELETE FROM sessions WHERE id IN (${placeholders})`;
|
||||
const idsToDelete = rows.map(r => r.id);
|
||||
const placeholders = idsToDelete.map(() => '?').join(',');
|
||||
const deleteQuery = `DELETE FROM sessions WHERE id IN (${placeholders})`;
|
||||
|
||||
console.log(`[DB Cleanup] Found ${idsToDelete.length} empty sessions. Deleting...`);
|
||||
this.db.run(deleteQuery, idsToDelete, function(deleteErr) {
|
||||
if (deleteErr) {
|
||||
console.error('[DB Cleanup] Error deleting empty sessions:', deleteErr);
|
||||
return reject(deleteErr);
|
||||
}
|
||||
console.log(`[DB Cleanup] Successfully deleted ${this.changes} empty sessions.`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
console.log(`[DB Cleanup] Found ${idsToDelete.length} empty sessions. Deleting...`);
|
||||
const result = this.db.prepare(deleteQuery).run(idsToDelete);
|
||||
console.log(`[DB Cleanup] Successfully deleted ${result.changes} empty sessions.`);
|
||||
}
|
||||
|
||||
async initTables() {
|
||||
await this.synchronizeSchema();
|
||||
await this.initDefaultData();
|
||||
initTables() {
|
||||
this.synchronizeSchema();
|
||||
this.initDefaultData();
|
||||
}
|
||||
|
||||
async initDefaultData() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const initUserQuery = `
|
||||
INSERT OR IGNORE INTO users (uid, display_name, email, created_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`;
|
||||
initDefaultData() {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const initUserQuery = `
|
||||
INSERT OR IGNORE INTO users (uid, display_name, email, created_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
this.db.run(initUserQuery, [this.defaultUserId, 'Default User', 'contact@pickle.com', now], (err) => {
|
||||
if (err) {
|
||||
console.error('Failed to initialize default user:', err);
|
||||
return reject(err);
|
||||
}
|
||||
this.db.prepare(initUserQuery).run(this.defaultUserId, 'Default User', 'contact@pickle.com', now);
|
||||
|
||||
const defaultPresets = [
|
||||
['school', 'School', 'You are a school and lecture assistant. Your goal is to help the user, a student, understand academic material and answer questions.\n\nWhenever a question appears on the user\'s screen or is asked aloud, you provide a direct, step-by-step answer, showing all necessary reasoning or calculations.\n\nIf the user is watching a lecture or working through new material, you offer concise explanations of key concepts and clarify definitions as they come up.', 1],
|
||||
['meetings', 'Meetings', 'You are a meeting assistant. Your goal is to help the user capture key information during meetings and follow up effectively.\n\nYou help capture meeting notes, track action items, identify key decisions, and summarize important points discussed during meetings.', 1],
|
||||
['sales', 'Sales', 'You are a real-time AI sales assistant, and your goal is to help the user close deals during sales interactions.\n\nYou provide real-time sales support, suggest responses to objections, help identify customer needs, and recommend strategies to advance deals.', 1],
|
||||
['recruiting', 'Recruiting', 'You are a recruiting assistant. Your goal is to help the user interview candidates and evaluate talent effectively.\n\nYou help evaluate candidates, suggest interview questions, analyze responses, and provide insights about candidate fit for positions.', 1],
|
||||
['customer-support', 'Customer Support', 'You are a customer support assistant. Your goal is to help resolve customer issues efficiently and thoroughly.\n\nYou help diagnose customer problems, suggest solutions, provide step-by-step troubleshooting guidance, and ensure customer satisfaction.', 1],
|
||||
];
|
||||
const defaultPresets = [
|
||||
['school', 'School', 'You are a school and lecture assistant. Your goal is to help the user, a student, understand academic material and answer questions.\n\nWhenever a question appears on the user\'s screen or is asked aloud, you provide a direct, step-by-step answer, showing all necessary reasoning or calculations.\n\nIf the user is watching a lecture or working through new material, you offer concise explanations of key concepts and clarify definitions as they come up.', 1],
|
||||
['meetings', 'Meetings', 'You are a meeting assistant. Your goal is to help the user capture key information during meetings and follow up effectively.\n\nYou help capture meeting notes, track action items, identify key decisions, and summarize important points discussed during meetings.', 1],
|
||||
['sales', 'Sales', 'You are a real-time AI sales assistant, and your goal is to help the user close deals during sales interactions.\n\nYou provide real-time sales support, suggest responses to objections, help identify customer needs, and recommend strategies to advance deals.', 1],
|
||||
['recruiting', 'Recruiting', 'You are a recruiting assistant. Your goal is to help the user interview candidates and evaluate talent effectively.\n\nYou help evaluate candidates, suggest interview questions, analyze responses, and provide insights about candidate fit for positions.', 1],
|
||||
['customer-support', 'Customer Support', 'You are a customer support assistant. Your goal is to help resolve customer issues efficiently and thoroughly.\n\nYou help diagnose customer problems, suggest solutions, provide step-by-step troubleshooting guidance, and ensure customer satisfaction.', 1],
|
||||
];
|
||||
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT OR IGNORE INTO prompt_presets (id, uid, title, prompt, is_default, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT OR IGNORE INTO prompt_presets (id, uid, title, prompt, is_default, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
for (const preset of defaultPresets) {
|
||||
stmt.run(preset[0], this.defaultUserId, preset[1], preset[2], preset[3], now);
|
||||
}
|
||||
for (const preset of defaultPresets) {
|
||||
stmt.run(preset[0], this.defaultUserId, preset[1], preset[2], preset[3], now);
|
||||
}
|
||||
|
||||
stmt.finalize((err) => {
|
||||
if (err) {
|
||||
console.error('Failed to finalize preset statement:', err);
|
||||
return reject(err);
|
||||
}
|
||||
console.log('Default data initialized.');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
console.log('Default data initialized.');
|
||||
}
|
||||
|
||||
async markPermissionsAsCompleted() {
|
||||
markPermissionsAsCompleted() {
|
||||
return this.query(
|
||||
'INSERT OR REPLACE INTO system_settings (key, value) VALUES (?, ?)',
|
||||
['permissions_completed', 'true']
|
||||
);
|
||||
}
|
||||
|
||||
async checkPermissionsCompleted() {
|
||||
const result = await this.query(
|
||||
checkPermissionsCompleted() {
|
||||
const result = this.query(
|
||||
'SELECT value FROM system_settings WHERE key = ?',
|
||||
['permissions_completed']
|
||||
);
|
||||
@ -218,43 +159,32 @@ class SQLiteClient {
|
||||
|
||||
close() {
|
||||
if (this.db) {
|
||||
this.db.close((err) => {
|
||||
if (err) {
|
||||
console.error('SQLite connection close failed:', err);
|
||||
} else {
|
||||
console.log('SQLite connection closed.');
|
||||
}
|
||||
});
|
||||
try {
|
||||
this.db.close();
|
||||
console.log('SQLite connection closed.');
|
||||
} catch (err) {
|
||||
console.error('SQLite connection close failed:', err);
|
||||
}
|
||||
this.db = null;
|
||||
}
|
||||
}
|
||||
|
||||
async query(sql, params = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.db) {
|
||||
return reject(new Error('Database not connected'));
|
||||
}
|
||||
query(sql, params = []) {
|
||||
if (!this.db) {
|
||||
throw new Error('Database not connected');
|
||||
}
|
||||
|
||||
try {
|
||||
if (sql.toUpperCase().startsWith('SELECT')) {
|
||||
this.db.all(sql, params, (err, rows) => {
|
||||
if (err) {
|
||||
console.error('Query error:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows);
|
||||
}
|
||||
});
|
||||
return this.db.prepare(sql).all(params);
|
||||
} else {
|
||||
this.db.run(sql, params, function(err) {
|
||||
if (err) {
|
||||
console.error('Query error:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({ changes: this.changes, lastID: this.lastID });
|
||||
}
|
||||
});
|
||||
const result = this.db.prepare(sql).run(params);
|
||||
return { changes: result.changes, lastID: result.lastID };
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Query error:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
const { BrowserWindow, globalShortcut, ipcMain, screen, app, shell, desktopCapturer } = require('electron');
|
||||
const WindowLayoutManager = require('./windowLayoutManager');
|
||||
const SmoothMovementManager = require('./smoothMovementManager');
|
||||
const liquidGlass = require('electron-liquid-glass');
|
||||
const path = require('node:path');
|
||||
const fs = require('node:fs');
|
||||
const os = require('os');
|
||||
@ -15,6 +14,7 @@ const fetch = require('node-fetch');
|
||||
|
||||
|
||||
/* ────────────────[ GLASS BYPASS ]─────────────── */
|
||||
let liquidGlass;
|
||||
const isLiquidGlassSupported = () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
return false;
|
||||
@ -23,7 +23,15 @@ const isLiquidGlassSupported = () => {
|
||||
// return majorVersion >= 25; // macOS 26+ (Darwin 25+)
|
||||
return majorVersion >= 26; // See you soon!
|
||||
};
|
||||
const shouldUseLiquidGlass = isLiquidGlassSupported();
|
||||
let shouldUseLiquidGlass = isLiquidGlassSupported();
|
||||
if (shouldUseLiquidGlass) {
|
||||
try {
|
||||
liquidGlass = require('electron-liquid-glass');
|
||||
} catch (e) {
|
||||
console.warn('Could not load optional dependency "electron-liquid-glass". The feature will be disabled.');
|
||||
shouldUseLiquidGlass = false;
|
||||
}
|
||||
}
|
||||
/* ────────────────[ GLASS BYPASS ]─────────────── */
|
||||
|
||||
let isContentProtectionOn = true;
|
||||
|
@ -2,31 +2,23 @@ const sqliteClient = require('../../../common/services/sqliteClient');
|
||||
|
||||
function addAiMessage({ sessionId, role, content, model = 'gpt-4.1' }) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const messageId = require('crypto').randomUUID();
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `INSERT INTO ai_messages (id, session_id, sent_at, role, content, model, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`;
|
||||
db.run(query, [messageId, sessionId, now, role, content, model, now], function(err) {
|
||||
if (err) {
|
||||
console.error('SQLite: Failed to add AI message:', err);
|
||||
reject(err);
|
||||
}
|
||||
else {
|
||||
resolve({ id: messageId });
|
||||
}
|
||||
});
|
||||
});
|
||||
const messageId = require('crypto').randomUUID();
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `INSERT INTO ai_messages (id, session_id, sent_at, role, content, model, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`;
|
||||
|
||||
try {
|
||||
db.prepare(query).run(messageId, sessionId, now, role, content, model, now);
|
||||
return { id: messageId };
|
||||
} catch (err) {
|
||||
console.error('SQLite: Failed to add AI message:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function getAllAiMessagesBySessionId(sessionId) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = "SELECT * FROM ai_messages WHERE session_id = ? ORDER BY sent_at ASC";
|
||||
db.all(query, [sessionId], (err, rows) => {
|
||||
if (err) reject(err);
|
||||
else resolve(rows);
|
||||
});
|
||||
});
|
||||
const query = "SELECT * FROM ai_messages WHERE session_id = ? ORDER BY sent_at ASC";
|
||||
return db.prepare(query).all(sessionId);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -2,33 +2,23 @@ const sqliteClient = require('../../../../common/services/sqliteClient');
|
||||
|
||||
function addTranscript({ sessionId, speaker, text }) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const transcriptId = require('crypto').randomUUID();
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `INSERT INTO transcripts (id, session_id, start_at, speaker, text, created_at) VALUES (?, ?, ?, ?, ?, ?)`;
|
||||
db.run(query, [transcriptId, sessionId, now, speaker, text, now], function(err) {
|
||||
if (err) {
|
||||
console.error('Error adding transcript:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({ id: transcriptId });
|
||||
}
|
||||
});
|
||||
});
|
||||
const transcriptId = require('crypto').randomUUID();
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `INSERT INTO transcripts (id, session_id, start_at, speaker, text, created_at) VALUES (?, ?, ?, ?, ?, ?)`;
|
||||
|
||||
try {
|
||||
db.prepare(query).run(transcriptId, sessionId, now, speaker, text, now);
|
||||
return { id: transcriptId };
|
||||
} catch (err) {
|
||||
console.error('Error adding transcript:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function getAllTranscriptsBySessionId(sessionId) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = "SELECT * FROM transcripts WHERE session_id = ? ORDER BY start_at ASC";
|
||||
db.all(query, [sessionId], (err, rows) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows);
|
||||
}
|
||||
});
|
||||
});
|
||||
const query = "SELECT * FROM transcripts WHERE session_id = ? ORDER BY start_at ASC";
|
||||
return db.prepare(query).all(sessionId);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -2,43 +2,33 @@ const sqliteClient = require('../../../../common/services/sqliteClient');
|
||||
|
||||
function saveSummary({ sessionId, tldr, text, bullet_json, action_json, model = 'gpt-4.1' }) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `
|
||||
INSERT INTO summaries (session_id, generated_at, model, text, tldr, bullet_json, action_json, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(session_id) DO UPDATE SET
|
||||
generated_at=excluded.generated_at,
|
||||
model=excluded.model,
|
||||
text=excluded.text,
|
||||
tldr=excluded.tldr,
|
||||
bullet_json=excluded.bullet_json,
|
||||
action_json=excluded.action_json,
|
||||
updated_at=excluded.updated_at
|
||||
`;
|
||||
db.run(query, [sessionId, now, model, text, tldr, bullet_json, action_json, now], function(err) {
|
||||
if (err) {
|
||||
console.error('Error saving summary:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({ changes: this.changes });
|
||||
}
|
||||
});
|
||||
});
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `
|
||||
INSERT INTO summaries (session_id, generated_at, model, text, tldr, bullet_json, action_json, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(session_id) DO UPDATE SET
|
||||
generated_at=excluded.generated_at,
|
||||
model=excluded.model,
|
||||
text=excluded.text,
|
||||
tldr=excluded.tldr,
|
||||
bullet_json=excluded.bullet_json,
|
||||
action_json=excluded.action_json,
|
||||
updated_at=excluded.updated_at
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = db.prepare(query).run(sessionId, now, model, text, tldr, bullet_json, action_json, now);
|
||||
return { changes: result.changes };
|
||||
} catch (err) {
|
||||
console.error('Error saving summary:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function getSummaryBySessionId(sessionId) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = "SELECT * FROM summaries WHERE session_id = ?";
|
||||
db.get(query, [sessionId], (err, row) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(row || null);
|
||||
}
|
||||
});
|
||||
});
|
||||
const query = "SELECT * FROM summaries WHERE session_id = ?";
|
||||
return db.prepare(query).get(sessionId) || null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -2,102 +2,92 @@ const sqliteClient = require('../../../common/services/sqliteClient');
|
||||
|
||||
function getPresets(uid) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = `
|
||||
SELECT * FROM prompt_presets
|
||||
WHERE uid = ? OR is_default = 1
|
||||
ORDER BY is_default DESC, title ASC
|
||||
`;
|
||||
db.all(query, [uid], (err, rows) => {
|
||||
if (err) {
|
||||
console.error('SQLite: Failed to get presets:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows || []);
|
||||
}
|
||||
});
|
||||
});
|
||||
const query = `
|
||||
SELECT * FROM prompt_presets
|
||||
WHERE uid = ? OR is_default = 1
|
||||
ORDER BY is_default DESC, title ASC
|
||||
`;
|
||||
|
||||
try {
|
||||
return db.prepare(query).all(uid) || [];
|
||||
} catch (err) {
|
||||
console.error('SQLite: Failed to get presets:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function getPresetTemplates() {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = `
|
||||
SELECT * FROM prompt_presets
|
||||
WHERE is_default = 1
|
||||
ORDER BY title ASC
|
||||
`;
|
||||
db.all(query, [], (err, rows) => {
|
||||
if (err) {
|
||||
console.error('SQLite: Failed to get preset templates:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows || []);
|
||||
}
|
||||
});
|
||||
});
|
||||
const query = `
|
||||
SELECT * FROM prompt_presets
|
||||
WHERE is_default = 1
|
||||
ORDER BY title ASC
|
||||
`;
|
||||
|
||||
try {
|
||||
return db.prepare(query).all() || [];
|
||||
} catch (err) {
|
||||
console.error('SQLite: Failed to get preset templates:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function createPreset({ uid, title, prompt }) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = require('crypto').randomUUID();
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `
|
||||
INSERT INTO prompt_presets (id, uid, title, prompt, is_default, created_at, sync_state)
|
||||
VALUES (?, ?, ?, ?, 0, ?, 'dirty')
|
||||
`;
|
||||
db.run(query, [id, uid, title, prompt, now], function(err) {
|
||||
if (err) {
|
||||
console.error('SQLite: Failed to create preset:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve({ id });
|
||||
}
|
||||
});
|
||||
});
|
||||
const id = require('crypto').randomUUID();
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `
|
||||
INSERT INTO prompt_presets (id, uid, title, prompt, is_default, created_at, sync_state)
|
||||
VALUES (?, ?, ?, ?, 0, ?, 'dirty')
|
||||
`;
|
||||
|
||||
try {
|
||||
db.prepare(query).run(id, uid, title, prompt, now);
|
||||
return { id };
|
||||
} catch (err) {
|
||||
console.error('SQLite: Failed to create preset:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePreset(id, { title, prompt }, uid) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `
|
||||
UPDATE prompt_presets
|
||||
SET title = ?, prompt = ?, sync_state = 'dirty', updated_at = ?
|
||||
WHERE id = ? AND uid = ? AND is_default = 0
|
||||
`;
|
||||
db.run(query, [title, prompt, now, id, uid], function(err) {
|
||||
if (err) {
|
||||
console.error('SQLite: Failed to update preset:', err);
|
||||
reject(err);
|
||||
} else if (this.changes === 0) {
|
||||
reject(new Error('Preset not found, is default, or permission denied'));
|
||||
} else {
|
||||
resolve({ changes: this.changes });
|
||||
}
|
||||
});
|
||||
});
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const query = `
|
||||
UPDATE prompt_presets
|
||||
SET title = ?, prompt = ?, sync_state = 'dirty', updated_at = ?
|
||||
WHERE id = ? AND uid = ? AND is_default = 0
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = db.prepare(query).run(title, prompt, now, id, uid);
|
||||
if (result.changes === 0) {
|
||||
throw new Error('Preset not found, is default, or permission denied');
|
||||
}
|
||||
return { changes: result.changes };
|
||||
} catch (err) {
|
||||
console.error('SQLite: Failed to update preset:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function deletePreset(id, uid) {
|
||||
const db = sqliteClient.getDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = `
|
||||
DELETE FROM prompt_presets
|
||||
WHERE id = ? AND uid = ? AND is_default = 0
|
||||
`;
|
||||
db.run(query, [id, uid], function(err) {
|
||||
if (err) {
|
||||
console.error('SQLite: Failed to delete preset:', err);
|
||||
reject(err);
|
||||
} else if (this.changes === 0) {
|
||||
reject(new Error('Preset not found, is default, or permission denied'));
|
||||
} else {
|
||||
resolve({ changes: this.changes });
|
||||
}
|
||||
});
|
||||
});
|
||||
const query = `
|
||||
DELETE FROM prompt_presets
|
||||
WHERE id = ? AND uid = ? AND is_default = 0
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = db.prepare(query).run(id, uid);
|
||||
if (result.changes === 0) {
|
||||
throw new Error('Preset not found, is default, or permission denied');
|
||||
}
|
||||
return { changes: result.changes };
|
||||
} catch (err) {
|
||||
console.error('SQLite: Failed to delete preset:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
211
src/index.js
211
src/index.js
@ -18,7 +18,6 @@ const { initializeFirebase } = require('./common/services/firebaseClient');
|
||||
const databaseInitializer = require('./common/services/databaseInitializer');
|
||||
const authService = require('./common/services/authService');
|
||||
const path = require('node:path');
|
||||
const { Deeplink } = require('electron-deeplink');
|
||||
const express = require('express');
|
||||
const fetch = require('node-fetch');
|
||||
const { autoUpdater } = require('electron-updater');
|
||||
@ -33,72 +32,86 @@ let WEB_PORT = 3000;
|
||||
const listenService = new ListenService();
|
||||
// Make listenService globally accessible so other modules (e.g., windowManager, askService) can reuse the same instance
|
||||
global.listenService = listenService;
|
||||
let deeplink = null; // Initialize as null
|
||||
let pendingDeepLinkUrl = null; // Store any deep link that arrives before initialization
|
||||
|
||||
function createMainWindows() {
|
||||
createWindows();
|
||||
// Native deep link handling - cross-platform compatible
|
||||
let pendingDeepLinkUrl = null;
|
||||
|
||||
const { windowPool } = require('./electron/windowManager');
|
||||
const headerWindow = windowPool.get('header');
|
||||
|
||||
// Initialize deeplink after windows are created
|
||||
if (!deeplink && headerWindow) {
|
||||
try {
|
||||
deeplink = new Deeplink({
|
||||
app,
|
||||
mainWindow: headerWindow,
|
||||
protocol: 'pickleglass',
|
||||
isDev: !app.isPackaged,
|
||||
debugLogging: true
|
||||
});
|
||||
|
||||
deeplink.on('received', (url) => {
|
||||
console.log('[deeplink] received:', url);
|
||||
handleCustomUrl(url);
|
||||
});
|
||||
|
||||
console.log('[deeplink] Initialized with main window');
|
||||
|
||||
// Handle any pending deep link
|
||||
if (pendingDeepLinkUrl) {
|
||||
console.log('[deeplink] Processing pending deep link:', pendingDeepLinkUrl);
|
||||
handleCustomUrl(pendingDeepLinkUrl);
|
||||
pendingDeepLinkUrl = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[deeplink] Failed to initialize deep link:', error);
|
||||
deeplink = null;
|
||||
}
|
||||
function setupProtocolHandling() {
|
||||
// Protocol registration - must be done before app is ready
|
||||
if (!app.isDefaultProtocolClient('pickleglass')) {
|
||||
app.setAsDefaultProtocolClient('pickleglass');
|
||||
console.log('[Protocol] Set as default protocol client for pickleglass://');
|
||||
}
|
||||
|
||||
// Handle protocol URLs on Windows/Linux
|
||||
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||
// Focus existing window first
|
||||
focusMainWindow();
|
||||
|
||||
// Look for protocol URL in command line arguments
|
||||
const protocolUrl = commandLine.find(arg => arg.startsWith('pickleglass://'));
|
||||
if (protocolUrl) {
|
||||
console.log('[Protocol] Received URL from second instance:', protocolUrl);
|
||||
handleCustomUrl(protocolUrl);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle protocol URLs on macOS
|
||||
app.on('open-url', (event, url) => {
|
||||
event.preventDefault();
|
||||
console.log('[Protocol] Received URL via open-url:', url);
|
||||
|
||||
if (!url || !url.startsWith('pickleglass://')) {
|
||||
console.warn('[Protocol] Invalid URL format:', url);
|
||||
return;
|
||||
}
|
||||
|
||||
if (app.isReady()) {
|
||||
handleCustomUrl(url);
|
||||
} else {
|
||||
pendingDeepLinkUrl = url;
|
||||
console.log('[Protocol] App not ready, storing URL for later');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function focusMainWindow() {
|
||||
const { windowPool } = require('./electron/windowManager');
|
||||
if (windowPool) {
|
||||
const header = windowPool.get('header');
|
||||
if (header && !header.isDestroyed()) {
|
||||
if (header.isMinimized()) header.restore();
|
||||
header.focus();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: focus any available window
|
||||
const windows = BrowserWindow.getAllWindows();
|
||||
if (windows.length > 0) {
|
||||
const mainWindow = windows[0];
|
||||
if (!mainWindow.isDestroyed()) {
|
||||
if (mainWindow.isMinimized()) mainWindow.restore();
|
||||
mainWindow.focus();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup protocol handling before app.whenReady()
|
||||
setupProtocolHandling();
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
// Single instance lock - must be first
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) {
|
||||
app.quit();
|
||||
return;
|
||||
} else {
|
||||
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||
const { windowPool } = require('./electron/windowManager');
|
||||
if (windowPool) {
|
||||
const header = windowPool.get('header');
|
||||
if (header) {
|
||||
if (header.isMinimized()) header.restore();
|
||||
header.focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const windows = BrowserWindow.getAllWindows();
|
||||
if (windows.length > 0) {
|
||||
const mainWindow = windows[0];
|
||||
if (mainWindow.isMinimized()) mainWindow.restore();
|
||||
mainWindow.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize core services
|
||||
initializeFirebase();
|
||||
|
||||
databaseInitializer.initialize()
|
||||
@ -121,9 +134,16 @@ app.whenReady().then(async () => {
|
||||
WEB_PORT = await startWebStack();
|
||||
console.log('Web front-end listening on', WEB_PORT);
|
||||
|
||||
createMainWindows();
|
||||
createWindows();
|
||||
|
||||
initAutoUpdater();
|
||||
|
||||
// Process any pending deep link after everything is initialized
|
||||
if (pendingDeepLinkUrl) {
|
||||
console.log('[Protocol] Processing pending URL:', pendingDeepLinkUrl);
|
||||
handleCustomUrl(pendingDeepLinkUrl);
|
||||
pendingDeepLinkUrl = null;
|
||||
}
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
@ -142,34 +162,17 @@ app.on('before-quit', async () => {
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createMainWindows();
|
||||
createWindows();
|
||||
}
|
||||
});
|
||||
|
||||
// Add macOS native deep link handling as fallback
|
||||
app.on('open-url', (event, url) => {
|
||||
event.preventDefault();
|
||||
console.log('[app] open-url received:', url);
|
||||
|
||||
if (!deeplink) {
|
||||
// Store the URL if deeplink isn't ready yet
|
||||
pendingDeepLinkUrl = url;
|
||||
console.log('[app] Deep link stored for later processing');
|
||||
} else {
|
||||
handleCustomUrl(url);
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure app can handle the protocol
|
||||
app.setAsDefaultProtocolClient('pickleglass');
|
||||
|
||||
function setupGeneralIpcHandlers() {
|
||||
const userRepository = require('./common/repositories/user');
|
||||
const presetRepository = require('./common/repositories/preset');
|
||||
|
||||
ipcMain.handle('save-api-key', async (event, apiKey) => {
|
||||
ipcMain.handle('save-api-key', (event, apiKey) => {
|
||||
try {
|
||||
await userRepository.saveApiKey(apiKey, authService.getCurrentUserId());
|
||||
userRepository.saveApiKey(apiKey, authService.getCurrentUserId());
|
||||
BrowserWindow.getAllWindows().forEach(win => {
|
||||
win.webContents.send('api-key-updated');
|
||||
});
|
||||
@ -180,12 +183,12 @@ function setupGeneralIpcHandlers() {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-user-presets', async () => {
|
||||
return await presetRepository.getPresets(authService.getCurrentUserId());
|
||||
ipcMain.handle('get-user-presets', () => {
|
||||
return presetRepository.getPresets(authService.getCurrentUserId());
|
||||
});
|
||||
|
||||
ipcMain.handle('get-preset-templates', async () => {
|
||||
return await presetRepository.getPresetTemplates();
|
||||
ipcMain.handle('get-preset-templates', () => {
|
||||
return presetRepository.getPresetTemplates();
|
||||
});
|
||||
|
||||
ipcMain.handle('start-firebase-auth', async () => {
|
||||
@ -204,7 +207,7 @@ function setupGeneralIpcHandlers() {
|
||||
return process.env.pickleglass_WEB_URL || 'http://localhost:3000';
|
||||
});
|
||||
|
||||
ipcMain.handle('get-current-user', async () => {
|
||||
ipcMain.handle('get-current-user', () => {
|
||||
return authService.getCurrentUser();
|
||||
});
|
||||
|
||||
@ -220,72 +223,72 @@ function setupWebDataHandlers() {
|
||||
const userRepository = require('./common/repositories/user');
|
||||
const presetRepository = require('./common/repositories/preset');
|
||||
|
||||
const handleRequest = async (channel, responseChannel, payload) => {
|
||||
const handleRequest = (channel, responseChannel, payload) => {
|
||||
let result;
|
||||
const currentUserId = authService.getCurrentUserId();
|
||||
try {
|
||||
switch (channel) {
|
||||
// SESSION
|
||||
case 'get-sessions':
|
||||
result = await sessionRepository.getAllByUserId(currentUserId);
|
||||
result = sessionRepository.getAllByUserId(currentUserId);
|
||||
break;
|
||||
case 'get-session-details':
|
||||
const session = await sessionRepository.getById(payload);
|
||||
const session = sessionRepository.getById(payload);
|
||||
if (!session) {
|
||||
result = null;
|
||||
break;
|
||||
}
|
||||
const transcripts = await sttRepository.getAllTranscriptsBySessionId(payload);
|
||||
const ai_messages = await askRepository.getAllAiMessagesBySessionId(payload);
|
||||
const summary = await summaryRepository.getSummaryBySessionId(payload);
|
||||
const transcripts = sttRepository.getAllTranscriptsBySessionId(payload);
|
||||
const ai_messages = askRepository.getAllAiMessagesBySessionId(payload);
|
||||
const summary = summaryRepository.getSummaryBySessionId(payload);
|
||||
result = { session, transcripts, ai_messages, summary };
|
||||
break;
|
||||
case 'delete-session':
|
||||
result = await sessionRepository.deleteWithRelatedData(payload);
|
||||
result = sessionRepository.deleteWithRelatedData(payload);
|
||||
break;
|
||||
case 'create-session':
|
||||
const id = await sessionRepository.create(currentUserId, 'ask');
|
||||
const id = sessionRepository.create(currentUserId, 'ask');
|
||||
if (payload.title) {
|
||||
await sessionRepository.updateTitle(id, payload.title);
|
||||
sessionRepository.updateTitle(id, payload.title);
|
||||
}
|
||||
result = { id };
|
||||
break;
|
||||
|
||||
// USER
|
||||
case 'get-user-profile':
|
||||
result = await userRepository.getById(currentUserId);
|
||||
result = userRepository.getById(currentUserId);
|
||||
break;
|
||||
case 'update-user-profile':
|
||||
result = await userRepository.update({ uid: currentUserId, ...payload });
|
||||
result = userRepository.update({ uid: currentUserId, ...payload });
|
||||
break;
|
||||
case 'find-or-create-user':
|
||||
result = await userRepository.findOrCreate(payload);
|
||||
result = userRepository.findOrCreate(payload);
|
||||
break;
|
||||
case 'save-api-key':
|
||||
result = await userRepository.saveApiKey(payload, currentUserId);
|
||||
result = userRepository.saveApiKey(payload, currentUserId);
|
||||
break;
|
||||
case 'check-api-key-status':
|
||||
const user = await userRepository.getById(currentUserId);
|
||||
const user = userRepository.getById(currentUserId);
|
||||
result = { hasApiKey: !!user?.api_key && user.api_key.length > 0 };
|
||||
break;
|
||||
case 'delete-account':
|
||||
result = await userRepository.deleteById(currentUserId);
|
||||
result = userRepository.deleteById(currentUserId);
|
||||
break;
|
||||
|
||||
// PRESET
|
||||
case 'get-presets':
|
||||
result = await presetRepository.getPresets(currentUserId);
|
||||
result = presetRepository.getPresets(currentUserId);
|
||||
break;
|
||||
case 'create-preset':
|
||||
result = await presetRepository.create({ ...payload, uid: currentUserId });
|
||||
result = presetRepository.create({ ...payload, uid: currentUserId });
|
||||
settingsService.notifyPresetUpdate('created', result.id, payload.title);
|
||||
break;
|
||||
case 'update-preset':
|
||||
result = await presetRepository.update(payload.id, payload.data, currentUserId);
|
||||
result = presetRepository.update(payload.id, payload.data, currentUserId);
|
||||
settingsService.notifyPresetUpdate('updated', payload.id, payload.data.title);
|
||||
break;
|
||||
case 'delete-preset':
|
||||
result = await presetRepository.delete(payload, currentUserId);
|
||||
result = presetRepository.delete(payload, currentUserId);
|
||||
settingsService.notifyPresetUpdate('deleted', payload);
|
||||
break;
|
||||
|
||||
@ -295,13 +298,13 @@ function setupWebDataHandlers() {
|
||||
const batchResult = {};
|
||||
|
||||
if (includes.includes('profile')) {
|
||||
batchResult.profile = await userRepository.getById(currentUserId);
|
||||
batchResult.profile = userRepository.getById(currentUserId);
|
||||
}
|
||||
if (includes.includes('presets')) {
|
||||
batchResult.presets = await presetRepository.getPresets(currentUserId);
|
||||
batchResult.presets = presetRepository.getPresets(currentUserId);
|
||||
}
|
||||
if (includes.includes('sessions')) {
|
||||
batchResult.sessions = await sessionRepository.getAllByUserId(currentUserId);
|
||||
batchResult.sessions = sessionRepository.getAllByUserId(currentUserId);
|
||||
}
|
||||
result = batchResult;
|
||||
break;
|
||||
@ -392,7 +395,7 @@ async function handleFirebaseAuthCallback(params) {
|
||||
};
|
||||
|
||||
// 1. Sync user data to local DB
|
||||
await userRepository.findOrCreate(firebaseUser);
|
||||
userRepository.findOrCreate(firebaseUser);
|
||||
console.log('[Auth] User data synced with local DB.');
|
||||
|
||||
// 2. Sign in using the authService in the main process
|
||||
|
Loading…
x
Reference in New Issue
Block a user