Compare commits
75 Commits
3b5fc1c8c1
...
7aa122f749
| Author | SHA1 | Date | |
|---|---|---|---|
| 7aa122f749 | |||
| 82281239e2 | |||
| 0dcc24f173 | |||
| 0e12ac604e | |||
| 82d9c45bda | |||
|
|
2ae1255629 | ||
|
|
37f12638b8 | ||
|
|
5474bfe44a | ||
|
|
672bb86307 | ||
|
|
7d2a746eaa | ||
|
|
5246f56aa8 | ||
|
|
07aa6ede4c | ||
|
|
45bfc8c23c | ||
|
|
a4ba98f2ac | ||
|
|
27890eb55e | ||
|
|
f87aded9c7 | ||
|
|
3824543efb | ||
|
|
54b8b4c5cd | ||
|
|
849ff08d61 | ||
|
|
3d72ac6d35 | ||
|
|
50f842fffb | ||
|
|
d615bd0df0 | ||
|
|
d4162c37d6 | ||
|
|
2eb45ac67f | ||
|
|
ddf534e72b | ||
|
|
bade8c3c09 | ||
|
|
09c6ef5041 | ||
|
|
f52eb353ab | ||
|
|
27ad9225e9 | ||
|
|
b10e8e2126 | ||
|
|
789a8c0129 | ||
|
|
a30328d219 | ||
|
|
81185ffc9a | ||
|
|
0224a1cb8c | ||
|
|
4c7823ea7a | ||
|
|
5b367ca08a | ||
|
|
b19d5632fd | ||
|
|
a504bc3b25 | ||
|
|
2f159affb4 | ||
|
|
2c969a971c | ||
|
|
8500cb2bd7 | ||
|
|
1b271e12a4 | ||
|
|
bed4c79c5b | ||
|
|
ccd52e2655 | ||
|
|
4a91b4ddce | ||
|
|
dd42885945 | ||
|
|
400222a7f0 | ||
|
|
8f044501bd | ||
|
|
a72d8d3e1c | ||
|
|
38a5c9d94e | ||
|
|
a2ef40b674 | ||
|
|
035b0131ad | ||
|
|
9cf980a3c3 | ||
|
|
938e7e151c | ||
|
|
8a1c659b0e | ||
|
|
5db41a2e4d | ||
|
|
719f624c9b | ||
|
|
b52a0b8504 | ||
|
|
da29c63e32 | ||
|
|
52f045ca64 | ||
|
|
9ed3ae0d40 | ||
|
|
f5dc2683cd | ||
|
|
f211e80fa1 | ||
|
|
0a82fc7499 | ||
|
|
f7b3b4bd04 | ||
|
|
ad8b063c08 | ||
|
|
3ce410fc4d | ||
|
|
05e2ed2060 | ||
|
|
4536810ecb | ||
|
|
9e708b731c | ||
|
|
cb83f14453 | ||
|
|
7a4622ad4a | ||
|
|
6a0c2787fa | ||
|
|
58e79310cc | ||
|
|
4b330d0a7f |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -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
|
||||
|
||||
31
README.md
31
README.md
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
12
index.html
12
index.html
@@ -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>
|
||||
|
||||
|
||||
53
package.json
53
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
77
preload.js
77
preload.js
@@ -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 }));
|
||||
});
|
||||
@@ -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 Node‐Executable
|
||||
// [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);
|
||||
63
renderer.js
63
renderer.js
@@ -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');
|
||||
|
||||
14
scripts/prepare-tauri-frontend.mjs
Normal file
14
scripts/prepare-tauri-frontend.mjs
Normal 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 });
|
||||
@@ -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
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
31
src-tauri/Cargo.toml
Normal 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
3
src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
13
src-tauri/capabilities/default.json
Normal file
13
src-tauri/capabilities/default.json
Normal 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
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
40
src-tauri/tauri.conf.json
Normal 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
128
tauriBridge.js
Normal 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(() => {});
|
||||
}
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user