Compare commits
26 Commits
72fa7026bf
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 34731c4e26 | |||
| 22ea72bccc | |||
| 56397d11e2 | |||
| 2e082a4bca | |||
| 09349a9477 | |||
| a967587181 | |||
| f047d61731 | |||
| f087e13267 | |||
| ce73d0273a | |||
| 6bf77cebab | |||
| d6dc207f2c | |||
| 8769d32855 | |||
| a0cb179d6e | |||
| 9412fb7036 | |||
| 1887ee7a41 | |||
| ade0f2dcbd | |||
| 89a3b47948 | |||
| 82914e969d | |||
| b73af714a5 | |||
| 39fdfb5cac | |||
| ca8527ac85 | |||
| 23e08b4162 | |||
| 498fb0112b | |||
| 975561f83e | |||
| a972396412 | |||
| 1db2a401b8 |
17
README.md
17
README.md
@@ -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
|
||||
|
||||
86
index.html
86
index.html
@@ -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">
|
||||
|
||||
@@ -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": {
|
||||
|
||||
248
renderer.js
248
renderer.js
@@ -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') {
|
||||
|
||||
@@ -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
12
src-tauri/Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user