1
0

Compare commits

...

26 Commits

Author SHA1 Message Date
34731c4e26 Ensure all settings are saved independently and alert on errors 2026-09-17 01:48:20 +02:00
22ea72bccc Simplify pushToGitea logic by removing dirty worktree check 2026-07-16 16:01:08 +02:00
56397d11e2 Update version number and improve commit rewriting logic 2026-07-16 15:37:46 +02:00
2e082a4bca feat(core): Improve stability, logging, and LLM context handling 2026-07-12 14:10:13 +02:00
09349a9477 Update README and dependencies for Auto-Git v1.0.7 2026-07-12 14:10:13 +02:00
a967587181 auto-git:
[change] index.html
 [change] renderer.js
 [change] settings.html
 [change] src-tauri/src/main.rs
 [change] tauriBridge.js
2026-07-12 12:19:00 +02:00
f047d61731 Improve button container layout in renderer and add unit test for preserving commit identities after rewording. 2026-07-12 11:28:42 +02:00
f087e13267 Refactor: Enhance manual content rewrite job reliability
Improves error handling for LLM message generation by checking Ollama status and logging failures.
Also updates progress reporting and refines the identification of commits outside current history.
2026-07-12 11:28:14 +02:00
ce73d0273a Improve reword_commits_sequentially robustness by verifying commit hash before interactive rebase 2026-07-12 11:26:49 +02:00
6bf77cebab Format HashMap::insert call for better readability 2026-07-12 11:26:49 +02:00
d6dc207f2c Test: Add unit tests for git workflow, commit message validation, and temporary repository setup 2026-07-12 11:26:04 +02:00
8769d32855 Refactor: Improves error handling syntax, cleans up option binding, and fixes return type consistency in src-tauri/src/main.rs 2026-07-12 11:25:32 +02:00
a0cb179d6e Implement progress tracking and detailed error handling during content rewriting 2026-07-12 11:25:12 +02:00
9412fb7036 Standardize and improve individual commit rewrite triggering using dedicated function 2026-07-12 11:25:02 +02:00
1887ee7a41 Feat: Add LLM-powered commit rewording and rewrite queue management 2026-07-12 11:24:51 +02:00
ade0f2dcbd Feature: Add 'Rewrite pending' button next to folder title 2026-07-12 11:24:28 +02:00
89a3b47948 Feature: Add API support for rewriting specific or pending commits 2026-07-12 11:23:59 +02:00
82914e969d Refactor candidate collection in run_manual_rewrite_job to use dedicated filtering for hashes lacking identity mapping 2026-07-12 11:23:50 +02:00
b73af714a5 feat(git): Implement manual and bulk rewrite of git commit messages via LLM 2026-07-12 11:23:35 +02:00
39fdfb5cac Add utility functions for fetching, mapping, and reporting commit identities 2026-07-12 11:22:32 +02:00
ca8527ac85 Emit 'repo-updated' event after saving application store 2026-07-12 11:22:32 +02:00
23e08b4162 Improve reliability of LLM commit rewriting by adding message validation and enhancing retry candidate collection. 2026-07-12 11:21:51 +02:00
498fb0112b Feat(llm): Implement LLM integration for automated commit message generation and validation 2026-07-12 11:21:32 +02:00
975561f83e Enhance commit listing to track queued hashes, calculate pending rewrite count, and check against current history. 2026-07-12 11:21:23 +02:00
a972396412 Implement utilities for analyzing commit history and determining pending rewrites 2026-07-12 11:21:09 +02:00
1db2a401b8 Add tracking fields for commit rewriting status 2026-07-12 11:21:09 +02:00
10 changed files with 3031 additions and 464 deletions

View File

@@ -74,6 +74,23 @@ Download the latest release for your platform:
5. **Push to Gitea**
- Configure your Gitea API key in **Settings**, then push commits directly from Auto-Git.
### Git safety behavior
- Starting monitoring establishes a baseline; changes that already existed are left untouched.
- Monitoring commits only paths reported by filesystem events. It never stages the whole repository.
- Build output and dependency folders such as `node_modules`, `dist-tauri`, and `src-tauri/target` are always excluded.
- Auto-Git refuses to commit when the index already contains staged work or when Git is rebasing, merging, resolving conflicts, or using a detached HEAD.
- Threshold- and timer-based commit-message rewrites rebuild commit objects directly without checkout, rebase, hooks, signing, or staging. Every rewritten and descendant commit is checked for identical tree, topology, metadata, and the expected message before the guarded branch compare-and-swap. They do not retain a duplicate history ref. The Reword button preference affects only that button, not scheduled rewrites.
- Commit history follows local branches, so **Jump Here** can navigate backward and forward while `HEAD` is detached. Jumping to a commit that is the unique tip of a local branch reattaches that branch.
- Starting monitoring reconciles Git changes already present in the working tree, including changes made while monitoring was paused, and commits only those reported paths through the normal guarded monitoring pipeline.
- Adding any folder immediately enables monitoring in the UI. Existing Git repositories start their watcher at once; non-repositories stay armed and attach the watcher automatically when **Init Repo** completes, without another Play click.
- Automatic commit-message rewriting performs all Git safety checks before contacting Ollama, bounds diffs to the configured model context, requests an exact JSON schema, and stops after the first failed automatic attempt instead of retrying on each scheduler tick.
- **Rewrite all** is shown whenever the current HEAD history contains paw-marked commits and rewrites all of them in one manually initiated model request and one transactional history update.
- Every automatic candidate batch receives a persisted one-shot identifier before its model request. The same batch can never be sent automatically again, including after a failure or app restart.
- Reword, paw retry, and Rewrite all run their Git preflight synchronously. Dirty, staged, and untracked working changes are preserved because message rewriting never touches the worktree or index. Conflicts, detached HEAD, and in-progress Git operations return an immediate UI error and start no model request.
- Automatic stashing, forced checkout, rebase abort, and hard-reset recovery are not used by monitoring or commit-message rewriting.
- Commit squashing is temporarily disabled until it uses an equivalently isolated transactional mechanism.
---
## Settings

View File

@@ -247,16 +247,23 @@
<!-- Content-Area -->
<div class="flex-1 p-4 overflow-y-auto content-area">
<!-- Title -->
<div id="folderTitleDrop"
class="relative flex items-baseline space-x-1 cursor-pointer select-none group pb-2">
<svg id="folderTitleArrow"
class="h-4 w-6 flex-none transition-transform"
viewBox="0 0 24 24">
<path stroke="currentColor" stroke-width="2" fill="none"
stroke-linecap="round" stroke-linejoin="round"
d="M9 5l7 7-7 7"/>
</svg>
<span id="currentTitle" class="font-semibold text-xl truncate"></span>
<div class="flex items-center pb-2">
<div id="folderTitleDrop"
class="relative flex flex-1 min-w-0 items-baseline space-x-1 cursor-pointer select-none group">
<svg id="folderTitleArrow"
class="h-4 w-6 flex-none transition-transform"
viewBox="0 0 24 24">
<path stroke="currentColor" stroke-width="2" fill="none"
stroke-linecap="round" stroke-linejoin="round"
d="M9 5l7 7-7 7"/>
</svg>
<span id="currentTitle" class="font-semibold text-xl truncate"></span>
</div>
<button id="rewritePendingBtn"
class="hidden ml-3 px-3 py-1.5 text-sm border rounded font-semibold whitespace-nowrap"
style="background: var(--accent); color: #fff; border-color: var(--border)">
Rewrite all (0)
</button>
</div>
<pre id="folderHierarchyDropdown"
class="hidden text-sm font-mono select-text leading-snug mb-2 mt-1 max-h-80 overflow-auto"
@@ -303,6 +310,65 @@
</button>
</div>
<div id="rewordModeModal"
class="hidden fixed inset-0 z-50 items-center justify-center bg-black/40 px-4"
role="dialog" aria-modal="true" aria-labelledby="rewordModeTitle">
<div class="w-full max-w-md rounded-lg border bg-white p-5 shadow-xl"
style="border-color: var(--border)">
<h3 id="rewordModeTitle" class="text-lg font-semibold text-gray-900">
Reword commit message
</h3>
<p class="mt-2 text-sm text-gray-600">
Choose Automatic to ask the configured LLM, or Manual to type the message yourself.
</p>
<label class="mt-4 flex items-center gap-2 text-sm text-gray-700 select-none">
<input id="rememberRewordMode" type="checkbox" class="h-4 w-4" />
Remember my choice
</label>
<div class="mt-5 flex justify-end gap-2">
<button id="cancelRewordModeBtn" class="px-3 py-2 text-sm border rounded hover:bg-gray-100">
Cancel
</button>
<button id="manualRewordModeBtn" class="px-3 py-2 text-sm border rounded hover:bg-gray-100">
Manual
</button>
<button id="autoRewordModeBtn" class="px-3 py-2 text-sm border rounded font-semibold"
style="background: var(--accent); color: #fff; border-color: var(--border)">
Automatic
</button>
</div>
</div>
</div>
<div id="manualRewordModal"
class="hidden fixed inset-0 z-50 items-center justify-center bg-black/40 px-4"
role="dialog" aria-modal="true" aria-labelledby="manualRewordTitle">
<form id="manualRewordForm"
class="w-full max-w-lg rounded-lg border bg-white p-5 shadow-xl"
style="border-color: var(--border)">
<h3 id="manualRewordTitle" class="text-lg font-semibold text-gray-900">
Enter commit message
</h3>
<p class="mt-2 text-sm text-gray-600">
Type the new single-line message for this commit.
</p>
<input id="manualRewordInput" type="text" required autocomplete="off"
class="mt-4 w-full rounded border border-gray-300 px-3 py-2 text-sm text-gray-900 focus:outline-none focus:ring-2"
style="--tw-ring-color: var(--accent)" />
<div class="mt-5 flex justify-end gap-2">
<button id="cancelManualRewordBtn" type="button"
class="px-3 py-2 text-sm border rounded hover:bg-gray-100">
Cancel
</button>
<button id="applyManualRewordBtn" type="submit"
class="px-3 py-2 text-sm border rounded font-semibold"
style="background: var(--accent); color: #fff; border-color: var(--border)">
Apply
</button>
</div>
</form>
</div>
<script src="renderer.js"></script>
<script src="animeCat.js"></script>
<script type="module">

View File

@@ -1,7 +1,7 @@
{
"productName": "Auto-Git",
"name": "Auto-Git",
"version": "1.0.0",
"version": "1.0.11",
"description": "Auto-Git: Git-Überwachung mit automatischer LLM-Commit-Message und README-Erstellung",
"author": "Victor Giers",
"scripts": {

View File

@@ -9,6 +9,16 @@ window.addEventListener('DOMContentLoaded', async () => {
const readmeBtn = document.getElementById('readmeBtn');
const initRepoBtn = document.getElementById('initRepoBtn');
const pushBtn = document.getElementById('pushBtn');
const rewritePendingBtn = document.getElementById('rewritePendingBtn');
const rewordModeModal = document.getElementById('rewordModeModal');
const rememberRewordMode = document.getElementById('rememberRewordMode');
const cancelRewordModeBtn = document.getElementById('cancelRewordModeBtn');
const manualRewordModeBtn = document.getElementById('manualRewordModeBtn');
const autoRewordModeBtn = document.getElementById('autoRewordModeBtn');
const manualRewordModal = document.getElementById('manualRewordModal');
const manualRewordForm = document.getElementById('manualRewordForm');
const manualRewordInput = document.getElementById('manualRewordInput');
const cancelManualRewordBtn = document.getElementById('cancelManualRewordBtn');
const panel = document.querySelector('.flex-1.p-4.overflow-y-auto');
const PAGE_SIZE = 50;
@@ -20,6 +30,59 @@ window.addEventListener('DOMContentLoaded', async () => {
let lastFolderPath = null;
let lastPage = null;
function chooseRewordMode() {
rememberRewordMode.checked = false;
rewordModeModal.classList.remove('hidden');
rewordModeModal.classList.add('flex');
return new Promise(resolve => {
const finish = mode => {
rewordModeModal.classList.add('hidden');
rewordModeModal.classList.remove('flex');
resolve(mode ? { mode, remember: rememberRewordMode.checked } : null);
};
cancelRewordModeBtn.onclick = () => finish(null);
manualRewordModeBtn.onclick = () => finish('manual');
autoRewordModeBtn.onclick = () => finish('auto');
});
}
function promptForCommitMessage(currentMessage) {
manualRewordInput.value = currentMessage || '';
manualRewordInput.setCustomValidity('');
manualRewordModal.classList.remove('hidden');
manualRewordModal.classList.add('flex');
requestAnimationFrame(() => {
manualRewordInput.focus();
manualRewordInput.select();
});
return new Promise(resolve => {
const finish = message => {
manualRewordModal.classList.add('hidden');
manualRewordModal.classList.remove('flex');
resolve(message);
};
cancelManualRewordBtn.onclick = () => finish(null);
manualRewordInput.oninput = () => manualRewordInput.setCustomValidity('');
manualRewordInput.onkeydown = event => {
if (event.key === 'Escape') {
event.preventDefault();
finish(null);
}
};
manualRewordForm.onsubmit = event => {
event.preventDefault();
const message = manualRewordInput.value.trim();
if (!message) {
manualRewordInput.setCustomValidity('The commit message cannot be empty.');
manualRewordInput.reportValidity();
return;
}
manualRewordInput.setCustomValidity('');
finish(message);
};
});
}
const slot = document.getElementById('catSlot');
window.cat = new window.AnimeCat(slot, {
images: {
@@ -59,16 +122,8 @@ window.addEventListener('DOMContentLoaded', async () => {
pushBtn.textContent = 'Pushing…';
try {
const hasUncommittedFiles = await window.electronAPI.hasDiffs(selected);
if (hasUncommittedFiles) {
const confirmed = confirm(
'This repository contains uncommitted or untracked files.\n\n' +
'Only committed files will be pushed. Continue anyway?'
);
if (!confirmed) return;
}
// send the folder‐path to main via a new IPC channel 'push-to-gitea'
const result = await window.electronAPI.pushToGitea(selected.path, hasUncommittedFiles);
const result = await window.electronAPI.pushToGitea(selected.path);
if (result.success) {
alert('✔ Pushed successfully to Gitea: ' + result.repoUrl);
} else {
@@ -107,6 +162,24 @@ window.addEventListener('DOMContentLoaded', async () => {
}
});
rewritePendingBtn.addEventListener('click', async () => {
const selected = await window.electronAPI.getSelected();
if (!selected?.path) return;
rewritePendingBtn.disabled = true;
rewritePendingBtn.textContent = 'Starting rewrite…';
try {
const result = await window.electronAPI.rewritePendingCommits(selected.path);
if (!result?.success) {
alert('Rewrite could not start:\n' + (result?.error || 'Unknown error'));
}
} catch (err) {
alert('Rewrite could not start:\n' + (err.message || err));
} finally {
const refreshed = await getFolderObjByPath(selected.path);
if (refreshed) await renderContent(refreshed, lastPage);
}
});
// Drag and Drop
document.body.addEventListener('dragover', e => {
@@ -312,8 +385,14 @@ window.addEventListener('DOMContentLoaded', async () => {
const pauseBtn = li.querySelector('.pause-play-btn');
pauseBtn.addEventListener('click', async e => {
e.stopPropagation();
await window.electronAPI.setMonitoring(folderObj, !isMonitoring);
await renderSidebar();
pauseBtn.disabled = true;
try {
await window.electronAPI.setMonitoring(folderObj, !isMonitoring);
} catch (err) {
alert(`Monitoring could not be changed:\n${err?.message || err}`);
} finally {
await renderSidebar();
}
});
// Remove-Button
@@ -532,6 +611,7 @@ window.addEventListener('DOMContentLoaded', async () => {
pushBtn.disabled = !isGit;
pushBtn.classList.toggle('hidden', !isGit);
if (!isGit) {
rewritePendingBtn.classList.add('hidden');
contentList.innerHTML = '<div class="p-6 text-gray-500">Not a Git repository. Click "Init Repo" to initialize.</div>';
paginationEl.innerHTML = '';
lastFolderPath = folder;
@@ -558,19 +638,24 @@ window.addEventListener('DOMContentLoaded', async () => {
lastFolderPath = folder;
lastPage = usePage;
const { head, commits, total, page: currentPage, pageSize, pages } =
const { head, commits, total, page: currentPage, pageSize, pages, pendingRewriteCount } =
await window.electronAPI.getCommits(folderObj, usePage, PAGE_SIZE);
const pendingCount = pendingRewriteCount || 0;
rewritePendingBtn.classList.toggle('hidden', pendingCount === 0 && !folderObj.rewriteInProgress);
rewritePendingBtn.disabled = !!folderObj.rewriteInProgress || pendingCount === 0;
rewritePendingBtn.textContent = folderObj.rewriteInProgress
? 'Rewriting…'
: `Rewrite all (${pendingCount})`;
if (!commits || !commits.length) {
rewritePendingBtn.classList.add('hidden');
contentList.innerHTML = '<div class="p-6 text-gray-500">No commits found.</div>';
paginationEl.innerHTML = '';
return;
}
contentList.innerHTML = commits.map(c => {
const isQueued = folderObj.llmCandidates && folderObj.llmCandidates.some(fullHash =>
fullHash.startsWith(c.hash)
);
return `
<li style="position:relative;" class="w-full p-3 mb-2 bg-white border border-gray-200 rounded shadow-sm
${c.hash === head ? 'current-commit' : ''}">
@@ -579,7 +664,7 @@ window.addEventListener('DOMContentLoaded', async () => {
<span>${new Date(c.date).toLocaleString()}</span>
</div>
<div class="text-gray-800 mb-2">${c.message}</div>
<div class="flex space-x-2 mb-2">
<div class="flex flex-wrap gap-2 mb-2">
<button class="diff-btn flex items-center px-2 py-1 text-xs border rounded hover:bg-gray-100" data-hash="${c.hash}">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1 rotate" viewBox="0 0 24 24"
stroke="currentColor" fill="none">
@@ -605,6 +690,17 @@ window.addEventListener('DOMContentLoaded', async () => {
</svg>
Jump Here
</button>
<button
class="reword-btn flex items-center px-2 py-1 text-xs border rounded ${!c.canReword || folderObj.rewriteInProgress ? 'disabled' : 'hover:bg-gray-100'}"
data-hash="${c.hash}"
title="${c.canReword ? 'Generate and apply a new LLM commit message' : 'This commit is not in the current HEAD history'}"
${!c.canReword || folderObj.rewriteInProgress ? 'disabled' : ''}>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
Reword
</button>
</div>
<div class="diff-container relative">
<button class="copy-diff-btn absolute top-1 right-1 p-1 border rounded hover:bg-gray-100 flex items-center justify-center">
@@ -618,11 +714,12 @@ window.addEventListener('DOMContentLoaded', async () => {
<pre class="m-0"></pre>
</div>
${
isQueued
c.needsRewrite
? `<img src="assets/cat/paw.png"
alt="In Rewrite Queue"
title="In Rewrite Queue"
alt="Needs LLM rewrite"
title="Needs LLM rewrite — click to retry this commit"
class="paw-queued"
data-hash="${c.hash}"
style="pointer-events: auto; cursor: pointer; z-index:10;">`
: ''
}
@@ -718,16 +815,84 @@ window.addEventListener('DOMContentLoaded', async () => {
});
});
// Paw click: manually trigger rewrite for this folder
async function startSingleRewrite(hash, control) {
control.disabled = true;
try {
const result = await window.electronAPI.rewriteCommit(folderObj.path, hash);
if (!result?.success) {
alert('Rewrite could not start:\n' + (result?.error || 'Unknown error'));
}
} catch (e) {
console.error('Commit rewrite error:', e);
alert('Rewrite could not start:\n' + (e.message || e));
} finally {
const refreshed = await getFolderObjByPath(folderObj.path);
if (refreshed) await renderContent(refreshed, currentPage);
}
}
async function startTypedRewrite(hash, currentMessage, control) {
const message = await promptForCommitMessage(currentMessage);
if (message === null) return;
control.disabled = true;
try {
const result = await window.electronAPI.rewriteCommitWithMessage(
folderObj.path,
hash,
message
);
if (!result?.success) {
alert('Rewrite could not start:\n' + (result?.error || 'Unknown error'));
}
} catch (e) {
console.error('Typed commit rewrite error:', e);
alert('Rewrite could not start:\n' + (e.message || e));
} finally {
const refreshed = await getFolderObjByPath(folderObj.path);
if (refreshed) await renderContent(refreshed, currentPage);
}
}
async function handleRewordButton(commit, control) {
let mode = 'ask';
try {
mode = await window.settingsAPI.getRewordMode();
} catch (e) {
console.error('Could not load Reword choice:', e);
}
if (!['ask', 'auto', 'manual'].includes(mode)) mode = 'ask';
if (mode === 'ask') {
const choice = await chooseRewordMode();
if (!choice) return;
mode = choice.mode;
if (choice.remember) {
try {
await window.settingsAPI.setRewordMode(mode);
} catch (e) {
console.error('Could not remember Reword choice:', e);
alert('Your choice could not be saved, but this rewrite will continue.');
}
}
}
if (mode === 'manual') {
await startTypedRewrite(commit.hash, commit.message, control);
} else {
await startSingleRewrite(commit.hash, control);
}
}
// Reword is available for every commit in the current HEAD history.
contentList.querySelectorAll('.reword-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const commit = commits.find(item => item.hash === btn.dataset.hash);
if (commit) await handleRewordButton(commit, btn);
});
});
// A paw is both the durable "needs rewrite" marker and its individual retry action.
contentList.querySelectorAll('.paw-queued').forEach(img => {
img.addEventListener('click', async () => {
try {
await window.electronAPI.triggerRewriteNow(folderObj.path);
} catch (e) {
console.error('Manual rewrite trigger error:', e);
} finally {
await renderContent(folderObj, currentPage);
}
await startSingleRewrite(img.dataset.hash, img);
});
});
@@ -788,6 +953,35 @@ window.addEventListener('DOMContentLoaded', async () => {
await renderContent(obj);
});
window.addEventListener('rewrite-progress', async e => {
const progress = e.detail || {};
const selected = await window.electronAPI.getSelected();
if (!selected || selected.path !== progress.folderPath) return;
if (progress.status === 'running') {
rewritePendingBtn.classList.remove('hidden');
rewritePendingBtn.disabled = true;
rewritePendingBtn.textContent = `Rewriting ${progress.current}/${progress.total}…`;
return;
}
const refreshed = await getFolderObjByPath(progress.folderPath);
if (refreshed) await renderContent(refreshed, lastPage);
if (progress.status === 'failed' || progress.status === 'partial') {
const heading = progress.status === 'partial'
? 'Some commit messages could not be rewritten. Their paws remain.'
: 'The commit message could not be rewritten. Its paw remains.';
const details = progress.error ? `\n\n${String(progress.error).slice(0, 1200)}` : '';
alert(heading + details);
}
});
window.addEventListener('monitoring-error', e => {
const detail = e.detail || {};
const message = detail.message || detail.code || 'Unknown monitoring error';
alert(`Auto-Git paused a repository operation:\n${message}`);
});
titleEl.addEventListener('contextmenu', e => {
e.preventDefault();
if (titleEl.textContent !== 'No folder selected') {

View File

@@ -176,6 +176,7 @@
const cbSkip = document.getElementById('skipPrompt');
const thresholdInput = document.getElementById('intelligentCommitThresholdInput');
const minutesInput = document.getElementById('intelligentCommitMinutesInput');
const rewordModeSelect = document.getElementById('rewordModeSelect');
const ok = document.getElementById('okBtn');
const cancel = document.getElementById('cancelBtn');
const cbAutostart = document.getElementById('autostart');
@@ -190,11 +191,12 @@
cbCloseToTray.checked = initialCloseToTray;
// Initialwerte parallel laden
const [initialTheme, initialSkip, initialThreshold, initialMinutes] = await Promise.all([
const [initialTheme, initialSkip, initialThreshold, initialMinutes, initialRewordMode] = await Promise.all([
window.settingsAPI.getTheme(),
window.settingsAPI.getSkipPrompt(),
window.electronAPI.getIntelligentCommitThreshold(),
window.electronAPI.getMinutesCommitThreshold()
window.electronAPI.getMinutesCommitThreshold(),
window.settingsAPI.getRewordMode()
]);
const fallbackTheme = VALID_THEMES.includes(initialTheme) ? initialTheme : 'sky';
@@ -204,6 +206,9 @@
cbSkip.checked = initialSkip;
thresholdInput.value = initialThreshold;
minutesInput.value = initialMinutes;
rewordModeSelect.value = ['ask', 'auto', 'manual'].includes(initialRewordMode)
? initialRewordMode
: 'ask';
// **Vorschau**: Theme sofort übernehmen
themeSelect.addEventListener('change', async () => {
@@ -218,35 +223,44 @@
// OK: alles final speichern
// OK: alles final speichern – eine fehlschlagende Einstellung darf die
// anderen nicht blockieren und das Fenster muss sich immer schließen.
ok.addEventListener('click', async () => {
await window.settingsAPI.setSkipPrompt(cbSkip.checked);
await window.settingsAPI.setAutostart(cbAutostart.checked);
await window.settingsAPI.setCloseToTray(cbCloseToTray.checked);
await window.settingsAPI.setTheme(themeSelect.value || fallbackTheme);
const errors = [];
const safe = async (name, promise) => {
try { await promise; } catch (e) { errors.push(`${name}: ${e}`); }
};
await safe('Skip-Prompt', window.settingsAPI.setSkipPrompt(cbSkip.checked));
await safe('Autostart', window.settingsAPI.setAutostart(cbAutostart.checked));
await safe('Close-to-Tray', window.settingsAPI.setCloseToTray(cbCloseToTray.checked));
await safe('Theme', window.settingsAPI.setTheme(themeSelect.value || fallbackTheme));
await safe('Reword-Mode', window.settingsAPI.setRewordMode(rewordModeSelect.value || 'ask'));
// Threshold speichern (immer Wert aus Feld nehmen!)
let threshold = parseInt(thresholdInput.value, 10);
if (isNaN(threshold)) threshold = 10;
if (threshold < 1) threshold = 1;
if (threshold > 1000) threshold = 1000;
await window.settingsAPI.setIntelligentCommitThreshold(threshold);
await safe('Commit-Threshold', window.settingsAPI.setIntelligentCommitThreshold(threshold));
let minutes = parseInt(minutesInput.value, 10);
if (isNaN(minutes)) minutes = 10;
if (minutes < 1) minutes = 1;
if (minutes > 1000) minutes = 60;
await window.settingsAPI.setMinutesCommitThreshold(minutes);
await safe('Minutes-Threshold', window.settingsAPI.setMinutesCommitThreshold(minutes));
// Dropdowns speichern
const commitSel = document.getElementById('commitModelSelect');
const readmeSel = document.getElementById('readmeModelSelect');
if (commitSel) await window.settingsAPI.setCommitModel(commitSel.value);
if (readmeSel) await window.settingsAPI.setReadmeModel(readmeSel.value);
await window.settingsAPI.setGiteaToken(giteaTokenInput.value.trim());
if (commitSel) await safe('Commit-Model', window.settingsAPI.setCommitModel(commitSel.value));
if (readmeSel) await safe('Readme-Model', window.settingsAPI.setReadmeModel(readmeSel.value));
await safe('Gitea-Token', window.settingsAPI.setGiteaToken(giteaTokenInput.value.trim()));
if (errors.length) {
alert('Nicht alle Einstellungen konnten gespeichert werden:\n\n' + errors.join('\n'));
}
window.settingsAPI.close();
});
// Cancel / Close: Theme zurücksetzen, dann schließen
@@ -343,6 +357,14 @@
<option value="grey">Grey</option>
</select>
</label>
<label class="option" style="align-items:center;">
Reword button:
<select id="rewordModeSelect" style="margin-left:1em;">
<option value="ask">Ask every time</option>
<option value="auto">Automatic (LLM)</option>
<option value="manual">Manual input</option>
</select>
</label>
<label class="option">
<input type="checkbox" id="skipPrompt" class="flex-none" />
Skip prompt asking to remove .git folder if it has only one commit and no changes

12
src-tauri/Cargo.lock generated
View File

@@ -273,7 +273,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "auto-git"
version = "1.0.0"
version = "1.0.11"
dependencies = [
"arboard",
"auto-launch",
@@ -291,6 +291,7 @@ dependencies = [
"tauri-build",
"tempfile",
"thiserror 2.0.18",
"wait-timeout",
]
[[package]]
@@ -4798,6 +4799,15 @@ dependencies = [
"libc",
]
[[package]]
name = "wait-timeout"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
dependencies = [
"libc",
]
[[package]]
name = "walkdir"
version = "2.5.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "auto-git"
version = "1.0.0"
version = "1.0.11"
description = "Auto-Git: Git monitoring with automatic LLM commit messages and README generation"
authors = ["Victor Giers"]
edition = "2021"
@@ -23,6 +23,7 @@ tempfile = "3.23.0"
tauri = { version = "2.11.0", features = ["tray-icon", "image-png", "image-ico"] }
thiserror = "2.0.17"
reqwest = { version = "0.12.24", default-features = false, features = ["blocking", "json", "rustls-tls"] }
wait-timeout = "0.2.1"
[target.'cfg(target_os = "macos")'.dependencies]
auto-launch = "0.5.0"

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Auto-Git",
"version": "1.0.0",
"version": "1.0.11",
"identifier": "com.victorgiers.auto-git",
"build": {
"frontendDist": "../dist-tauri"

View File

@@ -26,6 +26,14 @@
window.dispatchEvent(new CustomEvent('repo-updated', { detail: event.payload }));
});
listen('rewrite-progress', event => {
window.dispatchEvent(new CustomEvent('rewrite-progress', { detail: event.payload }));
});
listen('monitoring-error', event => {
window.dispatchEvent(new CustomEvent('monitoring-error', { detail: event.payload }));
});
listen('theme-changed', event => {
const theme = event.payload;
window.dispatchEvent(new CustomEvent('theme-changed', { detail: theme }));
@@ -60,6 +68,8 @@
setCommitModel: model => invoke('set_commit_model', { val: model }),
getReadmeModel: () => invoke('get_readme_model'),
setReadmeModel: model => invoke('set_readme_model', { val: model }),
getRewordMode: () => invoke('get_reword_mode'),
setRewordMode: val => invoke('set_reword_mode', { val }),
close: () => invoke('close_settings'),
getAutostart: () => invoke('get_autostart'),
setAutostart: val => invoke('set_autostart', { enabled: val }),
@@ -115,9 +125,12 @@
hasReadme: folderPath => invoke('has_readme', { folderPath }),
generateReadme: folderPath => invoke('generate_readme', { folderPath }),
squashCommits: folderPath => invoke('squash_commits', { folderPath }),
pushToGitea: (folderPath, allowDirty = false) => invoke('push_to_gitea', { folderPath, allowDirty }),
pushToGitea: folderPath => invoke('push_to_gitea', { folderPath }),
initRepo: folderPath => invoke('init_repo', { folderPath }),
triggerRewriteNow: folderPath => invoke('trigger_rewrite_now', { folderPath })
triggerRewriteNow: folderPath => invoke('trigger_rewrite_now', { folderPath }),
rewriteCommit: (folderPath, hash) => invoke('rewrite_commit', { folderPath, hash }),
rewriteCommitWithMessage: (folderPath, hash, message) => invoke('rewrite_commit_with_message', { folderPath, hash, message }),
rewritePendingCommits: folderPath => invoke('rewrite_pending_commits', { folderPath })
};
window.addEventListener('beforeunload', () => {