feat: enhance Whisper worker integration with system Node.js detection
This commit is contained in:
+364
-344
@@ -1,554 +1,574 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
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'
|
||||
configVersion: CONFIG_VERSION,
|
||||
onboarded: false,
|
||||
layout: "normal",
|
||||
};
|
||||
|
||||
const DEFAULT_CREDENTIALS = {
|
||||
apiKey: '',
|
||||
groqApiKey: '',
|
||||
openaiCompatibleApiKey: '',
|
||||
openaiCompatibleBaseUrl: '',
|
||||
openaiCompatibleModel: ''
|
||||
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',
|
||||
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 } } }
|
||||
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;
|
||||
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');
|
||||
}
|
||||
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;
|
||||
return configDir;
|
||||
}
|
||||
|
||||
// File paths
|
||||
function getConfigPath() {
|
||||
return path.join(getConfigDir(), 'config.json');
|
||||
return path.join(getConfigDir(), "config.json");
|
||||
}
|
||||
|
||||
function getCredentialsPath() {
|
||||
return path.join(getConfigDir(), 'credentials.json');
|
||||
return path.join(getConfigDir(), "credentials.json");
|
||||
}
|
||||
|
||||
function getPreferencesPath() {
|
||||
return path.join(getConfigDir(), 'preferences.json');
|
||||
return path.join(getConfigDir(), "preferences.json");
|
||||
}
|
||||
|
||||
function getKeybindsPath() {
|
||||
return path.join(getConfigDir(), 'keybinds.json');
|
||||
return path.join(getConfigDir(), "keybinds.json");
|
||||
}
|
||||
|
||||
function getLimitsPath() {
|
||||
return path.join(getConfigDir(), 'limits.json');
|
||||
return path.join(getConfigDir(), "limits.json");
|
||||
}
|
||||
|
||||
function getHistoryDir() {
|
||||
return path.join(getConfigDir(), 'history');
|
||||
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);
|
||||
try {
|
||||
if (fs.existsSync(filePath)) {
|
||||
const data = fs.readFileSync(filePath, "utf8");
|
||||
return JSON.parse(data);
|
||||
}
|
||||
return defaultValue;
|
||||
} 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;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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();
|
||||
const configDir = getConfigDir();
|
||||
|
||||
console.log('Resetting config directory...');
|
||||
console.log("Resetting config directory...");
|
||||
|
||||
// Remove existing directory if it exists
|
||||
if (fs.existsSync(configDir)) {
|
||||
fs.rmSync(configDir, { recursive: true, force: true });
|
||||
}
|
||||
// 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 });
|
||||
// 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);
|
||||
// Initialize with defaults
|
||||
writeJsonFile(getConfigPath(), DEFAULT_CONFIG);
|
||||
writeJsonFile(getCredentialsPath(), DEFAULT_CREDENTIALS);
|
||||
writeJsonFile(getPreferencesPath(), DEFAULT_PREFERENCES);
|
||||
|
||||
console.log('Config directory initialized with defaults');
|
||||
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 });
|
||||
}
|
||||
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);
|
||||
return readJsonFile(getConfigPath(), DEFAULT_CONFIG);
|
||||
}
|
||||
|
||||
function setConfig(config) {
|
||||
const current = getConfig();
|
||||
const updated = { ...current, ...config, configVersion: CONFIG_VERSION };
|
||||
return writeJsonFile(getConfigPath(), updated);
|
||||
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);
|
||||
const config = getConfig();
|
||||
config[key] = value;
|
||||
return writeJsonFile(getConfigPath(), config);
|
||||
}
|
||||
|
||||
// ============ CREDENTIALS ============
|
||||
|
||||
function getCredentials() {
|
||||
return readJsonFile(getCredentialsPath(), DEFAULT_CREDENTIALS);
|
||||
return readJsonFile(getCredentialsPath(), DEFAULT_CREDENTIALS);
|
||||
}
|
||||
|
||||
function setCredentials(credentials) {
|
||||
const current = getCredentials();
|
||||
const updated = { ...current, ...credentials };
|
||||
return writeJsonFile(getCredentialsPath(), updated);
|
||||
const current = getCredentials();
|
||||
const updated = { ...current, ...credentials };
|
||||
return writeJsonFile(getCredentialsPath(), updated);
|
||||
}
|
||||
|
||||
function getApiKey() {
|
||||
return getCredentials().apiKey || '';
|
||||
return getCredentials().apiKey || "";
|
||||
}
|
||||
|
||||
function setApiKey(apiKey) {
|
||||
return setCredentials({ apiKey });
|
||||
return setCredentials({ apiKey });
|
||||
}
|
||||
|
||||
function getGroqApiKey() {
|
||||
return getCredentials().groqApiKey || '';
|
||||
return getCredentials().groqApiKey || "";
|
||||
}
|
||||
|
||||
function setGroqApiKey(groqApiKey) {
|
||||
return setCredentials({ groqApiKey });
|
||||
return setCredentials({ groqApiKey });
|
||||
}
|
||||
|
||||
function getOpenAICompatibleConfig() {
|
||||
const creds = getCredentials();
|
||||
return {
|
||||
apiKey: creds.openaiCompatibleApiKey || '',
|
||||
baseUrl: creds.openaiCompatibleBaseUrl || '',
|
||||
model: creds.openaiCompatibleModel || ''
|
||||
};
|
||||
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
|
||||
});
|
||||
return setCredentials({
|
||||
openaiCompatibleApiKey: apiKey,
|
||||
openaiCompatibleBaseUrl: baseUrl,
|
||||
openaiCompatibleModel: model,
|
||||
});
|
||||
}
|
||||
|
||||
// ============ PREFERENCES ============
|
||||
|
||||
function getPreferences() {
|
||||
const saved = readJsonFile(getPreferencesPath(), {});
|
||||
return { ...DEFAULT_PREFERENCES, ...saved };
|
||||
const saved = readJsonFile(getPreferencesPath(), {});
|
||||
return { ...DEFAULT_PREFERENCES, ...saved };
|
||||
}
|
||||
|
||||
function setPreferences(preferences) {
|
||||
const current = getPreferences();
|
||||
const updated = { ...current, ...preferences };
|
||||
return writeJsonFile(getPreferencesPath(), updated);
|
||||
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);
|
||||
const preferences = getPreferences();
|
||||
preferences[key] = value;
|
||||
return writeJsonFile(getPreferencesPath(), preferences);
|
||||
}
|
||||
|
||||
// ============ KEYBINDS ============
|
||||
|
||||
function getKeybinds() {
|
||||
return readJsonFile(getKeybindsPath(), DEFAULT_KEYBINDS);
|
||||
return readJsonFile(getKeybindsPath(), DEFAULT_KEYBINDS);
|
||||
}
|
||||
|
||||
function setKeybinds(keybinds) {
|
||||
return writeJsonFile(getKeybindsPath(), keybinds);
|
||||
return writeJsonFile(getKeybindsPath(), keybinds);
|
||||
}
|
||||
|
||||
// ============ LIMITS (Rate Limiting) ============
|
||||
|
||||
function getLimits() {
|
||||
return readJsonFile(getLimitsPath(), DEFAULT_LIMITS);
|
||||
return readJsonFile(getLimitsPath(), DEFAULT_LIMITS);
|
||||
}
|
||||
|
||||
function setLimits(limits) {
|
||||
return writeJsonFile(getLimitsPath(), limits);
|
||||
return writeJsonFile(getLimitsPath(), limits);
|
||||
}
|
||||
|
||||
function getTodayDateString() {
|
||||
const now = new Date();
|
||||
return now.toISOString().split('T')[0]; // YYYY-MM-DD
|
||||
const now = new Date();
|
||||
return now.toISOString().split("T")[0]; // YYYY-MM-DD
|
||||
}
|
||||
|
||||
function getTodayLimits() {
|
||||
const limits = getLimits();
|
||||
const today = getTodayDateString();
|
||||
const limits = getLimits();
|
||||
const today = getTodayDateString();
|
||||
|
||||
// Find today's entry
|
||||
const todayEntry = limits.data.find(entry => entry.date === today);
|
||||
// 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;
|
||||
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 },
|
||||
};
|
||||
}
|
||||
|
||||
// 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 todayEntry;
|
||||
}
|
||||
|
||||
return newEntry;
|
||||
// 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();
|
||||
const limits = getLimits();
|
||||
const today = getTodayDateString();
|
||||
|
||||
// Find or create today's entry
|
||||
let todayEntry = limits.data.find(entry => entry.date === today);
|
||||
// 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);
|
||||
}
|
||||
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++;
|
||||
}
|
||||
// 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;
|
||||
setLimits(limits);
|
||||
return todayEntry;
|
||||
}
|
||||
|
||||
function incrementCharUsage(provider, model, charCount) {
|
||||
getTodayLimits();
|
||||
getTodayLimits();
|
||||
|
||||
const limits = getLimits();
|
||||
const today = getTodayDateString();
|
||||
const todayEntry = limits.data.find(entry => entry.date === today);
|
||||
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);
|
||||
}
|
||||
if (todayEntry[provider] && todayEntry[provider][model]) {
|
||||
todayEntry[provider][model].chars += charCount;
|
||||
setLimits(limits);
|
||||
}
|
||||
|
||||
return todayEntry;
|
||||
return todayEntry;
|
||||
}
|
||||
|
||||
function getAvailableModel() {
|
||||
const todayLimits = getTodayLimits();
|
||||
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';
|
||||
}
|
||||
// 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
|
||||
return "gemini-2.5-flash"; // Default to flash for paid API users
|
||||
}
|
||||
|
||||
function getModelForToday() {
|
||||
const todayEntry = getTodayLimits();
|
||||
const groq = todayEntry.groq;
|
||||
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';
|
||||
}
|
||||
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;
|
||||
// All limits exhausted
|
||||
return null;
|
||||
}
|
||||
|
||||
// ============ HISTORY ============
|
||||
|
||||
function getSessionPath(sessionId) {
|
||||
return path.join(getHistoryDir(), `${sessionId}.json`);
|
||||
return path.join(getHistoryDir(), `${sessionId}.json`);
|
||||
}
|
||||
|
||||
function saveSession(sessionId, data) {
|
||||
const sessionPath = getSessionPath(sessionId);
|
||||
const sessionPath = getSessionPath(sessionId);
|
||||
|
||||
// Load existing session to preserve metadata
|
||||
const existingSession = readJsonFile(sessionPath, null);
|
||||
// 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);
|
||||
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);
|
||||
return readJsonFile(getSessionPath(sessionId), null);
|
||||
}
|
||||
|
||||
function getAllSessions() {
|
||||
const historyDir = getHistoryDir();
|
||||
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 [];
|
||||
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);
|
||||
const sessionPath = getSessionPath(sessionId);
|
||||
try {
|
||||
if (fs.existsSync(sessionPath)) {
|
||||
fs.unlinkSync(sessionPath);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} 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;
|
||||
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;
|
||||
resetConfigDir();
|
||||
return true;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
// Initialization
|
||||
initializeStorage,
|
||||
getConfigDir,
|
||||
// Initialization
|
||||
initializeStorage,
|
||||
getConfigDir,
|
||||
|
||||
// Config
|
||||
getConfig,
|
||||
setConfig,
|
||||
updateConfig,
|
||||
// Config
|
||||
getConfig,
|
||||
setConfig,
|
||||
updateConfig,
|
||||
|
||||
// Credentials
|
||||
getCredentials,
|
||||
setCredentials,
|
||||
getApiKey,
|
||||
setApiKey,
|
||||
getGroqApiKey,
|
||||
setGroqApiKey,
|
||||
getOpenAICompatibleConfig,
|
||||
setOpenAICompatibleConfig,
|
||||
// Credentials
|
||||
getCredentials,
|
||||
setCredentials,
|
||||
getApiKey,
|
||||
setApiKey,
|
||||
getGroqApiKey,
|
||||
setGroqApiKey,
|
||||
getOpenAICompatibleConfig,
|
||||
setOpenAICompatibleConfig,
|
||||
|
||||
// Preferences
|
||||
getPreferences,
|
||||
setPreferences,
|
||||
updatePreference,
|
||||
// Preferences
|
||||
getPreferences,
|
||||
setPreferences,
|
||||
updatePreference,
|
||||
|
||||
// Keybinds
|
||||
getKeybinds,
|
||||
setKeybinds,
|
||||
// Keybinds
|
||||
getKeybinds,
|
||||
setKeybinds,
|
||||
|
||||
// Limits (Rate Limiting)
|
||||
getLimits,
|
||||
setLimits,
|
||||
getTodayLimits,
|
||||
incrementLimitCount,
|
||||
getAvailableModel,
|
||||
incrementCharUsage,
|
||||
getModelForToday,
|
||||
// Limits (Rate Limiting)
|
||||
getLimits,
|
||||
setLimits,
|
||||
getTodayLimits,
|
||||
incrementLimitCount,
|
||||
getAvailableModel,
|
||||
incrementCharUsage,
|
||||
getModelForToday,
|
||||
|
||||
// History
|
||||
saveSession,
|
||||
getSession,
|
||||
getAllSessions,
|
||||
deleteSession,
|
||||
deleteAllSessions,
|
||||
// History
|
||||
saveSession,
|
||||
getSession,
|
||||
getAllSessions,
|
||||
deleteSession,
|
||||
deleteAllSessions,
|
||||
|
||||
// Clear all
|
||||
clearAllData
|
||||
// Clear all
|
||||
clearAllData,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user