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
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## 1.3.0 — 2026-10-28

Un giro di pulizia: i LoRA di Klein funzionano davvero, Klein parte dai 16 passi indicati da Black Forest Labs, Crea si usa subito (scrivi, scegli il formato, premi CREA) e le Opzioni sono corte, con tutto il resto in «Avanzate».

### LoRA di Klein

- **I LoRA FLUX 3 in formato Diffusers/SimpleTuner ora funzionano.** Esistono due formati. Quelli fatti con AI Toolkit usano i nomi di ComfyUI (`diffusion_model.content_mode_blocks…`) e andavano già. Quelli di Diffusers e SimpleTuner (`transformer.mode_blocks…`, `single_transformer_blocks…ff.linear_in`: per esempio il LoRA delle angolazioni di akhaliq) il motore non li conosce: caricava il file senza errori ma scartava tutte le chiavi, e l'app li scambiava per «FLUX.1, non compatibile». Ora l'app li riconosce e, quando li importi o li scarichi, li converte ai nomi di ComfyUI: riscrive solo l'intestazione del `.safetensors`, i pesi non cambiano di un byte, e l'originale resta accanto come `.diffusers.bak`. Se alpha è diverso dal rango aggiunge i tensori `alpha`. Provato sul motore vero con un LoRA reale: prima «0 patches attached», dopo «172 patches attached», nessuna chiave scartata.
- **Forza di partenza 0,75** per i LoRA di Klein (il valore indicato da BFL; da 0,5 a 1 va bene).
- Corretto un riconoscimento sbagliato già presente: i LoRA di FLUX.2 in formato Diffusers (`single_transformer_blocks…to_qkv_mlp_proj`) venivano scambiati per LoRA di Qwen 2.1.
- Prove: `test/lora-converti.mjs` (mappa dei nomi, pesi identici byte per byte, alpha, file che non è un LoRA di Klein) e nuovi casi in `test/lora.mjs`.

### Klein

- **Klein veloce parte da 16 passi** (erano 8): è quello che consiglia BFL. Misurato su RTX 4060, 1024×1024, stesso seme: circa 6 s a 8 passi, 10 s a 16; a 16 volti, mani, tessuti e testi escono più puliti. Le qualità «bozza» (8) e «massima» (24) restano.
- Non fatto, da valutare: il prompt upsampler ufficiale di BFL (Qwen3.5 9B, 6,7 GB in W4A8, troppo per 8 GB insieme al resto) e i LoRA di distillazione di terzi per Klein Base a 4-8 passi (di due giorni fa).

### Interfaccia

- **Crea si usa subito.** In cima il prompt, poi Formato e Misura; **Genere, Stile e Altro** (sfondo, foto di riferimento, quante immagini, seed) sono sezioni chiuse che si aprono con un clic. Prima, 12 tessere dei generi spingevano il prompt sotto la piega.
- **Modifica**: tolte le tessere «Estrai» ed «Espandi» che duplicavano le schede sopra la tela; le categorie delle Idee non partono più aperte (un clic apre, un altro chiude).
- **Opzioni corte.** In cima: Motore, Modelli (pronto o da scaricare), Come lavora (scheda video, traduzione, arricchimento del prompt di Klein, anteprima, libera la scheda dopo), Cartelle, Aggiornamenti. Tutto il resto (file dei modelli, download, file inutilizzati, memoria e velocità, account, log) sta in **Avanzate**, chiuso di serie, con testi di una riga.

## 1.2.1 — 2026-10-23

Tre correzioni dell'interfaccia Orbita e una semplificazione: i modelli si scelgono dalla barra in alto e sono due (Qwen e Klein 3), l'Editor box non si blocca più dopo qualche riquadro, e i LoRA hanno due scaffali, Qwen e Flux, che ora funzionano anche con Klein.
Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ interfaccia**, installato e guidato dall'app.

## FLUX 3 Klein (famiglie di modelli)
- `Famiglia = 'qwen' | 'klein' | 'klein-base'` (`shared/tipi.ts`, `FAMIGLIE` = passi per qualità), `Richiesta.famiglia`, `impostazioni.famiglia`.
- **Tre modelli, niente Turbo di Qwen** (1.1, `src/shared/modelli.ts`, la mappa unica, provata da `node test/modelli.mjs`): `qwen-base` (40 passi), `klein` (veloce, 8 passi), `klein-base` (50, CFG 3,5). `impostazioni.modelloScelto` va sempre d'accordo con `famiglia` (`allineaScelta` in `salvaImpostazioni`); le impostazioni vecchie si migrano una volta (`migra`: Turbo GGUF → UC dello stesso quant). La qualità «turbo» salvata vale «alta» (`senzaTurbo`): l'interfaccia non manda più `turbo: true`, i grafi del Turbo restano per i PNG vecchi.
- **Tre modelli, niente Turbo di Qwen** (1.1, `src/shared/modelli.ts`, la mappa unica, provata da `node test/modelli.mjs`): `qwen-base` (40 passi), `klein` (veloce, 16 passi: quelli di BFL, fino alla 1.2.1 erano 8; 16 costa 4 s in più a 1 MP e dà dettagli più puliti), `klein-base` (50, CFG 3,5). `impostazioni.modelloScelto` va sempre d'accordo con `famiglia` (`allineaScelta` in `salvaImpostazioni`); le impostazioni vecchie si migrano una volta (`migra`: Turbo GGUF → UC dello stesso quant). La qualità «turbo» salvata vale «alta» (`senzaTurbo`): l'interfaccia non manda più `turbo: true`, i grafi del Turbo restano per i PNG vecchi.
Il selettore è `componenti/ScegliModello.tsx` (3 schede, "da scaricare", download di modello + `klein-te` + `klein-vae`), la qualità `ScegliQualita.tsx`.
- Catalogo: `klein-int8/bf16`, `klein-base-int8/bf16`, `klein-te` (Qwen3-VL 4B), `klein-vae` dal repo `black-forest-labs/flux-3-image-klein-comfy`
(`repo` + `cerca` regex, risolti a runtime da `installa/risolvi.ts`: **i nomi vengono dai template ufficiali, vanno confermati con i file veri**).
Expand All @@ -63,6 +63,7 @@ interfaccia**, installato e guidato dall'app.
- Non provato con la GPU qui: qualità, INT8 su 6-8 GB, `ReferenceLatent` in catena (alternativa `ReferenceMultiLatent`).
- **1.2.1: i modelli sono tre pillole** (`qwen-base`, `klein`, `klein-base`: «Qwen», «Klein 3», «Klein 3 Base») e si scelgono **solo dalla barra in alto** (`App.tsx` → `SelettoreModello` in `componenti/ScegliModello.tsx`, da qualsiasi pagina; nei pannelli di Crea/Modifica/Box non c'è un secondo selettore). **FLUX.2 Klein 4B è stato tolto** (niente famiglia `klein4b`, niente voci `klein4b-*` nel catalogo): le impostazioni vecchie passano a Qwen in `modelloDaImpostazioni`, i suoi file in `models/` si vedono come «tolto dal catalogo» (`idle.ts`) e `modelli:file` non li scambia per file di Klein 3 (`eFlux2`).
- **LoRA a due scaffali** (1.2.1): `GruppoLora = 'qwen' | 'flux'` (`shared/loraBase.ts`: `gruppoDiFamiglia`, `gruppoDa`, `vaBeneCon`; Klein veloce e Klein Base hanno gli stessi). File in `loras/qwen` e `loras/flux` (`lora.ts` `scaffale()`: i nomi dei tensori decidono, se non dicono niente vale la scheda da cui si carica, SDXL & co. restano fuori in «Altri» e si assegnano a mano con `InfoLora.gruppo`). **`elencoLora()` deve portare `famiglie` e `gruppo`**: nella 1.2.0 non lo faceva e il renderer dava «non compatibile» a ogni LoRA di Klein. Lo store ha **due pile** (`loraPile`, `loraAttive` = quella del modello scelto; `setLoraPila`/`toggleLoraPer` per scaffale); `richiestaBase()` manda la pila del modello, `loraPer`/`fileLoraCamera` (azioni rapide, Camera) solo LoRA che vanno col modello, e `soloLoraDelModello` in `lavori.ts` toglie quelli dell'altro scaffale. Prove: `test/lora-scaffali.mjs`, `test/box-lora-ui.mjs`.
**Formato dei LoRA di Klein (1.3):** i LoRA FLUX 3 esistono in due formati. AI Toolkit usa i nomi di ComfyUI (`diffusion_model.content_mode_blocks.image.N.q_proj`, `single_blocks.N.mlp_in`…) e il motore li carica; Diffusers/SimpleTuner usano `transformer.mode_blocks.image.N.attn.to_q`, `single_transformer_blocks.N.ff.linear_in`, e ComfyUI **non li conosce** (tutte le chiavi «not loaded», 0 patch: il LoRA non fa niente). `shared/loraBase.ts` li riconosce (`formato: 'diffusers'`), `shared/loraConverti.ts` ha la mappa dei nomi e `main/loraConverti.ts` riscrive solo l'intestazione del .safetensors (l'originale resta come `.diffusers.bak`; se alpha ≠ rango aggiunge i tensori `.alpha`). Si converte in `importaLora`, `scaricaLora` e in `elencoLora` per i file messi a mano; la forza di partenza di un LoRA di Klein è 0,75 (BFL). Prova: `node test/lora-converti.mjs`. Verificato sul motore vero con il LoRA delle angolazioni di akhaliq: prima 0 patch, dopo 172.

## I grafi (src/main/motore/grafi.ts)
- Pipeline ufficiale: `UnetLoaderGGUF` → LoRA (`LoraLoaderModelOnly`) → `QwenImage21Cache`; `CLIPLoader` tipo `qwen_image`
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Qwen-Image capisce meglio. I testi fra virgolette, quelli da scrivere nell'immag
formato e risoluzione giusti, chiede due o tre cose in italiano, ha i suoi stili e scrive il prompt da solo.
- **Risoluzioni da 256p al massimo**: 256p per un'idea in pochi secondi, poi 480p, 720p, 1K, 1080p, 1440p (per i
testi piccoli) e MAX, i 4,2 MP del 2K nativo. Formati da 1:1 a 21:9, compresi 4:5 e 9:16.
- **Il modello lo scegli in alto**: **Klein veloce** (8 passi) per esplorare tante idee, **Klein Base** o **Qwen-Image 2.1** quando conta la qualità.
- **Il modello lo scegli in alto**: **Klein veloce** (16 passi, quelli indicati da Black Forest Labs) per esplorare tante idee, **Klein Base** o **Qwen-Image 2.1** quando conta la qualità.
Un risultato di Klein veloce lo rifai con **Rifai in qualità piena** (Klein Base, stesso prompt e stesso seme) o **Rifai con Qwen**.
- **Migliora** riscrive il prompt in un paragrafo ricco; **Da foto** trasforma una foto in prompt; **Sorprendimi** ne inventa uno.
- **Altro** (chiuso di serie) tiene seme, megapixel, misura e sfondo, per non riempire la pagina.
Expand Down Expand Up @@ -119,7 +119,7 @@ Modifica → Estrai e nelle ricette. Diventa un manichino grigio che il modello

## Tre modelli: Qwen e FLUX 3 Klein

Nella barra in alto scegli **chi disegna**: **Qwen base** (Qwen-Image 2.1, 40 passi, con i LoRA), **Klein veloce** (8 passi) o **Klein Base** (la versione normale, 50 passi). I file di Klein si scaricano con un clic dal repository ufficiale di Black Forest Labs.
Nella barra in alto scegli **chi disegna**: **Qwen base** (Qwen-Image 2.1, 40 passi, con i LoRA), **Klein veloce** (16 passi) o **Klein Base** (la versione normale, 50 passi). I file di Klein si scaricano con un clic dal repository ufficiale di Black Forest Labs.
Il Turbo di Qwen non c'è più: Klein veloce è il modo rapido.

Anche la scrittura dei testi (Migliora prompt, Foto → prompt, Idee, Estrai, Unisci foto, Come l'esempio) usa il **Qwen3-VL 4B** di Klein, con Klein e con Qwen: scrive a 46 token al secondo sulla RTX 4060,
Expand Down Expand Up @@ -172,7 +172,7 @@ se la scheda è da 6 GB, il profilo **6 GB** in Opzioni. L'app ti avvisa da sola
prima di "rimuovi" e accordano i colori dopo il VAE, così l'incollaggio non si vede.
- Anteprime dal vivo con **TAEQI 2.1** di madebyollin; traduzione dei prompt con **Opus-MT** (un modello di sola
traduzione, sul processore, ~300 MB, scaricato da solo); foto convertite con WIC di Windows, come in [DaP Convertitore](https://github.com/cammo22/DaP-Convertitore).
- Su una RTX 4060 da 8 GB, a 1 MP: Qwen base circa 3 secondi per passo (40 passi in poco meno di 3 minuti a freddo), **Klein veloce 8 passi in circa 7-16 secondi**,
- Su una RTX 4060 da 8 GB, a 1 MP: Qwen base circa 3 secondi per passo (40 passi in poco meno di 3 minuti a freddo), **Klein veloce in circa 10 secondi a 16 passi (6 a 8 passi)**,
Klein Base 50 passi in circa un minuto. Le misure complete sono in [docs/misure-1.1.0.md](docs/misure-1.1.0.md).

## Sviluppare
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "daprod-image",
"productName": "DaProd Image",
"version": "1.2.1",
"version": "1.3.0",
"description": "DaProd Image: crea e modifica foto con Qwen-Image-2.1 sul tuo PC (CUDA, da 6 GB di VRAM). Turbo a 8 passi, preset per poster e infografiche, zone con anteprima dal vivo, LoRA con un clic, galleria.",
"author": "DaProd",
"license": "MIT",
Expand All @@ -14,7 +14,7 @@
"build": "electron-vite build",
"start": "electron-vite preview",
"typecheck": "tsc --noEmit -p tsconfig.node.json && tsc --noEmit -p tsconfig.web.json",
"test": "node test/modelli.mjs && node test/catalogo.mjs && node test/spazio.mjs && node test/lora.mjs && node test/lora-scaffali.mjs && node test/regioni.mjs && node test/adatta.mjs && node test/grafi_v2.mjs && node test/confronta_grafi.mjs && node test/asset.mjs",
"test": "node test/modelli.mjs && node test/catalogo.mjs && node test/spazio.mjs && node test/lora.mjs && node test/lora-scaffali.mjs && node test/lora-converti.mjs && node test/regioni.mjs && node test/adatta.mjs && node test/grafi_v2.mjs && node test/confronta_grafi.mjs && node test/asset.mjs",
"prendi-uv": "node scripts/prendi-uv.mjs",
"dist": "npm run prendi-uv && npm run build && electron-builder --win --x64",
"prova": "node test/prove.mjs",
Expand Down
32 changes: 26 additions & 6 deletions src/main/lora.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { copyFileSync, existsSync, openSync, readFileSync, readSync, closeSync,
import { basename, extname, join, relative, resolve, sep } from 'node:path'
import type { Famiglia, GruppoLora, InfoLora } from '@shared/tipi'
import { analizzaChiavi, gruppoDa } from '@shared/loraBase'
import { convertiSulPosto } from './loraConverti'
import { DATI, assicura, scriviAtomico } from './percorsi'
import { impostazioni } from './impostazioni'
import { scarica } from './installa/scarica'
Expand Down Expand Up @@ -56,14 +57,14 @@ function intestazione(percorso: string): Record<string, unknown> | null {
}

/** Qwen-Image 2.1 è a flusso singolo (solo img_mlp); Qwen-Image 1.x ha txt_mlp/add_q_proj; SD/SDXL/Flux hanno altri nomi */
function analizza(percorso: string): { compatibile: InfoLora['compatibile']; base?: string; famiglie?: Famiglia[]; parole?: string; nome?: string } {
function analizza(percorso: string): { compatibile: InfoLora['compatibile']; base?: string; famiglie?: Famiglia[]; formato?: 'diffusers'; parole?: string; nome?: string } {
const h = intestazione(percorso)
if (!h) return { compatibile: '?' }
const chiavi = Object.keys(h).filter((k) => k !== '__metadata__')
const meta = (h.__metadata__ || {}) as Record<string, string>
const forme: Record<string, number[] | undefined> = {}
for (const k of chiavi) forme[k] = (h[k] as { shape?: number[] } | undefined)?.shape
const { compatibile, famiglie, base: baseRiconosciuta } = analizzaChiavi(chiavi, forme)
const { compatibile, famiglie, formato, base: baseRiconosciuta } = analizzaChiavi(chiavi, forme)
let base = baseRiconosciuta
const archi = meta['modelspec.architecture'] || meta.ss_base_model_version || ''
if (archi) base = base ? `${base} (${archi})` : archi
Expand All @@ -82,7 +83,7 @@ function analizza(percorso: string): { compatibile: InfoLora['compatibile']; bas
/* niente */
}
}
return { compatibile, base, famiglie, parole, nome: meta['modelspec.title'] || meta.ss_output_name }
return { compatibile, base, famiglie, formato, parole, nome: meta['modelspec.title'] || meta.ss_output_name }
}

export function elencoLora(): InfoLora[] {
Expand Down Expand Up @@ -110,9 +111,11 @@ export function elencoLora(): InfoLora[] {
let nota = n[file]
// (le note di prima della 1.2.1 possono dire 'klein4b', un modello che l'app non ha più: si rianalizzano)
if (!nota || nota.compatibile === undefined || nota.famiglie === undefined || nota.famiglie.some((f) => !FAMIGLIE_VALIDE.includes(f))) {
// un LoRA di Klein alla Diffusers messo a mano nella cartella: si porta ai nomi di ComfyUI prima di leggerlo
portaInComfy(p)
const a = analizza(p)
const delCatalogo = CATALOGO.find((x) => x.tipo === 'lora' && x.file === v)
nota = { nome: delCatalogo?.nome || a.nome || basename(v, extname(v)), parole: a.parole || '', forza: 1, aggiunta: st.mtimeMs, ...nota, compatibile: a.compatibile, base: a.base, famiglie: a.famiglie }
nota = { nome: delCatalogo?.nome || a.nome || basename(v, extname(v)), parole: a.parole || '', forza: a.famiglie?.includes('klein') ? 0.75 : 1, aggiunta: st.mtimeMs, ...nota, compatibile: a.compatibile, base: a.base, famiglie: a.famiglie }
n[file] = nota
cambiate = true
}
Expand Down Expand Up @@ -154,6 +157,19 @@ function scaffale(percorso: string, scelto?: GruppoLora): { cartella: string; gr
return { cartella: gruppo ? join(cartellaLora(), gruppo) : cartellaLora(), gruppo, riconosciuto: !!riconosciuto }
}

/** un LoRA di Klein nel formato Diffusers/SimpleTuner (`transformer.mode_blocks…`) ComfyUI non lo legge: lo si porta ai suoi nomi sul posto
* (l'originale resta accanto come `.diffusers.bak`). Ritorna true se l'ha convertito; se non ce la fa lascia il file com'è e dice perché. */
function portaInComfy(percorso: string): boolean {
try {
if (analizza(percorso).formato !== 'diffusers') return false
convertiSulPosto(percorso)
return true
} catch (e) {
console.error('conversione del LoRA non riuscita:', percorso, (e as Error).message)
return false
}
}

/** `gruppo`: lo scaffale della scheda da cui si carica (Qwen o Flux). Se i nomi dei tensori dicono un altro modello, vince quello. */
export function importaLora(percorsi: string[], gruppo?: GruppoLora): string[] {
const c = cartellaLora()
Expand All @@ -164,7 +180,10 @@ export function importaLora(percorsi: string[], gruppo?: GruppoLora): string[] {
const s = scaffale(p, gruppo)
assicura(s.cartella)
const dest = join(s.cartella, basename(p))
if (!existsSync(dest)) copyFileSync(p, dest)
if (!existsSync(dest)) {
copyFileSync(p, dest)
portaInComfy(dest)
}
const file = relative(c, dest)
aggiornaLora(file, { aggiunta: Date.now(), ...(s.gruppo && !s.riconosciuto ? { gruppo: s.gruppo } : {}) })
fatti.push(file)
Expand Down Expand Up @@ -253,8 +272,9 @@ export async function scaricaLora(link: string, avanz: (r: number, t: number, v:
assicura(cartellaLora())
const dest = join(cartellaLora(), file)
await scarica({ url: d.url, destinazione: dest, intestazioni: d.intestazioni, segnale, avanzamento: avanz })
// arrivato, si guarda di che modello è e lo si mette nel suo scaffale
// arrivato, si guarda di che modello è e lo si mette nel suo scaffale (e se è un LoRA di Klein alla Diffusers lo si porta ai nomi di ComfyUI)
const s = scaffale(dest, gruppo)
portaInComfy(dest)
let finale = dest
if (s.gruppo) {
assicura(s.cartella)
Expand Down
Loading
Loading