Skip to content
Merged
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## Non rilasciato — engine v2

Il motore impara cose nuove (la versione dell'app non cambia qui: la alza chi pubblica). `engine/versioni.json`: motore 9, quindi al primo avvio l'app aggiorna il motore da sola. Il contratto con l'interfaccia è in `docs/contratto-engine-gui.md`.

- **FLUX.2 Klein 4B** (Apache-2.0, 4 passi) come quarto modello. Misurato su RTX 4060: Crea 5,4 s contro 6,4 s di Klein 3, zona 21 s contro 25 s, Modifica 9,6 s contro 11,5 s (`docs/bench-engine-v2.md`). Non è il predefinito.
- **LoRA multipli con pesi** su tutti i modelli (Qwen e Klein): in fila, forza da −2 a 2. Il riconoscimento dai nomi dei tensori dice per quale modello è fatto un LoRA (`src/shared/loraBase.ts`); nuovo IPC `lora.compatibili(famiglia)`.
- **Senza sfondo** (BiRefNet, MIT) e **estrai fedele** (SAM 2.1, Apache-2.0): ritagliano dai pixel della tua foto, senza ridisegnarla; i modelli si scaricano al primo uso e girano a parte. Con Klein, «sfondo trasparente» in Crea = disegno + BiRefNet.
- **Riquadri nell'Editor box** (regional prompting): Klein 3 li legge nel suo formato (JSON con le coordinate), Qwen e Klein 4B a parole. Nuovo adattatore dalla richiesta della GUI (`box`, `promptScena`, `sfondo`).
- **Catalogo con licenza e ruolo**, modelli «uncensored» marcati e scaricabili solo su richiesta: interruttore acceso, avviso accettato e licenza valida (Apache, MIT, BSD, CC-BY o Qwen Research con avviso; mai FLUX Kommunity). Un LLM uncensored (Qwen3-VL 4B abliterated) per i testi.
- Correzioni: Klein 4B ha la sua chiave VRAM nella regola «una cosa alla volta»; le parole chiave dei LoRA entrano nel prompt anche con Klein.
- Prove: `npm test` (modelli, catalogo, spazio, lora, regioni, adatta, grafi v2, grafi Qwen identici).

## 1.1.0 — 2026-10-21

Un'app più semplice da usare: tre modelli al posto delle «qualità», una sola scheda per scrivere cosa cambiare (tutta la foto o solo una zona), le ricette dentro Modifica e il testo scritto dal Qwen3-VL 4B anche con Qwen. Il motore passa a ComfyUI 0.42.3.
Expand Down
38 changes: 38 additions & 0 deletions docs/bench-engine-v2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Benchmark engine v2 — prima / dopo

Misurato il 22/10/2026 su RTX 4060 8 GB (driver 617.42), ComfyUI 0.42.3 (engine installato, motore 8), tutto 1024×1024, stesso prompt e stessa maschera di `scripts/misure_gpu.mjs`.
Grafi veri dell'app (`grafi.ts` impacchettato con esbuild). GPU libera prima di ogni giro (< 15 %), un benchmark alla volta, ComfyUI di prova sulla porta 8819 (non quello dell'app, non quello degli asset: il 8818 era fermo e vuoto). Dati completi: `docs/bench-engine-v2.json`.
Come si ripete: `node scripts/bench_serie.mjs --repo . --out <cartella> --etichetta <nome> --rip 3 --porta 8819 <prove>` con `MOTORE_OUT` = cartella di output del motore (`node scripts/misure_gpu.mjs --prova elenco` stampa le prove).

**Prima** = Klein 3 veloce, 8 passi (il percorso rapido di prima di engine v2). **Dopo** = FLUX.2 Klein 4B FP8, 4 passi (`Flux2Scheduler` + `CFGGuider`).

## A caldo (mediana di 3 giri dopo un giro di riscaldamento)

| Scenario | Modello | Passi | Totale | s/passo | VRAM picco* | Peggiore |
|---|---|---:|---:|---:|---:|---:|
| Crea | Klein 3 veloce (prima) | 8 | 6,4 s | 0,471 | 6,1 GB | 6,4 s |
| Crea | **Klein 4B (dopo)** | 4 | **5,4 s** | 0,572 | 5,3 GB | 5,4 s |
| Crea | Klein 4B, 8 passi (controllo) | 8 | 8,5 s | 0,711 | 7,1 GB | 8,5 s |
| Modifica zona | Klein 3 veloce (prima) | 8 | 24,9 s | 1,873 | 7,4 GB | 25,7 s |
| Modifica zona | **Klein 4B (dopo)** | 4 | **21,3 s** | 2,258 | 6,1 GB | 21,5 s |
| Modifica tutta la foto | Klein 3 veloce (prima) | 8 | 11,5 s | 1,050 | 6,9 GB | 11,5 s |
| Modifica tutta la foto | **Klein 4B (dopo)** | 4 | **9,6 s** | 1,307 | 7,3 GB | 9,6 s |

## A freddo (1 giro; modelli scaricati con `/free`, i file restano nella cache del disco)

| Scenario | Modello | Passi | Totale | s/passo | VRAM picco* |
|---|---|---:|---:|---:|---:|
| Crea | Klein 3 veloce | 8 | 9,7 s | 0,468 | 6,8 GB |
| Crea | Klein 4B | 4 | 8,6 s | 0,623 | 6,9 GB |
| Crea | Klein 3 Base (CFG 3,5, riferimento) | 50 | 59,4 s | 1,006 | 6,8 GB |
| Crea | Qwen-Image 2.1 Q4_K_M (riferimento) | 40 | 162,9 s | 2,880 | 5,9 GB |

\* picco letto con `nvidia-smi` una volta al secondo (include la cache di PyTorch): serve a vedere che nessun giro va fuori memoria, non a confrontare i megabyte fra modelli. Il primissimo avvio a freddo di Klein 4B (file mai letti dal disco) è costato 15,6 s.

## Che cosa si tiene e che cosa no

- **Tenuto: Klein 4B come quarto modello.** Guadagno reale ma piccolo: −16 % in Crea, −14 % in zona, −17 % in Modifica, perché ogni passo costa di più (0,57 contro 0,47 s) e a 8 passi il 4B non va più veloce di Klein 3 (8,5 contro 6,4 s). Un'immagine di Crea (teiera) e le due modifiche rosse sono pulite a occhio; **non** è stato fatto un confronto di qualità alla cieca con Klein 3. Non diventa il predefinito: il vantaggio vero è la licenza Apache-2.0, non la velocità.
- **Niente altro è stato aggiunto in nome della velocità.** Le ottimizzazioni suggerite dalla ricerca (SageAttention, `torch.compile`, cache approssimate, Nunchaku/SVDQuant) non sono state installate né misurate: richiedono pacchetti esterni nell'engine produttivo (wheel Windows, graph break con l'offload) e la ricerca stessa le classifica «esperimenti separati». Non c'è un numero a loro favore, quindi non entrano.
- Già in 1.1.0 e non rimisurato qui: il 4B che scrive i testi (185,9 → 133,5 s su «Migliora + Crea» con Qwen), vedi `docs/misure-1.1.0.md`.
- BiRefNet e SAM 2 non sono stati cronometrati: i loro nodi (`DaProdSfondo`, `DaProdSegmenta`) arrivano con il motore 9 e il motore installato qui è l'8. Girano a parte, senza il modello che disegna in VRAM.
- Un incidente di misura, scartato: un secondo `bench_serie` partito per errore in parallelo ha sporcato un primo tentativo; i dati sopra vengono da una ripetizione con un solo benchmark vivo (controllato con la lista dei processi).
70 changes: 70 additions & 0 deletions docs/contratto-engine-gui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Contratto engine ↔ GUI (engine v2)

Chi lo usa: la GUI v2 (`feat/gui-v2`). Chi lo possiede: l'engine (`feat/engine-v2`): **`src/shared/tipi.ts`, `src/shared/modelli.ts`, `src/shared/loraBase.ts`, `src/renderer/src/api.ts`, `src/preload/index.ts` sono dell'engine**. La GUI tocca solo pagine e componenti del renderer; se serve un campo nuovo lo chiede (commento sul task), non lo aggiunge.
Tutto passa da `window.daprod` (tipo `Api` in `api.ts`); i canali IPC sono `area:azione` (`gestisci('lavori:accoda', …)` in `src/main/index.ts`).
Provato da: `npm test` (modelli, catalogo, spazio, lora, regioni, adatta, grafi_v2, confronto grafi Qwen) e `npm run typecheck`.

## 1. Modelli (famiglie)

`Famiglia = 'qwen' | 'klein' | 'klein-base' | 'klein4b'`. `IdModello` (`modelli.ts`) ha le stesse chiavi più `qwen-base`; `MODELLI[id]` dà nome, passi, CFG, `lora: true` per tutti.

| Famiglia | Modello | Passi (bozza/alta/massima) | CFG | Licenza pesi |
|---|---|---|---|---|
| `qwen` | Qwen-Image 2.1 GGUF | 20/40/50 | 1 | Qwen Research (solo ricerca) |
| `klein` | FLUX 3 Klein (INT8) | 8/8/16 | 1 | FLUX Kommunity |
| `klein-base` | FLUX 3 Klein Base | 25/50/64 | 3,5 | FLUX Kommunity |
| `klein4b` | FLUX.2 Klein 4B (FP8) | 4/4/8 | 1 | **Apache-2.0** |

`Richiesta.famiglia` (se manca vale `Impostazioni.famiglia`). Tre cose da sapere: Klein 4B usa `Flux2Scheduler` + `CFGGuider`; con Klein i testi (migliora prompt, descrivi…) li scrive il Qwen3-VL 4B; nelle opere in galleria il modello è `«FLUX.2 Klein 4B · file»` (`modelloDaOpera` lo riconosce).

## 2. Richiesta (`lavori.accoda(q): Promise<string[]>` → id dei lavori)

Campi comuni: `modalita`, `famiglia`, `prompt`, `negativo`, `seed` (−1 = casuale), `passi`, `cfg`, `larghezza/altezza`, `megapixel`, `immagini[]` (percorsi; [0] è la foto), `quante`, `uscita`, `lora`.

Modalità: `crea`, `modifica`, `zona` (maschera), `espandi`, `rifinisci`, `varia`, `ingrandisci`, `descrivi` (testo), `traduci`, **`sfondo`**, **`segmenta`**.

### 2.1 LoRA multipli con pesi — `Richiesta.lora: LoraAttiva[]`
`{ file, forza }`, **l'ordine della lista è l'ordine di applicazione**, forza −2…2 (fuori scala si tronca; 0 = ignorato). Vale per **tutte** le famiglie. Il motore mette un `LoraLoaderModelOnly` per ciascuno, in catena. La parola chiave di ogni LoRA acceso entra nel prompt da sola (`conParoleLora`, ora anche con Klein).
Compatibilità: `InfoLora.famiglie: Famiglia[]` (dai nomi dei tensori, `shared/loraBase.ts`) e `InfoLora.compatibile: 'si'|'no'|'?'`. `vaBeneCon(lora, famiglia)` (shared, importabile dal renderer) dice `si|no|?` (`?` = si lascia provare). Un LoRA di Klein 3 **non** va su Klein 4B (e viceversa): sono architetture diverse.
**IPC nuovo:** `lora.compatibili(famiglia): Promise<InfoLora[]>` = solo i `si` e `?` per quel modello. (`lora.elenco()` resta: tutti.)

### 2.2 Senza sfondo
- Foto esistente: `modalita: 'sfondo'`, `immagini: [foto]` → PNG RGBA (BiRefNet, MIT). I pixel del soggetto restano quelli originali, i bordi sono ripuliti dagli aloni. Non serve il modello che disegna.
- In Crea: `trasparente: true` oppure `sfondo: 'trasparente'`. Qwen usa il suo canale alfa; **Klein (tutte) genera e poi toglie lo sfondo con BiRefNet** (la GUI può dire «Klein: sfondo tolto dopo»).
- `sfondo: { tinta: '#rrggbb' }` → una riga in più nel prompt («on a plain solid #rrggbb background»). `sfondo: 'scena'` = niente.
- Il primo uso scarica BiRefNet (~450 MB) da solo; si vede nella `fase` del lavoro («Scarico BiRefNet · 40%»). Stato: `modelli.segmentatori()`.

### 2.3 Estrai personaggi/oggetti (SAM 2)
`modalita: 'segmenta'`, `immagini: [foto]`, `segmenta: { box?: [x0,y0,x1,y1], punti?: {x,y,positivo}[], ritaglia?: boolean }`. Coordinate **0–1 sulla foto**, origine in alto a sinistra. `ritaglia` true (predefinito) = PNG stretto attorno alla cosa (+12 px), false = tela intera col resto trasparente. Senza `box` né `punti` il lavoro fallisce con «dimmi cosa estrarre». Sempre dai pixel originali (nessuna ricreazione). Scarica SAM 2.1 small (~185 MB) al primo uso.
**IPC nuovo:** `modelli.segmentatori(): Promise<StatoVoce[]>` = le voci BiRefNet e SAM 2 con `presente`.

### 2.4 Regional prompting (Editor box) — per `crea` e `modifica`
Due modi equivalenti, il motore normalizza (`adattaRichiesta`, `motore/adatta.ts`):

1. **`box: BoxRegione[]` + `promptScena`** (quello che manda la GUI v2): `{ id, rect:[x0,y0,x1,y1] (0–1 sulla tela finale), tipo, prompt, rif?, rigidita, ordine, nascosta? }`. Le `nascosta` e quelle senza prompt non partono; l'ordine è `ordine`; `promptScena` (se c'è e ci sono riquadri validi) diventa il `prompt`.
2. `regioni: Regione[]` (`{ id, box, prompt }`) già pronte.

Traduzione per modello: **Klein 3 / Base** → prompt «didascalia che cita `<obj_N>`» + righe JSON con `bbox [y_min,x_min,y_max,x_max]` su 0–1000 (formato nativo, `modifica`: `tgt_bbox`); **Qwen e Klein 4B** → la posizione a parole («at the top left of the frame»). Al massimo 16 riquadri, minimo 2 % per lato. I prompt dei riquadri vengono tradotti in inglese come quello della scena (se `traduci` è acceso).
**Limiti, detti chiari:** i riquadri sono una *guida morbida* (l'elemento può uscire un po'); `rigidita: 'blocca'` per ora vale `'decisa'`; `rif` (foto di riferimento del box) non è ancora usato dal motore.

### 2.5 Modifica con istruzioni
`modalita: 'modifica'` (tutta la foto) e `'zona'` (solo la maschera, il resto identico al pixel): il `prompt` è l'istruzione («make the teapot red»). Famiglie Klein: le foto di riferimento sono `immagini[1..]` (`<image2>`…).

## 3. Catalogo, licenze, uncensored

`modelli.stato(): StatoVoce[]` restituisce **tutte** le voci con: `id`, `nome`, `tipo`, `presente`, `byte`, `licenza`, `licenzaUrl`, `ruolo` (`denoiser|encoder|llm|vae|lora|segmentatore|sfondo`), `uncensored?`, `avviso?`. `modelli.scarica(id)` / `annulla(id)` / evento `su.download`.
**La guardia è nel main**, non nella UI (`motivoDiBlocco`): una voce `uncensored` si scarica solo se **tutte e tre** le condizioni valgono, e se no `modelli.scarica` va in errore con il motivo in italiano:
1. `Impostazioni.uncensored === true` (interruttore «Modelli uncensored», spento di serie);
2. `Impostazioni.uncensoredAccettato === true` (ha letto l'avviso, `AVVISO_UNCENSORED` in `catalogo.ts`);
3. **licenza valida** (`motivoLicenza`): Apache-2.0, MIT, BSD-3-Clause, CC-BY-4.0 oppure «Qwen Research» con l'avviso sul limite d'uso. FLUX Kommunity (§4(d), niente scorciatoie per togliere le protezioni), «non-commercial» non letti, licenza o link mancanti = mai.
Nessuna voce uncensored è `consigliato`: non si scarica mai da sola né in idle. Nel catalogo ci sono: encoder Qwen3-VL 8B uncensored (AtomicChat, mradermacher; Apache), LoRA `lora-uc` (Qwen Research, con avviso), LLM **Qwen3-VL 4B abliterated** (Huihui, Apache) per i testi con Klein (`Impostazioni.testoUncensored`, serve anche l'interruttore). **Non c'è un denoiser FLUX uncensored** e non verrà aggiunto.
`Impostazioni.uncensored` è un **booleano**, non l'oggetto `{abilitato, accettatoIl…}` che la GUI v2 aveva ipotizzato: la GUI deve leggere/scrivere `uncensored`, `uncensoredAccettato`, `testoUncensored` con `impostazioni.salva`.

## 4. Coda, eventi, VRAM
Un lavoro alla volta al motore (`lavori.elenco`, `su.lavori`, `su.lavoro`). Il lavoro ha `fase` (testo in italiano, anche i download di BiRefNet/SAM2), `passo/passiTotali`, `stima`, `anteprima`, `risultati[]` (percorsi in galleria), `errore`. «Una cosa alla volta in VRAM»: cambiando famiglia (Qwen → Klein → Klein 4B) il motore scarica il modello precedente (`/free`) e la fase dice «Scarico FLUX.2 Klein 4B». `sfondo` e `segmenta` non chiedono VRAM (il nodo carica e toglie il segmentatore).

## 5. File e cartelle da conoscere
`modelli.file()` ora include `klein4b` e `klein4bTe`. Un modello Klein 4B è «pronto» con modello + text encoder (Qwen3 4B) + VAE di FLUX.2 (`kleinVae`, lo stesso di Klein 3); voci del catalogo `klein4b-fp8`, `klein4b-te`, `klein-vae`.

## 6. Cosa NON c'è (ancora)
`estrai.ritaglia` come IPC a parte: si usa `lavori.accoda` con `modalita: 'segmenta'`. `box.rif` e `rigidita: 'blocca'` non cambiano il risultato. Nessuna prova di qualità con GPU di `sfondo`/`segmenta` sul motore installato (i nodi `DaProdSfondo`/`DaProdSegmenta` arrivano col motore 9: al primo avvio dopo l'aggiornamento l'app aggiorna il motore da sola).
21 changes: 21 additions & 0 deletions engine/custom_nodes/DaProd-Nodi/BIREFNET_LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 ZhengPeng

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
5 changes: 5 additions & 0 deletions engine/custom_nodes/DaProd-Nodi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import torch.nn.functional as F

from .traduzione import DaProdTraduci
from .segmentazione import DaProdSegmenta, DaProdSfondo


def _maschera_come(maschera, h, w):
Expand Down Expand Up @@ -115,9 +116,13 @@ def esegui(self, image, reference, mask, strength):
"DaProdRiempiZona": DaProdRiempiZona,
"DaProdAccordaColori": DaProdAccordaColori,
"DaProdTraduci": DaProdTraduci,
"DaProdSfondo": DaProdSfondo,
"DaProdSegmenta": DaProdSegmenta,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"DaProdRiempiZona": "DaProd · Riempi la zona",
"DaProdAccordaColori": "DaProd · Accorda i colori",
"DaProdTraduci": "DaProd · Traduci il prompt",
"DaProdSfondo": "DaProd · Togli lo sfondo (BiRefNet)",
"DaProdSegmenta": "DaProd · Estrai fedele (SAM 2)",
}
54 changes: 54 additions & 0 deletions engine/custom_nodes/DaProd-Nodi/birefnet_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Le poche cose che il codice di BiRefNet (birefnet_modello.py, di ZhengPeng7, licenza MIT: vedi BIREFNET_LICENSE.txt) prendeva da `timm`:
# le abbiamo qui, così il motore non ha bisogno di un pacchetto in più. Sono le stesse funzioni (DropPath = profondità stocastica, spenta in inferenza).
import collections.abc
from itertools import repeat

import torch
import torch.nn as nn
from transformers import PretrainedConfig

trunc_normal_ = nn.init.trunc_normal_


def _ntuple(n):
def parse(x):
if isinstance(x, collections.abc.Iterable) and not isinstance(x, str):
return tuple(x)
return tuple(repeat(x, n))

return parse


to_2tuple = _ntuple(2)


def drop_path(x, drop_prob: float = 0.0, training: bool = False, scale_by_keep: bool = True):
if drop_prob == 0.0 or not training:
return x
keep_prob = 1 - drop_prob
shape = (x.shape[0],) + (1,) * (x.ndim - 1)
random_tensor = x.new_empty(shape).bernoulli_(keep_prob)
if keep_prob > 0.0 and scale_by_keep:
random_tensor.div_(keep_prob)
return x * random_tensor


class DropPath(nn.Module):
def __init__(self, drop_prob: float = 0.0, scale_by_keep: bool = True):
super().__init__()
self.drop_prob = drop_prob
self.scale_by_keep = scale_by_keep

def forward(self, x):
return drop_path(x, self.drop_prob, self.training, self.scale_by_keep)

def extra_repr(self):
return f"drop_prob={round(self.drop_prob, 3):0.3f}"


class BiRefNetConfig(PretrainedConfig):
model_type = "SegformerForSemanticSegmentation"

def __init__(self, bb_pretrained=False, **kwargs):
self.bb_pretrained = bb_pretrained
super().__init__(**kwargs)
Loading
Loading