532 lines
14 KiB
JavaScript
532 lines
14 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const os = require('os');
|
|
|
|
const CONFIG_VERSION = 1;
|
|
|
|
// Default values
|
|
const DEFAULT_CONFIG = {
|
|
configVersion: CONFIG_VERSION,
|
|
onboarded: false,
|
|
layout: 'normal'
|
|
};
|
|
|
|
const DEFAULT_CREDENTIALS = {
|
|
apiKey: '',
|
|
groqApiKey: ''
|
|
};
|
|
|
|
const DEFAULT_PREFERENCES = {
|
|
customPrompt: '',
|
|
selectedProfile: 'interview',
|
|
selectedLanguage: 'en-US',
|
|
selectedScreenshotInterval: '5',
|
|
selectedImageQuality: 'medium',
|
|
advancedMode: false,
|
|
audioMode: 'speaker_only',
|
|
fontSize: 'medium',
|
|
backgroundTransparency: 0.8,
|
|
googleSearchEnabled: false,
|
|
ollamaHost: 'http://127.0.0.1:11434',
|
|
ollamaModel: 'llama3.1',
|
|
whisperModel: 'Xenova/whisper-small',
|
|
};
|
|
|
|
const DEFAULT_KEYBINDS = null; // null means use system defaults
|
|
|
|
const DEFAULT_LIMITS = {
|
|
data: [] // Array of { date: 'YYYY-MM-DD', flash: { count }, flashLite: { count }, groq: { 'qwen3-32b': { chars, limit }, 'gpt-oss-120b': { chars, limit }, 'gpt-oss-20b': { chars, limit } }, gemini: { 'gemma-3-27b-it': { chars } } }
|
|
};
|
|
|
|
// Get the config directory path based on OS
|
|
function getConfigDir() {
|
|
const platform = os.platform();
|
|
let configDir;
|
|
|
|
if (platform === 'win32') {
|
|
configDir = path.join(os.homedir(), 'AppData', 'Roaming', 'cheating-daddy-config');
|
|
} else if (platform === 'darwin') {
|
|
configDir = path.join(os.homedir(), 'Library', 'Application Support', 'cheating-daddy-config');
|
|
} else {
|
|
configDir = path.join(os.homedir(), '.config', 'cheating-daddy-config');
|
|
}
|
|
|
|
return configDir;
|
|
}
|
|
|
|
// File paths
|
|
function getConfigPath() {
|
|
return path.join(getConfigDir(), 'config.json');
|
|
}
|
|
|
|
function getCredentialsPath() {
|
|
return path.join(getConfigDir(), 'credentials.json');
|
|
}
|
|
|
|
function getPreferencesPath() {
|
|
return path.join(getConfigDir(), 'preferences.json');
|
|
}
|
|
|
|
function getKeybindsPath() {
|
|
return path.join(getConfigDir(), 'keybinds.json');
|
|
}
|
|
|
|
function getLimitsPath() {
|
|
return path.join(getConfigDir(), 'limits.json');
|
|
}
|
|
|
|
function getHistoryDir() {
|
|
return path.join(getConfigDir(), 'history');
|
|
}
|
|
|
|
// Helper to read JSON file safely
|
|
function readJsonFile(filePath, defaultValue) {
|
|
try {
|
|
if (fs.existsSync(filePath)) {
|
|
const data = fs.readFileSync(filePath, 'utf8');
|
|
return JSON.parse(data);
|
|
}
|
|
} catch (error) {
|
|
console.warn(`Error reading ${filePath}:`, error.message);
|
|
}
|
|
return defaultValue;
|
|
}
|
|
|
|
// Helper to write JSON file safely
|
|
function writeJsonFile(filePath, data) {
|
|
try {
|
|
const dir = path.dirname(filePath);
|
|
if (!fs.existsSync(dir)) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
}
|
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
|
|
return true;
|
|
} catch (error) {
|
|
console.error(`Error writing ${filePath}:`, error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check if we need to reset (no configVersion or wrong version)
|
|
function needsReset() {
|
|
const configPath = getConfigPath();
|
|
if (!fs.existsSync(configPath)) {
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
return !config.configVersion || config.configVersion !== CONFIG_VERSION;
|
|
} catch {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Wipe and reinitialize the config directory
|
|
function resetConfigDir() {
|
|
const configDir = getConfigDir();
|
|
|
|
console.log('Resetting config directory...');
|
|
|
|
// Remove existing directory if it exists
|
|
if (fs.existsSync(configDir)) {
|
|
fs.rmSync(configDir, { recursive: true, force: true });
|
|
}
|
|
|
|
// Create fresh directory structure
|
|
fs.mkdirSync(configDir, { recursive: true });
|
|
fs.mkdirSync(getHistoryDir(), { recursive: true });
|
|
|
|
// Initialize with defaults
|
|
writeJsonFile(getConfigPath(), DEFAULT_CONFIG);
|
|
writeJsonFile(getCredentialsPath(), DEFAULT_CREDENTIALS);
|
|
writeJsonFile(getPreferencesPath(), DEFAULT_PREFERENCES);
|
|
|
|
console.log('Config directory initialized with defaults');
|
|
}
|
|
|
|
// Initialize storage - call this on app startup
|
|
function initializeStorage() {
|
|
if (needsReset()) {
|
|
resetConfigDir();
|
|
} else {
|
|
// Ensure history directory exists
|
|
const historyDir = getHistoryDir();
|
|
if (!fs.existsSync(historyDir)) {
|
|
fs.mkdirSync(historyDir, { recursive: true });
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============ CONFIG ============
|
|
|
|
function getConfig() {
|
|
return readJsonFile(getConfigPath(), DEFAULT_CONFIG);
|
|
}
|
|
|
|
function setConfig(config) {
|
|
const current = getConfig();
|
|
const updated = { ...current, ...config, configVersion: CONFIG_VERSION };
|
|
return writeJsonFile(getConfigPath(), updated);
|
|
}
|
|
|
|
function updateConfig(key, value) {
|
|
const config = getConfig();
|
|
config[key] = value;
|
|
return writeJsonFile(getConfigPath(), config);
|
|
}
|
|
|
|
// ============ CREDENTIALS ============
|
|
|
|
function getCredentials() {
|
|
return readJsonFile(getCredentialsPath(), DEFAULT_CREDENTIALS);
|
|
}
|
|
|
|
function setCredentials(credentials) {
|
|
const current = getCredentials();
|
|
const updated = { ...current, ...credentials };
|
|
return writeJsonFile(getCredentialsPath(), updated);
|
|
}
|
|
|
|
function getApiKey() {
|
|
return getCredentials().apiKey || '';
|
|
}
|
|
|
|
function setApiKey(apiKey) {
|
|
return setCredentials({ apiKey });
|
|
}
|
|
|
|
function getGroqApiKey() {
|
|
return getCredentials().groqApiKey || '';
|
|
}
|
|
|
|
function setGroqApiKey(groqApiKey) {
|
|
return setCredentials({ groqApiKey });
|
|
}
|
|
|
|
// ============ PREFERENCES ============
|
|
|
|
function getPreferences() {
|
|
const saved = readJsonFile(getPreferencesPath(), {});
|
|
return { ...DEFAULT_PREFERENCES, ...saved };
|
|
}
|
|
|
|
function setPreferences(preferences) {
|
|
const current = getPreferences();
|
|
const updated = { ...current, ...preferences };
|
|
return writeJsonFile(getPreferencesPath(), updated);
|
|
}
|
|
|
|
function updatePreference(key, value) {
|
|
const preferences = getPreferences();
|
|
preferences[key] = value;
|
|
return writeJsonFile(getPreferencesPath(), preferences);
|
|
}
|
|
|
|
// ============ KEYBINDS ============
|
|
|
|
function getKeybinds() {
|
|
return readJsonFile(getKeybindsPath(), DEFAULT_KEYBINDS);
|
|
}
|
|
|
|
function setKeybinds(keybinds) {
|
|
return writeJsonFile(getKeybindsPath(), keybinds);
|
|
}
|
|
|
|
// ============ LIMITS (Rate Limiting) ============
|
|
|
|
function getLimits() {
|
|
return readJsonFile(getLimitsPath(), DEFAULT_LIMITS);
|
|
}
|
|
|
|
function setLimits(limits) {
|
|
return writeJsonFile(getLimitsPath(), limits);
|
|
}
|
|
|
|
function getTodayDateString() {
|
|
const now = new Date();
|
|
return now.toISOString().split('T')[0]; // YYYY-MM-DD
|
|
}
|
|
|
|
function getTodayLimits() {
|
|
const limits = getLimits();
|
|
const today = getTodayDateString();
|
|
|
|
// Find today's entry
|
|
const todayEntry = limits.data.find(entry => entry.date === today);
|
|
|
|
if (todayEntry) {
|
|
// ensure new fields exist
|
|
if(!todayEntry.groq) {
|
|
todayEntry.groq = {
|
|
'qwen3-32b': { chars: 0, limit: 1500000 },
|
|
'gpt-oss-120b': { chars: 0, limit: 600000 },
|
|
'gpt-oss-20b': { chars: 0, limit: 600000 },
|
|
'kimi-k2-instruct': { chars: 0, limit: 600000 }
|
|
};
|
|
}
|
|
if(!todayEntry.gemini) {
|
|
todayEntry.gemini = {
|
|
'gemma-3-27b-it': { chars: 0 }
|
|
};
|
|
}
|
|
setLimits(limits);
|
|
return todayEntry;
|
|
}
|
|
|
|
// No entry for today - clean old entries and create new one
|
|
limits.data = limits.data.filter(entry => entry.date === today);
|
|
const newEntry = {
|
|
date: today,
|
|
flash: { count: 0 },
|
|
flashLite: { count: 0 },
|
|
groq: {
|
|
'qwen3-32b': { chars: 0, limit: 1500000 },
|
|
'gpt-oss-120b': { chars: 0, limit: 600000 },
|
|
'gpt-oss-20b': { chars: 0, limit: 600000 },
|
|
'kimi-k2-instruct': { chars: 0, limit: 600000 }
|
|
},
|
|
gemini: {
|
|
'gemma-3-27b-it': { chars: 0 }
|
|
}
|
|
};
|
|
limits.data.push(newEntry);
|
|
setLimits(limits);
|
|
|
|
return newEntry;
|
|
}
|
|
|
|
function incrementLimitCount(model) {
|
|
const limits = getLimits();
|
|
const today = getTodayDateString();
|
|
|
|
// Find or create today's entry
|
|
let todayEntry = limits.data.find(entry => entry.date === today);
|
|
|
|
if (!todayEntry) {
|
|
// Clean old entries and create new one
|
|
limits.data = [];
|
|
todayEntry = {
|
|
date: today,
|
|
flash: { count: 0 },
|
|
flashLite: { count: 0 }
|
|
};
|
|
limits.data.push(todayEntry);
|
|
} else {
|
|
// Clean old entries, keep only today
|
|
limits.data = limits.data.filter(entry => entry.date === today);
|
|
}
|
|
|
|
// Increment the appropriate model count
|
|
if (model === 'gemini-2.5-flash') {
|
|
todayEntry.flash.count++;
|
|
} else if (model === 'gemini-2.5-flash-lite') {
|
|
todayEntry.flashLite.count++;
|
|
}
|
|
|
|
setLimits(limits);
|
|
return todayEntry;
|
|
}
|
|
|
|
function incrementCharUsage(provider, model, charCount) {
|
|
getTodayLimits();
|
|
|
|
const limits = getLimits();
|
|
const today = getTodayDateString();
|
|
const todayEntry = limits.data.find(entry => entry.date === today);
|
|
|
|
if(todayEntry[provider] && todayEntry[provider][model]) {
|
|
todayEntry[provider][model].chars += charCount;
|
|
setLimits(limits);
|
|
}
|
|
|
|
return todayEntry;
|
|
}
|
|
|
|
function getAvailableModel() {
|
|
const todayLimits = getTodayLimits();
|
|
|
|
// RPD limits: flash = 20, flash-lite = 20
|
|
// After both exhausted, fall back to flash (for paid API users)
|
|
if (todayLimits.flash.count < 20) {
|
|
return 'gemini-2.5-flash';
|
|
} else if (todayLimits.flashLite.count < 20) {
|
|
return 'gemini-2.5-flash-lite';
|
|
}
|
|
|
|
return 'gemini-2.5-flash'; // Default to flash for paid API users
|
|
}
|
|
|
|
function getModelForToday() {
|
|
const todayEntry = getTodayLimits();
|
|
const groq = todayEntry.groq;
|
|
|
|
if (groq['qwen3-32b'].chars < groq['qwen3-32b'].limit) {
|
|
return 'qwen/qwen3-32b';
|
|
}
|
|
if (groq['gpt-oss-120b'].chars < groq['gpt-oss-120b'].limit) {
|
|
return 'openai/gpt-oss-120b';
|
|
}
|
|
if (groq['gpt-oss-20b'].chars < groq['gpt-oss-20b'].limit) {
|
|
return 'openai/gpt-oss-20b';
|
|
}
|
|
if (groq['kimi-k2-instruct'].chars < groq['kimi-k2-instruct'].limit) {
|
|
return 'moonshotai/kimi-k2-instruct';
|
|
}
|
|
|
|
// All limits exhausted
|
|
return null;
|
|
}
|
|
|
|
// ============ HISTORY ============
|
|
|
|
function getSessionPath(sessionId) {
|
|
return path.join(getHistoryDir(), `${sessionId}.json`);
|
|
}
|
|
|
|
function saveSession(sessionId, data) {
|
|
const sessionPath = getSessionPath(sessionId);
|
|
|
|
// Load existing session to preserve metadata
|
|
const existingSession = readJsonFile(sessionPath, null);
|
|
|
|
const sessionData = {
|
|
sessionId,
|
|
createdAt: existingSession?.createdAt || parseInt(sessionId),
|
|
lastUpdated: Date.now(),
|
|
// Profile context - set once when session starts
|
|
profile: data.profile || existingSession?.profile || null,
|
|
customPrompt: data.customPrompt || existingSession?.customPrompt || null,
|
|
// Conversation data
|
|
conversationHistory: data.conversationHistory || existingSession?.conversationHistory || [],
|
|
screenAnalysisHistory: data.screenAnalysisHistory || existingSession?.screenAnalysisHistory || []
|
|
};
|
|
return writeJsonFile(sessionPath, sessionData);
|
|
}
|
|
|
|
function getSession(sessionId) {
|
|
return readJsonFile(getSessionPath(sessionId), null);
|
|
}
|
|
|
|
function getAllSessions() {
|
|
const historyDir = getHistoryDir();
|
|
|
|
try {
|
|
if (!fs.existsSync(historyDir)) {
|
|
return [];
|
|
}
|
|
|
|
const files = fs.readdirSync(historyDir)
|
|
.filter(f => f.endsWith('.json'))
|
|
.sort((a, b) => {
|
|
// Sort by timestamp descending (newest first)
|
|
const tsA = parseInt(a.replace('.json', ''));
|
|
const tsB = parseInt(b.replace('.json', ''));
|
|
return tsB - tsA;
|
|
});
|
|
|
|
return files.map(file => {
|
|
const sessionId = file.replace('.json', '');
|
|
const data = readJsonFile(path.join(historyDir, file), null);
|
|
if (data) {
|
|
return {
|
|
sessionId,
|
|
createdAt: data.createdAt,
|
|
lastUpdated: data.lastUpdated,
|
|
messageCount: data.conversationHistory?.length || 0,
|
|
screenAnalysisCount: data.screenAnalysisHistory?.length || 0,
|
|
profile: data.profile || null,
|
|
customPrompt: data.customPrompt || null
|
|
};
|
|
}
|
|
return null;
|
|
}).filter(Boolean);
|
|
} catch (error) {
|
|
console.error('Error reading sessions:', error.message);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function deleteSession(sessionId) {
|
|
const sessionPath = getSessionPath(sessionId);
|
|
try {
|
|
if (fs.existsSync(sessionPath)) {
|
|
fs.unlinkSync(sessionPath);
|
|
return true;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error deleting session:', error.message);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function deleteAllSessions() {
|
|
const historyDir = getHistoryDir();
|
|
try {
|
|
if (fs.existsSync(historyDir)) {
|
|
const files = fs.readdirSync(historyDir).filter(f => f.endsWith('.json'));
|
|
files.forEach(file => {
|
|
fs.unlinkSync(path.join(historyDir, file));
|
|
});
|
|
}
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Error deleting all sessions:', error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ============ CLEAR ALL DATA ============
|
|
|
|
function clearAllData() {
|
|
resetConfigDir();
|
|
return true;
|
|
}
|
|
|
|
module.exports = {
|
|
// Initialization
|
|
initializeStorage,
|
|
getConfigDir,
|
|
|
|
// Config
|
|
getConfig,
|
|
setConfig,
|
|
updateConfig,
|
|
|
|
// Credentials
|
|
getCredentials,
|
|
setCredentials,
|
|
getApiKey,
|
|
setApiKey,
|
|
getGroqApiKey,
|
|
setGroqApiKey,
|
|
|
|
// Preferences
|
|
getPreferences,
|
|
setPreferences,
|
|
updatePreference,
|
|
|
|
// Keybinds
|
|
getKeybinds,
|
|
setKeybinds,
|
|
|
|
// Limits (Rate Limiting)
|
|
getLimits,
|
|
setLimits,
|
|
getTodayLimits,
|
|
incrementLimitCount,
|
|
getAvailableModel,
|
|
incrementCharUsage,
|
|
getModelForToday,
|
|
|
|
// History
|
|
saveSession,
|
|
getSession,
|
|
getAllSessions,
|
|
deleteSession,
|
|
deleteAllSessions,
|
|
|
|
// Clear all
|
|
clearAllData
|
|
};
|