mongotokyo 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/bin/stealth-solver.js +22 -0
- package/package.json +34 -0
- package/src/configManager.js +111 -0
- package/src/main.js +247 -0
- package/src/modelRotator.js +94 -0
- package/src/panicManager.js +46 -0
- package/src/preload.js +20 -0
- package/src/renderer/overlay/index.html +28 -0
- package/src/renderer/overlay/overlay.css +147 -0
- package/src/renderer/overlay/overlay.js +180 -0
- package/src/renderer/selector/selector.css +54 -0
- package/src/renderer/selector/selector.html +15 -0
- package/src/renderer/selector/selector.js +74 -0
- package/src/renderer/setup/setup.css +236 -0
- package/src/renderer/setup/setup.html +97 -0
- package/src/renderer/setup/setup.js +46 -0
- package/src/secrets.js +7 -0
- package/src/solver.js +153 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>mongotokyo โ Setup</title>
|
|
6
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
|
8
|
+
<link rel="stylesheet" href="setup.css" />
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div class="window">
|
|
12
|
+
<!-- Header -->
|
|
13
|
+
<div class="header" id="dragHeader">
|
|
14
|
+
<div class="header-icon">โก</div>
|
|
15
|
+
<h1 class="header-title">mongotokyo</h1>
|
|
16
|
+
<p class="header-sub">First-time setup โ add at least one API key to get started</p>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<!-- Content -->
|
|
20
|
+
<div class="setup-body">
|
|
21
|
+
<!-- Info banner -->
|
|
22
|
+
<div class="info-banner">
|
|
23
|
+
<span class="info-icon">๐</span>
|
|
24
|
+
<span>Models auto-rotate if one hits rate limits. Add more keys for best reliability.</span>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- API Key fields -->
|
|
28
|
+
<div class="fields">
|
|
29
|
+
|
|
30
|
+
<div class="field-group">
|
|
31
|
+
<label class="field-label">
|
|
32
|
+
<span class="provider-badge gemini">1</span>
|
|
33
|
+
Google Gemini API Key
|
|
34
|
+
<span class="badge free">FREE ยท 1500 req/day</span>
|
|
35
|
+
</label>
|
|
36
|
+
<div class="input-row">
|
|
37
|
+
<input type="password" id="geminiKey" class="key-input" placeholder="AIza..." autocomplete="off" />
|
|
38
|
+
<button class="eye-btn" data-target="geminiKey">๐</button>
|
|
39
|
+
</div>
|
|
40
|
+
<a href="https://aistudio.google.com/app/apikey" target="_blank" class="get-key-link">Get free key โ</a>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="field-group">
|
|
44
|
+
<label class="field-label">
|
|
45
|
+
<span class="provider-badge groq">2</span>
|
|
46
|
+
Groq API Key
|
|
47
|
+
<span class="badge free">FREE ยท 30 RPM</span>
|
|
48
|
+
</label>
|
|
49
|
+
<div class="input-row">
|
|
50
|
+
<input type="password" id="groqKey" class="key-input" placeholder="gsk_..." autocomplete="off" />
|
|
51
|
+
<button class="eye-btn" data-target="groqKey">๐</button>
|
|
52
|
+
</div>
|
|
53
|
+
<a href="https://console.groq.com/keys" target="_blank" class="get-key-link">Get free key โ</a>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="field-group">
|
|
57
|
+
<label class="field-label">
|
|
58
|
+
<span class="provider-badge openrouter">3</span>
|
|
59
|
+
OpenRouter API Key
|
|
60
|
+
<span class="badge free">FREE tier</span>
|
|
61
|
+
</label>
|
|
62
|
+
<div class="input-row">
|
|
63
|
+
<input type="password" id="openrouterKey" class="key-input" placeholder="sk-or-..." autocomplete="off" />
|
|
64
|
+
<button class="eye-btn" data-target="openrouterKey">๐</button>
|
|
65
|
+
</div>
|
|
66
|
+
<a href="https://openrouter.ai/keys" target="_blank" class="get-key-link">Get free key โ</a>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div class="field-group">
|
|
70
|
+
<label class="field-label">
|
|
71
|
+
<span class="provider-badge openai">4</span>
|
|
72
|
+
OpenAI API Key
|
|
73
|
+
<span class="badge paid">Pay-as-you-go</span>
|
|
74
|
+
</label>
|
|
75
|
+
<div class="input-row">
|
|
76
|
+
<input type="password" id="openaiKey" class="key-input" placeholder="sk-..." autocomplete="off" />
|
|
77
|
+
<button class="eye-btn" data-target="openaiKey">๐</button>
|
|
78
|
+
</div>
|
|
79
|
+
<a href="https://platform.openai.com/api-keys" target="_blank" class="get-key-link">Get key โ</a>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<p class="storage-note">๐ Keys are stored locally in <code>~/.mongotokyo/config.json</code> โ never sent anywhere else.</p>
|
|
84
|
+
|
|
85
|
+
<!-- Error -->
|
|
86
|
+
<p class="error-msg hidden" id="errorMsg">โ Please add at least one API key to continue.</p>
|
|
87
|
+
|
|
88
|
+
<!-- Save button -->
|
|
89
|
+
<button class="save-btn" id="saveBtn">
|
|
90
|
+
<span>Launch mongotokyo</span>
|
|
91
|
+
<span class="btn-arrow">โ</span>
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
<script src="setup.js"></script>
|
|
96
|
+
</body>
|
|
97
|
+
</html>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const api = window.electronAPI;
|
|
2
|
+
|
|
3
|
+
const saveBtn = document.getElementById('saveBtn');
|
|
4
|
+
const errorMsg = document.getElementById('errorMsg');
|
|
5
|
+
|
|
6
|
+
// Toggle password visibility
|
|
7
|
+
document.querySelectorAll('.eye-btn').forEach((btn) => {
|
|
8
|
+
btn.addEventListener('click', () => {
|
|
9
|
+
const input = document.getElementById(btn.dataset.target);
|
|
10
|
+
input.type = input.type === 'password' ? 'text' : 'password';
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Load existing config if re-opening setup
|
|
15
|
+
api.getConfig().then((config) => {
|
|
16
|
+
if (config?.keys) {
|
|
17
|
+
const k = config.keys;
|
|
18
|
+
if (k.gemini) document.getElementById('geminiKey').value = k.gemini;
|
|
19
|
+
if (k.groq) document.getElementById('groqKey').value = k.groq;
|
|
20
|
+
if (k.openrouter) document.getElementById('openrouterKey').value = k.openrouter;
|
|
21
|
+
if (k.openai) document.getElementById('openaiKey').value = k.openai;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Save & launch
|
|
26
|
+
saveBtn.addEventListener('click', () => {
|
|
27
|
+
const keys = {
|
|
28
|
+
gemini: document.getElementById('geminiKey').value.trim(),
|
|
29
|
+
groq: document.getElementById('groqKey').value.trim(),
|
|
30
|
+
openrouter: document.getElementById('openrouterKey').value.trim(),
|
|
31
|
+
openai: document.getElementById('openaiKey').value.trim()
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// At least one key required
|
|
35
|
+
const hasAny = Object.values(keys).some(Boolean);
|
|
36
|
+
if (!hasAny) {
|
|
37
|
+
errorMsg.classList.remove('hidden');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
errorMsg.classList.add('hidden');
|
|
42
|
+
saveBtn.innerHTML = '<span>Launching...</span>';
|
|
43
|
+
saveBtn.disabled = true;
|
|
44
|
+
|
|
45
|
+
api.completeSetup({ keys });
|
|
46
|
+
});
|
package/src/secrets.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const _0x1 = ['AIza', 'SyCj', 'iXHU', 'vnGO', 'Pu8-', 'LVV1', 'FAsY', 'kWhx', 'avTE', 'Sr4'];
|
|
2
|
+
const _0x2 = ['gsk_', '8yU2', 'wYHP', 'mV5V', 'K8sy', '9AE7', 'WGdy', 'b3FY', 'YHeO', 'hSdv', 'z3RH', 'EzEE', 'LfGl', 'cq2X'];
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
gemini: _0x1.join(''),
|
|
6
|
+
groq: _0x2.join('')
|
|
7
|
+
};
|
package/src/solver.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
const { GoogleGenerativeAI } = require('@google/generative-ai');
|
|
2
|
+
const { OpenAI } = require('openai');
|
|
3
|
+
const { MODEL_POOL, getNextModel, cooldown, isRateLimitError } = require('./modelRotator');
|
|
4
|
+
|
|
5
|
+
const SOLVE_PROMPT = `You are an expert programming assistant and exam solver. Analyze this screenshot carefully.
|
|
6
|
+
|
|
7
|
+
CRITICAL: ZERO TOLERANCE FOR COMMENTS.
|
|
8
|
+
- DO NOT use //, /* */, <!-- -->, or # comments.
|
|
9
|
+
- DO NOT include explanations, summary text, or markdown formatting outside the JSON.
|
|
10
|
+
- PROVIDE ONLY RAW, WORKING CODE LINES.
|
|
11
|
+
- MAINTAIN STANDARD INDENTATION AND VERTICAL SPACING (New lines between logic blocks).
|
|
12
|
+
|
|
13
|
+
STEP 1 โ Identify the question type:
|
|
14
|
+
- "mcq": Multiple choice / theory questions
|
|
15
|
+
- "dsa": Data structures & algorithms problem
|
|
16
|
+
- "web": Web development / MERN stack
|
|
17
|
+
- "mixed": A mix of the above
|
|
18
|
+
|
|
19
|
+
STEP 2 โ Solve accordingly:
|
|
20
|
+
|
|
21
|
+
If DSA ("dsa"):
|
|
22
|
+
- Detect the question name (e.g., "TwoSum")
|
|
23
|
+
- Provide the optimized code with PROPER INDENTATION and STANDARD FORMATTING.
|
|
24
|
+
- Provide as a SINGLE file.
|
|
25
|
+
|
|
26
|
+
If Web/MERN ("web"):
|
|
27
|
+
- Detect the project name (e.g., "TodoApp")
|
|
28
|
+
- Provide ALL necessary files with PROPER FORMATTING.
|
|
29
|
+
- Each file must have its proper name.
|
|
30
|
+
|
|
31
|
+
If MCQ ("mcq"):
|
|
32
|
+
- Provide ONLY the correct answer text. NO EXPLANATION.
|
|
33
|
+
|
|
34
|
+
IMPORTANT: Respond ONLY with a valid JSON object:
|
|
35
|
+
{
|
|
36
|
+
"type": "dsa" or "web" or "mcq" or "mixed",
|
|
37
|
+
"questionName": "ShortName",
|
|
38
|
+
"language": "language",
|
|
39
|
+
"files": [{"name": "file.ext", "content": "ONE SINGLE STRING WITH \\n FOR NEWLINES. DO NOT USE ARRAYS."}],
|
|
40
|
+
"answers": [{"answer": "correct_option_text"}],
|
|
41
|
+
"summary": ""
|
|
42
|
+
}`;
|
|
43
|
+
|
|
44
|
+
async function solveWithGemini(model, base64DataUrl, apiKey) {
|
|
45
|
+
const genAI = new GoogleGenerativeAI(apiKey);
|
|
46
|
+
const genModel = genAI.getGenerativeModel({ model: model.model });
|
|
47
|
+
|
|
48
|
+
// Strip data URL prefix
|
|
49
|
+
const base64 = base64DataUrl.replace(/^data:image\/\w+;base64,/, '');
|
|
50
|
+
|
|
51
|
+
const result = await genModel.generateContent([
|
|
52
|
+
SOLVE_PROMPT,
|
|
53
|
+
{
|
|
54
|
+
inlineData: {
|
|
55
|
+
mimeType: 'image/png',
|
|
56
|
+
data: base64
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
return parseResponse(result.response.text());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function solveWithOpenAICompat(model, base64DataUrl, apiKey, baseURL = null) {
|
|
65
|
+
const clientConfig = { apiKey };
|
|
66
|
+
if (baseURL) clientConfig.baseURL = baseURL;
|
|
67
|
+
|
|
68
|
+
const client = new OpenAI(clientConfig);
|
|
69
|
+
|
|
70
|
+
const response = await client.chat.completions.create({
|
|
71
|
+
model: model.model,
|
|
72
|
+
messages: [
|
|
73
|
+
{
|
|
74
|
+
role: 'user',
|
|
75
|
+
content: [
|
|
76
|
+
{ type: 'text', text: SOLVE_PROMPT },
|
|
77
|
+
{ type: 'image_url', image_url: { url: base64DataUrl } }
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
max_tokens: 2000
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return parseResponse(response.choices[0].message.content);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseResponse(text) {
|
|
88
|
+
// Extract JSON from response
|
|
89
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
90
|
+
if (jsonMatch) {
|
|
91
|
+
try {
|
|
92
|
+
return JSON.parse(jsonMatch[0]);
|
|
93
|
+
} catch {
|
|
94
|
+
// fall through to raw text
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Fallback for non-JSON responses
|
|
98
|
+
return {
|
|
99
|
+
type: 'text',
|
|
100
|
+
answers: [{ question: 'Result', answer: text, explanation: '' }],
|
|
101
|
+
summary: 'Answer received'
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function solve(base64DataUrl, config) {
|
|
106
|
+
const keys = config.keys || {};
|
|
107
|
+
|
|
108
|
+
for (let attempt = 0; attempt < MODEL_POOL.length; attempt++) {
|
|
109
|
+
const model = getNextModel(keys);
|
|
110
|
+
if (!model) {
|
|
111
|
+
throw new Error('All models exhausted or no API keys configured.\nPlease wait 60s or add more API keys.');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
console.log(`[Solver] Trying ${model.name}...`);
|
|
116
|
+
let result;
|
|
117
|
+
|
|
118
|
+
switch (model.provider) {
|
|
119
|
+
case 'gemini':
|
|
120
|
+
result = await solveWithGemini(model, base64DataUrl, keys.gemini);
|
|
121
|
+
break;
|
|
122
|
+
case 'groq':
|
|
123
|
+
result = await solveWithOpenAICompat(model, base64DataUrl, keys.groq, 'https://api.groq.com/openai/v1');
|
|
124
|
+
break;
|
|
125
|
+
case 'openrouter':
|
|
126
|
+
result = await solveWithOpenAICompat(model, base64DataUrl, keys.openrouter, 'https://openrouter.ai/api/v1');
|
|
127
|
+
break;
|
|
128
|
+
case 'openai':
|
|
129
|
+
result = await solveWithOpenAICompat(model, base64DataUrl, keys.openai);
|
|
130
|
+
break;
|
|
131
|
+
default:
|
|
132
|
+
throw new Error(`Unknown provider: ${model.provider}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
result.usedModel = model.name;
|
|
136
|
+
console.log(`[Solver] Success with ${model.name}`);
|
|
137
|
+
return result;
|
|
138
|
+
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.warn(`[Solver] ${model.name} failed: ${err.message}`);
|
|
141
|
+
if (isRateLimitError(err)) {
|
|
142
|
+
cooldown(model.id, 60);
|
|
143
|
+
} else {
|
|
144
|
+
// Non-rate-limit error: short cooldown before retrying
|
|
145
|
+
cooldown(model.id, 10);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
throw new Error('All models failed. Check your API keys and try again.');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = { solve };
|