Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,34 +273,41 @@ npm start

<table>
<tr>
<td align="center" width="25%">
<td align="center" width="20%">
<br>
<strong>ChatGPT</strong>
<br>
OpenAI's GPT
<br><br>
</td>
<td align="center" width="25%">
<td align="center" width="20%">
<br>
<strong>Claude</strong>
<br>
Anthropic's Claude
<br><br>
</td>
<td align="center" width="25%">
<td align="center" width="20%">
<br>
<strong>Gemini</strong>
<br>
Google's Gemini
<br><br>
</td>
<td align="center" width="25%">
<td align="center" width="20%">
<br>
<strong>Perplexity</strong>
<br>
Web search & research
<br><br>
</td>
<td align="center" width="20%">
<br>
<strong>GLM</strong>
<br>
Zhipu's GLM (z.ai)
<br><br>
</td>
</tr>
</table>

Expand Down Expand Up @@ -335,6 +342,7 @@ Your editor → MCP tool call → Proxima local server
<tr><td><code>claude-engine.js</code></td><td>Claude</td><td>Org-level auth handling, SSE streaming, auto-recovery</td></tr>
<tr><td><code>gemini-engine.js</code></td><td>Gemini</td><td>SSE streaming with auto-reconnect</td></tr>
<tr><td><code>perplexity-engine.js</code></td><td>Perplexity</td><td>SSE streaming</td></tr>
<tr><td><code>zai-engine.js</code></td><td>GLM (z.ai)</td><td>Guest JWT auth, signed requests (X-Signature HMAC), SSE streaming</td></tr>
</table>


Expand Down Expand Up @@ -494,6 +502,15 @@ curl http://localhost:3210/v1/chat/completions \
-d '{"model": "claude", "message": "What is AI?"}'
```

**Chat (GLM via z.ai):**
```bash
curl http://localhost:3210/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "glm-5.2", "message": "What is AI?"}'
```

> GLM accepts the aliases `zai`, `z.ai`, `glm`, `glm-5.2`, `chatglm` — all route to the z.ai provider. The engine targets the latest **GLM 5.2** model through your existing browser session.

**Search:**
```bash
curl http://localhost:3210/v1/chat/completions \
Expand Down
19 changes: 18 additions & 1 deletion electron/browser-manager.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ class BrowserManager {
url: 'https://gemini.google.com/app',
partition: 'persist:gemini',
color: '#4285f4'
},
zai: {
url: 'https://chat.z.ai/',
partition: 'persist:zai',
color: '#3b6cf6'
}
};

Expand Down Expand Up @@ -429,7 +434,8 @@ class BrowserManager {
perplexity: 'perplexity.ai',
chatgpt: 'chatgpt.com',
claude: 'claude.ai',
gemini: 'gemini.google.com'
gemini: 'gemini.google.com',
zai: 'z.ai'
};

const domain = providerDomains[provider];
Expand Down Expand Up @@ -585,6 +591,17 @@ class BrowserManager {
return hasInput && !hasSignIn;
})()
`);
case 'zai':
// chat.z.ai works with an auto-minted guest session, so a usable
// state just means the page loaded with a token or a chat input.
return await webContents.executeJavaScript(`
(function() {
try { if (window.localStorage.getItem('token')) return true; } catch(e) {}
const hasInput = !!document.querySelector('textarea') ||
!!document.querySelector('[contenteditable="true"]');
return hasInput;
})()
`);
default:
return false;
}
Expand Down
20 changes: 20 additions & 0 deletions electron/index-v2.html
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@
background: linear-gradient(135deg, #4285f4, #8ab4f8);
}

.tab-icon.zai {
background: linear-gradient(135deg, #3b6cf6, #6f9bff);
}

.tab-icon.settings {
background: transparent;
border: none;
Expand Down Expand Up @@ -1010,6 +1014,11 @@
<span class="tab-name">Gemini</span>
<div class="tab-status offline" id="status-dot-gemini"></div>
</div>
<div class="tab" data-provider="zai" onclick="switchTab('zai')">
<div class="tab-icon zai">G</div>
<span class="tab-name">GLM</span>
<div class="tab-status offline" id="status-dot-zai"></div>
</div>
<div class="tab-spacer"></div>
<div class="tab" data-tab="settings" onclick="switchTab('settings')">
<div class="tab-icon settings">⚙️</div>
Expand Down Expand Up @@ -1127,6 +1136,17 @@ <h2 class="settings-title"><span class="section-icon"
</div>
<div class="toggle-switch active" data-provider="gemini" onclick="toggleProvider(this)"></div>
</div>
<div class="provider-card disabled" data-provider="zai">
<div class="provider-card-icon"
style="background: linear-gradient(135deg, #3b6cf6, #6f9bff); display: flex; align-items: center; justify-content: center; color: #fff; font-weight: 700;">
G
</div>
<div class="provider-card-info">
<div class="provider-card-name">GLM-5.2 (z.ai)</div>
<div class="provider-card-status not-ready" id="card-status-zai">Disabled</div>
</div>
<div class="toggle-switch" data-provider="zai" onclick="toggleProvider(this)"></div>
</div>
</div>
</div>

Expand Down
19 changes: 14 additions & 5 deletions electron/main-v2.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ const responseState = {
perplexity: { fingerprint: '', blockCount: 0 },
chatgpt: { fingerprint: '' },
claude: { fingerprint: '' },
gemini: { fingerprint: '' }
gemini: { fingerprint: '' },
zai: { fingerprint: '' }
};

// Default settings
Expand All @@ -62,7 +63,8 @@ const defaultSettings = {
perplexity: { enabled: true, loggedIn: false },
chatgpt: { enabled: true, loggedIn: false },
claude: { enabled: false, loggedIn: false },
gemini: { enabled: true, loggedIn: false }
gemini: { enabled: true, loggedIn: false },
zai: { enabled: false, loggedIn: false }
Comment on lines 63 to +67

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Settings merge drops zai 🐞 Bug ☼ Reliability

loadSettings() shallow-merges saved settings over defaultSettings, so older settings.json
files replace the whole providers object and can omit the newly added zai entry. The new GLM
toggle/tab then dereferences settings.providers.zai (e.g. in toggleProvider) and can throw,
preventing users from enabling GLM after upgrading.
Agent Prompt
## Issue description
`loadSettings()` does a shallow object spread (`{...defaultSettings, ...saved}`), which overwrites the entire nested `providers` map when an older `settings.json` is present. After adding the new `zai` provider UI toggle, `settings.providers.zai` can be missing, causing runtime errors when toggling or switching to ZAI.

## Issue Context
This primarily impacts upgrade/migration scenarios (existing installs) where `settings.json` was created before `zai` existed.

## Fix Focus Areas
- electron/main-v2.cjs[61-85]
- electron/index-v2.html[1139-1149]
- electron/index-v2.html[1471-1485]

## Implementation notes
- In `loadSettings()`, deep-merge nested provider defaults:
  - `providers: { ...defaultSettings.providers, ...(saved.providers || {}) }`
  - (Optionally) also deep-merge per-provider objects to backfill new fields like `loggedIn`.
- Consider adding a defensive guard either in the renderer (`toggleProvider`) or, preferably, in the main process before returning settings to the UI: if `settings.providers[provider]` is missing, initialize it from defaults.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

},
ipcPort: 19222, // Port for MCP server IPC communication
theme: 'dark',
Expand Down Expand Up @@ -182,7 +184,8 @@ async function restoreCookies(provider, ses) {
perplexity: { domain: 'perplexity.ai', authCookies: ['__Secure-next-auth.session-token', 'pplx_'] },
chatgpt: { domain: 'openai.com', authCookies: ['__Secure-next-auth.session-token', '__cf_bm'] },
claude: { domain: 'claude.ai', authCookies: ['sessionKey', '__cf_bm'] },
gemini: { domain: 'google.com', authCookies: ['SID', 'HSID', 'SSID', '__Secure-1PSID', '__Secure-3PSID'] }
gemini: { domain: 'google.com', authCookies: ['SID', 'HSID', 'SSID', '__Secure-1PSID', '__Secure-3PSID'] },
zai: { domain: 'z.ai', authCookies: ['token', '__cf_bm'] }
};
const authConfig = providerAuthDomains[provider];
if (authConfig) {
Expand Down Expand Up @@ -767,6 +770,10 @@ async function sendMessageToProvider(provider, message, forceDOM = false) {
return await sendToClaude(webContents, message);
case 'gemini':
return await sendToGemini(webContents, message);
case 'zai':
// Z.AI (GLM) is engine-only — it has no DOM scraping fallback.
// Reaching here means the injected engine didn't return content.
throw new Error('Z.AI engine unavailable — open chat.z.ai and ensure it loaded');
default:
throw new Error(`Unknown provider: ${provider}`);
}
Expand Down Expand Up @@ -2479,7 +2486,8 @@ ipcMain.handle('open-in-system-browser', (event, provider) => {
perplexity: 'https://www.perplexity.ai/',
chatgpt: 'https://chat.openai.com/',
claude: 'https://claude.ai/',
gemini: 'https://gemini.google.com/'
gemini: 'https://gemini.google.com/',
zai: 'https://chat.z.ai/'
};
if (urls[provider]) {
shell.openExternal(urls[provider]);
Expand Down Expand Up @@ -2606,7 +2614,8 @@ ipcMain.handle('get-cookies', async (event, provider) => {
perplexity: 'perplexity.ai',
chatgpt: 'openai.com',
claude: 'claude.ai',
gemini: 'google.com'
gemini: 'google.com',
zai: 'z.ai'
};

const domain = providerDomains[provider];
Expand Down
14 changes: 9 additions & 5 deletions electron/provider-api.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ function _loadScript(provider) {
chatgpt: 'chatgpt-engine.js',
claude: 'claude-engine.js',
gemini: 'gemini-engine.js',
perplexity: 'perplexity-engine.js'
perplexity: 'perplexity-engine.js',
zai: 'zai-engine.js'
};

const filename = scriptMap[provider];
Expand Down Expand Up @@ -65,7 +66,8 @@ async function isAPIReady(provider, webContents) {
chatgpt: 'typeof window.__proximaChatGPT !== "undefined"',
claude: 'typeof window.__proximaClaude !== "undefined"',
gemini: 'typeof window.__proximaGemini !== "undefined"',
perplexity: 'typeof window.__proximaPerplexity !== "undefined"'
perplexity: 'typeof window.__proximaPerplexity !== "undefined"',
zai: 'typeof window.__proximaZAI !== "undefined"'
};

const check = checkMap[provider];
Expand Down Expand Up @@ -103,7 +105,8 @@ async function sendViaAPI(provider, webContents, message) {
chatgpt: '__proximaChatGPT',
claude: '__proximaClaude',
gemini: '__proximaGemini',
perplexity: '__proximaPerplexity'
perplexity: '__proximaPerplexity',
zai: '__proximaZAI'
};

const apiObj = sendMap[provider];
Expand Down Expand Up @@ -149,10 +152,11 @@ async function resetConversation(provider, webContentsGetter) {
chatgpt: '__proximaChatGPT',
claude: '__proximaClaude',
gemini: '__proximaGemini',
perplexity: '__proximaPerplexity'
perplexity: '__proximaPerplexity',
zai: '__proximaZAI'
};

const providers = provider ? [provider] : ['chatgpt', 'claude', 'gemini', 'perplexity'];
const providers = provider ? [provider] : ['chatgpt', 'claude', 'gemini', 'perplexity', 'zai'];

for (const p of providers) {
const apiObj = resetMap[p];
Expand Down
Loading