auto-git:
[change] index.html [change] main.js [change] preload.js [change] renderer.js [change] settings.html
This commit is contained in:
@@ -295,6 +295,11 @@
|
||||
style="background: var(--accent); color: #fff; border-color: var(--border)">
|
||||
Generate README
|
||||
</button>
|
||||
<button id="squashBtn"
|
||||
class="ml-2 px-4 py-2 border rounded font-semibold"
|
||||
style="background: var(--accent); color: #fff; border-color: var(--border)">
|
||||
Squash
|
||||
</button>
|
||||
<button id="pushBtn"
|
||||
class="ml-2 px-4 py-2 border rounded font-semibold"
|
||||
style="background: var(--accent); color: #fff; border-color: var(--border)">
|
||||
|
||||
436
main.js
436
main.js
@@ -107,6 +107,13 @@ console.log("Startup-Folders:", store.get('folders'));
|
||||
let tray = null;
|
||||
|
||||
const MAX_FILES_PER_COMMIT = 20;
|
||||
const EMPTY_TREE_HASH = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
|
||||
const SQUASH_CHUNK_WINDOW_MS = 2 * 60 * 1000;
|
||||
const MAX_SQUASH_PROMPT_CHARS = 25000;
|
||||
const MAX_SQUASH_PROMPT_MESSAGE_CHARS = 400;
|
||||
const MAX_SQUASH_NAME_STATUS_CHARS = 6000;
|
||||
const MAX_SQUASH_DIFFSTAT_CHARS = 4000;
|
||||
const MAX_SQUASH_COMMIT_MESSAGE_CHARS = 160;
|
||||
|
||||
function createTray(win) {
|
||||
const iconPath = path.join(__dirname, 'assets/icon/trayicon.png');
|
||||
@@ -340,6 +347,422 @@ function buildCommitMessageFromStatus(status, prefix = '[auto]') {
|
||||
return prefix + '\n' + changes.map(l => ` ${l}`).join('\n');
|
||||
}
|
||||
|
||||
function truncateText(text, maxChars) {
|
||||
const normalized = String(text || '').trim();
|
||||
if (normalized.length <= maxChars) return normalized;
|
||||
return normalized.slice(0, Math.max(0, maxChars - 1)).trimEnd() + '…';
|
||||
}
|
||||
|
||||
function normalizeSingleLine(text) {
|
||||
return String(text || '')
|
||||
.replace(/^```(?:\w+)?|```$/gmi, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function truncatePromptBlock(text, maxChars) {
|
||||
const normalized = String(text || '').trim();
|
||||
if (!normalized) return '';
|
||||
if (normalized.length <= maxChars) return normalized;
|
||||
return normalized.slice(0, maxChars).trimEnd() + '\n…';
|
||||
}
|
||||
|
||||
function buildSquashFallbackMessage(commits) {
|
||||
const shortHashes = commits.map(commit => commit.hash.substring(0, 7));
|
||||
return truncateText(`auto-git: [squash] ${shortHashes.join(', ')}`, MAX_SQUASH_COMMIT_MESSAGE_CHARS);
|
||||
}
|
||||
|
||||
function sanitizeSquashCommitMessage(rawOutput, commits) {
|
||||
let cleaned = normalizeSingleLine(rawOutput)
|
||||
.replace(/^["'`]+|["'`]+$/g, '')
|
||||
.replace(/^commit message\s*[:\-]\s*/i, '')
|
||||
.trim();
|
||||
|
||||
if (!cleaned) {
|
||||
cleaned = buildSquashFallbackMessage(commits);
|
||||
}
|
||||
|
||||
return truncateText(cleaned, MAX_SQUASH_COMMIT_MESSAGE_CHARS);
|
||||
}
|
||||
|
||||
async function runGitCommand(repoPath, args, options = {}) {
|
||||
const { env = {}, input = '' } = options;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = spawn('git', args, {
|
||||
cwd: repoPath,
|
||||
env: { ...process.env, ...env },
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
proc.stdout.on('data', chunk => { stdout += chunk.toString(); });
|
||||
proc.stderr.on('data', chunk => { stderr += chunk.toString(); });
|
||||
proc.on('error', reject);
|
||||
|
||||
if (input) {
|
||||
proc.stdin.write(input);
|
||||
}
|
||||
proc.stdin.end();
|
||||
|
||||
proc.on('close', code => {
|
||||
if (code === 0) {
|
||||
resolve({ stdout, stderr });
|
||||
return;
|
||||
}
|
||||
reject(new Error((stderr || stdout || `git ${args.join(' ')} failed`).trim()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function getCommitHistoryForSquash(repoPath) {
|
||||
const git = simpleGit(repoPath);
|
||||
const mergeCommits = (await git.raw(['rev-list', '--min-parents=2', 'HEAD'])).trim();
|
||||
if (mergeCommits) {
|
||||
throw new Error('Smart squash currently only supports linear commit history.');
|
||||
}
|
||||
|
||||
const raw = await git.raw([
|
||||
'log',
|
||||
'--reverse',
|
||||
'--format=%H%x1f%T%x1f%P%x1f%ct%x1f%an%x1f%ae%x1f%aI%x1f%cn%x1f%ce%x1f%cI%x1f%B%x1e',
|
||||
'HEAD'
|
||||
]);
|
||||
|
||||
return raw
|
||||
.split('\x1e')
|
||||
.map(entry => entry.trim())
|
||||
.filter(Boolean)
|
||||
.map(entry => {
|
||||
const parts = entry.split('\x1f');
|
||||
if (parts.length < 11) return null;
|
||||
|
||||
const [
|
||||
hash,
|
||||
tree,
|
||||
parentsRaw,
|
||||
timestampRaw,
|
||||
authorName,
|
||||
authorEmail,
|
||||
authorDate,
|
||||
committerName,
|
||||
committerEmail,
|
||||
committerDate,
|
||||
...messageParts
|
||||
] = parts;
|
||||
|
||||
return {
|
||||
hash,
|
||||
tree,
|
||||
parents: parentsRaw ? parentsRaw.split(' ').filter(Boolean) : [],
|
||||
timestampMs: Number(timestampRaw) * 1000,
|
||||
authorName,
|
||||
authorEmail,
|
||||
authorDate,
|
||||
committerName,
|
||||
committerEmail,
|
||||
committerDate,
|
||||
message: messageParts.join('\x1f').replace(/\s+$/, '')
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function detectSquashChunks(commits) {
|
||||
if (!commits.length) return [];
|
||||
|
||||
const chunks = [[commits[0]]];
|
||||
for (let i = 1; i < commits.length; i++) {
|
||||
const previous = commits[i - 1];
|
||||
const current = commits[i];
|
||||
const sameChunk = Math.abs(current.timestampMs - previous.timestampMs) <= SQUASH_CHUNK_WINDOW_MS;
|
||||
|
||||
if (sameChunk) {
|
||||
chunks[chunks.length - 1].push(current);
|
||||
} else {
|
||||
chunks.push([current]);
|
||||
}
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
async function generateSquashCommitMessagePrompt(repoPath, commits) {
|
||||
const git = simpleGit(repoPath);
|
||||
const oldestCommit = commits[0];
|
||||
const newestCommit = commits[commits.length - 1];
|
||||
const diffBase = oldestCommit.parents[0] || EMPTY_TREE_HASH;
|
||||
|
||||
const [nameStatusRaw, diffStatRaw] = await Promise.all([
|
||||
git.diff(['--name-status', diffBase, newestCommit.hash]),
|
||||
git.diff(['--stat', '--compact-summary', diffBase, newestCommit.hash])
|
||||
]);
|
||||
|
||||
let omittedMessages = 0;
|
||||
const commitsForPrompt = commits.map(commit => {
|
||||
const item = { hash: commit.hash.substring(0, 7) };
|
||||
const normalizedMessage = normalizeSingleLine(commit.message);
|
||||
|
||||
if (normalizedMessage && normalizedMessage.length <= MAX_SQUASH_PROMPT_MESSAGE_CHARS) {
|
||||
item.message = normalizedMessage;
|
||||
} else if (normalizedMessage) {
|
||||
omittedMessages += 1;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
const omissionNote = omittedMessages
|
||||
? `Some original commit messages were omitted because they were too long (${omittedMessages}).`
|
||||
: '';
|
||||
|
||||
const prompt = `
|
||||
Analyze the following git commits that will be squashed into one commit.
|
||||
Generate one concise commit message summarizing the combined actual change.
|
||||
- Output ONLY the literal commit message text.
|
||||
- Do NOT add markdown, quotes, bullet points, or explanations.
|
||||
- Keep it under 140 characters.
|
||||
|
||||
COMMITS:
|
||||
${JSON.stringify(commitsForPrompt, null, 2)}
|
||||
|
||||
${omissionNote}
|
||||
|
||||
CHANGED FILES:
|
||||
${truncatePromptBlock(nameStatusRaw, MAX_SQUASH_NAME_STATUS_CHARS) || '(none)'}
|
||||
|
||||
DIFF STAT:
|
||||
${truncatePromptBlock(diffStatRaw, MAX_SQUASH_DIFFSTAT_CHARS) || '(none)'}
|
||||
`.trim();
|
||||
|
||||
if (prompt.length > MAX_SQUASH_PROMPT_CHARS) {
|
||||
throw new Error(`Squash prompt too large (${prompt.length} chars) for ${repoPath}`);
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
async function generateSquashCommitMessage(repoPath, commits, win) {
|
||||
const prompt = await generateSquashCommitMessagePrompt(repoPath, commits);
|
||||
const llmRaw = await streamLLMCommitMessages(prompt, chunk => process.stdout.write(chunk), win);
|
||||
return sanitizeSquashCommitMessage(llmRaw, commits);
|
||||
}
|
||||
|
||||
async function buildSquashPlan(repoPath, chunks, win) {
|
||||
const plan = [];
|
||||
|
||||
for (const chunk of chunks) {
|
||||
if (chunk.length === 1) {
|
||||
plan.push({
|
||||
commits: chunk,
|
||||
message: chunk[0].message || 'auto-git'
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
let message;
|
||||
try {
|
||||
debug(`[smartSquash] Generating message for chunk of ${chunk.length} commits in ${repoPath}`);
|
||||
message = await generateSquashCommitMessage(repoPath, chunk, win);
|
||||
} catch (err) {
|
||||
debug(`[smartSquash] Falling back for chunk in ${repoPath}: ${err.message || err}`);
|
||||
message = buildSquashFallbackMessage(chunk);
|
||||
}
|
||||
|
||||
plan.push({ commits: chunk, message });
|
||||
}
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
async function smartSquashCommits(folderPath, win) {
|
||||
if (!folderPath || !fs.existsSync(folderPath)) {
|
||||
throw new Error('Folder not found.');
|
||||
}
|
||||
|
||||
let folders = store.get('folders') || [];
|
||||
let idx = folders.findIndex(f => f.path === folderPath);
|
||||
if (idx === -1) {
|
||||
throw new Error('Folder is not tracked by auto-git.');
|
||||
}
|
||||
|
||||
const folderObj = folders[idx];
|
||||
if (folderObj.needsRelocation) {
|
||||
throw new Error('This folder needs to be relocated before squashing commits.');
|
||||
}
|
||||
if (folderObj.rewriteInProgress) {
|
||||
throw new Error('Another rewrite is already running for this repository.');
|
||||
}
|
||||
if (isRebaseInProgress(folderPath)) {
|
||||
throw new Error('A git rebase is already in progress for this repository.');
|
||||
}
|
||||
|
||||
const git = simpleGit(folderPath);
|
||||
const currentBranch = (await git.revparse(['--abbrev-ref', 'HEAD'])).trim();
|
||||
if (!currentBranch || currentBranch === 'HEAD') {
|
||||
throw new Error('Cannot squash commits while HEAD is detached.');
|
||||
}
|
||||
|
||||
const commits = await getCommitHistoryForSquash(folderPath);
|
||||
if (commits.length < 2) {
|
||||
return {
|
||||
success: true,
|
||||
squashedChunks: 0,
|
||||
removedCommits: 0,
|
||||
message: 'Not enough commits to squash.'
|
||||
};
|
||||
}
|
||||
|
||||
const chunks = detectSquashChunks(commits);
|
||||
const squashableChunks = chunks.filter(chunk => chunk.length > 1);
|
||||
if (!squashableChunks.length) {
|
||||
return {
|
||||
success: true,
|
||||
squashedChunks: 0,
|
||||
removedCommits: 0,
|
||||
message: 'No quick-succession commit chunks found.'
|
||||
};
|
||||
}
|
||||
|
||||
const originalState = {
|
||||
llmCandidates: Array.isArray(folderObj.llmCandidates) ? folderObj.llmCandidates.slice() : [],
|
||||
llmBuffer: Array.isArray(folderObj.llmBuffer) ? folderObj.llmBuffer.slice() : [],
|
||||
linesChanged: folderObj.linesChanged || 0,
|
||||
firstCandidateBirthday: folderObj.firstCandidateBirthday || null,
|
||||
lastHeadHash: folderObj.lastHeadHash || null
|
||||
};
|
||||
|
||||
folders[idx] = {
|
||||
...folderObj,
|
||||
rewriteInProgress: true,
|
||||
rewriteStartedAt: Date.now()
|
||||
};
|
||||
store.set('folders', folders);
|
||||
|
||||
let originalHead = null;
|
||||
let stashed = false;
|
||||
let stashWarning = null;
|
||||
|
||||
try {
|
||||
originalHead = (await git.revparse(['HEAD'])).trim();
|
||||
|
||||
const status = await git.status();
|
||||
const hasChanges =
|
||||
status.not_added.length > 0 ||
|
||||
status.created.length > 0 ||
|
||||
status.deleted.length > 0 ||
|
||||
status.modified.length > 0 ||
|
||||
status.renamed.length > 0;
|
||||
|
||||
if (hasChanges) {
|
||||
debug(`[smartSquash] Stashing working tree for ${folderPath}`);
|
||||
await git.stash(['push', '--include-untracked']);
|
||||
stashed = true;
|
||||
}
|
||||
|
||||
const plan = await buildSquashPlan(folderPath, chunks, win);
|
||||
let newHead = null;
|
||||
|
||||
for (const entry of plan) {
|
||||
const lastCommit = entry.commits[entry.commits.length - 1];
|
||||
const env = {
|
||||
GIT_AUTHOR_NAME: lastCommit.authorName || '',
|
||||
GIT_AUTHOR_EMAIL: lastCommit.authorEmail || '',
|
||||
GIT_AUTHOR_DATE: lastCommit.authorDate || '',
|
||||
GIT_COMMITTER_NAME: lastCommit.committerName || lastCommit.authorName || '',
|
||||
GIT_COMMITTER_EMAIL: lastCommit.committerEmail || lastCommit.authorEmail || '',
|
||||
GIT_COMMITTER_DATE: lastCommit.committerDate || lastCommit.authorDate || ''
|
||||
};
|
||||
|
||||
const args = ['commit-tree', lastCommit.tree];
|
||||
if (newHead) {
|
||||
args.push('-p', newHead);
|
||||
}
|
||||
|
||||
const { stdout } = await runGitCommand(folderPath, args, {
|
||||
env,
|
||||
input: `${entry.message || 'auto-git'}\n`
|
||||
});
|
||||
|
||||
newHead = stdout.trim();
|
||||
if (!newHead) {
|
||||
throw new Error('git commit-tree did not return a commit hash.');
|
||||
}
|
||||
}
|
||||
|
||||
await git.reset(['--hard', newHead]);
|
||||
|
||||
if (stashed) {
|
||||
try {
|
||||
await git.stash(['pop']);
|
||||
} catch (err) {
|
||||
stashWarning = err.message || String(err);
|
||||
}
|
||||
}
|
||||
|
||||
folders = store.get('folders') || [];
|
||||
idx = folders.findIndex(f => f.path === folderPath);
|
||||
if (idx !== -1) {
|
||||
folders[idx] = {
|
||||
...folders[idx],
|
||||
rewriteInProgress: false,
|
||||
rewriteStartedAt: null,
|
||||
llmCandidates: [],
|
||||
llmBuffer: [],
|
||||
linesChanged: 0,
|
||||
firstCandidateBirthday: null,
|
||||
lastHeadHash: newHead
|
||||
};
|
||||
store.set('folders', folders);
|
||||
}
|
||||
|
||||
win.webContents.send('repo-updated', folderPath);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
squashedChunks: squashableChunks.length,
|
||||
removedCommits: squashableChunks.reduce((sum, chunk) => sum + (chunk.length - 1), 0),
|
||||
warning: stashWarning
|
||||
};
|
||||
} catch (err) {
|
||||
if (originalHead) {
|
||||
try {
|
||||
await git.reset(['--hard', originalHead]);
|
||||
} catch (_) {
|
||||
// ignore rollback errors, original error is more useful
|
||||
}
|
||||
}
|
||||
|
||||
if (stashed) {
|
||||
try {
|
||||
await git.stash(['pop']);
|
||||
} catch (_) {
|
||||
// keep the original error
|
||||
}
|
||||
}
|
||||
|
||||
folders = store.get('folders') || [];
|
||||
idx = folders.findIndex(f => f.path === folderPath);
|
||||
if (idx !== -1) {
|
||||
folders[idx] = {
|
||||
...folders[idx],
|
||||
rewriteInProgress: false,
|
||||
rewriteStartedAt: null,
|
||||
llmCandidates: originalState.llmCandidates,
|
||||
llmBuffer: originalState.llmBuffer,
|
||||
linesChanged: originalState.linesChanged,
|
||||
firstCandidateBirthday: originalState.firstCandidateBirthday,
|
||||
lastHeadHash: originalState.lastHeadHash
|
||||
};
|
||||
store.set('folders', folders);
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const IGNORED_NAMES = [
|
||||
// Betriebssystem-spezifische Dateien
|
||||
'.DS_Store', // macOS
|
||||
@@ -2797,9 +3220,16 @@ Source Code:
|
||||
return final;
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
ipcMain.handle('squash-commits', async (evt, folderPath) => {
|
||||
try {
|
||||
return await smartSquashCommits(folderPath, BrowserWindow.fromWebContents(evt.sender));
|
||||
} catch (err) {
|
||||
return {
|
||||
success: false,
|
||||
error: err.message || String(err)
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('push-to-gitea', async (_evt, folderPath) => {
|
||||
try {
|
||||
|
||||
@@ -67,6 +67,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
getDailyCommitStats: () => ipcRenderer.invoke('get-daily-commit-stats'),
|
||||
hasReadme: (folderPath) => ipcRenderer.invoke('has-readme', folderPath),
|
||||
generateReadme: (folderPath) => ipcRenderer.invoke('generate-readme', folderPath),
|
||||
squashCommits: (folderPath) => ipcRenderer.invoke('squash-commits', folderPath),
|
||||
pushToGitea: (folderPath) => ipcRenderer.invoke('push-to-gitea', folderPath),
|
||||
initRepo: (folderPath) => ipcRenderer.invoke('init-repo', folderPath),
|
||||
triggerRewriteNow: (folderPath) => ipcRenderer.invoke('trigger-rewrite-now', folderPath),
|
||||
|
||||
32
renderer.js
32
renderer.js
@@ -7,6 +7,7 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
const titleArrow = document.getElementById('folderTitleArrow');
|
||||
const contentList = document.getElementById('contentList');
|
||||
const readmeBtn = document.getElementById('readmeBtn');
|
||||
const squashBtn = document.getElementById('squashBtn');
|
||||
const initRepoBtn = document.getElementById('initRepoBtn');
|
||||
const pushBtn = document.getElementById('pushBtn');
|
||||
const panel = document.querySelector('.flex-1.p-4.overflow-y-auto');
|
||||
@@ -49,6 +50,35 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
readmeBtn.textContent = hasReadme ? 'Update README' : 'Generate README';
|
||||
});
|
||||
|
||||
squashBtn.addEventListener('click', async () => {
|
||||
const selected = await window.electronAPI.getSelected();
|
||||
if (!selected || !selected.path) {
|
||||
return alert('No folder selected to squash!');
|
||||
}
|
||||
|
||||
squashBtn.disabled = true;
|
||||
squashBtn.textContent = 'Squashing…';
|
||||
|
||||
try {
|
||||
const result = await window.electronAPI.squashCommits(selected.path);
|
||||
if (!result?.success) {
|
||||
throw new Error(result?.error || 'Squash failed');
|
||||
}
|
||||
|
||||
const summary = result.squashedChunks
|
||||
? `Squashed ${result.removedCommits} commit(s) across ${result.squashedChunks} chunk(s).`
|
||||
: (result.message || 'No quick-succession commit chunks found.');
|
||||
|
||||
const warning = result.warning ? `\n\nWarning:\n${result.warning}` : '';
|
||||
alert(summary + warning);
|
||||
} catch (err) {
|
||||
alert('Squash failed:\n' + (err.message || err));
|
||||
} finally {
|
||||
squashBtn.disabled = false;
|
||||
squashBtn.textContent = 'Squash';
|
||||
}
|
||||
});
|
||||
|
||||
pushBtn.addEventListener('click', async () => {
|
||||
const selected = await window.electronAPI.getSelected();
|
||||
if (!selected || !selected.path) {
|
||||
@@ -509,7 +539,9 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
initRepoBtn.classList.toggle('hidden', isGit);
|
||||
initRepoBtn.disabled = isGit;
|
||||
readmeBtn.disabled = !isGit;
|
||||
squashBtn.disabled = !isGit;
|
||||
pushBtn.disabled = !isGit;
|
||||
squashBtn.classList.toggle('hidden', !isGit);
|
||||
pushBtn.classList.toggle('hidden', !isGit);
|
||||
if (!isGit) {
|
||||
contentList.innerHTML = '<div class="p-6 text-gray-500">Not a Git repository. Click "Init Repo" to initialize.</div>';
|
||||
|
||||
@@ -274,11 +274,11 @@
|
||||
}
|
||||
|
||||
// Liste der Modell-Namen extrahieren
|
||||
const names = res.models.map(m => m.name || m.model).filter(Boolean);
|
||||
const names = [...new Set(res.models.map(m => m.name || m.model).filter(Boolean))];
|
||||
const qwen = names.filter(n => /^qwen2\.5-coder(:[\w\-.]+)?$/.test(n));
|
||||
|
||||
if (!qwen.length) {
|
||||
// keine qwen2.5-coder → Pull-Buttons
|
||||
if (!names.length) {
|
||||
// keine lokalen Modelle → Pull-Buttons für die bisherigen Defaults
|
||||
container.innerHTML = `
|
||||
<button id="pullCommitModelBtn" style="margin-bottom:8px;">
|
||||
ollama pull qwen2.5-coder:7b
|
||||
@@ -302,8 +302,8 @@
|
||||
arr.map(m => `<option ${m===sel?'selected':''}>${m}</option>`).join('');
|
||||
|
||||
// Default-Auswahl aus Settings (oder erstes gefundenes)
|
||||
const commitDefault = qwen.find(m=>m.includes('7b'))||qwen[0];
|
||||
const readmeDefault= qwen.find(m=>m.includes('32b'))||qwen[0];
|
||||
const commitDefault = qwen.find(m=>m.includes('7b')) || names[0];
|
||||
const readmeDefault= qwen.find(m=>m.includes('32b')) || names[0];
|
||||
const currentCommit = await window.settingsAPI.getCommitModel?.() || commitDefault;
|
||||
const currentReadme= await window.settingsAPI.getReadmeModel?.() || readmeDefault;
|
||||
|
||||
@@ -311,12 +311,12 @@
|
||||
<div class="row">
|
||||
<label>Model for commit message generation:
|
||||
<select id="commitModelSelect">
|
||||
${makeOpts(qwen, currentCommit)}
|
||||
${makeOpts(names, currentCommit)}
|
||||
</select>
|
||||
</label>
|
||||
<label>Model for README generation:
|
||||
<select id="readmeModelSelect">
|
||||
${makeOpts(qwen, currentReadme)}
|
||||
${makeOpts(names, currentReadme)}
|
||||
</select>
|
||||
</label>
|
||||
</div>`;
|
||||
|
||||
Reference in New Issue
Block a user