Skip to content

Commit d2b59fe

Browse files
committed
fix(ai): dynamically fetch models from APIs on startup and persist selected AI provider
1 parent b37cda8 commit d2b59fe

1 file changed

Lines changed: 97 additions & 66 deletions

File tree

src/main.js

Lines changed: 97 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -723,7 +723,7 @@ function promptGitInit(path) {
723723
const btnClose = document.getElementById('btn-cancel-init-repo');
724724

725725
if (!modal || !btnConfirm || !btnDecline) {
726-
const ok = confirm(`Bu klasör bir Git deposu değil:\n\n${path}\n\nGit deposu olarak ilklendirilsin mi (git init)?`);
726+
const ok = confirm(`Selected directory is not a Git repository:\n\n${path}\n\nInitialize as a Git repository (git init)?`);
727727
return resolve(ok);
728728
}
729729

@@ -2332,44 +2332,57 @@ function updateAiModelSelectOptions() {
23322332
const currentProvider = appSettings.aiProvider || 'ollama';
23332333
let html = '';
23342334

2335+
// 1. Ollama (Local)
23352336
html += '<optgroup label="Ollama (Local)">';
23362337
const ollamaModels = currentSystemInfo?.installed_models || ['gemma4:12b', 'gemma2:9b', 'gemma:2b'];
23372338
const curOllama = appSettings.selectedModel || 'gemma4:12b';
23382339
ollamaModels.forEach((m) => {
2339-
const isSel = (currentProvider === 'ollama' && m === curOllama) ? 'selected' : '';
2340-
html += `<option value="ollama:${escapeHtml(m)}" ${isSel}>Ollama: ${escapeHtml(m)}</option>`;
2340+
html += `<option value="ollama:${escapeHtml(m)}">Ollama: ${escapeHtml(m)}</option>`;
23412341
});
23422342
html += '</optgroup>';
23432343

2344-
if (state.geminiModels && state.geminiModels.length > 0) {
2345-
html += '<optgroup label="Gemini (Cloud)">';
2346-
const curGem = appSettings.geminiModel || 'gemini-2.5-flash';
2347-
state.geminiModels.forEach((m) => {
2348-
const isSel = (currentProvider === 'gemini' && m === curGem) ? 'selected' : '';
2349-
html += `<option value="gemini:${escapeHtml(m)}" ${isSel}>Gemini: ${escapeHtml(m)}</option>`;
2350-
});
2351-
html += '</optgroup>';
2352-
} else {
2353-
const curGem = appSettings.geminiModel || 'gemini-2.5-flash';
2354-
const isSel = (currentProvider === 'gemini') ? 'selected' : '';
2355-
html += `<optgroup label="Gemini (Cloud)"><option value="gemini:${escapeHtml(curGem)}" ${isSel}>Gemini: ${escapeHtml(curGem)}</option></optgroup>`;
2344+
// 2. Gemini (Cloud)
2345+
html += '<optgroup label="Gemini (Cloud)">';
2346+
const geminiList = (state.geminiModels && state.geminiModels.length > 0)
2347+
? state.geminiModels
2348+
: ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-1.5-flash', 'gemini-1.5-pro'];
2349+
if (appSettings.geminiModel && !geminiList.includes(appSettings.geminiModel)) {
2350+
geminiList.unshift(appSettings.geminiModel);
23562351
}
2352+
geminiList.forEach((m) => {
2353+
html += `<option value="gemini:${escapeHtml(m)}">Gemini: ${escapeHtml(m)}</option>`;
2354+
});
2355+
html += '</optgroup>';
23572356

2358-
if (state.lmStudioModels && state.lmStudioModels.length > 0) {
2359-
html += '<optgroup label="LM Studio (Local)">';
2360-
const curLm = appSettings.lmStudioModel || state.lmStudioModels[0];
2361-
state.lmStudioModels.forEach((m) => {
2362-
const isSel = (currentProvider === 'lmstudio' && m === curLm) ? 'selected' : '';
2363-
html += `<option value="lmstudio:${escapeHtml(m)}" ${isSel}>LM Studio: ${escapeHtml(m)}</option>`;
2364-
});
2365-
html += '</optgroup>';
2366-
} else {
2367-
const curLm = appSettings.lmStudioModel || 'local-model';
2368-
const isSel = (currentProvider === 'lmstudio') ? 'selected' : '';
2369-
html += `<optgroup label="LM Studio (Local)"><option value="lmstudio:${escapeHtml(curLm)}" ${isSel}>LM Studio: ${escapeHtml(curLm)}</option></optgroup>`;
2357+
// 3. LM Studio (Local)
2358+
html += '<optgroup label="LM Studio (Local)">';
2359+
const lmList = (state.lmStudioModels && state.lmStudioModels.length > 0)
2360+
? state.lmStudioModels
2361+
: ['local-model'];
2362+
if (appSettings.lmStudioModel && !lmList.includes(appSettings.lmStudioModel)) {
2363+
lmList.unshift(appSettings.lmStudioModel);
23702364
}
2365+
lmList.forEach((m) => {
2366+
html += `<option value="lmstudio:${escapeHtml(m)}">LM Studio: ${escapeHtml(m)}</option>`;
2367+
});
2368+
html += '</optgroup>';
23712369

23722370
selectEl.innerHTML = html;
2371+
2372+
// Set the selected value explicitly
2373+
let activeVal = `ollama:${appSettings.selectedModel || 'gemma4:12b'}`;
2374+
if (currentProvider === 'gemini') {
2375+
activeVal = `gemini:${appSettings.geminiModel || 'gemini-2.5-flash'}`;
2376+
} else if (currentProvider === 'lmstudio') {
2377+
activeVal = `lmstudio:${appSettings.lmStudioModel || 'local-model'}`;
2378+
}
2379+
2380+
selectEl.value = activeVal;
2381+
2382+
if (els.commitAiModelTag) {
2383+
const selectedOpt = selectEl.options[selectEl.selectedIndex];
2384+
els.commitAiModelTag.textContent = selectedOpt ? selectedOpt.text : activeVal;
2385+
}
23732386
}
23742387

23752388
async function fetchGeminiModels() {
@@ -2378,48 +2391,68 @@ async function fetchGeminiModels() {
23782391
const statusEl = els.geminiModelStatus;
23792392

23802393
if (!apiKey) {
2381-
statusEl.textContent = 'Enter an API key first.';
2382-
statusEl.className = 'gemini-model-status error';
2394+
if (statusEl) {
2395+
statusEl.textContent = 'Enter an API key first.';
2396+
statusEl.className = 'gemini-model-status error';
2397+
}
23832398
return;
23842399
}
23852400

23862401
const savedModel = appSettings.geminiModel || '';
2387-
statusEl.textContent = 'Fetching models...';
2388-
statusEl.className = 'gemini-model-status';
2402+
if (statusEl) {
2403+
statusEl.textContent = 'Fetching models...';
2404+
statusEl.className = 'gemini-model-status';
2405+
}
23892406

23902407
try {
2391-
els.btnFetchGeminiModels.disabled = true;
2392-
els.btnFetchGeminiModels.textContent = 'Loading...';
2408+
if (els.btnFetchGeminiModels) {
2409+
els.btnFetchGeminiModels.disabled = true;
2410+
els.btnFetchGeminiModels.textContent = 'Loading...';
2411+
}
23932412

23942413
const models = await invoke('list_gemini_models', { apiKey });
23952414

23962415
if (!models || models.length === 0) {
2397-
statusEl.textContent = 'No generateContent models found for this key.';
2398-
statusEl.className = 'gemini-model-status error';
2416+
if (statusEl) {
2417+
statusEl.textContent = 'No generateContent models found for this key.';
2418+
statusEl.className = 'gemini-model-status error';
2419+
}
23992420
return;
24002421
}
24012422

2402-
select.innerHTML = '';
2403-
models.forEach((m) => {
2404-
const opt = document.createElement('option');
2405-
opt.value = m;
2406-
opt.textContent = m;
2407-
select.appendChild(opt);
2408-
});
2423+
state.geminiModels = models;
2424+
2425+
if (select) {
2426+
select.innerHTML = '';
2427+
models.forEach((m) => {
2428+
const opt = document.createElement('option');
2429+
opt.value = m;
2430+
opt.textContent = m;
2431+
select.appendChild(opt);
2432+
});
2433+
2434+
if (savedModel && models.includes(savedModel)) {
2435+
select.value = savedModel;
2436+
}
2437+
}
24092438

2410-
if (savedModel && models.includes(savedModel)) {
2411-
select.value = savedModel;
2439+
if (statusEl) {
2440+
statusEl.textContent = `${models.length} model(s) loaded.`;
2441+
statusEl.className = 'gemini-model-status ok';
24122442
}
24132443

2414-
statusEl.textContent = `${models.length} model(s) loaded.`;
2415-
statusEl.className = 'gemini-model-status ok';
2444+
updateAiModelSelectOptions();
24162445
} catch (err) {
24172446
console.error('fetchGeminiModels error:', err);
2418-
statusEl.textContent = `Error: ${err}`;
2419-
statusEl.className = 'gemini-model-status error';
2447+
if (statusEl) {
2448+
statusEl.textContent = `Error: ${err}`;
2449+
statusEl.className = 'gemini-model-status error';
2450+
}
24202451
} finally {
2421-
els.btnFetchGeminiModels.disabled = false;
2422-
els.btnFetchGeminiModels.textContent = 'Refresh';
2452+
if (els.btnFetchGeminiModels) {
2453+
els.btnFetchGeminiModels.disabled = false;
2454+
els.btnFetchGeminiModels.textContent = 'Refresh';
2455+
}
24232456
}
24242457
}
24252458

@@ -2839,7 +2872,7 @@ function initEventHandlers() {
28392872
saveAppSettings(appSettings);
28402873
updateAiModelSelectOptions();
28412874
setStatus(`Ollama (${appSettings.selectedModel}) set as default AI model`);
2842-
alert(`Ollama (${appSettings.selectedModel}) varsayılan AI modeli olarak kaydedildi.`);
2875+
alert(`Ollama (${appSettings.selectedModel}) saved as default AI model.`);
28432876
});
28442877
}
28452878

@@ -2852,7 +2885,7 @@ function initEventHandlers() {
28522885
saveAppSettings(appSettings);
28532886
updateAiModelSelectOptions();
28542887
setStatus(`Gemini (${m}) set as default AI model`);
2855-
alert(`Gemini (${m}) varsayılan AI modeli olarak kaydedildi.`);
2888+
alert(`Gemini (${m}) saved as default AI model.`);
28562889
});
28572890
}
28582891

@@ -2865,7 +2898,7 @@ function initEventHandlers() {
28652898
saveAppSettings(appSettings);
28662899
updateAiModelSelectOptions();
28672900
setStatus(`LM Studio (${m}) set as default AI model`);
2868-
alert(`LM Studio (${m}) varsayılan AI modeli olarak kaydedildi.`);
2901+
alert(`LM Studio (${m}) saved as default AI model.`);
28692902
});
28702903
}
28712904

@@ -2875,18 +2908,8 @@ function initEventHandlers() {
28752908
checkLmStudioStatus();
28762909
});
28772910
}
2878-
const initProvider = appSettings.aiProvider || 'ollama';
2879-
let initTag = `Ollama: ${appSettings.selectedModel || 'gemma4:12b'}`;
2880-
if (initProvider === 'gemini') {
2881-
initTag = `Gemini: ${appSettings.geminiModel || 'gemini-2.5-flash'}`;
2882-
} else if (initProvider === 'lmstudio') {
2883-
initTag = `LM Studio`;
2884-
checkLmStudioStatus().then((res) => {
2885-
const modelName = (res.models && res.models[0]) ? res.models[0] : 'local-model';
2886-
if (els.commitAiModelTag) els.commitAiModelTag.textContent = `LM Studio: ${modelName}`;
2887-
});
2888-
}
2889-
if (els.commitAiModelTag) els.commitAiModelTag.textContent = initTag;
2911+
2912+
updateAiModelSelectOptions();
28902913
els.topPushBtn.addEventListener('click', pushOrigin);
28912914
if (els.btnUnpushedBannerPush) {
28922915
els.btnUnpushedBannerPush.addEventListener('click', pushOrigin);
@@ -3098,10 +3121,18 @@ function initEventHandlers() {
30983121
});
30993122
}
31003123

3101-
document.addEventListener('DOMContentLoaded', () => {
3124+
document.addEventListener('DOMContentLoaded', async () => {
31023125
applyUiScale(appSettings.uiScale);
31033126
initEventHandlers();
3127+
updateAiModelSelectOptions();
31043128
renderRecentRepos();
3129+
3130+
// Auto-fetch Gemini models if Gemini API key exists
3131+
if (appSettings.geminiApiKey) {
3132+
fetchGeminiModels();
3133+
}
3134+
// Auto-fetch LM Studio models
3135+
checkLmStudioStatus();
31053136
});
31063137

31073138
// ─── GitHub Functions ──────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)