Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ GOOGLE_MODEL_NAME=gemini-2.0-flash
OLLAMA_BASE_URL=http://localhost:11434
# Pentru rulare din interiorul Docker (docker-compose run --rm app pytest):
# OLLAMA_BASE_URL=http://ollama:11434

# ─── Modele AI Locale ─────────────────────────────────────────────────────────
# Aceste modele se creaza automat prin docker-compose up sau manual:
# ck-model = gemma2:2b (clasificare + decizie)
# ck-extractor = gemma2:2b (extragere informatii)
# ck-vision = llava:7b (identificare vizuala imagini)
73 changes: 62 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

ClutterKill is a multi-agent desktop application for automated file organization using local AI.

> **Cross-Platform:** Această aplicație este construită în Python și PyQt6, ceea ce o face 100% **Cross-Platform**. Funcționează perfect pe macOS, Windows și Linux, atâta timp cât ai Python instalat. Mici diferențe pot apărea la instalarea dependențelor de sistem (ex. Tesseract OCR).

# User Stories

## US 1 (Selecție foldere):
Expand Down Expand Up @@ -62,18 +64,22 @@ ClutterKill/
├── 📂 ai/ # MODULUL AI (Inteligența Aplicației)
│ ├── 📄 __init__.py
│ ├── 📄 Modelfile # Definiția modelului Gemma 2:2b (temperature 0.1)
│ ├── 📄 llm_config.py # Conexiunea LangChain cu localhost:11434
│ ├── 📄 tools.py # Funcții: extract_text_from_pdf, extract_text_from_image
│ ├── 📄 Modelfile # Definiția modelului Gemma 2:2b (Agent 0 & 2 - Clasificare)
│ ├── 📄 Modelfile.extractor # Definiția modelului Gemma 2:2b (Agent 1 - Extragere)
│ ├── 📄 Modelfile.vision # Definiția modelului LLaVA 7B (Vision - Analiză vizuală imagini)
│ ├── 📄 llm_config.py # Factory + configurare LLM (Ollama / Google)
│ ├── 📄 tools.py # Funcții: extract_text_from_pdf, extract_text_from_image, extract_text_from_docx
│ ├── 📄 vision_tools.py # Modul Vision AI: describe_image() — trimite poze la LLaVA pentru identificare
│ ├── 📄 agent_compiler.py # AGENT 0: Traduce promptul natural în JSON (Reguli)
│ ├── 📄 agent_extractor.py # AGENT 1: Rezumă fișierul fizic
│ ├── 📄 agent_extractor.py # AGENT 1: Rezumă fișierul fizic (chain-of-thought extraction)
│ └── 📄 agent_decider.py # AGENT 2: Combină Agent 0 cu Agent 1 și ia decizia finală
├── 📂 core/ # LOGICA BACKEND (Sistemul de operare)
│ ├── 📄 __init__.py
│ ├── 📄 file_manager.py # Mutare, redenumire (cross-platform cu pathlib)
│ ├── 📄 undo_manager.py # Logica pentru stiva de Undo (ultimele 50 acțiuni)
│ └── 📄 quarantine_db.py # Baza de date SQLite pentru fișierele nesigure
│ ├── 📄 quarantine_db.py # Baza de date SQLite pentru fișierele nesigure
│ └── 📄 scan_worker.py # QThread: pipeline-ul complet de scanare (Vision + Extragere + Decizie)
├── 📂 ui/ # INTERFAȚA GRAFICĂ (PyQt6)
│ ├── 📄 __init__.py
Expand All @@ -92,6 +98,28 @@ ClutterKill/
└── test_agents.py # Verifică dacă Agenții 0 și 2 scot JSON valid
```

## 🧠 AI Model Registry

ClutterKill folosește **4 modele AI locale** rulând prin Ollama în Docker:

| Model | Bază | Scop | Fișier Config | Mărime |
|-------|------|------|---------------|--------|
| `ck-model` | `gemma2:2b` | Agent 0 (Compiler) + Agent 2 (Decider) — clasificare și decizie | `ai/Modelfile` | ~1.6GB |
| `ck-extractor` | `gemma2:2b` | Agent 1 (Extractor) — rezumat tehnic documente | `ai/Modelfile.extractor` | ~1.6GB |
| `ck-vision` | `llava:7b` | Vision AI — identificare vizuală imagini (dog, cat, sign...) | `ai/Modelfile.vision` | ~4.5GB |
| `gemma2:2b` | - | Model de bază (descărcat automat) | - | ~1.6GB |
| `llava:7b` | - | Model de bază multimodal (descărcat automat) | - | ~4.5GB |

### Pipeline de procesare imagini

```text
📷 Imagine (.jpg/.png) → Vision AI (ck-vision / llava:7b) → "dog"
→ OCR (Tesseract) → text extras
→ Combinate în rezumat → Decizie: Dog.jpeg
```

Pentru **documente** (PDF, DOCX, TXT) se folosește doar pipeline-ul text clasic (fără Vision AI).

## 📁 Directory Architecture

The project follows a structured modular architecture:
Expand All @@ -116,6 +144,14 @@ The project's Python dependencies are listed in `requirements.txt`, which includ
- **Ruff**: For extremely fast Python linting and code formatting.

### How to Install:

**1. System Dependencies (OCR)**
To process images (PNG, JPG), you must install Tesseract OCR on your host machine:
- **macOS**: `brew install tesseract tesseract-lang`
- **Windows**: Download the installer from [UB-Mannheim](https://github.com/UB-Mannheim/tesseract/wiki)
- **Linux (Ubuntu/Debian)**: `sudo apt-get install tesseract-ocr tesseract-ocr-ron`

**2. Python Dependencies**
To set up your environment, install the dependencies using `pip`:
```bash
pip install -r requirements.txt
Expand Down Expand Up @@ -155,32 +191,47 @@ The application leverages Docker to seamlessly run local AI models without compl
docker-compose up -d ollama
```

3. **Pull base model and Create Custom AI Models**:
ClutterKill uses two distinct models for processing (Classifier and Extractor):
3. **Pull base models and Create Custom AI Models**:
ClutterKill uses three distinct custom models:
```bash
# Pull the base model (Wait for the download to finish)
# Pull the base models (Wait for each download to finish)
docker exec -it clutterkill_ollama ollama pull gemma2:2b
# Note: If you get a 'manifest does not exist' error on older machines, use 'gemma:2b' instead and update the Modelfiles.
docker exec -it clutterkill_ollama ollama pull llava:7b

# Create Agent 0 & 2 (Classifier)
# Create Agent 0 & 2 (Classifier / Decider)
docker exec -it clutterkill_ollama ollama create ck-model -f /app/ai/Modelfile

# Create Agent 1 (Extractor)
docker exec -it clutterkill_ollama ollama create ck-extractor -f /app/ai/Modelfile.extractor

# Create Vision AI (Image identification)
docker exec -it clutterkill_ollama ollama create ck-vision -f /app/ai/Modelfile.vision
```
> **Note**: If you get a 'manifest does not exist' error on older machines for gemma2:2b, use 'gemma:2b' instead and update the Modelfiles.

4. **Verify the models are running**:
```bash
curl http://localhost:11434/api/tags
```

5. **Run the Application**:
Activate your virtual environment and run the graphical interface:
Cea mai simplă metodă de a rula aplicația este folosind scriptul automat `start.sh`. Acesta va crea mediul virtual, va instala toate dependențele lipsă și va porni aplicația cu o singură comandă:
```bash
./start.sh
```
*Alternativ (manual):*
```bash
source .venv/bin/activate
python main.py
```

## 🌟 Changelog / Update-uri Recente
- **UI Revamp (Dark Theme Premium):** Interfața a primit un redesign complet bazat pe paleta Catppuccin Macchiato. Colțuri rotunjite, butoane colorate vibrant și efecte subtile.
- **Visual Builder Funcțional:** Tab-ul "Rules" suportă acum șabloane stricte (Templates). A fost adăugată o paletă de butoane "Click-to-Insert" pentru a introduce automat variabile matematice (ex: `[An]`, `[Luna]`) fără a le tasta manual.
- **Simplificare Arhitectură Directoare (Flattened Output):** La cererea utilizatorilor, fișierele redenumite de AI nu mai sunt forțate în sub-foldere. Ele sunt exportate direct în root-ul folderului destinație ales de tine.
- **Reparare ExtractorAgent (Bug-ul "Strip"):** Rezolvată o problemă critică cauzată de noul SDK Google V2 care bloca parsarea imaginilor aruncând toate rezultatele direct în carantină.
- **Rate-Limiting Gemini V2:** Adăugat mecanism automat de sleep (15 secunde) atunci când API-ul gratuit Google atinge limitele HTTP 429.

*Note: The project configuration also provides environment variables (`AI_PROVIDER`, `GOOGLE_API_KEY`) to easily switch between local `ollama` processing and cloud-based alternatives like `google`.*

For more detailed DevOps and QA instructions, please refer to [README_ingineri.md](README_ingineri.md).
80 changes: 61 additions & 19 deletions README_ingineri.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,45 +36,87 @@ docker-compose down -v
docker-compose up -d
```

## 2. Configurarea Modelului Local (Gemma 2:2b)
## 2. Configurarea Modelelor Locale

Am creat fișierul `ai/Modelfile` bazat pe modelul `gemma2:2b`.
Acest fișier setează:
- **`temperature 0.1`**: Garantează răspunsuri precise, deterministe și lipsite de halucinații, ideale pentru clasificarea exactă a fișierelor.
- **`SYSTEM prompt`**: Forțează AI-ul să dea doar rezultate scurte și la obiect, fără conversații introductive inutile.
ClutterKill folosește **3 modele AI personalizate** + 2 modele de bază:

### Crearea și încărcarea modelului personalizat:
Odată ce containerul de Ollama rulează (pasul 1), executați următoarea comandă pentru a crea modelul `ck-model`:
### 2.1 Modelul de Clasificare — `ck-model` (Agent 0 & 2)

Bazat pe `gemma2:2b`. Fișierul `ai/Modelfile` setează:
- **`temperature 0.1`**: Răspunsuri precise, deterministe.
- **`SYSTEM prompt`**: Forțează AI-ul să returneze doar JSON structurat.

```bash
docker exec -it clutterkill_ollama ollama create ck-model -f /app/ai/Modelfile
```

## 3. Verificarea Modelului
### 2.2 Modelul de Extragere — `ck-extractor` (Agent 1)

Bazat pe `gemma2:2b`. Fișierul `ai/Modelfile.extractor` setează un prompt specializat pentru extragerea de entități din documente (Emitent, Dată, Sumă, Tip).

Pentru a verifica dacă modelul a fost compilat cu succes și este recunoscut de API-ul Ollama, executați:
```bash
curl http://localhost:11434/api/tags
docker exec -it clutterkill_ollama ollama create ck-extractor -f /app/ai/Modelfile.extractor
```

### 2.3 Modelul Vision — `ck-vision` (Identificare Imagini)

Bazat pe **`llava:7b`** (~4.5GB). Acesta este un model **multimodal** capabil să "vadă" imagini și să identifice subiectul principal.

**Ce face:**
- Primește o imagine (JPEG/PNG/BMP) codificată în base64
- Returnează **un singur cuvânt** care descrie subiectul principal (ex: `dog`, `cat`, `sign`, `car`)
- Folosit doar pentru fișiere imagine, nu pentru PDF/DOCX

**Fișier configurare:** `ai/Modelfile.vision`
```
FROM llava:7b
PARAMETER temperature 0.1
SYSTEM "You identify images. Reply with ONE word only..."
```

**Setup:**
```bash
# 1. Pull modelul de bază llava:7b (~4.5GB, durează câteva minute)
docker exec -it clutterkill_ollama ollama pull llava:7b

# 2. Creează modelul personalizat ck-vision
docker exec -it clutterkill_ollama ollama create ck-vision -f /app/ai/Modelfile.vision
```
Rezultatul (în format JSON) trebuie să includă modelul `ck-model` și modelul de bază `gemma2:2b`.

## 4. Testarea/Apelarea Modelului
**Modul Python:** `ai/vision_tools.py` — funcția `describe_image(path)` gestionează:
- Codificarea imaginii în base64
- Detectarea MIME type (jpeg, png, bmp)
- Trimiterea către Ollama (local) sau Google Gemini (cloud) în funcție de `AI_PROVIDER`
- Returnarea descrierii ca string

Pentru a testa capacitățile modelului direct din terminal, fără a rula interfața grafică a aplicației, aveți două variante:
**Note de performanță:**
- Prima inferență durează ~30-60s (încărcare model în RAM)
- Inferențele următoare: ~5-15s per imagine
- Necesită minim 8GB RAM disponibil

### Varianta A: Apel prin REST API
## 3. Verificarea Modelelor

Pentru a verifica dacă toate modelele au fost create cu succes:
```bash
curl -X POST http://localhost:11434/api/generate -d '{
"model": "ck-model",
"prompt": "Clasifică documentul: Curs_MDS_Sem2.pdf"
}'
curl http://localhost:11434/api/tags
```
Rezultatul (JSON) trebuie să includă: `ck-model`, `ck-extractor`, `ck-vision`, `gemma2:2b`, `llava:7b`.

### Varianta B: Apel direct prin CLI (Ollama Run)
## 4. Testarea/Apelarea Modelelor

Pentru a testa capacitățile modelelor direct din terminal:

### Varianta A: Test clasificare (ck-model)
```bash
docker exec -it clutterkill_ollama ollama run ck-model "Clasifică documentul: Curs_MDS_Sem2.pdf"
```

### Varianta B: Test Vision (ck-vision) - direct din CLI
```bash
# ck-vision nu poate fi testat direct din CLI fără imagine.
# Folosiți scriptul Python pentru test complet (Varianta C).
```

### Varianta C: Apel prin Python (citind PDF-ul fals generat)
Aceasta este metoda recomandată pentru a testa ecosistemul real (PyMuPDF extrage textul -> LangChain îl trimite către Ollama).

Expand Down
5 changes: 5 additions & 0 deletions ai/Modelfile.vision
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM llava:7b
PARAMETER temperature 0.1
SYSTEM """
You identify images. Reply with ONE word only: the main subject. Examples: dog, cat, sign, car, mountain, person, food, building.
"""
19 changes: 15 additions & 4 deletions ai/agent_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ class CompiledRule(BaseModel):
User instruction: "{user_prompt}"

Extract the category, folder structure, and naming convention.
If the naming convention is not explicitly stated, use a default placeholder like "{{original_filename}}" or infer a sensible one if the context implies it.
CRITICAL RULES FOR EXTRACTION:
1. CATEGORY: If the user instruction applies to ALL files without filtering (e.g. "organize them", "rename them", "toate", "toate pozele", "orice"), set category to "any". Otherwise, extract the specific category (e.g. "factura").
2. NAMING CONVENTION: If the user explicitly asks to give files suggestive names, new names, or rename them (e.g. "da le nume sugestive", "redenumeste-le", "nume dupa continut", "nume sugestiv"), you MUST set naming_convention to "descriptive_name_based_on_content". If they do not ask for renames, set it to "{{original_filename}}".

CRITICAL: You must return ONLY the raw JSON object containing the ACTUAL values based on the user instruction. Do NOT return a JSON schema. Do NOT return properties definitions. DO NOT echo back the format instructions.

Expand Down Expand Up @@ -112,9 +114,18 @@ def compile(self, user_prompt: str) -> CompiledRule:
"""
logger.info("CompilerAgent: compiling prompt: '%s'", user_prompt)
try:
# invoke the chain which formats the prompt, calls LLM, and parses output
result = self._chain.invoke({"user_prompt": user_prompt})
return result
import time
max_attempts = 3
for attempt in range(max_attempts):
try:
result = self._chain.invoke({"user_prompt": user_prompt})
return result
except Exception as loop_e:
if "429" in str(loop_e) and attempt < max_attempts - 1:
logger.warning(f"API Rate Limit Hit (429) in Compiler. Sleeping 15s... (Attempt {attempt+1}/{max_attempts})")
time.sleep(15)
else:
raise loop_e
except Exception as e:
logger.error("Failed to compile rule: %s", e)
raise
Expand Down
Loading
Loading