1
0

Compare commits

...

75 Commits

Author SHA1 Message Date
7aa122f749 Added dist/to .itignore 2026-05-04 22:06:41 +02:00
82281239e2 Remove squash feature and simplify push button text 2026-05-04 21:53:56 +02:00
0dcc24f173 Enhance Ollama integration via API and update settings UI 2026-05-04 21:50:46 +02:00
0e12ac604e auto-git:
[add] scripts/
 [add] src-tauri/
 [add] tauriBridge.js
 [change] .gitignore
 [change] README.md
 [change] index.html
 [change] package.json
 [change] renderer.js
 [change] settings.html
 [unlink] editor-reword.js
 [unlink] main.js
 [unlink] preload.js
 [unlink] rebase-sequence-windows.js
2026-05-04 21:33:17 +02:00
82d9c45bda auto-git:
[change] index.html
 [change] main.js
 [change] preload.js
 [change] renderer.js
 [change] settings.html
2026-05-04 20:46:51 +02:00
Victor Giers
2ae1255629 Add theme validation and update settings.html accordingly 2025-12-12 11:34:52 +01:00
Victor Giers
37f12638b8 Remove redundant theme change event emission in main.js 2025-12-12 11:34:52 +01:00
Victor Giers
5474bfe44a Refactor theme handling in main.js 2025-12-12 11:33:20 +01:00
Victor Giers
672bb86307 Handle legacy theme settings and validation in main.js 2025-12-12 11:33:10 +01:00
Victor Giers
7d2a746eaa Initialize theme and skymode settings in main.js 2025-12-12 11:33:10 +01:00
Victor Giers
5246f56aa8 Add skymode event handling in preload.js 2025-12-12 11:33:10 +01:00
Victor Giers
07aa6ede4c Update preload.js to support new theme system with legacy sky mode compatibility 2025-12-12 11:32:42 +01:00
Victor Giers
45bfc8c23c Simplify and update theme options in settings.html 2025-12-12 11:32:15 +01:00
Victor Giers
a4ba98f2ac Update settings.html to include theme setting 2025-12-12 11:32:15 +01:00
Victor Giers
27890eb55e Update settings to use theme selection and apply theme class 2025-12-12 11:31:44 +01:00
Victor Giers
f87aded9c7 Refactor theme handling in settings.html 2025-12-12 11:31:38 +01:00
Victor Giers
3824543efb Refactor settings.html theme classes and add grey theme 2025-12-12 11:31:02 +01:00
Victor Giers
54b8b4c5cd Update renderer.js to use currentTheme for text color 2025-12-12 11:31:02 +01:00
Victor Giers
849ff08d61 Add grey theme and update theme handling in renderer.js 2025-12-12 11:30:40 +01:00
Victor Giers
3d72ac6d35 Enhance commit rewording logic with debugging and error handling 2025-12-08 14:33:56 +01:00
Victor Giers
50f842fffb Simplify error handling in runLLMCommitRewrite 2025-12-08 14:33:48 +01:00
Victor Giers
d615bd0df0 auto-git:
[change] main.js
2025-12-08 14:15:35 +01:00
Victor Giers
d4162c37d6 auto-git:
[change] main.js
2025-12-08 14:13:40 +01:00
Victor Giers
2eb45ac67f auto-git:
[change] main.js
2025-12-08 14:11:23 +01:00
Victor Giers
ddf534e72b auto-git:
[change] main.js
2025-12-08 14:11:15 +01:00
Victor Giers
bade8c3c09 auto-git:
[change] main.js
2025-12-08 14:11:15 +01:00
Victor Giers
09c6ef5041 auto-git:
[change] main.js
2025-12-08 14:11:15 +01:00
Victor Giers
f52eb353ab auto-git:
[change] main.js
 [change] preload.js
 [change] renderer.js
2025-12-08 14:11:15 +01:00
Victor Giers
27ad9225e9 auto-git:
[change] main.js
2025-12-08 14:11:15 +01:00
Victor Giers
b10e8e2126 auto-git:
[change] main.js
2025-12-08 14:11:15 +01:00
Victor Giers
789a8c0129 auto-git:
[change] main.js
2025-12-08 14:11:15 +01:00
Victor Giers
a30328d219 auto-git:
[change] main.js
2025-12-08 14:11:15 +01:00
Victor Giers
81185ffc9a auto-git:
[change] main.js
2025-12-08 14:11:15 +01:00
Victor Giers
0224a1cb8c auto-git:
[change] main.js
2025-12-08 14:11:15 +01:00
Victor Giers
4c7823ea7a auto-git:
[change] main.js
2025-12-08 12:27:13 +01:00
Victor Giers
5b367ca08a Add recovery logic for stuck rewrite flag in main.js 2025-12-08 12:27:13 +01:00
Victor Giers
b19d5632fd Remove rewrite-trigger.txt file 2025-12-08 12:19:23 +01:00
Victor Giers
a504bc3b25 auto-git:
[change] rewrite-trigger.txt
2025-12-07 23:14:42 +01:00
Victor Giers
2f159affb4 auto-git:
[change] rewrite-trigger.txt
2025-12-07 23:14:42 +01:00
Victor Giers
2c969a971c auto-git:
[change] rewrite-trigger.txt
2025-12-07 23:14:42 +01:00
Victor Giers
8500cb2bd7 Expand content in rewrite-trigger.txt 2025-12-07 23:14:42 +01:00
Victor Giers
1b271e12a4 auto-git:
[change] rewrite-trigger.txt
2025-12-07 23:14:27 +01:00
Victor Giers
bed4c79c5b auto-git:
[change] rewrite-trigger.txt
2025-12-07 23:14:27 +01:00
Victor Giers
ccd52e2655 Update rewrite-trigger.txt with multiple lines for testing 2025-12-07 23:14:27 +01:00
Victor Giers
4a91b4ddce auto-git:
[change] rewrite-trigger.txt
2025-12-07 23:14:02 +01:00
Victor Giers
dd42885945 Restore initial content to rewrite-trigger.txt 2025-12-07 23:14:02 +01:00
Victor Giers
400222a7f0 auto-git:
[change] rewrite-trigger.txt
2025-12-07 23:13:38 +01:00
Victor Giers
8f044501bd Trim repeated lines in rewrite-trigger.txt 2025-12-07 23:13:38 +01:00
Victor Giers
a72d8d3e1c auto-git:
[change] rewrite-trigger.txt
2025-12-07 23:13:04 +01:00
Victor Giers
38a5c9d94e auto-git:
[change] rewrite-trigger.txt
2025-12-07 23:13:04 +01:00
Victor Giers
a2ef40b674 Update rewrite-trigger.txt with minor changes 2025-12-07 23:13:04 +01:00
Victor Giers
035b0131ad auto-git:
[change] main.js
2025-12-07 23:11:21 +01:00
Victor Giers
9cf980a3c3 Skip auto-commit if rewrite/rebase is in progress for the folder 2025-12-07 23:11:21 +01:00
Victor Giers
938e7e151c Simplify rewrite-trigger.txt content 2025-12-07 23:02:47 +01:00
Victor Giers
8a1c659b0e auto-git:
[change] rewrite-trigger.txt
2025-12-07 23:01:04 +01:00
Victor Giers
5db41a2e4d auto-git:
[change] rewrite-trigger.txt
2025-12-07 23:01:04 +01:00
Victor Giers
719f624c9b Remove content from rewrite-trigger.txt 2025-12-07 23:01:04 +01:00
Victor Giers
b52a0b8504 Append more content to rewrite-trigger.txt 2025-12-07 22:59:27 +01:00
Victor Giers
da29c63e32 auto-git:
[change] rewrite-trigger.txt
2025-12-07 22:58:23 +01:00
Victor Giers
52f045ca64 Remove content from rewrite-trigger.txt 2025-12-07 22:58:23 +01:00
Victor Giers
9ed3ae0d40 auto-git:
[change] rewrite-trigger.txt
2025-12-07 22:58:13 +01:00
Victor Giers
f5dc2683cd auto-git:
[change] rewrite-trigger.txt
2025-12-07 22:58:13 +01:00
Victor Giers
f211e80fa1 auto-git:
[change] rewrite-trigger.txt
2025-12-07 22:58:13 +01:00
Victor Giers
0a82fc7499 auto-git:
[change] rewrite-trigger.txt
2025-12-07 22:58:13 +01:00
Victor Giers
f7b3b4bd04 Simplify rewrite-trigger.txt 2025-12-07 22:58:13 +01:00
Victor Giers
ad8b063c08 auto-git:
[change] rewrite-trigger.txt
2025-12-07 22:57:44 +01:00
Victor Giers
3ce410fc4d Extend rewrite-trigger.txt with multiple lines 2025-12-07 22:57:44 +01:00
Victor Giers
05e2ed2060 Update rewrite-trigger.txt with second line 2025-12-07 22:57:44 +01:00
Victor Giers
4536810ecb Add rewrite-trigger.txt 2025-12-07 22:57:44 +01:00
Victor Giers
9e708b731c Add llmBuffer and rewriteInProgress fields to folder object in main.js 2025-12-07 22:56:03 +01:00
Victor Giers
cb83f14453 Enhance runLLMCommitRewrite logic in main.js 2025-12-07 22:54:40 +01:00
Victor Giers
7a4622ad4a Add check for rewriteInProgress in main.js 2025-12-07 22:54:40 +01:00
Victor Giers
6a0c2787fa Enhance autoCommit function with rewrite buffer and progress tracking 2025-12-07 22:54:15 +01:00
Victor Giers
58e79310cc Add rewriteInProgress and llmBuffer to store and folder objects 2025-12-07 22:54:05 +01:00
Victor Giers
4b330d0a7f Add rebase check and abort functionality in autoCommit and commitCurrentFolder 2025-12-07 22:50:03 +01:00
18 changed files with 9940 additions and 2913 deletions

5
.gitignore vendored
View File

@@ -1,5 +1,9 @@
node_modules
package-lock.json
dist/
dist-tauri
src-tauri/target
src-tauri/gen
dist.DS_Store
.git
.gitignore
@@ -7,6 +11,7 @@ dist.DS_Store
*.iml
yarn.lock
*.lock
!src-tauri/Cargo.lock
Makefile
*.exe
tsconfig.tsbuildinfo

View File

@@ -99,38 +99,23 @@ Download the latest release for your platform:
If you want to build Auto-Git yourself, follow these steps:
1. Clone or download the repository to your local machine.
2. Install Node.js (version 16+ recommended) and npm.
2. Install Node.js, npm, Rust, and the Tauri prerequisites for your operating system.
3. Open a terminal, navigate into the project folder, and run:
```bash
npm install
```
4. Optional: If you need to adjust architectures or targets, modify `package.json` under the `"build"` section.
- Example for Windows x64 only:
```json
"build": {
"win": {
"icon": "win/icon.ico",
"target": [
{
"target": "nsis",
"arch": ["x64"]
}
]
}
}
```
5. Build the distributables:
4. Run the app in development mode:
```bash
npm start
```
5. Build the Tauri distributables:
```bash
npm run dist
```
- On an ARM64 machine, to produce an x64 Windows installer, first ensure `"arch": ["x64"]` is under `"win.target"`, then:
```bash
npm run dist
```
6. The output installers/packages will be located in the `dist/` directory.
6. The output installers/packages will be located under `src-tauri/target/release/bundle/`.
---
## License
This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.

View File

@@ -1,13 +0,0 @@
// editor-reword.js
const fs = require('fs');
const map = JSON.parse(process.env.REBASE_COMMIT_MAP);
const msgFile = process.argv[2];
const origMsg = fs.readFileSync(msgFile, 'utf-8');
// Hash suchen
const hashMatch = origMsg.match(/commit\\s+([a-f0-9]{7,40})/i);
const hash = hashMatch ? hashMatch[1] : null;
if (hash && map[hash]) {
fs.writeFileSync(msgFile, map[hash] + '\n');
}
process.exit(0);

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<title>auto-git</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="tauriBridge.js"></script>
<style>
:root {
/* Default-Modus: Weißes Panel mit Rosa-Akzenten */
@@ -12,13 +13,20 @@
--accent: #9f1239; /* Rosa */
--border: #ffe4e6;
}
body.sky-mode {
body.theme-sky {
/* Sky-Mode: Himmelshintergrund und passende Akzente */
--bg-main: rgb(173,216,230); /* sanftes Baby-Blau */
--bg-sidebar: rgb(200,220,240); /* etwas dunkleres Blau */
--accent: rgb(20,60,100); /* dunkles Marine-Blau als Akzent */
--border: rgb(180,200,220);
}
body.theme-grey {
/* Grey theme: ruhige, neutrale Akzente */
--bg-main: #f5f5f5;
--bg-sidebar: #eceff1;
--accent: #374151;
--border: #d1d5db;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
@@ -291,7 +299,7 @@
<button id="pushBtn"
class="ml-2 px-4 py-2 border rounded font-semibold"
style="background: var(--accent); color: #fff; border-color: var(--border)">
Push to Gitea
Push
</button>
</div>

2663
main.js

File diff suppressed because it is too large Load Diff

View File

@@ -4,52 +4,15 @@
"version": "1.0.0",
"description": "Auto-Git: Git-Überwachung mit automatischer LLM-Commit-Message und README-Erstellung",
"author": "Victor Giers",
"main": "main.js",
"scripts": {
"start": "electron .",
"dist": "electron-builder --publish never"
},
"dependencies": {
"chokidar": "^4.0.3",
"electron-store": "^8.2.0",
"ignore": "^7.0.4",
"micromatch": "^4.0.8",
"node-fetch": "^3.3.2",
"simple-git": "^3.20.0",
"suncalc": "^1.9.0"
"prepare:tauri-frontend": "node scripts/prepare-tauri-frontend.mjs",
"start": "npm run prepare:tauri-frontend && tauri dev",
"tauri:dev": "npm run prepare:tauri-frontend && tauri dev",
"dist": "npm run prepare:tauri-frontend && tauri build"
},
"dependencies": {},
"devDependencies": {
"autoprefixer": "^10.4.21",
"electron": "^25.0.0",
"electron-builder": "^26.0.12",
"postcss": "^8.5.3",
"tailwindcss": "^4.1.7"
},
"build": {
"appId": "com.victorgiers.auto-git",
"productName": "Auto-Git",
"files": [
"main.js",
"preload.js",
"renderer.js",
"index.html",
"settings.html",
"animeCat.js",
"rewrite-commit-msg.js.template",
"assets/**/*",
"node_modules/**/*"
],
"directories": {
"buildResources": "assets/icon"
},
"mac": {
"icon": "mac/icon.icns"
},
"win": {
"icon": "win/icon.ico"
},
"linux": {
"icon": "linux/icon.png"
}
"@tauri-apps/api": "^2.11.0",
"@tauri-apps/cli": "^2.11.0"
}
}
}

View File

@@ -1,77 +0,0 @@
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('settingsAPI', {
getSkyMode: () => ipcRenderer.invoke('get-skymode'),
setSkyMode: val => ipcRenderer.invoke('set-skymode', val),
getSkipPrompt: () => ipcRenderer.invoke('get-skip-git-prompt'),
setSkipPrompt: val => ipcRenderer.invoke('set-skip-git-prompt', val),
getIntelligentCommitThreshold: () => ipcRenderer.invoke('get-intelligent-commit-threshold'),
setIntelligentCommitThreshold: value => ipcRenderer.invoke('set-intelligent-commit-threshold', value),
getMinutesCommitThreshold: () => ipcRenderer.invoke('get-minutes-commit-threshold'),
setMinutesCommitThreshold: value => ipcRenderer.invoke('set-minutes-commit-threshold', value),
getCommitModel: () => ipcRenderer.invoke('get-commit-model'),
setCommitModel: (model) => ipcRenderer.invoke('set-commit-model', model),
getReadmeModel: () => ipcRenderer.invoke('get-readme-model'),
setReadmeModel: (model) => ipcRenderer.invoke('set-readme-model', model),
close: () => ipcRenderer.send('close-settings'),
getAutostart: () => ipcRenderer.invoke('get-autostart'),
setAutostart: val => ipcRenderer.invoke('set-autostart', val),
getCloseToTray: () => ipcRenderer.invoke('get-close-to-tray'),
setCloseToTray: val => ipcRenderer.invoke('set-close-to-tray', val),
getGiteaToken: () => ipcRenderer.invoke('get-gitea-token'),
setGiteaToken: (token) => ipcRenderer.invoke('set-gitea-token', token),
});
contextBridge.exposeInMainWorld('electronAPI', {
getFolders: () => ipcRenderer.invoke('get-folders'),
addFolder: () => ipcRenderer.invoke('add-folder'),
removeFolder: folderObj=> ipcRenderer.invoke('remove-folder', folderObj),
getSelected: () => ipcRenderer.invoke('get-selected'),
setSelected: folderObj=> ipcRenderer.invoke('set-selected', folderObj),
getCommits: (folderObj, page, pageSize) => ipcRenderer.invoke('get-commits', folderObj, page, pageSize),
getAllCommitHashes: (folderObj) => ipcRenderer.invoke('get-all-commit-hashes', folderObj),
diffCommit: (folderObj, hash) => ipcRenderer.invoke('diff-commit', folderObj, hash),
revertCommit: (folderObj, hash) => ipcRenderer.invoke('revert-commit', folderObj, hash),
snapshotCommit: (folderObj, hash) => ipcRenderer.invoke('snapshot-commit', folderObj, hash),
checkoutCommit: (folderObj, hash) => ipcRenderer.invoke('checkout-commit', folderObj, hash),
getCommitCount: folderObj=> ipcRenderer.invoke('get-commit-count', folderObj),
hasDiffs: folderObj=> ipcRenderer.invoke('has-diffs', folderObj),
removeGitFolder:folderObj=> ipcRenderer.invoke('remove-git-folder', folderObj),
commitCurrentFolder: (folderObj, message) => ipcRenderer.invoke('commit-current-folder', folderObj, message),
showFolderContextMenu: folderPath => ipcRenderer.send('show-folder-context-menu', folderPath),
setMonitoring: (folderObj, monitoring) => ipcRenderer.invoke('set-monitoring', folderObj.path, monitoring),
getFolderTree: (folderPath) => ipcRenderer.invoke('get-folder-tree', folderPath),
showTreeContextMenu: (info) => ipcRenderer.send('show-tree-context-menu', info),
getSelectedSync: () => ipcRenderer.sendSync('get-selected-sync'),
getIntelligentCommitThreshold: () => ipcRenderer.invoke('get-intelligent-commit-threshold'),
setIntelligentCommitThreshold: value => ipcRenderer.invoke('set-minutes-commit-threshold', value),
getMinutesCommitThreshold: () => ipcRenderer.invoke('get-minutes-commit-threshold'),
setMinutesCommitThreshold: value => ipcRenderer.invoke('set-intelligent-commit-threshold', value),
ollamaList: () => ipcRenderer.invoke('ollama-list'),
ollamaPull: (model) => ipcRenderer.invoke('ollama-pull', model),
onTrayToggleMonitoring: (callback) => ipcRenderer.on('tray-toggle-monitoring', callback),
onTrayRemoveFolder: (callback) => ipcRenderer.on('tray-remove-folder', callback),
onTrayAddFolder: (callback) => ipcRenderer.on('tray-add-folder', callback),
onFoldersLocationUpdated: (callback) => ipcRenderer.on('folders-location-updated', (e, folderObj) => callback(folderObj)),
isGitRepo: (path) => ipcRenderer.invoke('is-git-repo', path),
relocateFolder: (oldPath, newPath) => ipcRenderer.invoke('relocate-folder', oldPath, newPath),
pickFolder: () => ipcRenderer.invoke('pick-folder'),
repoHasCommit: (repoPath, commitHash) => ipcRenderer.invoke('repo-has-commit', repoPath, commitHash),
addFolderByPath: (folderPath) => ipcRenderer.invoke('add-folder-by-path', folderPath),
onCatBegin: (cb) => ipcRenderer.on('cat-begin', cb),
onCatChunk: (cb) => ipcRenderer.on('cat-chunk', cb),
onCatEnd: (cb) => ipcRenderer.on('cat-end', cb),
getDailyCommitStats: () => ipcRenderer.invoke('get-daily-commit-stats'),
hasReadme: (folderPath) => ipcRenderer.invoke('has-readme', folderPath),
generateReadme: (folderPath) => ipcRenderer.invoke('generate-readme', folderPath),
pushToGitea: (folderPath) => ipcRenderer.invoke('push-to-gitea', folderPath),
initRepo: (folderPath) => ipcRenderer.invoke('init-repo', folderPath),
});
ipcRenderer.on('repo-updated', (_e, folder) => {
window.dispatchEvent(new CustomEvent('repo-updated', { detail: folder }));
});
ipcRenderer.on('skymode-changed', (_e, val) => {
window.dispatchEvent(new CustomEvent('skymode-changed', { detail: val }));
});

View File

@@ -1,34 +0,0 @@
#!/usr/bin/env node
// ─────────────────────────────────────────────────
// rebase-sequence-windows.js
// Dieses Skript wird von Git (als GIT_SEQUENCE_EDITOR) aufgerufen.
// Git übergibt die "todo"-Datei als erstes Argument (process.argv[2]).
// Wir ersetzen dort "pick ..." in der ersten Zeile durch "reword ...".
// ─────────────────────────────────────────────────
const fs = require('fs');
// Git ruft den Editor so auf:
// node rebase-sequence-windows.js <pfad-zur-todo-Datei>
// In process.argv ist:
// [0] = Pfad zur NodeExecutable
// [1] = Pfad zu diesem Skript
// [2] = Pfad zur temporären Todo-Datei
const todoFile = process.argv[2];
if (!todoFile) {
console.error('Usage: rebase-sequence-windows.js <todoFile>');
process.exit(1);
}
try {
const content = fs.readFileSync(todoFile, 'utf8').split(/\r?\n/);
if (content.length > 0 && content[0].startsWith('pick ')) {
content[0] = content[0].replace(/^pick /, 'reword ');
}
fs.writeFileSync(todoFile, content.join('\n'), 'utf8');
} catch (err) {
console.error('Fehler beim Bearbeiten der Rebase-TODO-Datei:', err);
process.exit(1);
}
process.exit(0);

View File

@@ -70,7 +70,7 @@ window.addEventListener('DOMContentLoaded', async () => {
alert('❌ Unexpected error:\n' + (err.message || err));
} finally {
pushBtn.disabled = false;
pushBtn.textContent = 'Push to Gitea';
pushBtn.textContent = 'Push';
}
});
@@ -118,10 +118,23 @@ window.addEventListener('DOMContentLoaded', async () => {
}
}
});
window.addEventListener('tauri-folder-drop', async e => {
const paths = Array.isArray(e.detail) ? e.detail : [];
for (const folderPath of paths) {
await window.electronAPI.addFolderByPath(folderPath);
}
if (paths.length) {
await renderSidebar();
const sel = await window.electronAPI.getSelected();
if (sel) await renderContent(sel);
}
});
// Sky-Mode Setup
// Theme Setup (Sky = dynamic, Default/Grey = static)
const DAY_COLOR = [173, 216, 230];
const NIGHT_COLOR = [0, 0, 50];
const VALID_THEMES = ['default', 'sky', 'grey'];
let currentTheme = 'default';
function lerpColor(c1, c2, t) {
return c1.map((v, i) => Math.round(v + t * (c2[i] - v)));
}
@@ -140,28 +153,36 @@ window.addEventListener('DOMContentLoaded', async () => {
panel.style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
}
let skyIntervalId, titleIntervalId;
function applySkyMode(enabled) {
document.body.classList.toggle('sky-mode', enabled);
function applyTheme(theme) {
const normalized = VALID_THEMES.includes(theme) ? theme : 'default';
currentTheme = normalized;
document.body.classList.remove('theme-sky', 'theme-grey');
if (normalized === 'sky') document.body.classList.add('theme-sky');
if (normalized === 'grey') document.body.classList.add('theme-grey');
clearInterval(skyIntervalId);
clearInterval(titleIntervalId);
if (enabled) {
if (normalized === 'sky') {
updateBackground();
skyIntervalId = setInterval(updateBackground, 60_000);
function updateAllTextColors() { setTextColor('sky'); }
const updateAllTextColors = () => setTextColor('sky');
updateAllTextColors();
titleIntervalId = setInterval(updateAllTextColors, 60_000);
} else {
panel.style.backgroundColor = '';
setTextColor('default');
setTextColor(normalized);
}
}
const initialSky = await window.settingsAPI.getSkyMode();
applySkyMode(initialSky);
window.addEventListener('skymode-changed', e => applySkyMode(e.detail));
const initialTheme = await window.settingsAPI.getTheme();
applyTheme(initialTheme);
window.addEventListener('theme-changed', e => applyTheme(e.detail));
function setTextColor(mode) {
let textColor = '#111';
if (mode === 'sky') {
if (mode === 'grey') {
textColor = '#1f2937';
} else if (mode === 'sky') {
const hour = new Date().getHours();
textColor = (hour >= 18 || hour < 6) ? '#fff' : '#111';
}
@@ -442,7 +463,7 @@ window.addEventListener('DOMContentLoaded', async () => {
const tree = await window.electronAPI.getFolderTree(selected.path);
folderHierarchyDropdown.innerHTML = renderFolderTreeAscii(tree, '.', '');
setTextColor(document.body.classList.contains('sky-mode') ? 'sky' : 'default');
setTextColor(currentTheme);
});
folderHierarchyDropdown.addEventListener('contextmenu', function(e) {
@@ -490,10 +511,11 @@ window.addEventListener('DOMContentLoaded', async () => {
async function renderContent(folderObj, page) {
closeDropdown();
window.currentSelectedFolderObj = folderObj;
const folder = folderObj.path;
await updateInteractionBar(folderObj);
titleEl.textContent = folder;
setTextColor(document.body.classList.contains('sky-mode') ? 'sky' : 'default');
setTextColor(currentTheme);
const isGit = await window.electronAPI.isGitRepo(folder);
initRepoBtn.classList.toggle('hidden', isGit);
@@ -593,7 +615,7 @@ window.addEventListener('DOMContentLoaded', async () => {
alt="In Rewrite Queue"
title="In Rewrite Queue"
class="paw-queued"
style="pointer-events: none; z-index:10;">`
style="pointer-events: auto; cursor: pointer; z-index:10;">`
: ''
}
</li>`;
@@ -688,6 +710,19 @@ window.addEventListener('DOMContentLoaded', async () => {
});
});
// Paw click: manually trigger rewrite for this folder
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);
}
});
});
// Copy-Diff-Button
contentList.querySelectorAll('.diff-container').forEach(container => {
const btn = container.querySelector('.copy-diff-btn');

View File

@@ -0,0 +1,14 @@
import { cp, mkdir, rm } from 'node:fs/promises';
import path from 'node:path';
const root = process.cwd();
const out = path.join(root, 'dist-tauri');
await rm(out, { recursive: true, force: true });
await mkdir(out, { recursive: true });
for (const file of ['index.html', 'settings.html', 'renderer.js', 'animeCat.js', 'tauriBridge.js']) {
await cp(path.join(root, file), path.join(out, file));
}
await cp(path.join(root, 'assets'), path.join(out, 'assets'), { recursive: true });

View File

@@ -14,7 +14,7 @@
--accent: #9f1239; /* Rosa */
--border: #fff;
}
body.sky-mode {
body.theme-sky {
/* Sky-Mode: Himmelshintergrund und passende Akzente */
--bg-main: rgb(173,216,230); /* sanftes Baby-Blau */
--bg-sidebar: rgb(200,220,240); /* etwas dunkleres Blau */
@@ -23,6 +23,13 @@
background: var(--border);
}
body.theme-grey {
--bg-main: #f5f5f5;
--bg-sidebar: #eceff1;
--accent: #374151;
--border: #d1d5db;
background: var(--bg-main);
}
html, body {
width: 100%; height: 100%;
@@ -154,22 +161,26 @@
vertical-align: middle;
}
</style>
<script src="tauriBridge.js"></script>
<script>
window.addEventListener('DOMContentLoaded', async () => {
const isSky = await window.settingsAPI.getSkyMode();
document.body.classList.toggle('sky-mode', isSky);
const applyThemeClass = (theme) => {
document.body.classList.remove('theme-sky', 'theme-grey');
if (theme === 'sky') document.body.classList.add('theme-sky');
if (theme === 'grey') document.body.classList.add('theme-grey');
};
const VALID_THEMES = ['sky', 'default', 'grey'];
// Elemente holen
const themeSelect = document.getElementById('themeSelect');
const cbSky = document.getElementById('skymode');
const cbSkip = document.getElementById('skipPrompt');
const thresholdInput = document.getElementById('intelligentCommitThresholdInput');
const minutesInput = document.getElementById('intelligentCommitMinutesInput');
const ok = document.getElementById('okBtn');
const cancel = document.getElementById('cancelBtn');
const cbAutostart = document.getElementById('autostart');
const cbCloseToTray= document.getElementById('closeToTray');
const giteaTokenInput = document.getElementById('giteaTokenInput');
const themeSelect = document.getElementById('themeSelect');
const cbSkip = document.getElementById('skipPrompt');
const thresholdInput = document.getElementById('intelligentCommitThresholdInput');
const minutesInput = document.getElementById('intelligentCommitMinutesInput');
const ok = document.getElementById('okBtn');
const cancel = document.getElementById('cancelBtn');
const cbAutostart = document.getElementById('autostart');
const cbCloseToTray = document.getElementById('closeToTray');
const giteaTokenInput = document.getElementById('giteaTokenInput');
const [initialAutostart, initialCloseToTray] = await Promise.all([
window.settingsAPI.getAutostart(),
@@ -179,23 +190,26 @@
cbCloseToTray.checked = initialCloseToTray;
// Initialwerte parallel laden
const [initialSky, initialSkip, initialThreshold, initialMinutes] = await Promise.all([
window.settingsAPI.getSkyMode(),
const [initialTheme, initialSkip, initialThreshold, initialMinutes] = await Promise.all([
window.settingsAPI.getTheme(),
window.settingsAPI.getSkipPrompt(),
window.electronAPI.getIntelligentCommitThreshold(),
window.electronAPI.getMinutesCommitThreshold()
]);
const fallbackTheme = VALID_THEMES.includes(initialTheme) ? initialTheme : 'sky';
themeSelect.value = fallbackTheme;
applyThemeClass(fallbackTheme);
// Inputs setzen
cbSky.checked = initialSky;
cbSkip.checked = initialSkip;
thresholdInput.value = initialThreshold;
minutesInput.value = initialMinutes;
// **Vorschau**: Sky-Mode sofort übernehmen
cbSky.addEventListener('change', async () => {
await window.settingsAPI.setSkyMode(cbSky.checked);
document.body.classList.toggle('sky-mode', cbSky.checked);
// **Vorschau**: Theme sofort übernehmen
themeSelect.addEventListener('change', async () => {
const val = themeSelect.value || 'default';
await window.settingsAPI.setTheme(val);
applyThemeClass(val);
});
@@ -209,7 +223,7 @@
await window.settingsAPI.setSkipPrompt(cbSkip.checked);
await window.settingsAPI.setAutostart(cbAutostart.checked);
await window.settingsAPI.setCloseToTray(cbCloseToTray.checked);
// SkyMode haben wir ja schon beim Change gesetzt
await window.settingsAPI.setTheme(themeSelect.value || fallbackTheme);
// Threshold speichern (immer Wert aus Feld nehmen!)
let threshold = parseInt(thresholdInput.value, 10);
@@ -235,9 +249,10 @@
window.settingsAPI.close();
});
// Cancel / Close: SkyMode zurücksetzen, dann schließen
// Cancel / Close: Theme zurücksetzen, dann schließen
const rollback = async () => {
await window.settingsAPI.setSkyMode(initialSky);
await window.settingsAPI.setTheme(fallbackTheme);
applyThemeClass(fallbackTheme);
window.settingsAPI.close();
};
cancel.addEventListener('click', rollback);
@@ -249,7 +264,7 @@
if (res.status === 'no-cli') {
container.innerHTML = `
<div style="color:red;font-weight:bold;margin:1em 0;">
You need to install Ollama to use the cool stuff !!
Ollama is not reachable. Start Ollama or install it to use the cool stuff !!
</div>`;
return;
}
@@ -260,11 +275,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
@@ -288,8 +303,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;
@@ -297,12 +312,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>`;
@@ -320,21 +335,14 @@
<input type="checkbox" id="closeToTray" checked />
Close to Tray
</label>
<label class="option">
<input type="checkbox" id="skymode" />
Sky Theme
</label>
<!--
<label class="option" style="align-items:center;">
Theme:
<select id="themeSelect" style="margin-left:1em;">
<option value="default">Cits Favorite</option>
<option value="sky">Sky</option>
<option value="epic">Epic</option>
<option value="yinyang" disabled>Yin/Yang (coming soon)</option>
<option value="sky">SkyMode</option>
<option value="default">Default</option>
<option value="grey">Grey</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
@@ -376,4 +384,4 @@
</div>
</div>
</body>
</html>
</html>

6016
src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

31
src-tauri/Cargo.toml Normal file
View File

@@ -0,0 +1,31 @@
[package]
name = "auto-git"
version = "1.0.0"
description = "Auto-Git: Git monitoring with automatic LLM commit messages and README generation"
authors = ["Victor Giers"]
edition = "2021"
[build-dependencies]
tauri-build = { version = "2.6.0", features = [] }
[dependencies]
arboard = "3.6.1"
base64 = "0.22.1"
dirs = "6.0.0"
globset = "0.4.18"
ignore = "0.4.25"
notify = "8.2.0"
open = "5.3.3"
rfd = "0.15.4"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
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"] }
[target.'cfg(target_os = "macos")'.dependencies]
auto-launch = "0.5.0"
[target.'cfg(not(target_os = "macos"))'.dependencies]
auto-launch = "0.5.0"

3
src-tauri/build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View File

@@ -0,0 +1,13 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Default desktop capability for Auto-Git",
"windows": ["main", "settings"],
"permissions": [
"core:default",
"core:event:default",
"core:window:default",
"core:menu:default",
"core:tray:default"
]
}

3565
src-tauri/src/main.rs Normal file

File diff suppressed because it is too large Load Diff

40
src-tauri/tauri.conf.json Normal file
View File

@@ -0,0 +1,40 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Auto-Git",
"version": "1.0.0",
"identifier": "com.victorgiers.auto-git",
"build": {
"frontendDist": "../dist-tauri"
},
"app": {
"withGlobalTauri": true,
"windows": [
{
"label": "main",
"title": "Auto-Git",
"width": 900,
"height": 600,
"minWidth": 800,
"minHeight": 500,
"titleBarStyle": "Transparent",
"hiddenTitle": true,
"backgroundColor": "#fff1f2"
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"../assets/icon/linux/icon.png",
"../assets/icon/mac/icon.icns",
"../assets/icon/win/icon.ico"
],
"resources": [
"../assets/**/*"
]
}
}

128
tauriBridge.js Normal file
View File

@@ -0,0 +1,128 @@
(function () {
const tauri = window.__TAURI__;
if (!tauri || window.electronAPI || window.settingsAPI) return;
const invoke = (command, args = {}) => tauri.core.invoke(command, args);
const listen = (event, handler) => tauri.event.listen(event, handler);
let selectedCache = null;
const eventUnsubscribers = [];
function onTauriEvent(eventName, callback, legacyEventArg = true) {
const promise = listen(eventName, event => {
if (legacyEventArg) callback({}, event.payload);
else callback(event.payload);
});
eventUnsubscribers.push(promise);
return promise;
}
async function refreshSelectedCache() {
selectedCache = await invoke('get_selected');
return selectedCache;
}
listen('repo-updated', event => {
window.dispatchEvent(new CustomEvent('repo-updated', { detail: event.payload }));
});
listen('theme-changed', event => {
const theme = event.payload;
window.dispatchEvent(new CustomEvent('theme-changed', { detail: theme }));
window.dispatchEvent(new CustomEvent('skymode-changed', { detail: theme === 'sky' }));
});
listen('skymode-changed', event => {
const val = !!event.payload;
window.dispatchEvent(new CustomEvent('theme-changed', { detail: val ? 'sky' : 'default' }));
window.dispatchEvent(new CustomEvent('skymode-changed', { detail: val }));
});
listen('tauri://drag-drop', event => {
const paths = event.payload?.paths || [];
if (paths.length) {
window.dispatchEvent(new CustomEvent('tauri-folder-drop', { detail: paths }));
}
});
window.settingsAPI = {
getTheme: () => invoke('get_theme'),
setTheme: val => invoke('set_theme', { val }),
getSkyMode: () => invoke('get_theme').then(theme => theme === 'sky'),
setSkyMode: val => invoke('set_theme', { val: val ? 'sky' : 'default' }),
getSkipPrompt: () => invoke('get_skip_git_prompt'),
setSkipPrompt: val => invoke('set_skip_git_prompt', { val }),
getIntelligentCommitThreshold: () => invoke('get_intelligent_commit_threshold'),
setIntelligentCommitThreshold: value => invoke('set_intelligent_commit_threshold', { value }),
getMinutesCommitThreshold: () => invoke('get_minutes_commit_threshold'),
setMinutesCommitThreshold: value => invoke('set_minutes_commit_threshold', { value }),
getCommitModel: () => invoke('get_commit_model'),
setCommitModel: model => invoke('set_commit_model', { val: model }),
getReadmeModel: () => invoke('get_readme_model'),
setReadmeModel: model => invoke('set_readme_model', { val: model }),
close: () => invoke('close_settings'),
getAutostart: () => invoke('get_autostart'),
setAutostart: val => invoke('set_autostart', { enabled: val }),
getCloseToTray: () => invoke('get_close_to_tray'),
setCloseToTray: val => invoke('set_close_to_tray', { val }),
getGiteaToken: () => invoke('get_gitea_token'),
setGiteaToken: token => invoke('set_gitea_token', { token })
};
window.electronAPI = {
getFolders: () => invoke('get_folders'),
addFolder: () => invoke('add_folder'),
removeFolder: folderObj => invoke('remove_folder', { folderObj }),
getSelected: refreshSelectedCache,
setSelected: async folderObj => {
selectedCache = await invoke('set_selected', { folderObjOrPath: folderObj });
return selectedCache;
},
getCommits: (folderObj, page, pageSize) => invoke('get_commits', { folderObj, page, pageSize }),
getAllCommitHashes: folderObj => invoke('get_all_commit_hashes', { folderObj }),
diffCommit: (folderObj, hash) => invoke('diff_commit', { folderObj, hash }),
revertCommit: (folderObj, hash) => invoke('revert_commit', { folderObj, hash }),
snapshotCommit: (folderObj, hash) => invoke('snapshot_commit', { folderObj, hash }),
checkoutCommit: (folderObj, hash) => invoke('checkout_commit', { folderObj, hash }),
getCommitCount: folderObj => invoke('get_commit_count', { folderObj }),
hasDiffs: folderObj => invoke('has_diffs', { folderObj }),
removeGitFolder: folderObj => invoke('remove_git_folder', { folderObj }),
commitCurrentFolder: (folderObj, message) => invoke('commit_current_folder', { folderObj, message }),
showFolderContextMenu: folderPath => invoke('show_folder_context_menu', { folderPath }),
setMonitoring: (folderObj, monitoring) => invoke('set_monitoring', { folderPath: folderObj.path, monitoring }),
getFolderTree: folderPath => invoke('get_folder_tree', { folderPath }),
showTreeContextMenu: info => invoke('show_tree_context_menu', { info }),
getSelectedSync: () => selectedCache || window.currentSelectedFolderObj || null,
getIntelligentCommitThreshold: () => invoke('get_intelligent_commit_threshold'),
setIntelligentCommitThreshold: value => invoke('set_intelligent_commit_threshold', { value }),
getMinutesCommitThreshold: () => invoke('get_minutes_commit_threshold'),
setMinutesCommitThreshold: value => invoke('set_minutes_commit_threshold', { value }),
ollamaList: () => invoke('ollama_list'),
ollamaPull: model => invoke('ollama_pull', { model }),
onTrayToggleMonitoring: callback => onTauriEvent('tray-toggle-monitoring', callback),
onTrayRemoveFolder: callback => onTauriEvent('tray-remove-folder', callback),
onTrayAddFolder: callback => onTauriEvent('tray-add-folder', callback),
onFoldersLocationUpdated: callback => onTauriEvent('folders-location-updated', callback, false),
isGitRepo: path => invoke('is_git_repo', { folderPath: path }),
relocateFolder: (oldPath, newPath) => invoke('relocate_folder', { oldPath, newPath }),
pickFolder: () => invoke('pick_folder'),
repoHasCommit: (repoPath, commitHash) => invoke('repo_has_commit', { repoPath, commitHash }),
addFolderByPath: folderPath => invoke('add_folder_by_path', { folderPath }),
onCatBegin: callback => onTauriEvent('cat-begin', callback),
onCatChunk: callback => onTauriEvent('cat-chunk', callback),
onCatEnd: callback => onTauriEvent('cat-end', callback),
getDailyCommitStats: () => invoke('get_daily_commit_stats'),
hasReadme: folderPath => invoke('has_readme', { folderPath }),
generateReadme: folderPath => invoke('generate_readme', { folderPath }),
squashCommits: folderPath => invoke('squash_commits', { folderPath }),
pushToGitea: folderPath => invoke('push_to_gitea', { folderPath }),
initRepo: folderPath => invoke('init_repo', { folderPath }),
triggerRewriteNow: folderPath => invoke('trigger_rewrite_now', { folderPath })
};
window.addEventListener('beforeunload', () => {
for (const pending of eventUnsubscribers) {
pending.then(unlisten => unlisten()).catch(() => {});
}
});
})();