From 608856bad340a1328774f0bc023ead993199a5e6 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 15:14:14 +0000 Subject: [PATCH] 0.10.0: Estrai persone e oggetti, casella prompt (tasto destro, cestino, si allarga), testa dell'omino con manichino pieno, risoluzioni sempre visibili Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01K9poJitXQ1x3ZDHxioN2Cf --- .claude/skills/prova-interfaccia/SKILL.md | 25 ++ .claude/skills/rilascio/SKILL.md | 21 ++ CHANGELOG.md | 24 ++ CLAUDE.md | 19 +- README.md | 11 +- package-lock.json | 4 +- package.json | 2 +- src/main/index.ts | 19 +- src/renderer/src/componenti/Casella.tsx | 58 ++++ src/renderer/src/componenti/EditorRicetta.tsx | 3 +- src/renderer/src/componenti/Estrai.tsx | 301 +++++++++++++++++ src/renderer/src/componenti/Misura.tsx | 52 ++- src/renderer/src/componenti/Omino.tsx | 53 ++- src/renderer/src/componenti/Personaggio.tsx | 311 ------------------ src/renderer/src/componenti/Preset.tsx | 3 +- src/renderer/src/estrai.ts | 256 ++++++++++++++ src/renderer/src/omino.ts | 234 +++++++++---- src/renderer/src/ominoImmagine.ts | 6 +- src/renderer/src/pagine/Crea.tsx | 15 +- src/renderer/src/pagine/Laboratorio.tsx | 20 +- src/renderer/src/pagine/Modifica.tsx | 95 +++--- src/renderer/src/personaggio.ts | 230 ------------- src/renderer/src/pulisciOmino.ts | 16 +- src/renderer/src/ricetteBase.ts | 2 +- src/renderer/src/riquadri.ts | 59 ++-- src/renderer/src/stato.ts | 4 +- src/renderer/src/stile.css | 25 ++ src/renderer/src/workflow.ts | 6 +- test/finto.js | 66 ++++ test/interfaccia.mjs | 43 +++ 30 files changed, 1248 insertions(+), 735 deletions(-) create mode 100644 .claude/skills/prova-interfaccia/SKILL.md create mode 100644 .claude/skills/rilascio/SKILL.md create mode 100644 src/renderer/src/componenti/Casella.tsx create mode 100644 src/renderer/src/componenti/Estrai.tsx delete mode 100644 src/renderer/src/componenti/Personaggio.tsx create mode 100644 src/renderer/src/estrai.ts delete mode 100644 src/renderer/src/personaggio.ts create mode 100644 test/finto.js create mode 100644 test/interfaccia.mjs diff --git a/.claude/skills/prova-interfaccia/SKILL.md b/.claude/skills/prova-interfaccia/SKILL.md new file mode 100644 index 0000000..1da3634 --- /dev/null +++ b/.claude/skills/prova-interfaccia/SKILL.md @@ -0,0 +1,25 @@ +--- +name: prova-interfaccia +description: Guarda l'interfaccia di DaProd Image senza Electron né motore (Chromium con un finto window.daprod), fa screenshot e stampa le richieste che l'app manderebbe al motore. Da usare dopo ogni modifica a pagine e componenti. +--- + +# Provare l'interfaccia senza motore + +Nel container non ci sono la GPU né ComfyUI, ma l'interfaccia (`src/renderer`) gira in Chromium con un **finto `window.daprod`** +(`test/finto.js`: foto finte, un lavoro che "avanza", `testo.scrivi` che risponde con riquadri di prova). + +```bash +npm run build # compila renderer, main e preload in out/ +python3 -m http.server 8765 --directory out/renderer & # serve la build (se non sta già girando) +node test/interfaccia.mjs # foto in test/.out/ + prompt e dimensioni delle richieste +``` + +- `test/interfaccia.mjs` apre Modifica → Estrai, sceglie un oggetto e una persona (con l'omino) e stampa `larghezza × altezza` e il + `prompt` dell'ultima richiesta (`window.__ultima` è la richiesta accodata, `window.__emetti('lavoro', …)` fa finire un lavoro). +- Per provare un'altra pagina copia il file e cambia i passaggi: i selettori sono quelli dell'interfaccia (`.rotaia button:has-text("Crea")`, + `.segmenti button:has-text("…")`, `.colonna.destra textarea`). +- Per una funzione pura (es. `src/renderer/src/omino.ts`, `riquadri.ts`) basta `npx esbuild file.ts --bundle --format=iife` e una + pagina HTML col canvas; si guarda lo screenshot con Playwright (Chromium è in `PLAYWRIGHT_BROWSERS_PATH`, non scaricare niente). +- Le prove con l'app vera e il motore (Windows) sono `node test/setup.mjs` e `node test/foto.mjs`: non girano qui. + +Non si può provare qui: la qualità delle immagini che il modello disegna. Di quella dillo chiaro nel messaggio finale. diff --git a/.claude/skills/rilascio/SKILL.md b/.claude/skills/rilascio/SKILL.md new file mode 100644 index 0000000..916270b --- /dev/null +++ b/.claude/skills/rilascio/SKILL.md @@ -0,0 +1,21 @@ +--- +name: rilascio +description: Prepara una nuova versione di DaProd Image (numero di versione, CHANGELOG, README, CLAUDE.md, typecheck, build, PR). Da usare quando si finisce un giro di modifiche e va consegnato come vX.Y.Z. +--- + +# Rilascio di DaProd Image + +Una versione = `version` in `package.json` (e `package-lock.json`, due punti in cima) + una voce in `CHANGELOG.md`. Unita su `main`, +la CI (`.github/workflows/app.yml`) compila l'installer e pubblica da sola la release. + +1. **Scegli il numero**: 0.X.0 per una novità visibile, 0.X.Y per correzioni. +2. **Alza la versione** in `package.json` e nelle prime righe di `package-lock.json` (`"version"` due volte). +3. **CHANGELOG.md**: nuova sezione in cima, `## X.Y.Z — AAAA-MM-GG`, una frase di riassunto e poi elenco in grassetto di cosa cambia. + Si scrive **in italiano parlato**, per l'utente (non per gli sviluppatori). +4. **README.md** e **CLAUDE.md**: aggiorna le parti toccate (CLAUDE.md è la mappa del codice: file nuovi, nomi cambiati, regole). +5. Se è cambiato il **motore** (ComfyUI, pacchetti, nodi in `engine/`): alza `motore` in `engine/versioni.json`. Altrimenti no. +6. **Prima di consegnare**: `npm run typecheck` e `npm run build` devono passare. Se hai toccato l'interfaccia, guarda anche le foto di + `node test/interfaccia.mjs` (vedi la skill `prova-interfaccia`). +7. Commit sul ramo di lavoro, push, PR in bozza. Dopo il merge la release esce da sola. + +Commenti, messaggi dell'interfaccia e testi per l'utente: sempre in italiano parlato. Nomi tecnici in inglese dove serve. diff --git a/CHANGELOG.md b/CHANGELOG.md index 69d638b..a897de8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## 0.10.0 — 2026-10-15 + +Estrai persone **e oggetti**, una casella di testo come si deve, la testa dell'omino e le risoluzioni sempre in vista. + +- **Estrai** (era «Personaggio»): il modello guarda **tutta la foto** e trova da solo persone e oggetti; clicchi quello che vuoi (si + illumina, il resto si abbassa; cliccando ancora nello stesso punto passi a quello più grande che lo contiene) o lo riquadri tu + trascinando. Niente più camera: la schermata è molto più pulita, con tre passi: **cosa estrarre**, **come lo vuoi**, **dimensioni e qualità**. + - Se è una **persona**: inquadratura (figura intera, fino alla vita, solo il volto) e posa (T-pose, A-pose, in piedi, come nella foto, disegnata con l'omino). + - Se è un **oggetto**: vista (come nella foto, davanti, ¾, dall'alto, di lato); se una parte è coperta o tagliata il modello la completa. + - Sfondo (bianco, grigio, trasparente, come la foto), ombra sul pavimento, note, e il pulsante «È una persona / È un oggetto» se il modello si sbaglia. +- **La casella dei prompt**: **tasto destro** con Incolla, Copia, Taglia, Annulla e Seleziona tutto in tutte le caselle di testo; il + **cestino** cancella tutto il testo in un colpo; la casella **si allarga da sola** col testo, senza barra di scorrimento (Crea, + Modifica, Laboratorio, editor delle ricette, preset, piani del modello). +- **L'omino**: la **testa** si regola (gira a sinistra/destra fino al profilo, su/giù, inclinata, con modi pronti) e va nel prompt a parole. + E la guida di posa non è più uno scheletro a linee (che il modello a volte lasciava nella foto): è un **manichino grigio pieno**, come + quello di legno degli artisti, con mani e dita; il prompt dice che è solo un riferimento da sostituire con la persona vera. Il + controllo automatico cerca anche tracce del manichino (parti grigie lisce) e, se le trova, le toglie. Un pulsante mostra il + manichino che vede il modello. +- **Le risoluzioni si vedono sempre**: in Modifica, Fusion, Esempio, Laboratorio ed Estrai c'è la riga «La foto esce a **1024 × 1376 px**», + le scelte dei megapixel mostrano la misura vera in pixel sotto ogni voce (e non sono più nascoste in «Avanzate»), il pulsante + principale dice a che misura esce, Crea mostra larghezza × altezza accanto alla risoluzione. In «Solo una zona» dice che la foto resta com'è. +- **Skill di progetto** per chi sviluppa (`.claude/skills/`): `rilascio` (come si prepara una versione) e `prova-interfaccia` (come + vedere l'interfaccia senza motore, con `node test/interfaccia.mjs`). + ## 0.9.1 — 2026-10-15 Lo scheletro dell'omino non resta più nella foto. diff --git a/CLAUDE.md b/CLAUDE.md index dda8e9d..d18265f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,8 @@ interfaccia**, installato e guidato dall'app. `engine/versioni.json`: al primo avvio dopo l'aggiornamento l'app vede che il motore non è alla versione giusta e lo aggiorna da sola (pagina di setup in modo "aggiorna", `mantieniModello`: niente scelta del modello, niente download). - **Prima di consegnare**: `npm run typecheck` e `npm run build`. Le prove con l'app vera: `node test/setup.mjs` - (foto della pagina di installazione, `--installa` la fa davvero) e `node test/foto.mjs` (foto delle pagine in `test/.out/`). + (foto della pagina di installazione, `--installa` la fa davvero) e `node test/foto.mjs` (foto delle pagine in `test/.out/`). Senza Electron né motore (qui nel container): `node test/interfaccia.mjs` (Chromium + finto `window.daprod` in `test/finto.js`), vedi `.claude/skills/prova-interfaccia`. + Per preparare una versione c'è la skill `.claude/skills/rilascio`. ## Il motore (src/main/installa, src/main/motore) - **Versioni fissate** in `engine/versioni.json`: ComfyUI a un commit preciso (zip da codeload, estratto col `tar.exe` @@ -78,7 +79,7 @@ interfaccia**, installato e guidato dall'app. - **Descrivi** (Migliora prompt, Foto → prompt): `TextGenerate` con lo stesso Qwen3-VL già caricato, niente modelli in più. ## Interfaccia (src/renderer) -- **Modifica** ha 6 modi: tutta la foto, zona, espandi, **Fusion**, **Esempio** e **Personaggio** (vedi sotto) (`componenti/Fusion.tsx`, richieste in `fusion.ts`: nessuna base, `m.riferimenti` sono le foto; +- **Modifica** ha 6 modi: tutta la foto, zona, espandi, **Fusion**, **Esempio** e **Estrai** (vedi sotto) (`componenti/Fusion.tsx`, richieste in `fusion.ts`: nessuna base, `m.riferimenti` sono le foto; Fusion = `modalita: 'crea'` con `images.image_N`, Esempio = `modalita: 'modifica'` con l'esempio come `image_1`; i risultati stanno nel componente, non diventano versioni; il confronto prima/dopo usa sempre `versioni[0]`); `★ base` scambia base e riferimento. **Dimensioni** (`componenti/Misura.tsx`, `Richiesta.uscita`): auto / come l'originale / misura @@ -94,10 +95,11 @@ interfaccia**, installato e guidato dall'app. qualunque pagina la mandi (il `conParoleLora` del renderer fa la stessa cosa, non raddoppia). **Esempio** (`fusion.ts`): `pianificaEsempio` fa un foglio con l'esempio (image 1) e i riferimenti numerati e lo dà al Qwen3-VL (`descrivi`, `SISTEMA_PIANO`), che scrive l'istruzione di modifica decidendo da solo cosa sostituire con cosa (niente regole per persone o altro); il piano sta in `pianoEsempio` (con `chiaveEsempio`: foto + nota), si corregge nell'interfaccia, e `richiestaEsempio` lo manda come prompt con l'esempio come `image_1`. **Galleria**: tasto destro (`MenuContestuale`), `galleria:elimina` con `definitivo`, `galleria:copia` con più id = file negli appunti con PowerShell `Set-Clipboard`. **Laboratorio**: un `Ingresso` di tipo `camera` mostra il comando Camera (valore JSON, `leggiCamera`) e le ricette aggiungono il passo `VISTA_TUA`. +- **Casella** (`componenti/Casella.tsx`): la textarea dei prompt (cresce col testo, cestino); il tasto destro nei campi di testo è nel main (`index.ts`, evento `context-menu`). + **Risoluzioni**: `Misura.tsx` ha `dimMp()` (le stesse misure del motore), `ScegliMp` (megapixel con i pixel sotto) e `Uscita` (la riga «La foto esce a…»). - **Omino** (`omino.ts` puro + `componenti/Omino.tsx`, `ominoImmagine.ts`): scheletro di 15 giunti in un riquadro 280×300 (`Dx`/`Sx` sono della persona: la destra sta a sinistra dello schermo); `trascina()` ruota l'osso attorno al genitore (lunghezze fisse, si porta dietro i discendenti; il bacino sposta tutto); mani = 5 dita con piegamento 0–1 + divaricazione + polso - (`geometriaMano`); `disegnaOpenPose()` fa la mappa di posa (colori OpenPose, dita ad arcobaleno) e `descriviPosa()` la posa in inglese. In **Personaggio** posa `omino` (crop + scheletro come - ``); nel **Laboratorio** un `Ingresso` di tipo `posa` (JSON) e il passo `POSA_TUA` con `PassoWf.posa`: `richiestaPasso` (async) mette lo scheletro ultima immagine e sostituisce `{posa}` con il suo ``. + (`geometriaMano`); `Posa.viso` (gira/su/inclina) è la testa; `disegnaManichino()` fa la guida di posa (manichino grigio pieno su fondo chiaro: niente linee da copiare) e `descriviPosa()` la posa in inglese. In **Estrai** posa `omino` (manichino come ``, ritaglio come ``); nel **Laboratorio** un `Ingresso` di tipo `posa` (JSON) e il passo `POSA_TUA` con `PassoWf.posa`: `richiestaPasso` (async) mette lo scheletro ultima immagine e sostituisce `{posa}` con il suo ``. `pulisciOmino.ts`: `controllaScheletro(lavoroId…)` — a lavoro finito il Qwen3-VL risponde YES/NO se resta lo scheletro, e se sì parte una modifica (`PROMPT_PULIZIA`) che lo toglie; `SENZA_SCHELETRO` chiude i prompt che usano l'omino. **Idee** (`regista.ts`): le azioni rapide di Modifica sono idee a toggle (`idee`); `pianificaIdee` (Qwen3-VL, `SISTEMA_IDEE`, foto + eventuale foglio con i riferimenti) le mescola in un'istruzione sola (`fantasia`: temperatura), piano in `pianoIdee` (con chiave). **Esempio**: il piano è JSON `{instruction, crops}` (attributi a parole + riquadri 0–1000 sul foglio, `leggiPiano`, `riquadroNellaFoto`, @@ -128,10 +130,11 @@ interfaccia**, installato e guidato dall'app. il vecchio `testo` del titolo si trasforma in elemento in `vecchioInNuovo()`. `costruisci.ts` raggruppa (densità aerea/normale/fitta) e sceglie un modello fra i primi senza ripetere; `disegna.ts` disegna su canvas (schermo e stampa: cornice, ombra, z, elementi); `esporta.ts` JPEG/PDF (`pdf.ts`, senza librerie). Le foto grandi non restano in cache. -- **Personaggio** (modo di Modifica, `componenti/Personaggio.tsx` + `personaggio.ts` + `riquadri.ts`): `trovaPersone` chiede al Qwen3-VL (`descrivi` con la foto) - un JSON `bbox_2d` 0–1000 (`leggiPersone`, tollera fence e JSON troncato); si può riquadrare a mano. `richiestaEstrai` ritaglia la persona (+12%) in un temp - e fa una **Crea** con quella come `` (posa T/A/in piedi, sfondo, proporzioni); `richiestaVista` fa una **Modifica** del risultato con - `componiCamera` (come il comando Camera, LoRA camera se c'è). I risultati stanno in `usaPersonaggio` (non nelle versioni). +- **Estrai** (modo di Modifica, `componenti/Estrai.tsx` + `estrai.ts` + `riquadri.ts`): a foto caricata `trovaElementi` chiede al Qwen3-VL (`descrivi` con la foto intera) + un JSON `{bbox_2d 0–1000, label, kind: person|object}` (`leggiElementi`, tollera fence e JSON troncato, toglie i riquadri doppi); si sceglie con un clic (più clic = il riquadro più grande + che contiene il punto) o riquadrando a mano; il tipo (persona/oggetto) si corregge. `richiestaEstrai` ritaglia la cosa (+12%) in un temp e fa una **Crea** con quella come ``: + **persona** = inquadratura (intera/vita/volto) + posa (T/A/in piedi/come/omino: manichino ``, ritaglio ``); **oggetto** = vista (come/davanti/¾/alto/lato), completa le parti coperte. + Proporzioni (`proporzioniEstrai`, auto = dal riquadro per gli oggetti) e `dimensioniEstrai` sono le stesse cifre che l'interfaccia mostra. I risultati stanno in `usaEstrai` (non nelle versioni). - Le impostazioni di ogni immagine stanno **dentro il PNG** (chunk iTXt `daprod`, `src/main/png.ts`): la galleria si ricostruisce dai file e un PNG trascinato in Galleria torna con prompt, seed e LoRA. - LoRA: dall'intestazione safetensors si capisce se sono per Qwen 2.1 (`transformer_blocks.N.attn.to_q`/`img_mlp`) diff --git a/README.md b/README.md index ed686f1..f191ca6 100644 --- a/README.md +++ b/README.md @@ -97,13 +97,14 @@ a 300 dpi con l'abbondanza. ## L'omino -Un manichino con le ossa che muovi come vuoi (anche **mani e dita**, una per una) per dare una posa al personaggio: in Modifica → -Personaggio e nelle ricette del Laboratorio. Diventa una mappa di posa che il modello segue. +Un manichino con le ossa che muovi come vuoi (anche **mani e dita**, una per una, e la **testa**) per dare una posa al personaggio: in +Modifica → Estrai e nelle ricette del Laboratorio. Diventa un manichino grigio che il modello usa solo come guida di posa. -## Personaggio +## Estrai -In Modifica: una foto con più persone, clic su quella che vuoi (si illumina), **estraila a figura intera** (T-pose, A-pose…) e **muovi la -camera** attorno a lei: una vista o il giro completo. +In Modifica: dai una foto e il modello trova **persone e oggetti**; clicchi quello che vuoi (si illumina) e lo ottieni da solo su sfondo +pulito: una persona a figura intera (T-pose, A-pose, disegnata con l'omino), fino alla vita o il volto; un oggetto intero, da davanti, +di lato o dall'alto. Vedi sempre a che risoluzione esce. ## Laboratorio diff --git a/package-lock.json b/package-lock.json index 73fdeb5..a3b7bf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "daprod-image", - "version": "0.9.1", + "version": "0.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "daprod-image", - "version": "0.9.1", + "version": "0.10.0", "license": "MIT", "dependencies": { "electron-updater": "^6.8.9" diff --git a/package.json b/package.json index 12a8a59..da90022 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "daprod-image", "productName": "DaProd Image", - "version": "0.9.1", + "version": "0.10.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", diff --git a/src/main/index.ts b/src/main/index.ts index e94bbe9..3f168e3 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,6 +1,6 @@ // DaProd Image — processo principale: finestra, protocollo daprod:// per le immagini, IPC verso l'interfaccia. import { execFile } from 'node:child_process' -import { app, BrowserWindow, clipboard, ClipboardItem, dialog, ipcMain, nativeImage, net, protocol, shell, type IpcMainInvokeEvent } from 'electron' +import { app, BrowserWindow, clipboard, ClipboardItem, dialog, ipcMain, Menu, nativeImage, net, protocol, shell, type IpcMainInvokeEvent } from 'electron' import { copyFileSync, existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs' import { basename, extname, join, normalize } from 'node:path' import { pathToFileURL } from 'node:url' @@ -60,6 +60,23 @@ function creaFinestra(): void { } }) finestra.once('ready-to-show', () => finestra?.show()) + // tasto destro nei campi di testo (prompt, note, nomi): Electron non lo fa da solo + finestra.webContents.on('context-menu', (_e, p) => { + if (!p.isEditable && !p.selectionText) return + const voci: Electron.MenuItemConstructorOptions[] = p.isEditable + ? [ + { label: 'Annulla', role: 'undo', enabled: p.editFlags.canUndo }, + { label: 'Ripeti', role: 'redo', enabled: p.editFlags.canRedo }, + { type: 'separator' }, + { label: 'Taglia', role: 'cut', enabled: p.editFlags.canCut }, + { label: 'Copia', role: 'copy', enabled: p.editFlags.canCopy }, + { label: 'Incolla', role: 'paste', enabled: p.editFlags.canPaste }, + { type: 'separator' }, + { label: 'Seleziona tutto', role: 'selectAll', enabled: p.editFlags.canSelectAll } + ] + : [{ label: 'Copia', role: 'copy' }] + Menu.buildFromTemplate(voci).popup() + }) finestra.webContents.setWindowOpenHandler(({ url }) => { if (/^https?:\/\//.test(url)) void shell.openExternal(url) return { action: 'deny' } diff --git a/src/renderer/src/componenti/Casella.tsx b/src/renderer/src/componenti/Casella.tsx new file mode 100644 index 0000000..a28a44e --- /dev/null +++ b/src/renderer/src/componenti/Casella.tsx @@ -0,0 +1,58 @@ +// La casella di testo dei prompt: si allarga da sola quanto il testo (niente barra di scorrimento) e ha il cestino per cancellare tutto. +import { useLayoutEffect, useRef, type JSX, type Ref } from 'react' +import { I } from './Icone' + +export function Casella(p: { + valore: string + cambia: (v: string) => void + placeholder?: string + /** altezza minima in px */ + minimo?: number + disabled?: boolean + /** senza cestino (per i testi corti) */ + senzaCestino?: boolean + inputRef?: Ref +}): JSX.Element { + const el = useRef(null) + const minimo = p.minimo ?? 96 + + const adatta = (): void => { + const t = el.current + if (!t) return + t.style.height = 'auto' + t.style.height = `${Math.max(minimo, t.scrollHeight + 2)}px` + } + // a ogni cambio di testo, e quando la colonna si allarga o si stringe (il testo va a capo in un altro punto) + useLayoutEffect(adatta, [p.valore, minimo]) + useLayoutEffect(() => { + const t = el.current + if (!t || typeof ResizeObserver === 'undefined') return + let larg = t.clientWidth + const o = new ResizeObserver(() => { + if (t.clientWidth !== larg) { + larg = t.clientWidth + adatta() + } + }) + o.observe(t) + return () => o.disconnect() + }, []) + + const aggancia = (t: HTMLTextAreaElement | null): void => { + el.current = t + const r = p.inputRef + if (typeof r === 'function') r(t) + else if (r) (r as { current: HTMLTextAreaElement | null }).current = t + } + + return ( +
+