575 lines
14 KiB
JavaScript
575 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: "",
|
|
openaiCompatibleApiKey: "",
|
|
openaiCompatibleBaseUrl: "",
|
|
openaiCompatibleModel: "",
|
|
};
|
|
|
|
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,
|
|
responseProvider: "gemini",
|
|
ollamaHost: "http://127.0.0.1:11434",
|
|
ollamaModel: "llama3.1",
|
|
whisperModel: "Xenova/whisper-small",
|
|
whisperDevice: "", // '' = auto-detect, 'cpu' = native, 'wasm' = compatible
|
|
};
|
|
|
|
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 });
|
|
}
|
|
|
|
function getOpenAICompatibleConfig() {
|
|
const creds = getCredentials();
|
|
return {
|
|
apiKey: creds.openaiCompatibleApiKey || "",
|
|
baseUrl: creds.openaiCompatibleBaseUrl || "",
|
|
model: creds.openaiCompatibleModel || "",
|
|
};
|
|
}
|
|
|
|
function setOpenAICompatibleConfig(apiKey, baseUrl, model) {
|
|
return setCredentials({
|
|
openaiCompatibleApiKey: apiKey,
|
|
openaiCompatibleBaseUrl: baseUrl,
|
|
openaiCompatibleModel: model,
|
|
});
|
|
}
|
|
|
|
// ============ 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,
|
|
getOpenAICompatibleConfig,
|
|
setOpenAICompatibleConfig,
|
|
|
|
// 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,
|
|
};
|