PySide6 desktop client for Local Dream: generate and edit images on any device running Local Dream with its web UI, through the Local Dream Bridge. Interface in Italian.
  • Python 99.2%
  • Batchfile 0.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Foxy 45bd19d893
Some checks failed
verify / verify (push) Has been cancelled
Scheda Prestazioni: tempi misurati e costo del cambio modello
Le misure si registrano da sole a ogni lavoro completato, senza pulsanti da
premere e senza sequenze di prova che occupano il dispositivo.

- lds/bench.py: archivio delle misure (senza Qt), statistiche per modello e per
  combinazione di parametri. Mediana invece di media, dispersione, ms per passo.
- Stima del costo di caricamento: differenza fra la mediana a freddo e quella a
  caldo a parita' di modello, risoluzione e passi. Si mostra solo con entrambe
  le misure, perche' altrimenti sarebbe un numero inventato.
- "A freddo" significa che il bridge ha dovuto caricare un modello diverso da
  quello in servizio: lo dice /api/status, confrontato al momento dell'invio.
- JobManager ora emette job_finished e job_failed, che erano dichiarati e non
  emessi da nessuno.
- Upscale: se il bridge non riporta la durata la si misura in locale, altrimenti
  quel tipo di lavoro non entrerebbe mai nelle statistiche.
- Nuova scheda Prestazioni (Ctrl+P) con tabelle per modello, per parametri e
  ultime misure, esportazione CSV e azzeramento. Le misure stanno accanto alle
  preferenze, non nella cartella delle immagini.
- README e Guida: dispositivo di prova dichiarato con i dati precisi (ASUS ROG
  Phone 7, Snapdragon 8 Gen 2, Adreno 740, 16 GB, SD 1.5 su NPU).
- smoke_test: la scheda viene visitata e, con una modifica reale, si verifica che
  la misura finisca nell'archivio. Il test usa un archivio separato, per non
  sporcare le misure di chi lo esegue.
2026-09-19 22:45:15 +02:00
.gitea Local Dream Studio 1.1 2026-09-19 20:09:17 +02:00
assets Local Dream Studio 1.1 2026-09-19 20:09:17 +02:00
docs Scheda Prestazioni: tempi misurati e costo del cambio modello 2026-09-19 22:45:15 +02:00
lds Scheda Prestazioni: tempi misurati e costo del cambio modello 2026-09-19 22:45:15 +02:00
tools Scheda Prestazioni: tempi misurati e costo del cambio modello 2026-09-19 22:45:15 +02:00
.editorconfig Local Dream Studio 1.1 2026-09-19 20:09:17 +02:00
.gitattributes Local Dream Studio 1.1 2026-09-19 20:09:17 +02:00
.gitignore Local Dream Studio 1.1 2026-09-19 20:09:17 +02:00
app.py Correzioni su zoom, contenitori e avvio senza console 2026-09-19 21:22:42 +02:00
CONTRIBUTING.md Local Dream Studio 1.1 2026-09-19 20:09:17 +02:00
LICENSE Local Dream Studio 1.1 2026-09-19 20:09:17 +02:00
README.it.md Scheda Prestazioni: tempi misurati e costo del cambio modello 2026-09-19 22:45:15 +02:00
README.md Scheda Prestazioni: tempi misurati e costo del cambio modello 2026-09-19 22:45:15 +02:00
requirements.txt Local Dream Studio 1.1 2026-09-19 20:09:17 +02:00
run-debug.bat Correzioni su zoom, contenitori e avvio senza console 2026-09-19 21:22:42 +02:00
run.bat Correzioni su zoom, contenitori e avvio senza console 2026-09-19 21:22:42 +02:00
SECURITY.md Local Dream Studio 1.1 2026-09-19 20:09:17 +02:00
studio_mcp.py Nessun riferimento a un modello di telefono specifico 2026-09-19 21:35:13 +02:00

Local Dream Studio

Local Dream Studio

A desktop app to generate and edit images with Local Dream, on any device that exposes its web UI as an HTTP service. Built with PySide6, so Windows and Linux.

Documentazione in italiano


First thing: it's in Italian

The app is in Italian. So are the docs in docs/. I'm Italian, I wrote it for myself, and I never got around to English.

I'm not going to translate it. Keeping several languages in sync is real work and I'd rather spend that time on the app. Two options, pick one.

Translate it yourself. The repo is laid out so this is a mechanical job. Every string the user sees is inside lds/pages/ and lds/widgets/. The manual is all in one file, lds/pages/help.py. Around 850 strings in total, a third of which is the manual. Attach an AI with access to the repo and ask it to translate the user-facing strings, leaving code and comments alone. Then do README.it.md and docs/, which are just markdown.

Learn Italian. It's a few hundred short labels and tooltips. Not much vocabulary, and the manual explains every parameter in plain words.

There are no official translations and there won't be.


You need the bridge running first

This app is a client. On its own it does nothing.

The image generation happens on the device, on its NPU, through Stable Diffusion 1.5. A separate project turns that device into an HTTP service, and Studio is a desktop front-end for that service.

 Local Dream Studio  ->  Local Dream Bridge  ->  device running Local Dream
 (this repo, PySide6)    (separate project)      (NPU, SD 1.5)
                         Flask or Docker

It isn't tied to one phone model. Any device running Local Dream with its web UI works: if the bridge answers, Studio works. What changes from one device to another is the models available and how long a generation takes.

Tested on: ASUS ROG Phone 7 — Qualcomm Snapdragon 8 Gen 2 (SM8550, octa-core up to 3.2 GHz), Adreno 740, 16 GB LPDDR5X, UFS 4.0, Android 13 — running Stable Diffusion 1.5 on the SoC's NPU. That's the test device, not a requirement: the numbers you read in the Prestazioni tab are that machine's, and another device will give different ones.

That split is on purpose. The same engine stays usable from the browser, from Open WebUI, from an MCP agent and from a shell script at the same time, and the phone-specific logic lives in one place instead of two.

The bridge is called FOXIT, Local Dream plus webui openapi and it lives in its own private repository. It listens on port 8095 by default. Studio doesn't install it, start it or configure it, so get it going first.

docs/DEPENDENCY.md has the details, in Italian: which endpoints Studio calls, what it does when one is missing, and where the boundary between the two projects sits.


Running it

Python 3.10 or newer. Tested on 3.13.

run.bat

The first run creates the venv, installs what's needed and opens the window. If your bridge isn't at the default address, change it in the Impostazioni tab or pass --url.

Then go to Impostazioni and hit Verifica. The dot top-right turns green when the bridge answers.

Other ways in:

Command What it does
run-debug.bat Same, with a console, and it checks the install and connection first
python app.py --selftest Checks dependencies, bridge, models and engine state without opening the GUI
python app.py --url http://... Different bridge address
python app.py --scale 1.25 Start with the interface at 125%
python app.py --theme light Start in the light theme
python app.py --output D:\images Different folder for the local library

Dependencies are PySide6, requests and Pillow. The MCP server also wants mcp, but that one's optional.


Screenshots

These are generated by tools/smoke_test.py, which builds the real window, walks the tabs and takes a picture. Not mockups.

Generate Edit
Gallery Settings
Compare Light theme

Sorry, they're in Italian too.


What it does

Six tabs.

Genera generates from a prompt. Model, resolution, steps, CFG, scheduler, seed, up to four images at a time. The model's own suggested prompt loads automatically and the quick buttons add the usual fragments (cinematic, portrait, landscape).

Modifica is img2img. Drop a file anywhere on the window, or pull in the last result, or pick something from the gallery. Six one-click transforms set prompt and strength together. The before/after comparison has a divider you can drag.

Upscale enlarges an image that's already on the server, because that's the only thing the API accepts. If the phone has no upscaler installed, the tab sits there disabled and explains why, then switches itself on when the catalogue reports one.

Galleria has two sources. The server shows the last 20 images. The local folder has everything, each image with its parameters. Filter by prompt or model, reopen in Modifica, save copies, delete from the server.

Prestazioni measures timings on its own; there's nothing to start by hand. Every finished job leaves a row: model, resolution, steps, duration. From there you can read what a given set of parameters costs and, more usefully, what changing model costs: the first generation after a switch includes loading the model onto the NPU and takes much longer, and the gap between the cold and warm medians at identical parameters is the estimate of that cost. Milliseconds per step make different configurations comparable. Measurements export to CSV and can be wiped whenever you want.

Impostazioni holds the connection, folders, endpoints, theme, interface zoom, window frame, and privacy.

Guida is the manual, inside the app. How to write prompts, what every parameter does, what the engine can't do, the HTTP error table, the shortcuts.

A few things I care about:

The denoise slider tells you what it's doing. In img2img the strength shortens the diffusion, so asking for 30 steps at 0.40 doesn't run 30 steps. The tab shows "effective steps: about 12 of 20" while you drag. That misunderstanding ruins more images than anything else.

The gallery preview always loads the original, never a stretched thumbnail.

The mouse wheel doesn't change parameter values. Qt does that by default on sliders and combo boxes even when they're not focused, which means scrolling a page silently edits your settings. I blocked it.

The interface zooms from 80% to 200%, text and icons together, applied live.


Privacy mode

The bridge always writes a copy of every image into localdream_out. That folder feeds its own web UI and gallery, and Studio can't stop it.

What Studio can do is undo it. Turn on "Non lasciare copie delle immagini sul server" in Impostazioni, and after each generation, edit or upscale it saves the image locally and then, only if that worked, deletes the copy from the server.

You keep the local file, so nothing is lost. This is for people who use Studio only and don't want to visit the web UI to clean the gallery after every image.

Three details, because "delete it right after" is easy to get wrong:

Autosave has to be on. Without a local copy the image would just be gone, so if autosave is off Studio deletes nothing and tells you why.

Edits switch endpoint. /v1/images/edits answers in OpenAI style and doesn't tell you the filename it saved, so there'd be nothing to delete. In privacy mode Studio moves to /api/generate with the image on its own.

If the bridge doesn't report the file, Studio says the copy stayed, instead of pretending it worked.

The real fix is a flag on the bridge that skips the write in the first place. That's in the roadmap. What's here works with today's bridge, no server changes needed.


MCP server

Studio can be driven by an agent too. studio_mcp.py exposes 14 tools with the same parameters, the same sidecar files and the same output folder as the GUI, so an agent can generate and edit, and also search and manage the local library.

python studio_mcp.py                          # uses saved preferences
python studio_mcp.py --url http://host:8095   # different bridge
python studio_mcp.py --output D:/images       # different library
python studio_mcp.py --selftest               # check the configuration

Or with LOCAL_DREAM_BRIDGE and LOCAL_DREAM_STUDIO_OUT.

The tools: studio_info, studio_health, studio_models, studio_generate, studio_edit, studio_upscale, studio_library, studio_library_get, studio_library_delete, studio_library_stats, studio_gallery, studio_server_delete, studio_settings, studio_set_setting.

Call studio_health before generating. If the phone is down nothing else will work and the error you get further along won't tell you why.

The image-producing tools return MCP image blocks, so the agent actually sees the result rather than a wall of base64.

To register it, in Hermes' config.yaml:

mcp_servers:
  localdream_studio:
    command: C:/path/to/localdream_studio/.venv/Scripts/python.exe
    args: ["C:/path/to/localdream_studio/studio_mcp.py"]
    enabled: true
    timeout: 600
    connect_timeout: 60

Or for Claude Desktop and Cursor:

{"mcpServers": {"localdream-studio": {
  "command": "C:/path/to/localdream_studio/.venv/Scripts/python.exe",
  "args": ["C:/path/to/localdream_studio/studio_mcp.py"]}}}

One thing worth knowing: the bridge project already ships its own MCP server, and that one talks straight to the phone. It's the faster way to get an image. Studio's talks to the bridge and adds the local library, the metadata, the input preparation, the upscale and the privacy mode. They don't conflict. Register both if you want.

PySide6 isn't needed for the MCP server. If it's installed Studio shares its preferences with the GUI, otherwise the server keeps its own JSON file.


Testing

Four tools, all re-runnable. They're not ceremony: every one of them found real bugs while I was building this.

python tools/style_check.py     # colours and contrast, from actual pixels
python tools/layout_audit.py    # widgets that are clipped or squeezed
python tools/smoke_test.py      # builds the GUI, walks the tabs, runs a real edit
python tools/mcp_test.py        # MCP handshake, tool schemas, a real call

style_check.py renders widgets and reads the pixels back, then checks that what the theme declares is what got painted, and computes WCAG contrast ratios for every text level. 40 checks, both themes, all passing, everything at or above 4.5:1.

layout_audit.py compares every widget's real geometry against the space its content needs. Zero findings at 80 to 200% zoom, across five window sizes. Run it on a real display. On the offscreen Qt platform the system fonts aren't there, text metrics change and it reports things that aren't true. It warns you when it notices.

smoke_test.py builds the actual window and navigates everything. Without --no-generate it also runs a real generation through the bridge. With --screen 1 it opens on the second monitor and with --no-focus it never takes focus or input from whoever is using the computer, so you can leave it running while you work:

python tools/smoke_test.py --no-generate --screen 1 --no-focus

mcp_test.py speaks the protocol over stdio. --call <tool> --args '<json>' invokes one.

.gitea/workflows/verify.yaml runs the headless ones on every push. It needs a runner registered on the Gitea instance; without one the file just sits there.


How it's laid out

app.py                  entry point, arguments, error hook
studio_mcp.py           the MCP server
run.bat, run-debug.bat  launch, with and without a console
LICENSE                 use it, tell me if you make money, 1% over 10k
CONTRIBUTING.md         what a change has to pass before a pull request
SECURITY.md             the threat model, such as it is
assets/                 the app icon
.gitea/                 issue templates, PR template, CI
lds/
  config.py             preferences (QSettings, or JSON when Qt isn't there)
  theme.py              palettes, design tokens, the stylesheet, zoom
  icons.py              icons drawn at runtime with QPainter
  input_guard.py        stops the mouse wheel from editing values
  api.py                HTTP client for the bridge
  bench.py              timing measurements and their statistics
  workers.py            job queue, status polling, thumbnail cache
  imaging.py            image conversion and input preparation
  library.py            local library, sidecars, stats
  storage.py            writing to disk
  context.py            shared state between pages
  main_window.py        window frame, navigation, notifications
  widgets/              reusable pieces
  pages/                the seven tabs
tools/                  the four test tools
docs/                   architecture, dependency, roadmap, devlog, screenshots

The reasoning behind the non-obvious choices, including the Qt traps that cost me the most time, is in docs/ARCHITECTURE.md. In Italian.


What it can't do

No inpainting. You can't mask a region, so you can't remove one object or put text in a specific spot. That needs masked inpainting and this engine doesn't have it.

Prompts have to be short, ten to twenty-five keyword phrases in English, comma separated. SD 1.5 weighs words, it doesn't read sentences.

One request at a time. The backend is single-shot, requests queue up.

Timing: five to twenty seconds at 512px, ten to sixty on the first use of a model, much longer at 1024.

The server gallery keeps 20 entries. The local folder is the real archive.

The phone has to stay on, in host mode, and charging. It works on the NPU and gets warm. If the screen locks the backend can die, though the bridge restarts it by itself.


Security

There's no authentication, anywhere. Not to the bridge, not to the phone. This is built for a private network, Tailscale or your own LAN, and the network is the whole security model. Don't put port 8095 on the internet.

The app talks to the configured address and nothing else. No telemetry, no update checks, no CDN.


Docs

Everything below is in Italian.

README.it.md The Italian README, longer than this one
docs/ARCHITECTURE.md How it's built and why, including the Qt traps
docs/DEPENDENCY.md The bridge: what it gives, what gets used, what happens without it
docs/ROADMAP.md Where it's going, including the installer with three modes
docs/DEVLOG.md What went wrong along the way and how I found it
docs/screenshots/ Generated by the smoke test

Contributing

Issues and pull requests go on the Gitea instance this project lives on.

Run the test tools before opening a PR. If style_check.py or layout_audit.py complain, it isn't ready.

And I don't maintain translations. If you translate it, that translation is yours.


License

Use it for whatever you want. If you make money with it, tell me, two words is enough. If you make more than 10,000 EUR with it in a year, I take 1% of what's over that. Full text in LICENSE.


Local Dream Studio, by - F0X1Y//So:ACD -

The Local Dream protocol was reverse-engineered from the xororz/local-dream v2.8.x source. It isn't officially documented.