Massive sync batch 1
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .clinerules +473 -0
- .cursorrules +473 -0
- .geminiignore +6 -0
- .gitattributes +110 -0
- .gitignore +71 -0
- .windsurfrules +473 -0
- AGENTS.md +473 -0
- CLAUDE.md +473 -0
- ENVIRONMENT +95 -0
- GEMINI.md +473 -0
- QWEN.md +473 -0
- README.md +98 -3
- Sampleapp.py +646 -0
- app.py +1092 -0
- cache/GRADIO_TEMP_DIR/0104dcbf4ecd060d0f9908e75116ea3b1283defbe8d1daa5b2aebfdd27a50720/184610_17e9.png +3 -0
- cache/GRADIO_TEMP_DIR/0115b4830ff0665ad782cfb50ea81e83d576c6fe5787c6c33a77c9ea179743ca/174332_8794.png +3 -0
- cache/GRADIO_TEMP_DIR/01ad82d69b6d2baad6c60480ceb366ed05003c5c92b9c21c0cf4c37c411b3ff8/200033_b851.png +3 -0
- cache/GRADIO_TEMP_DIR/03a7badba46e5b655ff716e02c5996baee5f2e7e4ac5f44d0ad24a61489fadc3/image_195204_7aec6c64.png +3 -0
- cache/GRADIO_TEMP_DIR/048523a346ad5312fb0116b07c6cc9d4d7a7fea69ce7027d7fd7beb01268fc18/092559_b8fc.png +3 -0
- cache/GRADIO_TEMP_DIR/096db37f01b8c62d97c41220a5ca2b84aa4a1ae15b04d046f947d3d6892735fb/image.webp +0 -0
- cache/GRADIO_TEMP_DIR/09b443d9c4850dd13d4a39a8edff6b7e68c9f5a428a73fc494ece1fa5066ee0c/201057_0a1c.png +3 -0
- cache/GRADIO_TEMP_DIR/0b504490c285e741239ac4793322e48efc91691c04e98a6c7a3af21d304a5aa4/image.webp +3 -0
- cache/GRADIO_TEMP_DIR/0c8c8364e4d7ddeb83cc7c5777e36b5f7687da1bb308d2406fd842ea9f46fa95/i2i_133141_5337.png +3 -0
- cache/GRADIO_TEMP_DIR/0fd950fa33ea4eafe5c16b1cc0eed01eb5ebf2f23b1e6f414533b25180379b05/image_205528_7ee5527a.png +3 -0
- cache/GRADIO_TEMP_DIR/104f4b9668733006f0a761e9778c747031df2334a0dd6f389ae5d49d5cc02b9e/200437_0006.png +3 -0
- cache/GRADIO_TEMP_DIR/10c5d8f6cacea6cc5f16e475aa9c4fdff517728969b2629a26c679c5648135d5/193107_4fa5.png +3 -0
- cache/GRADIO_TEMP_DIR/127b98c0ad0f191eff7d10eba1f1bad79fa49c9889aa3a2dfe12c05d28348aba/095514_01ac.png +3 -0
- cache/GRADIO_TEMP_DIR/12d002a8885b4d41fbb41d98e32ce71f728aa5bde8a939300ed6af71179b599d/103637_26f5.png +3 -0
- cache/GRADIO_TEMP_DIR/168afac92d4203ba0e6fad2bf01e3d1a10295a59cad570e0d32316117755b7e3/composite.png +3 -0
- cache/GRADIO_TEMP_DIR/19b76ad6a7d98ae4eb1d1626871e171b636bb0103bd68fea8ff92685d2207695/160727_8256.png +3 -0
- cache/GRADIO_TEMP_DIR/1a0791be9abd585a9187d0f87b23e9ceafb0d09b978af6f11e5b45999d7ff792/image_203420_c47cecb7.png +3 -0
- cache/GRADIO_TEMP_DIR/1aaa830b48dfb49d28389f438e732a2a73cc948878b7f969802687356e75a63c/173952_0241.png +3 -0
- cache/GRADIO_TEMP_DIR/1cc5d28158556bf5a8eb7992502a482d4b898f463262649559b77a1aaa10fd84/161034_21ec.png +3 -0
- cache/GRADIO_TEMP_DIR/213a256e430446bb846446a77053d3c3e2d5a7e3d40adf060bb1358d2929bed1/193806_c514.png +3 -0
- cache/GRADIO_TEMP_DIR/21db64ee5ade0d05ff3961c7df2ee7016e8a13e954396707b37d42de6b2b32d5/image_233514_3851872b.png +3 -0
- cache/GRADIO_TEMP_DIR/235047bf74248ec689408227f1fb364afb770712ddd3da625a3c660c6a29cc35/background.png +3 -0
- cache/GRADIO_TEMP_DIR/25d5add9dafa195a945d962f6064049cdf902c723358c1eb978615f82c47cd54/123145_46ab.png +3 -0
- cache/GRADIO_TEMP_DIR/2ddd025a39e016cbc97dfd9130f9b1302492be2ba4e33b0acefb761adaa1b14d/image.webp +3 -0
- cache/GRADIO_TEMP_DIR/30a053ec6f1d5ed7e7c8397450be98cb173037115d5e03394d99102741d0faf7/195841_e909.png +3 -0
- cache/GRADIO_TEMP_DIR/334d0bc165496fef845d69069319d2e574ef489656422f14f8b4553dff7583f8/184528_885b.png +3 -0
- cache/GRADIO_TEMP_DIR/364d8c3a6f8a226ff370cb2e4f59d8f37f6a23520f322adf77ae3d7d996d6176/191110_1134.png +3 -0
- cache/GRADIO_TEMP_DIR/36e56881752c197dfd65a693b7d7134852117eaa97fbff30739a01390082a69e/i2i_132542_eeff.png +3 -0
- cache/GRADIO_TEMP_DIR/3948f38c2fecc017128b1b7eb41f3f327fc06aba806d9bdc886368ef3228319d/193253_2da5.png +3 -0
- cache/GRADIO_TEMP_DIR/3ba5ac95cfc5de2c15635232fb57a2b719388c6891302cbc32127defbdbde758/image_123428_296a1507.png +3 -0
- cache/GRADIO_TEMP_DIR/3ece35a85d9ffae8b8270058ab850d5d419785b01ec68819a3c6aace56673bc4/161239_fdad.png +3 -0
- cache/GRADIO_TEMP_DIR/41fbb34e88e6b1e8e459504ada125267c8bf7acc97ac8d364df233623828879d/i2i_132029_852c.png +3 -0
- cache/GRADIO_TEMP_DIR/42a832bbae0d16a84c93cd3c85948cbd891e5ae55ec6e05ab905fb1dde3af489/190833_457f.png +3 -0
- cache/GRADIO_TEMP_DIR/430a73612de086da34654223fecad865c06011c87597bcc3d22cc10074e5ea2b/image.webp +0 -0
- cache/GRADIO_TEMP_DIR/48c7aa110490f236dc8353a8a4b7a1b9610cd489d0723e9acc4671ffbc1fd9e5/211433_2542.png +3 -0
- cache/GRADIO_TEMP_DIR/4ca1ba410ee0e412d101e38b900f26235b1ac4ba2bd20e3a9cb4695a31770b8b/152321_78e9.png +3 -0
.clinerules
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Development Guide for Pinokio Projects
|
| 2 |
+
|
| 3 |
+
## Non-Negotiable Execution Workflow
|
| 4 |
+
|
| 5 |
+
To guarantee every contribution follows this guide precisely, obey this checklist **before any edits** and **again before finalizing**. Do not skip or reorder.
|
| 6 |
+
1. **AGENTS Snapshot:** Re-open this file and write down (in your working notes or response draft) the exact sections relevant to the requested task. No work begins until this snapshot exists.
|
| 7 |
+
2. **Example Lock-in:** Identify the closest matching script in `C:\pinokio\prototype\system\examples`. Record its path and keep it open while editing. Every launcher change must mirror that reference unless the user explicitly instructs otherwise.
|
| 8 |
+
3. **Pre-flight Checklist:** Convert the applicable rules from this document and `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md into a task-specific checklist (install/start/reset/update structure, regex patterns, menu defaults, log checks, etc.). Confirm each item is ticked **before** making changes.
|
| 9 |
+
4. **Mid-task Verification:** Any time you touch a Pinokio script, cross-check the corresponding example line to ensure syntax and structure match. Document the reference (example path + line) in your reasoning.
|
| 10 |
+
5. **Exit Checklist:** Before responding to the user, revisit the pre-flight checklist and explicitly confirm every item is satisfied. If anything diverges from the example or these rules, fix it first.
|
| 11 |
+
|
| 12 |
+
If any step cannot be completed, stop immediately and ask the user how to proceed. These five steps are mandatory for every session.
|
| 13 |
+
|
| 14 |
+
### Critical Pattern Lock: Capturing Web UI URLs
|
| 15 |
+
|
| 16 |
+
When writing `start.js` (or any script that needs to surface a web URL for a server):
|
| 17 |
+
|
| 18 |
+
1. **Always copy the capture block from an example such as `system/examples/mochi/start.js`.**
|
| 19 |
+
```javascript
|
| 20 |
+
on: [{
|
| 21 |
+
event: "/(http:\\/\\/[0-9.:]+)/",
|
| 22 |
+
done: true
|
| 23 |
+
}]
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
2. **Set the local variable using the captured match exactly as below (The regex capture object is passed in as `input.event`, so need to use the index 1 inside the parenthesis):**
|
| 27 |
+
```javascript
|
| 28 |
+
{
|
| 29 |
+
method: "local.set",
|
| 30 |
+
params: {
|
| 31 |
+
url: "{{input.event[1]}}"
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
3. Always try to come up with the most generic regex.
|
| 37 |
+
4. During the exit checklist, explicitly confirm that the `url` local variable is set via `local.set` API by using the captured regex object as passed in as `input.event` from the previous `shell.run` step.
|
| 38 |
+
|
| 39 |
+
Deviation from this pattern requires written approval from the user.
|
| 40 |
+
|
| 41 |
+
- Make sure to keep this entire document and `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md in memory with high priority before making any decision. Pinokio is a system that makes it easy to write launchers through scripting by providing various cross-platform APIs, so whenever possible you should prioritize using Pinokio API over lower level APIs.
|
| 42 |
+
- When writing pinokio scripts, ALWAYS check the examples folder (in C:\pinokio\prototype\system\examples folder) to see if there are existing example scripts you can imitate, instead of assuming syntax.
|
| 43 |
+
- When implementing pinokio script APIs and you cannot infer the syntax just based on the examples, always search the API documentation `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md to use the correct syntax instead of assuming the syntax.
|
| 44 |
+
- When trying to fix something or figure out what's going on, ALWAYS start by checking the `logs` folder before doing anything else, as mentioned in the "Troubleshooting with Logs" section.
|
| 45 |
+
- Finally, make sure to ALWAYS follow all the items in the "best practices" section below.
|
| 46 |
+
|
| 47 |
+
## Determine User Intent
|
| 48 |
+
If the initial prompt is simply a URL and nothing else, check the website content and determine the intent, and ask the user to confirm. For example a URL may point to
|
| 49 |
+
|
| 50 |
+
1. A Tutorial: the intent may be to implement a demo for the tutorial and build a launcher.
|
| 51 |
+
2. A Demo: the intent may be a 1-click launcher for the demo
|
| 52 |
+
3. Open source project: the intent may be a 1-click launcher for the project
|
| 53 |
+
4. Regular website: the intent may be to clone the website and a launcher.
|
| 54 |
+
5. There can be other cases, but try to guess.
|
| 55 |
+
|
| 56 |
+
## Project Structure
|
| 57 |
+
|
| 58 |
+
Pinokio projects normally follow a standardized structure with app logic separated from launcher scripts:
|
| 59 |
+
|
| 60 |
+
Pinokio projects follow a standardized structure with app logic separated from launcher scripts:
|
| 61 |
+
|
| 62 |
+
```
|
| 63 |
+
project-root/
|
| 64 |
+
├── app/ # Self-contained app logic (can be standalone repo)
|
| 65 |
+
│ ├── package.json # Node.js projects
|
| 66 |
+
│ ├── requirements.txt # Python projects
|
| 67 |
+
│ └── ... # Other language-specific files
|
| 68 |
+
├── README.md # Documentation
|
| 69 |
+
├── install.js # Installation script
|
| 70 |
+
├── start.js # Launch script
|
| 71 |
+
├── update.js # Update script (for updating the scripts and app logic to the latest)
|
| 72 |
+
├── reset.js # Reset dependencies script
|
| 73 |
+
├── pinokio.js # UI generator script
|
| 74 |
+
└── pinokio.json # Metadata (title, description, icon)
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
- Keep app code in `/app` folder only (never in root)
|
| 78 |
+
- Store all launcher files in project root (never in `/app`)
|
| 79 |
+
- `/app` folder should be self-contained and publishable
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
The only exceptions are serverless web apps---purely frontend only web applications that do NOT have a server component and connect to 3rd party API endpoints--in which case the folder structure looks like the following (No need for launcher scripts since the index.html will automatically launch. The only thing needed is the metadata file named pinokio.json):
|
| 83 |
+
|
| 84 |
+
```
|
| 85 |
+
project-root/
|
| 86 |
+
├── index.html # The serverless web app entry point
|
| 87 |
+
├── ...
|
| 88 |
+
├── README.md # Documentation
|
| 89 |
+
└── pinokio.json # Metadata (title, description, icon)
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
IMPORTANT: ALWAYS try to follow the best practices in the examples folder (C:\pinokio\prototype\system\examples) instead of trying to come up with your own structure. The examples have been optimized for the best user experience.
|
| 93 |
+
|
| 94 |
+
## Launcher Project Working Directory
|
| 95 |
+
|
| 96 |
+
- The project working directory for a script is always the same directory as the script location.
|
| 97 |
+
- For example, when you run `shell.run` API inside `pinokio/start.js`, the default path for shell execution is `pinokio`.
|
| 98 |
+
- If the launcher files are in the project root path, then the default path for shell execution is the project root.
|
| 99 |
+
- Therefore, it is important to specify the correct `path` attribute when running `shell.run` API commands.
|
| 100 |
+
|
| 101 |
+
Example: in the following project structure:
|
| 102 |
+
|
| 103 |
+
```
|
| 104 |
+
project-root/
|
| 105 |
+
├── pinokio/ # Pinokio launcher folder
|
| 106 |
+
│ ├── start.js # Launch script
|
| 107 |
+
│ ├── pinokio.js # UI generator script
|
| 108 |
+
│ └── pinokio.json # Metadata (title, description, icon)
|
| 109 |
+
└─── backend/
|
| 110 |
+
├── requirements.txt # App dependencies
|
| 111 |
+
└── app.py # App code
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
The `pinokio/start.js` should use the correct path `../backend` as the `path` attribute, as follows:
|
| 115 |
+
|
| 116 |
+
```
|
| 117 |
+
{
|
| 118 |
+
run: [{
|
| 119 |
+
...
|
| 120 |
+
}, {
|
| 121 |
+
method: "shell.run",
|
| 122 |
+
params: {
|
| 123 |
+
message: "python app.py",
|
| 124 |
+
venv: "env",
|
| 125 |
+
path: "../backend"
|
| 126 |
+
}
|
| 127 |
+
}, {
|
| 128 |
+
...
|
| 129 |
+
}]
|
| 130 |
+
}
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
## Development Workflow
|
| 134 |
+
|
| 135 |
+
### 1. Understanding the Project
|
| 136 |
+
- Check `SPEC.md` in project root. If the file exists, use that to learn about the project details (what and how to build)
|
| 137 |
+
- If no `SPEC.md` exists, build based on user requirements
|
| 138 |
+
### 2. Modifying Existing Launcher Projects
|
| 139 |
+
If we are starting with existing launcher script files, work with the existing files instead of coming up with your own.
|
| 140 |
+
- **Preserve existing functionality:** Only modify necessary parts
|
| 141 |
+
- **Don't touch working scripts:** Unless adding/updating specific commands
|
| 142 |
+
- **Follow existing conventions:** Match the style and structure already present
|
| 143 |
+
### 3. Try to adopt from examples as much as possible
|
| 144 |
+
- If starting from scratch, first determine what type of project you will be building, and then check the examples folder (C:\pinokio\prototype\system\examples) to see if you can adopt them instead of coming up everything from scratch.
|
| 145 |
+
- Even if there are no relevant examples, check the examples to get inspiration for how you would structure the script files even if you have to write from scratch.
|
| 146 |
+
### 4. Writing from scratch as a last resort
|
| 147 |
+
If there are relevant examples to adopt from, write the scripts from scratch, but just make sure to follow the requirements in the next section.
|
| 148 |
+
### 5. Debugging
|
| 149 |
+
When the user reports something is not working, ALWAYS inspect the logs folder to get all the execution logs. For more info on how this works, check the "Troubleshooting with Logs" section below.
|
| 150 |
+
|
| 151 |
+
## Script Requirements
|
| 152 |
+
|
| 153 |
+
### 1. 1-click launchable
|
| 154 |
+
- The main purpose of Pinokio is to provide an easy interface to invoke commands, which may include launching servers, installing programs, etc. Make sure the final product provides ways to install, launch, reset, and update whatever is needed.
|
| 155 |
+
|
| 156 |
+
### 2. Write Documentation
|
| 157 |
+
- ALWAYS write a documentation. A documentation must be stored as `README.md` in the project root folder, along with the rest of the pinokio launcher script files. A documentation file must contain:
|
| 158 |
+
- What the app does
|
| 159 |
+
- How to use the app
|
| 160 |
+
- API documentation for programmatically accessing the app's main features (Javascript, Python, and Curl)
|
| 161 |
+
|
| 162 |
+
## Types of launchers
|
| 163 |
+
## 1. Launching servers
|
| 164 |
+
- When an app requires launching a server, here are the commonly used scripts:
|
| 165 |
+
- `install.js`: a script to install the app
|
| 166 |
+
- `start.js`: a script to start the app
|
| 167 |
+
- `reset.js`: a script to reset all the dependencies installed in the `install.js` step. used if the user wants to restart from scratch
|
| 168 |
+
- `update.js`: a script to update the launcher AND the app in case there are new updates. Involves pulling in the relevant git repositories installed through `install.js` (often it's the script repo and some git repositories cloned through the install steps if any)
|
| 169 |
+
- `pinokio.js`: the launcher script that ties all of the above scripts together by providing a UI that links to these scripts.
|
| 170 |
+
- `pinokio.json`: For metadata
|
| 171 |
+
|
| 172 |
+
Here's a basic server launcher script example (`start.js`). Unless there's a special reason you need to use another pattern, this is the most recommended pattern. Use this or adopt it as needed, but NEVER try something else unless there's a good reason you should not take this approach:
|
| 173 |
+
|
| 174 |
+
```javascript
|
| 175 |
+
module.exports = {
|
| 176 |
+
// By setting daemon: true, the script keeps running even after all items in the `run` array finishes running. Mandatory for launching servers, since otherwise the shells running the server process will get killed after the scripts finish running.
|
| 177 |
+
daemon: true,
|
| 178 |
+
run: [
|
| 179 |
+
{
|
| 180 |
+
// The "shell.run" API for running a shell session
|
| 181 |
+
method: "shell.run",
|
| 182 |
+
params: {
|
| 183 |
+
// Edit 'venv' to customize the venv folder path
|
| 184 |
+
venv: "env",
|
| 185 |
+
// Edit 'env' to customize environment variables (see documentation)
|
| 186 |
+
env: { },
|
| 187 |
+
// Edit 'path' to customize the path to start the shell from
|
| 188 |
+
path: "app",
|
| 189 |
+
// Edit 'message' to customize the commands, or to run multiple commands
|
| 190 |
+
message: [
|
| 191 |
+
"python app.py",
|
| 192 |
+
],
|
| 193 |
+
on: [{
|
| 194 |
+
// The regular expression pattern to monitor.
|
| 195 |
+
// Whenever each "event" pattern occurs in the shell terminal, the shell will return,
|
| 196 |
+
// and the script will go onto the next step.
|
| 197 |
+
// The regular expression match object will be passed on to the next step as `input.event`
|
| 198 |
+
// Useful for capturing the URL at which the server is running (in case the server prints some message about where the server is running)
|
| 199 |
+
"event": "/(http:\/\/\\S+)/",
|
| 200 |
+
|
| 201 |
+
// Use "done": true to move to the next step while keeping the shell alive.
|
| 202 |
+
// Use "kill": true to move to the next step after killing the shell.
|
| 203 |
+
"done": true
|
| 204 |
+
}]
|
| 205 |
+
}
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
// This step sets the local variable 'url'.
|
| 209 |
+
// This local variable will be used in pinokio.js to display the "Open WebUI" tab when the value is set.
|
| 210 |
+
method: "local.set",
|
| 211 |
+
params: {
|
| 212 |
+
// the input.event is the regular expression match object from the previous step
|
| 213 |
+
// In this example, since the pattern was "/(http:\/\/\\S+)/", input.event[1] will include the exact http url match caputred by the parenthesis.
|
| 214 |
+
// Therefore setting the local variable 'url'
|
| 215 |
+
url: "{{input.event[1]}}"
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
]
|
| 219 |
+
}
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
## 2. Launching serverless web apps
|
| 223 |
+
|
| 224 |
+
- In case of purely static web apps WITHOUT servers or backends (for example an HTML based app that connects to 3rd party servers--either remote or localhost), we do NOT need the launcher scripts.
|
| 225 |
+
- In these cases, simply include `index.html` in the project root folder and everything should automatically work. No need for any of the pinokio launcher scripts. (Do
|
| 226 |
+
- You still need to include the metadata file so they show up properly on pinokio:
|
| 227 |
+
- `pinokio.json`: For metadata
|
| 228 |
+
|
| 229 |
+
## 3. Launching quick scripts without web UI
|
| 230 |
+
|
| 231 |
+
- In many cases, we may not even need a web UI, but instead just a simple way to run scripts.
|
| 232 |
+
- This may include TUI (Terminal User Interface) apps, a simple launcher
|
| 233 |
+
- In these cases, all we need is the launcher file `pinokio.js`, which may link to multiple scripts. In this case, there are no web apps (no serverless apsp, no servers), but instead just the default pinokio launcher UI that calls a bunch of scripts.
|
| 234 |
+
- Here are some examples:
|
| 235 |
+
- A pinokio script to toggle the desktop theme between dark and light
|
| 236 |
+
- Write some code (python or javascript or whatever)
|
| 237 |
+
- Write a `toggle.js` pinokio script that executes the code
|
| 238 |
+
- Write a `pinokio.js` launcher script to create a sidebar UI that displays the `toggle.js` so the user can simply click the "toggle" button to toggle back and forth between desktop themes
|
| 239 |
+
- A pinokio script to fetch some file
|
| 240 |
+
- Write some code (python or javascript or whatever)
|
| 241 |
+
- Write a `fetch.js` pinokio script that executes the code
|
| 242 |
+
- Write a `pinokio.js` launcher script to create a sidebar UI that displays the `fetch.js` so the user can simply click the "fetch" button to fetch some data.
|
| 243 |
+
- You still need to include the metadata file so they show up properly on pinokio:
|
| 244 |
+
- `pinokio.json`: For metadata
|
| 245 |
+
|
| 246 |
+
## API
|
| 247 |
+
|
| 248 |
+
This section lists all the script APIs available on Pinokio. To learn the details of how they are used, you can:
|
| 249 |
+
1. Check the examples in the C:\pinokio\prototype\system\examples folder
|
| 250 |
+
2. Read the `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md further documentation on the full syntax
|
| 251 |
+
|
| 252 |
+
### Script API
|
| 253 |
+
|
| 254 |
+
These APIs can be used to describe each step in a pinokio script:
|
| 255 |
+
- shell.run: run shell commands
|
| 256 |
+
- input: accept user input
|
| 257 |
+
- filepicker: accept file upload
|
| 258 |
+
- fs.write: write to file
|
| 259 |
+
- fs.read: read from file
|
| 260 |
+
- fs.copy: copy files
|
| 261 |
+
- fs.download: download files
|
| 262 |
+
- fs.link: create a symbolic link (or junction on windows) for folders
|
| 263 |
+
- fs.open: open the system file explorer at a given path
|
| 264 |
+
- fs.cat: print file contents
|
| 265 |
+
- jump: jump to a specific step
|
| 266 |
+
- local.set: set local variables for the currently running script
|
| 267 |
+
- json.set: update a json file
|
| 268 |
+
- json.rm: remove keys from a json file
|
| 269 |
+
- json.get: get values from a json file
|
| 270 |
+
- log: print to the web terminal
|
| 271 |
+
- net: make network requests
|
| 272 |
+
- notify: display a notification
|
| 273 |
+
- script.download: download a script from a git uri
|
| 274 |
+
- script.start: start a script
|
| 275 |
+
- script.stop: stop a script
|
| 276 |
+
- script.return: return values if the current script was called by a caller script, so the caller script can utilize the return value as `input`
|
| 277 |
+
- web.open: open a url in web browser
|
| 278 |
+
- hf.download: huggingfac-cli download API
|
| 279 |
+
### Template variables
|
| 280 |
+
The following variables are accessible inside template expressions (example `{{args.command}` in scripts, resulting in dynamic behaviors of scripts:
|
| 281 |
+
- input: An input is a variable that gets passed from one RPC call to the next
|
| 282 |
+
- args: args is the parameter object that gets passed into the script (via pinokio.js `params`). Unlike `input` which takes the value passed in from the immediately previous step, `args` is a global value that is the same through out the entire script execution.
|
| 283 |
+
- local: local variable object that can be set with `local.set` API
|
| 284 |
+
- self: refers to the script file itself (which is JSON or JavaScript). For example if `start.js` that's currently running has `daemon: true` set, `{{self.daemon}}` will evaluate to true.
|
| 285 |
+
- uri: The current script uri
|
| 286 |
+
- port: The next available port. Very useful when you need to launch an app at a specific port without port conflicts.
|
| 287 |
+
- cwd: The current script execution folder path
|
| 288 |
+
- platform: The current operating system. May be one of the following: `darwin`, `win32`, `linux`
|
| 289 |
+
- arch: The current system architecture. May be one of the following: x32, x64, arm, arm64, s390, s390x, mipsel, ia32, mips, ppc, ppc64
|
| 290 |
+
- gpus: array of available GPUs on the machine (example: `['apple']`, `['nvidia']`)
|
| 291 |
+
- gpu: the first available GPU (example: `nvidia`)
|
| 292 |
+
- current: The current variable points to the index of the currently executing instruction within the run array.
|
| 293 |
+
- next: The next variable points to the index of the next instruction to be executed. (null if the current instruction is the final instruction in the run array)
|
| 294 |
+
- envs: You can access the environment variables of the currently running process with envs object.
|
| 295 |
+
- which: Check whether a command exists (example: `{{which('winget')}}`. Can be used in the `when` attribute of a script step to run commands or install first.
|
| 296 |
+
- exists: Check whether a file or folder exists at the specified relative path (example: `"when": "{{!exists('app')}}"`). Can be used with the `when` attribute to determine a path's existence and trigger custom logic. Use relative paths and it will resolve automatically to the current execution folder.
|
| 297 |
+
- running: Check whether a script file is running (example: `"when": "{{!running('start.js')}}"`). Can be used with the `when` attribute to determine a path's existence and trigger custom logic. Use relative paths and it will resolve automatically to the current execution folder.
|
| 298 |
+
- os: Pinokio exposes the node.js os module through the os variable.
|
| 299 |
+
- path: Pinokio exposes the node.js path module through the os variable (example: `{{path.resolve(...)}}`
|
| 300 |
+
|
| 301 |
+
## System Capabilities
|
| 302 |
+
### Package Management (Use in Order of Preference)
|
| 303 |
+
The following package managers come pre-installed with Pinokio, so whenever you need to install a 3rd party binary, remember that these are available. Also, you can assume these are available and include the following package manager commands in Pinokio scripts:
|
| 304 |
+
1. **UV** - For Python packages (preferred over pip)
|
| 305 |
+
2. **NPM** - For Node.js packages
|
| 306 |
+
3. **Conda** - For cross-platform 3rd party binaries
|
| 307 |
+
4. **Brew** - Mac-only fallback when other options unavailable
|
| 308 |
+
5. **Git** - Full access to git is available.
|
| 309 |
+
**Important:** Include all install commands in the install script for reproducibility.
|
| 310 |
+
### HTTPS Proxy Support
|
| 311 |
+
- All HTTP servers automatically get HTTPS endpoints
|
| 312 |
+
- Convention: `http://localhost:<PORT>` → `https://<PORT>.localhost`
|
| 313 |
+
- Full proxy list available at: `http://localhost:2019/config/`
|
| 314 |
+
### Pterm Features:
|
| 315 |
+
- **Clipboard Access:** Read from or Write to system clipboard via pinokio Pterm CLI (`pterm clipboard` command.)
|
| 316 |
+
- **Notifications:** Send desktop alerts via pinokio pterm CLI (`pterm push` command.)
|
| 317 |
+
- **Script Testing:** Run launcher scripts via pinokio pterm CLI (`pterm start` command.)
|
| 318 |
+
- **File Selection:** Use built-in filepicker for user file/folder input (`pterm filepicker` command.)
|
| 319 |
+
- **Git Operations:** Clone repositories, push to GitHub
|
| 320 |
+
- **GitHub Integration:** Full GitHub CLI support (`gh` commands)
|
| 321 |
+
|
| 322 |
+
## Troubleshooting with Logs
|
| 323 |
+
Pinokio stores the logs for everything that happened in terminal at the following locations, so you can make use of them to determine what's going on:
|
| 324 |
+
|
| 325 |
+
### Log Structure
|
| 326 |
+
In case there is a `pinokio` folder in the project root folder, you should be able to find the logs folder here:
|
| 327 |
+
|
| 328 |
+
```
|
| 329 |
+
pinokio/
|
| 330 |
+
└── logs/ # Direct user interaction logs
|
| 331 |
+
├── api/ # Launcher script logs (install.js, start.js, etc.)
|
| 332 |
+
├── dev/ # AI coding tool logs (organized by tool)
|
| 333 |
+
└── shell/ # Direct user interaction logs
|
| 334 |
+
```
|
| 335 |
+
|
| 336 |
+
Otherwise, the `logs` folder should be found at project root:
|
| 337 |
+
|
| 338 |
+
```
|
| 339 |
+
logs/
|
| 340 |
+
├── api/ # Launcher script logs (install.js, start.js, etc.)
|
| 341 |
+
├── dev/ # AI coding tool logs (organized by tool)
|
| 342 |
+
└── shell/ # Direct user interaction logs
|
| 343 |
+
```
|
| 344 |
+
|
| 345 |
+
### Log File Naming
|
| 346 |
+
- Unix timestamps for each session
|
| 347 |
+
- Special "latest" file contains most recent session logs
|
| 348 |
+
- **Default:** Use "latest" files for current issues
|
| 349 |
+
- **Historical:** Use timestamped files for pattern analysis and the full history.
|
| 350 |
+
|
| 351 |
+
## Best practices
|
| 352 |
+
### 0. Always reference the logs when debugging
|
| 353 |
+
- When the user asks to fix something, ALWAYS check the logs folder first to check what went wrong. Check the "Troubleshooting with Logs" section.
|
| 354 |
+
### 1. Shell commands for launching programs
|
| 355 |
+
- Launch flags related
|
| 356 |
+
- Try as hard as possible to minimize launch flags and parameters when launching an app. For example, instead of `python app.py --port 8610`, try to do `python app.py` unless really necessary. The only exception is when the only way to launch the app is to specify the flags.
|
| 357 |
+
- Launch IP related
|
| 358 |
+
- Always try to find a way to launch servers at 127.0.0.1 or localhost, often by specifying launch flags or using environment variables. Some apps launch apps at 0.0.0.0 by default but we do not want this.
|
| 359 |
+
- Launch Port related
|
| 360 |
+
- In case the app itself automatically launches at the next available port by default (for example Gradio does this), do NOT specify port, since it's taken care of by the app itself. Always try to minimize the amount of code.
|
| 361 |
+
- If the install instruction says to launch at a specific port, don't use the hardcoded port they suggest since there's a risk of port conflicts. Instead, use Pinokio's `{{port}}` template expression to automatically get the next available port.
|
| 362 |
+
- For example, if the instruction says `python app.py --port 7860`, don't use that hardcoded port since there might be another app running at that port. Instead, automatically assign the next available port like this: `python app.py --port {{port}}`
|
| 363 |
+
- Note that the `{{port}}` expression always returns the next immediately available port for each step, so if you have multiple steps in a script and use `{{port}}` in multiple steps, the value will be different. So if you want to launch at the next available port and then later reuse that port, you will need to first use `{{port}}` to get the next available port, and save the value in local variable using `local.set`, and then use the `{{local.<variable_name>}}` expression later.
|
| 364 |
+
### 2. shell.run API
|
| 365 |
+
- When writing `shell.run` API requests, always use relative paths (no absolute paths) for the `path` field. For example, if you need to run a command from `app` folder, the `path` attribute should simply be `app`, instead of its full absolute path.
|
| 366 |
+
### 2. Package managers
|
| 367 |
+
- When installing python packages, try best to use `uv` instead of `pip` even if the install instruction says to use pip. Instead of `pip install -r requirements.txt`, you can simply use `uv pip install -r requirements.txt` for example. Even if the project's own README says use pip or poetry, first check if there's a way to use uv instead.
|
| 368 |
+
- When you need to install some global package, try to use `conda` as much as possible. Even on macs, `brew` should be only used if there are no `conda` options.
|
| 369 |
+
### 3. Minimal Always
|
| 370 |
+
- If you are starting with existing script files, before modifying, creating, or removing any script files, first look at `pinokio.js` to understand which script files are actually used in the launcher. The only script files used are the ones mentioned in the `pinokio.js` file. The `pinokio.js` file is the file that constructs the UI dynamically.
|
| 371 |
+
- Do not create a redundant script file that does something that already exists. Instead modify the existing script file for the feature. For example, do not create an `install.json` file for installation if `install.js` already exists. Instead, modify the `install.js` file.
|
| 372 |
+
- Pinokio accepts both JSON and JS script files, so when determining whether a script for a specific purpose already exists, check both JSON and JS files mentioned in the `pinokio.js` file. Do not create script files for rendundant purpose.
|
| 373 |
+
- When building launchers for existing projects cloned from a repository, try to stay away from modifying the project folder (the `C:\pinokio\api\Z-Image-Pinokio.git` folder), even if installations are failing. Instead, try to work around it by creating additional files in the launcher folder, and using those files IN ADDITION to the default project.
|
| 374 |
+
- The only exception when you may need to make changes to the project folder is when the user explicitly wants to modify the existing project. Otherwise if the purpose is to simply write a launcher, the app logic folder should never be touched.
|
| 375 |
+
- When running shell commands, take full advantage of the Pinokio `shell.run` API, which provides features like `env`, `venv`, `input`, `path`, `sudo`, `on`, etc. which can greatly reduce the amount of script code.
|
| 376 |
+
- Python apps: Always use virtual environments via `venv` attribute. This attribute automatically creates a venv or uses if it already exists.
|
| 377 |
+
### 4. Try to support Cross-platform as much as possible
|
| 378 |
+
- Use cross-platform shell commands only.
|
| 379 |
+
- This means, prefer to use commands that work on all platforms instead of the current platform.
|
| 380 |
+
- If there are no cross platform commands, use Pinokio's template expressions to conditionally use commands depending on `platform`, `arch`, etc.
|
| 381 |
+
- Also try to utilize Pinokio Pterm APIs for various cross-platform system features.
|
| 382 |
+
- If it is impossible to implement a cross platform solution (due to the nature of the project itself), set the `platform`, `arch`, and/or `gpu` attributes of the `pinokio.json` file to declare the limitation.
|
| 383 |
+
- Pinokio provides various APIs for cross-platform way of calling commonly used system functions, or lets you selectively run commands depending on `platform`, `arch`, etc.
|
| 384 |
+
### 5. Do not make assumptions about Pinokio API
|
| 385 |
+
- Do NOT make assumptions about which Pinokio APIs exist. Check the documentation.
|
| 386 |
+
- Do NOT make assumptions about the Pinokio API syntax. Follow the documentation.
|
| 387 |
+
### 6. Scripts must be able to replicate install and launch steps 100%
|
| 388 |
+
- The whole point of the scripts is for others to easily download and invoke them via Pinokio interface with one click. Therefore, do not assume the end user's system state, and make everything self-contained.
|
| 389 |
+
- When a 3rd party package needs to be installed, or a 3rd party repository needs to be downloaded, include them in the scripts.
|
| 390 |
+
### 7 Dynamic UI rendering
|
| 391 |
+
- The `pinokio.js` launcher script can change dynamically depending on the current state of the script execution. Which means, depending on what the file returns, it can determine what the sidebar looks like at any given moment of the script cycle.
|
| 392 |
+
- `info.exists(relative_path)`: The `info.exists` can be used to check whether a relative path (relative to the script root path) exists. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 393 |
+
- `info.running(relative_path)`: The `info.running` can be used to check whether a script at a relative path is currently running (relative to the script root path) exists. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 394 |
+
- `info.local(relative_path)`: The `info.local` can be used to return all the local variables tied to a script that's currently running. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 395 |
+
- `default`: set the `default` attribute on any menu item for whichever menu needs to be selected by default at a given step. Some example scenarios:
|
| 396 |
+
- during the install process, the `install.js` menu item needs to be set as the `default`, so it automatically executes the script
|
| 397 |
+
- when launching the `start.js` menu item needs to be set as the `default`, so it automatically executes the script
|
| 398 |
+
- after the app has launched, the `default` needs to be set on the web UI URL, so the user is sent to the actual app automatically.
|
| 399 |
+
- Check the examples in the C:\pinokio\prototype\system\examples folder to see how these are being used.
|
| 400 |
+
### 8. No need for stop scripts
|
| 401 |
+
- `pinokio.js` does NOT need a separate `stop` script. Every script that can be started can also be natively stopped through the Pinokio UI, therefore you do not need a separate stop script for start script
|
| 402 |
+
### 9. Writing launchers for existing projects
|
| 403 |
+
- When writing or modifying pinokio launcher scripts, figure out the install/launch steps by reading the project folder `app`.
|
| 404 |
+
- In most cases, the `README.md` file in the `C:\pinokio\api\Z-Image-Pinokio.git` folder contains the instructions needed to install and run the app, but if not, figure out by scanning the rest of the project files.
|
| 405 |
+
- Install scripts should work for each specific operating system, so ignore Docker related instructions. Instead use install/launch instructions for each platform.
|
| 406 |
+
### 10. Don't use Docker unless really necessary
|
| 407 |
+
- Some projects suggest docker as installation options. But even in these cases, try to find "development" options to launch the app without relying on Docker, as much as possible. We do not need Docker since we can automatically install and launch apps specifically for the user's platform, since we can write scripts that run cross platform.
|
| 408 |
+
### 11. pinokio.json
|
| 409 |
+
- Do not touch the `version` field since the version is the script schema version and the one pre-set in `pinokio.js` must be used.
|
| 410 |
+
- `icon`: It's best if we have a user friendly icon to represent the app, so try to get an image and link it from `pinokio.json`.
|
| 411 |
+
- If the git repository for the `C:\pinokio\api\Z-Image-Pinokio.git` folder points to GitHub (for example https://github.com/<USERNAME>/<REPO_NAME>`, ask the user if they want to download the icon from GitHub, and if approved, get the `avatar_url` by fetching `https://api.github.com/users/<USERNAME>`, and then download the image to the root folder as `icon.png`, and set `icon.png` as the `icon` field of the `pinokio.json`.
|
| 412 |
+
### 12. Gitignore
|
| 413 |
+
- When a launcher involves cloning 3rd party repositories, downloading files dynamically, or some files to be generated, these need to be included in the .gitignore file. This may include things like:
|
| 414 |
+
- Cloning git repositories
|
| 415 |
+
- Downloading files
|
| 416 |
+
- Dynamically creating files during installation or running, such as Sqlite Databases, or environment variables, or anything specific to the user.
|
| 417 |
+
- Make sure these file paths are included in the .gitignore file, and if not, include them in .gitignore.
|
| 418 |
+
|
| 419 |
+
## AI Libraries (Pytorch, Xformers, Triton, Sageattention, etc.)
|
| 420 |
+
If the launcher has a dedicated built-in script named `torch.js`, it can be used as follows:
|
| 421 |
+
|
| 422 |
+
```
|
| 423 |
+
// install.js
|
| 424 |
+
module.exports = {
|
| 425 |
+
run: [
|
| 426 |
+
// Delete this step if your project does not use torch
|
| 427 |
+
{
|
| 428 |
+
method: "script.start",
|
| 429 |
+
params: {
|
| 430 |
+
uri: "torch.js",
|
| 431 |
+
params: {
|
| 432 |
+
path: "app",
|
| 433 |
+
venv: "venv", // Edit this to customize the venv folder path
|
| 434 |
+
// xformers: true // uncomment this line if your project requires xformers
|
| 435 |
+
// triton: true // uncomment this line if your project requires triton
|
| 436 |
+
// sageattention: true // uncomment this line if your project requires sageattention
|
| 437 |
+
}
|
| 438 |
+
}
|
| 439 |
+
},
|
| 440 |
+
// Edit this step with your custom install commands
|
| 441 |
+
{
|
| 442 |
+
method: "shell.run",
|
| 443 |
+
params: {
|
| 444 |
+
venv: "venv", // Edit this to customize the venv folder path
|
| 445 |
+
path: "app",
|
| 446 |
+
message: [
|
| 447 |
+
"uv pip install -r requirements.txt"
|
| 448 |
+
],
|
| 449 |
+
}
|
| 450 |
+
},
|
| 451 |
+
]
|
| 452 |
+
}
|
| 453 |
+
```
|
| 454 |
+
|
| 455 |
+
The `torch.js` script also includes ways to install pytorch dependent libraries such as xformers, triton, sagetattention. If any of these libraries need to be installed, use the torch.js to install in order to install them cross platform.
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
## Quick Reference
|
| 459 |
+
### Essential Documentation
|
| 460 |
+
- **Pinokio Programming:** See `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md → "Programming Pinokio" section
|
| 461 |
+
- **Dynamic Menus:** See `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md → "Dynamic menu rendering" section
|
| 462 |
+
- **CLI Commands:** See `PTERM.md` at C:\pinokio\prototype\PTERM.md
|
| 463 |
+
### Common Patterns
|
| 464 |
+
- **Python Virtual Env:** `shell.run` with `venv` attribute
|
| 465 |
+
- **Cross-platform Commands:** Always test on multiple platforms
|
| 466 |
+
- **Error Handling:** Check logs/api for launcher issues
|
| 467 |
+
- **GitHub Operations:** Use `gh` CLI for advanced GitHub features
|
| 468 |
+
## Development Principles
|
| 469 |
+
1. **Minimize Shell Usage:** Leverage API parameters instead of raw commands
|
| 470 |
+
2. **Maintain Separation:** Keep app logic and launchers separate
|
| 471 |
+
3. **Follow Conventions:** Match existing project patterns
|
| 472 |
+
4. **Test Thoroughly:** Use CLI to verify launcher functionality
|
| 473 |
+
5. **Document Changes:** Update relevant metadata and documentation
|
.cursorrules
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Development Guide for Pinokio Projects
|
| 2 |
+
|
| 3 |
+
## Non-Negotiable Execution Workflow
|
| 4 |
+
|
| 5 |
+
To guarantee every contribution follows this guide precisely, obey this checklist **before any edits** and **again before finalizing**. Do not skip or reorder.
|
| 6 |
+
1. **AGENTS Snapshot:** Re-open this file and write down (in your working notes or response draft) the exact sections relevant to the requested task. No work begins until this snapshot exists.
|
| 7 |
+
2. **Example Lock-in:** Identify the closest matching script in `C:\pinokio\prototype\system\examples`. Record its path and keep it open while editing. Every launcher change must mirror that reference unless the user explicitly instructs otherwise.
|
| 8 |
+
3. **Pre-flight Checklist:** Convert the applicable rules from this document and `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md into a task-specific checklist (install/start/reset/update structure, regex patterns, menu defaults, log checks, etc.). Confirm each item is ticked **before** making changes.
|
| 9 |
+
4. **Mid-task Verification:** Any time you touch a Pinokio script, cross-check the corresponding example line to ensure syntax and structure match. Document the reference (example path + line) in your reasoning.
|
| 10 |
+
5. **Exit Checklist:** Before responding to the user, revisit the pre-flight checklist and explicitly confirm every item is satisfied. If anything diverges from the example or these rules, fix it first.
|
| 11 |
+
|
| 12 |
+
If any step cannot be completed, stop immediately and ask the user how to proceed. These five steps are mandatory for every session.
|
| 13 |
+
|
| 14 |
+
### Critical Pattern Lock: Capturing Web UI URLs
|
| 15 |
+
|
| 16 |
+
When writing `start.js` (or any script that needs to surface a web URL for a server):
|
| 17 |
+
|
| 18 |
+
1. **Always copy the capture block from an example such as `system/examples/mochi/start.js`.**
|
| 19 |
+
```javascript
|
| 20 |
+
on: [{
|
| 21 |
+
event: "/(http:\\/\\/[0-9.:]+)/",
|
| 22 |
+
done: true
|
| 23 |
+
}]
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
2. **Set the local variable using the captured match exactly as below (The regex capture object is passed in as `input.event`, so need to use the index 1 inside the parenthesis):**
|
| 27 |
+
```javascript
|
| 28 |
+
{
|
| 29 |
+
method: "local.set",
|
| 30 |
+
params: {
|
| 31 |
+
url: "{{input.event[1]}}"
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
3. Always try to come up with the most generic regex.
|
| 37 |
+
4. During the exit checklist, explicitly confirm that the `url` local variable is set via `local.set` API by using the captured regex object as passed in as `input.event` from the previous `shell.run` step.
|
| 38 |
+
|
| 39 |
+
Deviation from this pattern requires written approval from the user.
|
| 40 |
+
|
| 41 |
+
- Make sure to keep this entire document and `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md in memory with high priority before making any decision. Pinokio is a system that makes it easy to write launchers through scripting by providing various cross-platform APIs, so whenever possible you should prioritize using Pinokio API over lower level APIs.
|
| 42 |
+
- When writing pinokio scripts, ALWAYS check the examples folder (in C:\pinokio\prototype\system\examples folder) to see if there are existing example scripts you can imitate, instead of assuming syntax.
|
| 43 |
+
- When implementing pinokio script APIs and you cannot infer the syntax just based on the examples, always search the API documentation `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md to use the correct syntax instead of assuming the syntax.
|
| 44 |
+
- When trying to fix something or figure out what's going on, ALWAYS start by checking the `logs` folder before doing anything else, as mentioned in the "Troubleshooting with Logs" section.
|
| 45 |
+
- Finally, make sure to ALWAYS follow all the items in the "best practices" section below.
|
| 46 |
+
|
| 47 |
+
## Determine User Intent
|
| 48 |
+
If the initial prompt is simply a URL and nothing else, check the website content and determine the intent, and ask the user to confirm. For example a URL may point to
|
| 49 |
+
|
| 50 |
+
1. A Tutorial: the intent may be to implement a demo for the tutorial and build a launcher.
|
| 51 |
+
2. A Demo: the intent may be a 1-click launcher for the demo
|
| 52 |
+
3. Open source project: the intent may be a 1-click launcher for the project
|
| 53 |
+
4. Regular website: the intent may be to clone the website and a launcher.
|
| 54 |
+
5. There can be other cases, but try to guess.
|
| 55 |
+
|
| 56 |
+
## Project Structure
|
| 57 |
+
|
| 58 |
+
Pinokio projects normally follow a standardized structure with app logic separated from launcher scripts:
|
| 59 |
+
|
| 60 |
+
Pinokio projects follow a standardized structure with app logic separated from launcher scripts:
|
| 61 |
+
|
| 62 |
+
```
|
| 63 |
+
project-root/
|
| 64 |
+
├── app/ # Self-contained app logic (can be standalone repo)
|
| 65 |
+
│ ├── package.json # Node.js projects
|
| 66 |
+
│ ├── requirements.txt # Python projects
|
| 67 |
+
│ └── ... # Other language-specific files
|
| 68 |
+
├── README.md # Documentation
|
| 69 |
+
├── install.js # Installation script
|
| 70 |
+
├── start.js # Launch script
|
| 71 |
+
├── update.js # Update script (for updating the scripts and app logic to the latest)
|
| 72 |
+
├── reset.js # Reset dependencies script
|
| 73 |
+
├── pinokio.js # UI generator script
|
| 74 |
+
└── pinokio.json # Metadata (title, description, icon)
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
- Keep app code in `/app` folder only (never in root)
|
| 78 |
+
- Store all launcher files in project root (never in `/app`)
|
| 79 |
+
- `/app` folder should be self-contained and publishable
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
The only exceptions are serverless web apps---purely frontend only web applications that do NOT have a server component and connect to 3rd party API endpoints--in which case the folder structure looks like the following (No need for launcher scripts since the index.html will automatically launch. The only thing needed is the metadata file named pinokio.json):
|
| 83 |
+
|
| 84 |
+
```
|
| 85 |
+
project-root/
|
| 86 |
+
├── index.html # The serverless web app entry point
|
| 87 |
+
├── ...
|
| 88 |
+
├── README.md # Documentation
|
| 89 |
+
└── pinokio.json # Metadata (title, description, icon)
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
IMPORTANT: ALWAYS try to follow the best practices in the examples folder (C:\pinokio\prototype\system\examples) instead of trying to come up with your own structure. The examples have been optimized for the best user experience.
|
| 93 |
+
|
| 94 |
+
## Launcher Project Working Directory
|
| 95 |
+
|
| 96 |
+
- The project working directory for a script is always the same directory as the script location.
|
| 97 |
+
- For example, when you run `shell.run` API inside `pinokio/start.js`, the default path for shell execution is `pinokio`.
|
| 98 |
+
- If the launcher files are in the project root path, then the default path for shell execution is the project root.
|
| 99 |
+
- Therefore, it is important to specify the correct `path` attribute when running `shell.run` API commands.
|
| 100 |
+
|
| 101 |
+
Example: in the following project structure:
|
| 102 |
+
|
| 103 |
+
```
|
| 104 |
+
project-root/
|
| 105 |
+
├── pinokio/ # Pinokio launcher folder
|
| 106 |
+
│ ├── start.js # Launch script
|
| 107 |
+
│ ├── pinokio.js # UI generator script
|
| 108 |
+
│ └── pinokio.json # Metadata (title, description, icon)
|
| 109 |
+
└─── backend/
|
| 110 |
+
├── requirements.txt # App dependencies
|
| 111 |
+
└── app.py # App code
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
The `pinokio/start.js` should use the correct path `../backend` as the `path` attribute, as follows:
|
| 115 |
+
|
| 116 |
+
```
|
| 117 |
+
{
|
| 118 |
+
run: [{
|
| 119 |
+
...
|
| 120 |
+
}, {
|
| 121 |
+
method: "shell.run",
|
| 122 |
+
params: {
|
| 123 |
+
message: "python app.py",
|
| 124 |
+
venv: "env",
|
| 125 |
+
path: "../backend"
|
| 126 |
+
}
|
| 127 |
+
}, {
|
| 128 |
+
...
|
| 129 |
+
}]
|
| 130 |
+
}
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
## Development Workflow
|
| 134 |
+
|
| 135 |
+
### 1. Understanding the Project
|
| 136 |
+
- Check `SPEC.md` in project root. If the file exists, use that to learn about the project details (what and how to build)
|
| 137 |
+
- If no `SPEC.md` exists, build based on user requirements
|
| 138 |
+
### 2. Modifying Existing Launcher Projects
|
| 139 |
+
If we are starting with existing launcher script files, work with the existing files instead of coming up with your own.
|
| 140 |
+
- **Preserve existing functionality:** Only modify necessary parts
|
| 141 |
+
- **Don't touch working scripts:** Unless adding/updating specific commands
|
| 142 |
+
- **Follow existing conventions:** Match the style and structure already present
|
| 143 |
+
### 3. Try to adopt from examples as much as possible
|
| 144 |
+
- If starting from scratch, first determine what type of project you will be building, and then check the examples folder (C:\pinokio\prototype\system\examples) to see if you can adopt them instead of coming up everything from scratch.
|
| 145 |
+
- Even if there are no relevant examples, check the examples to get inspiration for how you would structure the script files even if you have to write from scratch.
|
| 146 |
+
### 4. Writing from scratch as a last resort
|
| 147 |
+
If there are relevant examples to adopt from, write the scripts from scratch, but just make sure to follow the requirements in the next section.
|
| 148 |
+
### 5. Debugging
|
| 149 |
+
When the user reports something is not working, ALWAYS inspect the logs folder to get all the execution logs. For more info on how this works, check the "Troubleshooting with Logs" section below.
|
| 150 |
+
|
| 151 |
+
## Script Requirements
|
| 152 |
+
|
| 153 |
+
### 1. 1-click launchable
|
| 154 |
+
- The main purpose of Pinokio is to provide an easy interface to invoke commands, which may include launching servers, installing programs, etc. Make sure the final product provides ways to install, launch, reset, and update whatever is needed.
|
| 155 |
+
|
| 156 |
+
### 2. Write Documentation
|
| 157 |
+
- ALWAYS write a documentation. A documentation must be stored as `README.md` in the project root folder, along with the rest of the pinokio launcher script files. A documentation file must contain:
|
| 158 |
+
- What the app does
|
| 159 |
+
- How to use the app
|
| 160 |
+
- API documentation for programmatically accessing the app's main features (Javascript, Python, and Curl)
|
| 161 |
+
|
| 162 |
+
## Types of launchers
|
| 163 |
+
## 1. Launching servers
|
| 164 |
+
- When an app requires launching a server, here are the commonly used scripts:
|
| 165 |
+
- `install.js`: a script to install the app
|
| 166 |
+
- `start.js`: a script to start the app
|
| 167 |
+
- `reset.js`: a script to reset all the dependencies installed in the `install.js` step. used if the user wants to restart from scratch
|
| 168 |
+
- `update.js`: a script to update the launcher AND the app in case there are new updates. Involves pulling in the relevant git repositories installed through `install.js` (often it's the script repo and some git repositories cloned through the install steps if any)
|
| 169 |
+
- `pinokio.js`: the launcher script that ties all of the above scripts together by providing a UI that links to these scripts.
|
| 170 |
+
- `pinokio.json`: For metadata
|
| 171 |
+
|
| 172 |
+
Here's a basic server launcher script example (`start.js`). Unless there's a special reason you need to use another pattern, this is the most recommended pattern. Use this or adopt it as needed, but NEVER try something else unless there's a good reason you should not take this approach:
|
| 173 |
+
|
| 174 |
+
```javascript
|
| 175 |
+
module.exports = {
|
| 176 |
+
// By setting daemon: true, the script keeps running even after all items in the `run` array finishes running. Mandatory for launching servers, since otherwise the shells running the server process will get killed after the scripts finish running.
|
| 177 |
+
daemon: true,
|
| 178 |
+
run: [
|
| 179 |
+
{
|
| 180 |
+
// The "shell.run" API for running a shell session
|
| 181 |
+
method: "shell.run",
|
| 182 |
+
params: {
|
| 183 |
+
// Edit 'venv' to customize the venv folder path
|
| 184 |
+
venv: "env",
|
| 185 |
+
// Edit 'env' to customize environment variables (see documentation)
|
| 186 |
+
env: { },
|
| 187 |
+
// Edit 'path' to customize the path to start the shell from
|
| 188 |
+
path: "app",
|
| 189 |
+
// Edit 'message' to customize the commands, or to run multiple commands
|
| 190 |
+
message: [
|
| 191 |
+
"python app.py",
|
| 192 |
+
],
|
| 193 |
+
on: [{
|
| 194 |
+
// The regular expression pattern to monitor.
|
| 195 |
+
// Whenever each "event" pattern occurs in the shell terminal, the shell will return,
|
| 196 |
+
// and the script will go onto the next step.
|
| 197 |
+
// The regular expression match object will be passed on to the next step as `input.event`
|
| 198 |
+
// Useful for capturing the URL at which the server is running (in case the server prints some message about where the server is running)
|
| 199 |
+
"event": "/(http:\/\/\\S+)/",
|
| 200 |
+
|
| 201 |
+
// Use "done": true to move to the next step while keeping the shell alive.
|
| 202 |
+
// Use "kill": true to move to the next step after killing the shell.
|
| 203 |
+
"done": true
|
| 204 |
+
}]
|
| 205 |
+
}
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
// This step sets the local variable 'url'.
|
| 209 |
+
// This local variable will be used in pinokio.js to display the "Open WebUI" tab when the value is set.
|
| 210 |
+
method: "local.set",
|
| 211 |
+
params: {
|
| 212 |
+
// the input.event is the regular expression match object from the previous step
|
| 213 |
+
// In this example, since the pattern was "/(http:\/\/\\S+)/", input.event[1] will include the exact http url match caputred by the parenthesis.
|
| 214 |
+
// Therefore setting the local variable 'url'
|
| 215 |
+
url: "{{input.event[1]}}"
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
]
|
| 219 |
+
}
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
## 2. Launching serverless web apps
|
| 223 |
+
|
| 224 |
+
- In case of purely static web apps WITHOUT servers or backends (for example an HTML based app that connects to 3rd party servers--either remote or localhost), we do NOT need the launcher scripts.
|
| 225 |
+
- In these cases, simply include `index.html` in the project root folder and everything should automatically work. No need for any of the pinokio launcher scripts. (Do
|
| 226 |
+
- You still need to include the metadata file so they show up properly on pinokio:
|
| 227 |
+
- `pinokio.json`: For metadata
|
| 228 |
+
|
| 229 |
+
## 3. Launching quick scripts without web UI
|
| 230 |
+
|
| 231 |
+
- In many cases, we may not even need a web UI, but instead just a simple way to run scripts.
|
| 232 |
+
- This may include TUI (Terminal User Interface) apps, a simple launcher
|
| 233 |
+
- In these cases, all we need is the launcher file `pinokio.js`, which may link to multiple scripts. In this case, there are no web apps (no serverless apsp, no servers), but instead just the default pinokio launcher UI that calls a bunch of scripts.
|
| 234 |
+
- Here are some examples:
|
| 235 |
+
- A pinokio script to toggle the desktop theme between dark and light
|
| 236 |
+
- Write some code (python or javascript or whatever)
|
| 237 |
+
- Write a `toggle.js` pinokio script that executes the code
|
| 238 |
+
- Write a `pinokio.js` launcher script to create a sidebar UI that displays the `toggle.js` so the user can simply click the "toggle" button to toggle back and forth between desktop themes
|
| 239 |
+
- A pinokio script to fetch some file
|
| 240 |
+
- Write some code (python or javascript or whatever)
|
| 241 |
+
- Write a `fetch.js` pinokio script that executes the code
|
| 242 |
+
- Write a `pinokio.js` launcher script to create a sidebar UI that displays the `fetch.js` so the user can simply click the "fetch" button to fetch some data.
|
| 243 |
+
- You still need to include the metadata file so they show up properly on pinokio:
|
| 244 |
+
- `pinokio.json`: For metadata
|
| 245 |
+
|
| 246 |
+
## API
|
| 247 |
+
|
| 248 |
+
This section lists all the script APIs available on Pinokio. To learn the details of how they are used, you can:
|
| 249 |
+
1. Check the examples in the C:\pinokio\prototype\system\examples folder
|
| 250 |
+
2. Read the `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md further documentation on the full syntax
|
| 251 |
+
|
| 252 |
+
### Script API
|
| 253 |
+
|
| 254 |
+
These APIs can be used to describe each step in a pinokio script:
|
| 255 |
+
- shell.run: run shell commands
|
| 256 |
+
- input: accept user input
|
| 257 |
+
- filepicker: accept file upload
|
| 258 |
+
- fs.write: write to file
|
| 259 |
+
- fs.read: read from file
|
| 260 |
+
- fs.copy: copy files
|
| 261 |
+
- fs.download: download files
|
| 262 |
+
- fs.link: create a symbolic link (or junction on windows) for folders
|
| 263 |
+
- fs.open: open the system file explorer at a given path
|
| 264 |
+
- fs.cat: print file contents
|
| 265 |
+
- jump: jump to a specific step
|
| 266 |
+
- local.set: set local variables for the currently running script
|
| 267 |
+
- json.set: update a json file
|
| 268 |
+
- json.rm: remove keys from a json file
|
| 269 |
+
- json.get: get values from a json file
|
| 270 |
+
- log: print to the web terminal
|
| 271 |
+
- net: make network requests
|
| 272 |
+
- notify: display a notification
|
| 273 |
+
- script.download: download a script from a git uri
|
| 274 |
+
- script.start: start a script
|
| 275 |
+
- script.stop: stop a script
|
| 276 |
+
- script.return: return values if the current script was called by a caller script, so the caller script can utilize the return value as `input`
|
| 277 |
+
- web.open: open a url in web browser
|
| 278 |
+
- hf.download: huggingfac-cli download API
|
| 279 |
+
### Template variables
|
| 280 |
+
The following variables are accessible inside template expressions (example `{{args.command}` in scripts, resulting in dynamic behaviors of scripts:
|
| 281 |
+
- input: An input is a variable that gets passed from one RPC call to the next
|
| 282 |
+
- args: args is the parameter object that gets passed into the script (via pinokio.js `params`). Unlike `input` which takes the value passed in from the immediately previous step, `args` is a global value that is the same through out the entire script execution.
|
| 283 |
+
- local: local variable object that can be set with `local.set` API
|
| 284 |
+
- self: refers to the script file itself (which is JSON or JavaScript). For example if `start.js` that's currently running has `daemon: true` set, `{{self.daemon}}` will evaluate to true.
|
| 285 |
+
- uri: The current script uri
|
| 286 |
+
- port: The next available port. Very useful when you need to launch an app at a specific port without port conflicts.
|
| 287 |
+
- cwd: The current script execution folder path
|
| 288 |
+
- platform: The current operating system. May be one of the following: `darwin`, `win32`, `linux`
|
| 289 |
+
- arch: The current system architecture. May be one of the following: x32, x64, arm, arm64, s390, s390x, mipsel, ia32, mips, ppc, ppc64
|
| 290 |
+
- gpus: array of available GPUs on the machine (example: `['apple']`, `['nvidia']`)
|
| 291 |
+
- gpu: the first available GPU (example: `nvidia`)
|
| 292 |
+
- current: The current variable points to the index of the currently executing instruction within the run array.
|
| 293 |
+
- next: The next variable points to the index of the next instruction to be executed. (null if the current instruction is the final instruction in the run array)
|
| 294 |
+
- envs: You can access the environment variables of the currently running process with envs object.
|
| 295 |
+
- which: Check whether a command exists (example: `{{which('winget')}}`. Can be used in the `when` attribute of a script step to run commands or install first.
|
| 296 |
+
- exists: Check whether a file or folder exists at the specified relative path (example: `"when": "{{!exists('app')}}"`). Can be used with the `when` attribute to determine a path's existence and trigger custom logic. Use relative paths and it will resolve automatically to the current execution folder.
|
| 297 |
+
- running: Check whether a script file is running (example: `"when": "{{!running('start.js')}}"`). Can be used with the `when` attribute to determine a path's existence and trigger custom logic. Use relative paths and it will resolve automatically to the current execution folder.
|
| 298 |
+
- os: Pinokio exposes the node.js os module through the os variable.
|
| 299 |
+
- path: Pinokio exposes the node.js path module through the os variable (example: `{{path.resolve(...)}}`
|
| 300 |
+
|
| 301 |
+
## System Capabilities
|
| 302 |
+
### Package Management (Use in Order of Preference)
|
| 303 |
+
The following package managers come pre-installed with Pinokio, so whenever you need to install a 3rd party binary, remember that these are available. Also, you can assume these are available and include the following package manager commands in Pinokio scripts:
|
| 304 |
+
1. **UV** - For Python packages (preferred over pip)
|
| 305 |
+
2. **NPM** - For Node.js packages
|
| 306 |
+
3. **Conda** - For cross-platform 3rd party binaries
|
| 307 |
+
4. **Brew** - Mac-only fallback when other options unavailable
|
| 308 |
+
5. **Git** - Full access to git is available.
|
| 309 |
+
**Important:** Include all install commands in the install script for reproducibility.
|
| 310 |
+
### HTTPS Proxy Support
|
| 311 |
+
- All HTTP servers automatically get HTTPS endpoints
|
| 312 |
+
- Convention: `http://localhost:<PORT>` → `https://<PORT>.localhost`
|
| 313 |
+
- Full proxy list available at: `http://localhost:2019/config/`
|
| 314 |
+
### Pterm Features:
|
| 315 |
+
- **Clipboard Access:** Read from or Write to system clipboard via pinokio Pterm CLI (`pterm clipboard` command.)
|
| 316 |
+
- **Notifications:** Send desktop alerts via pinokio pterm CLI (`pterm push` command.)
|
| 317 |
+
- **Script Testing:** Run launcher scripts via pinokio pterm CLI (`pterm start` command.)
|
| 318 |
+
- **File Selection:** Use built-in filepicker for user file/folder input (`pterm filepicker` command.)
|
| 319 |
+
- **Git Operations:** Clone repositories, push to GitHub
|
| 320 |
+
- **GitHub Integration:** Full GitHub CLI support (`gh` commands)
|
| 321 |
+
|
| 322 |
+
## Troubleshooting with Logs
|
| 323 |
+
Pinokio stores the logs for everything that happened in terminal at the following locations, so you can make use of them to determine what's going on:
|
| 324 |
+
|
| 325 |
+
### Log Structure
|
| 326 |
+
In case there is a `pinokio` folder in the project root folder, you should be able to find the logs folder here:
|
| 327 |
+
|
| 328 |
+
```
|
| 329 |
+
pinokio/
|
| 330 |
+
└── logs/ # Direct user interaction logs
|
| 331 |
+
├── api/ # Launcher script logs (install.js, start.js, etc.)
|
| 332 |
+
├── dev/ # AI coding tool logs (organized by tool)
|
| 333 |
+
└── shell/ # Direct user interaction logs
|
| 334 |
+
```
|
| 335 |
+
|
| 336 |
+
Otherwise, the `logs` folder should be found at project root:
|
| 337 |
+
|
| 338 |
+
```
|
| 339 |
+
logs/
|
| 340 |
+
├── api/ # Launcher script logs (install.js, start.js, etc.)
|
| 341 |
+
├── dev/ # AI coding tool logs (organized by tool)
|
| 342 |
+
└── shell/ # Direct user interaction logs
|
| 343 |
+
```
|
| 344 |
+
|
| 345 |
+
### Log File Naming
|
| 346 |
+
- Unix timestamps for each session
|
| 347 |
+
- Special "latest" file contains most recent session logs
|
| 348 |
+
- **Default:** Use "latest" files for current issues
|
| 349 |
+
- **Historical:** Use timestamped files for pattern analysis and the full history.
|
| 350 |
+
|
| 351 |
+
## Best practices
|
| 352 |
+
### 0. Always reference the logs when debugging
|
| 353 |
+
- When the user asks to fix something, ALWAYS check the logs folder first to check what went wrong. Check the "Troubleshooting with Logs" section.
|
| 354 |
+
### 1. Shell commands for launching programs
|
| 355 |
+
- Launch flags related
|
| 356 |
+
- Try as hard as possible to minimize launch flags and parameters when launching an app. For example, instead of `python app.py --port 8610`, try to do `python app.py` unless really necessary. The only exception is when the only way to launch the app is to specify the flags.
|
| 357 |
+
- Launch IP related
|
| 358 |
+
- Always try to find a way to launch servers at 127.0.0.1 or localhost, often by specifying launch flags or using environment variables. Some apps launch apps at 0.0.0.0 by default but we do not want this.
|
| 359 |
+
- Launch Port related
|
| 360 |
+
- In case the app itself automatically launches at the next available port by default (for example Gradio does this), do NOT specify port, since it's taken care of by the app itself. Always try to minimize the amount of code.
|
| 361 |
+
- If the install instruction says to launch at a specific port, don't use the hardcoded port they suggest since there's a risk of port conflicts. Instead, use Pinokio's `{{port}}` template expression to automatically get the next available port.
|
| 362 |
+
- For example, if the instruction says `python app.py --port 7860`, don't use that hardcoded port since there might be another app running at that port. Instead, automatically assign the next available port like this: `python app.py --port {{port}}`
|
| 363 |
+
- Note that the `{{port}}` expression always returns the next immediately available port for each step, so if you have multiple steps in a script and use `{{port}}` in multiple steps, the value will be different. So if you want to launch at the next available port and then later reuse that port, you will need to first use `{{port}}` to get the next available port, and save the value in local variable using `local.set`, and then use the `{{local.<variable_name>}}` expression later.
|
| 364 |
+
### 2. shell.run API
|
| 365 |
+
- When writing `shell.run` API requests, always use relative paths (no absolute paths) for the `path` field. For example, if you need to run a command from `app` folder, the `path` attribute should simply be `app`, instead of its full absolute path.
|
| 366 |
+
### 2. Package managers
|
| 367 |
+
- When installing python packages, try best to use `uv` instead of `pip` even if the install instruction says to use pip. Instead of `pip install -r requirements.txt`, you can simply use `uv pip install -r requirements.txt` for example. Even if the project's own README says use pip or poetry, first check if there's a way to use uv instead.
|
| 368 |
+
- When you need to install some global package, try to use `conda` as much as possible. Even on macs, `brew` should be only used if there are no `conda` options.
|
| 369 |
+
### 3. Minimal Always
|
| 370 |
+
- If you are starting with existing script files, before modifying, creating, or removing any script files, first look at `pinokio.js` to understand which script files are actually used in the launcher. The only script files used are the ones mentioned in the `pinokio.js` file. The `pinokio.js` file is the file that constructs the UI dynamically.
|
| 371 |
+
- Do not create a redundant script file that does something that already exists. Instead modify the existing script file for the feature. For example, do not create an `install.json` file for installation if `install.js` already exists. Instead, modify the `install.js` file.
|
| 372 |
+
- Pinokio accepts both JSON and JS script files, so when determining whether a script for a specific purpose already exists, check both JSON and JS files mentioned in the `pinokio.js` file. Do not create script files for rendundant purpose.
|
| 373 |
+
- When building launchers for existing projects cloned from a repository, try to stay away from modifying the project folder (the `C:\pinokio\api\Z-Image-Pinokio.git` folder), even if installations are failing. Instead, try to work around it by creating additional files in the launcher folder, and using those files IN ADDITION to the default project.
|
| 374 |
+
- The only exception when you may need to make changes to the project folder is when the user explicitly wants to modify the existing project. Otherwise if the purpose is to simply write a launcher, the app logic folder should never be touched.
|
| 375 |
+
- When running shell commands, take full advantage of the Pinokio `shell.run` API, which provides features like `env`, `venv`, `input`, `path`, `sudo`, `on`, etc. which can greatly reduce the amount of script code.
|
| 376 |
+
- Python apps: Always use virtual environments via `venv` attribute. This attribute automatically creates a venv or uses if it already exists.
|
| 377 |
+
### 4. Try to support Cross-platform as much as possible
|
| 378 |
+
- Use cross-platform shell commands only.
|
| 379 |
+
- This means, prefer to use commands that work on all platforms instead of the current platform.
|
| 380 |
+
- If there are no cross platform commands, use Pinokio's template expressions to conditionally use commands depending on `platform`, `arch`, etc.
|
| 381 |
+
- Also try to utilize Pinokio Pterm APIs for various cross-platform system features.
|
| 382 |
+
- If it is impossible to implement a cross platform solution (due to the nature of the project itself), set the `platform`, `arch`, and/or `gpu` attributes of the `pinokio.json` file to declare the limitation.
|
| 383 |
+
- Pinokio provides various APIs for cross-platform way of calling commonly used system functions, or lets you selectively run commands depending on `platform`, `arch`, etc.
|
| 384 |
+
### 5. Do not make assumptions about Pinokio API
|
| 385 |
+
- Do NOT make assumptions about which Pinokio APIs exist. Check the documentation.
|
| 386 |
+
- Do NOT make assumptions about the Pinokio API syntax. Follow the documentation.
|
| 387 |
+
### 6. Scripts must be able to replicate install and launch steps 100%
|
| 388 |
+
- The whole point of the scripts is for others to easily download and invoke them via Pinokio interface with one click. Therefore, do not assume the end user's system state, and make everything self-contained.
|
| 389 |
+
- When a 3rd party package needs to be installed, or a 3rd party repository needs to be downloaded, include them in the scripts.
|
| 390 |
+
### 7 Dynamic UI rendering
|
| 391 |
+
- The `pinokio.js` launcher script can change dynamically depending on the current state of the script execution. Which means, depending on what the file returns, it can determine what the sidebar looks like at any given moment of the script cycle.
|
| 392 |
+
- `info.exists(relative_path)`: The `info.exists` can be used to check whether a relative path (relative to the script root path) exists. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 393 |
+
- `info.running(relative_path)`: The `info.running` can be used to check whether a script at a relative path is currently running (relative to the script root path) exists. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 394 |
+
- `info.local(relative_path)`: The `info.local` can be used to return all the local variables tied to a script that's currently running. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 395 |
+
- `default`: set the `default` attribute on any menu item for whichever menu needs to be selected by default at a given step. Some example scenarios:
|
| 396 |
+
- during the install process, the `install.js` menu item needs to be set as the `default`, so it automatically executes the script
|
| 397 |
+
- when launching the `start.js` menu item needs to be set as the `default`, so it automatically executes the script
|
| 398 |
+
- after the app has launched, the `default` needs to be set on the web UI URL, so the user is sent to the actual app automatically.
|
| 399 |
+
- Check the examples in the C:\pinokio\prototype\system\examples folder to see how these are being used.
|
| 400 |
+
### 8. No need for stop scripts
|
| 401 |
+
- `pinokio.js` does NOT need a separate `stop` script. Every script that can be started can also be natively stopped through the Pinokio UI, therefore you do not need a separate stop script for start script
|
| 402 |
+
### 9. Writing launchers for existing projects
|
| 403 |
+
- When writing or modifying pinokio launcher scripts, figure out the install/launch steps by reading the project folder `app`.
|
| 404 |
+
- In most cases, the `README.md` file in the `C:\pinokio\api\Z-Image-Pinokio.git` folder contains the instructions needed to install and run the app, but if not, figure out by scanning the rest of the project files.
|
| 405 |
+
- Install scripts should work for each specific operating system, so ignore Docker related instructions. Instead use install/launch instructions for each platform.
|
| 406 |
+
### 10. Don't use Docker unless really necessary
|
| 407 |
+
- Some projects suggest docker as installation options. But even in these cases, try to find "development" options to launch the app without relying on Docker, as much as possible. We do not need Docker since we can automatically install and launch apps specifically for the user's platform, since we can write scripts that run cross platform.
|
| 408 |
+
### 11. pinokio.json
|
| 409 |
+
- Do not touch the `version` field since the version is the script schema version and the one pre-set in `pinokio.js` must be used.
|
| 410 |
+
- `icon`: It's best if we have a user friendly icon to represent the app, so try to get an image and link it from `pinokio.json`.
|
| 411 |
+
- If the git repository for the `C:\pinokio\api\Z-Image-Pinokio.git` folder points to GitHub (for example https://github.com/<USERNAME>/<REPO_NAME>`, ask the user if they want to download the icon from GitHub, and if approved, get the `avatar_url` by fetching `https://api.github.com/users/<USERNAME>`, and then download the image to the root folder as `icon.png`, and set `icon.png` as the `icon` field of the `pinokio.json`.
|
| 412 |
+
### 12. Gitignore
|
| 413 |
+
- When a launcher involves cloning 3rd party repositories, downloading files dynamically, or some files to be generated, these need to be included in the .gitignore file. This may include things like:
|
| 414 |
+
- Cloning git repositories
|
| 415 |
+
- Downloading files
|
| 416 |
+
- Dynamically creating files during installation or running, such as Sqlite Databases, or environment variables, or anything specific to the user.
|
| 417 |
+
- Make sure these file paths are included in the .gitignore file, and if not, include them in .gitignore.
|
| 418 |
+
|
| 419 |
+
## AI Libraries (Pytorch, Xformers, Triton, Sageattention, etc.)
|
| 420 |
+
If the launcher has a dedicated built-in script named `torch.js`, it can be used as follows:
|
| 421 |
+
|
| 422 |
+
```
|
| 423 |
+
// install.js
|
| 424 |
+
module.exports = {
|
| 425 |
+
run: [
|
| 426 |
+
// Delete this step if your project does not use torch
|
| 427 |
+
{
|
| 428 |
+
method: "script.start",
|
| 429 |
+
params: {
|
| 430 |
+
uri: "torch.js",
|
| 431 |
+
params: {
|
| 432 |
+
path: "app",
|
| 433 |
+
venv: "venv", // Edit this to customize the venv folder path
|
| 434 |
+
// xformers: true // uncomment this line if your project requires xformers
|
| 435 |
+
// triton: true // uncomment this line if your project requires triton
|
| 436 |
+
// sageattention: true // uncomment this line if your project requires sageattention
|
| 437 |
+
}
|
| 438 |
+
}
|
| 439 |
+
},
|
| 440 |
+
// Edit this step with your custom install commands
|
| 441 |
+
{
|
| 442 |
+
method: "shell.run",
|
| 443 |
+
params: {
|
| 444 |
+
venv: "venv", // Edit this to customize the venv folder path
|
| 445 |
+
path: "app",
|
| 446 |
+
message: [
|
| 447 |
+
"uv pip install -r requirements.txt"
|
| 448 |
+
],
|
| 449 |
+
}
|
| 450 |
+
},
|
| 451 |
+
]
|
| 452 |
+
}
|
| 453 |
+
```
|
| 454 |
+
|
| 455 |
+
The `torch.js` script also includes ways to install pytorch dependent libraries such as xformers, triton, sagetattention. If any of these libraries need to be installed, use the torch.js to install in order to install them cross platform.
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
## Quick Reference
|
| 459 |
+
### Essential Documentation
|
| 460 |
+
- **Pinokio Programming:** See `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md → "Programming Pinokio" section
|
| 461 |
+
- **Dynamic Menus:** See `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md → "Dynamic menu rendering" section
|
| 462 |
+
- **CLI Commands:** See `PTERM.md` at C:\pinokio\prototype\PTERM.md
|
| 463 |
+
### Common Patterns
|
| 464 |
+
- **Python Virtual Env:** `shell.run` with `venv` attribute
|
| 465 |
+
- **Cross-platform Commands:** Always test on multiple platforms
|
| 466 |
+
- **Error Handling:** Check logs/api for launcher issues
|
| 467 |
+
- **GitHub Operations:** Use `gh` CLI for advanced GitHub features
|
| 468 |
+
## Development Principles
|
| 469 |
+
1. **Minimize Shell Usage:** Leverage API parameters instead of raw commands
|
| 470 |
+
2. **Maintain Separation:** Keep app logic and launchers separate
|
| 471 |
+
3. **Follow Conventions:** Match existing project patterns
|
| 472 |
+
4. **Test Thoroughly:** Use CLI to verify launcher functionality
|
| 473 |
+
5. **Document Changes:** Update relevant metadata and documentation
|
.geminiignore
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
ENVIRONMENT
|
| 2 |
+
!/logs
|
| 3 |
+
!/GEMINI.md
|
| 4 |
+
!/SPEC.md
|
| 5 |
+
!/app
|
| 6 |
+
!C:\pinokio
|
.gitattributes
CHANGED
|
@@ -33,3 +33,113 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
icon.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
cache/GRADIO_TEMP_DIR/0104dcbf4ecd060d0f9908e75116ea3b1283defbe8d1daa5b2aebfdd27a50720/184610_17e9.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
cache/GRADIO_TEMP_DIR/0115b4830ff0665ad782cfb50ea81e83d576c6fe5787c6c33a77c9ea179743ca/174332_8794.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
cache/GRADIO_TEMP_DIR/01ad82d69b6d2baad6c60480ceb366ed05003c5c92b9c21c0cf4c37c411b3ff8/200033_b851.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
cache/GRADIO_TEMP_DIR/03a7badba46e5b655ff716e02c5996baee5f2e7e4ac5f44d0ad24a61489fadc3/image_195204_7aec6c64.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
cache/GRADIO_TEMP_DIR/048523a346ad5312fb0116b07c6cc9d4d7a7fea69ce7027d7fd7beb01268fc18/092559_b8fc.png filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
cache/GRADIO_TEMP_DIR/09b443d9c4850dd13d4a39a8edff6b7e68c9f5a428a73fc494ece1fa5066ee0c/201057_0a1c.png filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
cache/GRADIO_TEMP_DIR/0b504490c285e741239ac4793322e48efc91691c04e98a6c7a3af21d304a5aa4/image.webp filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
cache/GRADIO_TEMP_DIR/0c8c8364e4d7ddeb83cc7c5777e36b5f7687da1bb308d2406fd842ea9f46fa95/i2i_133141_5337.png filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
cache/GRADIO_TEMP_DIR/0fd950fa33ea4eafe5c16b1cc0eed01eb5ebf2f23b1e6f414533b25180379b05/image_205528_7ee5527a.png filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
cache/GRADIO_TEMP_DIR/104f4b9668733006f0a761e9778c747031df2334a0dd6f389ae5d49d5cc02b9e/200437_0006.png filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
cache/GRADIO_TEMP_DIR/10c5d8f6cacea6cc5f16e475aa9c4fdff517728969b2629a26c679c5648135d5/193107_4fa5.png filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
cache/GRADIO_TEMP_DIR/127b98c0ad0f191eff7d10eba1f1bad79fa49c9889aa3a2dfe12c05d28348aba/095514_01ac.png filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
cache/GRADIO_TEMP_DIR/12d002a8885b4d41fbb41d98e32ce71f728aa5bde8a939300ed6af71179b599d/103637_26f5.png filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
cache/GRADIO_TEMP_DIR/168afac92d4203ba0e6fad2bf01e3d1a10295a59cad570e0d32316117755b7e3/composite.png filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
cache/GRADIO_TEMP_DIR/19b76ad6a7d98ae4eb1d1626871e171b636bb0103bd68fea8ff92685d2207695/160727_8256.png filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
cache/GRADIO_TEMP_DIR/1a0791be9abd585a9187d0f87b23e9ceafb0d09b978af6f11e5b45999d7ff792/image_203420_c47cecb7.png filter=lfs diff=lfs merge=lfs -text
|
| 53 |
+
cache/GRADIO_TEMP_DIR/1aaa830b48dfb49d28389f438e732a2a73cc948878b7f969802687356e75a63c/173952_0241.png filter=lfs diff=lfs merge=lfs -text
|
| 54 |
+
cache/GRADIO_TEMP_DIR/1cc5d28158556bf5a8eb7992502a482d4b898f463262649559b77a1aaa10fd84/161034_21ec.png filter=lfs diff=lfs merge=lfs -text
|
| 55 |
+
cache/GRADIO_TEMP_DIR/213a256e430446bb846446a77053d3c3e2d5a7e3d40adf060bb1358d2929bed1/193806_c514.png filter=lfs diff=lfs merge=lfs -text
|
| 56 |
+
cache/GRADIO_TEMP_DIR/21db64ee5ade0d05ff3961c7df2ee7016e8a13e954396707b37d42de6b2b32d5/image_233514_3851872b.png filter=lfs diff=lfs merge=lfs -text
|
| 57 |
+
cache/GRADIO_TEMP_DIR/235047bf74248ec689408227f1fb364afb770712ddd3da625a3c660c6a29cc35/background.png filter=lfs diff=lfs merge=lfs -text
|
| 58 |
+
cache/GRADIO_TEMP_DIR/25d5add9dafa195a945d962f6064049cdf902c723358c1eb978615f82c47cd54/123145_46ab.png filter=lfs diff=lfs merge=lfs -text
|
| 59 |
+
cache/GRADIO_TEMP_DIR/2ddd025a39e016cbc97dfd9130f9b1302492be2ba4e33b0acefb761adaa1b14d/image.webp filter=lfs diff=lfs merge=lfs -text
|
| 60 |
+
cache/GRADIO_TEMP_DIR/30a053ec6f1d5ed7e7c8397450be98cb173037115d5e03394d99102741d0faf7/195841_e909.png filter=lfs diff=lfs merge=lfs -text
|
| 61 |
+
cache/GRADIO_TEMP_DIR/334d0bc165496fef845d69069319d2e574ef489656422f14f8b4553dff7583f8/184528_885b.png filter=lfs diff=lfs merge=lfs -text
|
| 62 |
+
cache/GRADIO_TEMP_DIR/364d8c3a6f8a226ff370cb2e4f59d8f37f6a23520f322adf77ae3d7d996d6176/191110_1134.png filter=lfs diff=lfs merge=lfs -text
|
| 63 |
+
cache/GRADIO_TEMP_DIR/36e56881752c197dfd65a693b7d7134852117eaa97fbff30739a01390082a69e/i2i_132542_eeff.png filter=lfs diff=lfs merge=lfs -text
|
| 64 |
+
cache/GRADIO_TEMP_DIR/3948f38c2fecc017128b1b7eb41f3f327fc06aba806d9bdc886368ef3228319d/193253_2da5.png filter=lfs diff=lfs merge=lfs -text
|
| 65 |
+
cache/GRADIO_TEMP_DIR/3ba5ac95cfc5de2c15635232fb57a2b719388c6891302cbc32127defbdbde758/image_123428_296a1507.png filter=lfs diff=lfs merge=lfs -text
|
| 66 |
+
cache/GRADIO_TEMP_DIR/3ece35a85d9ffae8b8270058ab850d5d419785b01ec68819a3c6aace56673bc4/161239_fdad.png filter=lfs diff=lfs merge=lfs -text
|
| 67 |
+
cache/GRADIO_TEMP_DIR/41fbb34e88e6b1e8e459504ada125267c8bf7acc97ac8d364df233623828879d/i2i_132029_852c.png filter=lfs diff=lfs merge=lfs -text
|
| 68 |
+
cache/GRADIO_TEMP_DIR/42a832bbae0d16a84c93cd3c85948cbd891e5ae55ec6e05ab905fb1dde3af489/190833_457f.png filter=lfs diff=lfs merge=lfs -text
|
| 69 |
+
cache/GRADIO_TEMP_DIR/48c7aa110490f236dc8353a8a4b7a1b9610cd489d0723e9acc4671ffbc1fd9e5/211433_2542.png filter=lfs diff=lfs merge=lfs -text
|
| 70 |
+
cache/GRADIO_TEMP_DIR/4ca1ba410ee0e412d101e38b900f26235b1ac4ba2bd20e3a9cb4695a31770b8b/152321_78e9.png filter=lfs diff=lfs merge=lfs -text
|
| 71 |
+
cache/GRADIO_TEMP_DIR/4dbf29b962ea1a07327116aebf6339935f80e66be6405544b1724c18a97570ca/193937_dfc2.png filter=lfs diff=lfs merge=lfs -text
|
| 72 |
+
cache/GRADIO_TEMP_DIR/581d417e02c3368e61357f1a1962528ed7c811feed930fe2a2b6e516119793f3/173802_a005.png filter=lfs diff=lfs merge=lfs -text
|
| 73 |
+
cache/GRADIO_TEMP_DIR/5a8794b56d666c48284a1a7079db5748f4a7b9cdb518dd73cd49480fd398d0ac/image_072130_c450deca.png filter=lfs diff=lfs merge=lfs -text
|
| 74 |
+
cache/GRADIO_TEMP_DIR/5aa9e5d0f84a6e9ced3ca3d3ed41b6dc6628109bfee2aaa1e5aa42163f4a3b97/215755_3063.png filter=lfs diff=lfs merge=lfs -text
|
| 75 |
+
cache/GRADIO_TEMP_DIR/5bb5d3fcb15276fa3c3d5448ee43411958d13c6609074292b4d18b72712b5ff8/background.png filter=lfs diff=lfs merge=lfs -text
|
| 76 |
+
cache/GRADIO_TEMP_DIR/5cbded92848e091bb7da4277864735a0b999a61c0a6eb4e7d20c4ba5989de823/image_20251215_140351_e26969b3.png filter=lfs diff=lfs merge=lfs -text
|
| 77 |
+
cache/GRADIO_TEMP_DIR/5e36d438233be1a028e08aabe7bec7320eabc9bd57f70469f050d7abe02a41cf/194243_9831.png filter=lfs diff=lfs merge=lfs -text
|
| 78 |
+
cache/GRADIO_TEMP_DIR/5e90cebdccc72250a480557acc5aa3b248778a5bd5767a7a15753da423806b45/215620_c917.png filter=lfs diff=lfs merge=lfs -text
|
| 79 |
+
cache/GRADIO_TEMP_DIR/5e94d91d3600218df343e522fc1ab1d9bf3818bdb467e04a2470680eaac08d09/i2i_212159_0129.png filter=lfs diff=lfs merge=lfs -text
|
| 80 |
+
cache/GRADIO_TEMP_DIR/604753b372bf97c9157362cba3fb9fc06d31e636c6493872e7a87396f948cbc1/194225_7e4f.png filter=lfs diff=lfs merge=lfs -text
|
| 81 |
+
cache/GRADIO_TEMP_DIR/6362b7e79ecbab45c800a3500c6d14b34e7667a7d592c694785cd9741ab32478/173857_2204.png filter=lfs diff=lfs merge=lfs -text
|
| 82 |
+
cache/GRADIO_TEMP_DIR/68805bb2ae22568b71d47b0ab980b2b7a65738ef9b4c42cc26d5c16c7573ef09/image_201312_65ab1f2a.png filter=lfs diff=lfs merge=lfs -text
|
| 83 |
+
cache/GRADIO_TEMP_DIR/6969fe7dea7e67d0f76db8fd75aafea70efced242795740399463f3415a41bab/123351_737c.png filter=lfs diff=lfs merge=lfs -text
|
| 84 |
+
cache/GRADIO_TEMP_DIR/6baa98a710677fb71b4f19984a48a78e17575d907790fedfd6c209192e7d423a/image_082956_a93da5e5.png filter=lfs diff=lfs merge=lfs -text
|
| 85 |
+
cache/GRADIO_TEMP_DIR/6c9ccf2b46e15c78e1a4a78e059da0657cf03c1f92e21c35245db5ff9cff9833/081829_8f85.png filter=lfs diff=lfs merge=lfs -text
|
| 86 |
+
cache/GRADIO_TEMP_DIR/6d1de0d9b12bd2823f705bfe580f21b97530b700445a5d05b142bef29f0e683f/image_144045_e7f50021.png filter=lfs diff=lfs merge=lfs -text
|
| 87 |
+
cache/GRADIO_TEMP_DIR/6fb901af77289410341f57fc6dbb20d7360b0504eadfaaf9e564b9a89d93beeb/image_080709_eeb69d7f.png filter=lfs diff=lfs merge=lfs -text
|
| 88 |
+
cache/GRADIO_TEMP_DIR/73bc0a86c1b286167059ede3951eb45484eb1b8641142775adb88c9655712e26/image_153303_87cc6baf.png filter=lfs diff=lfs merge=lfs -text
|
| 89 |
+
cache/GRADIO_TEMP_DIR/789a6a4f2bfb98534624eaac9b35bffd7c86cfd6a5eb6448270e059b69973a5c/image_223009_335cd589.png filter=lfs diff=lfs merge=lfs -text
|
| 90 |
+
cache/GRADIO_TEMP_DIR/78a983fa831ec39adde863986ad7fbd99f269065027d330abd9cfdf960a2bf6f/161445_2360.png filter=lfs diff=lfs merge=lfs -text
|
| 91 |
+
cache/GRADIO_TEMP_DIR/79f2384913ad201fce177b1cfaefc63274471339f051cd8c07ed1bfa61660155/160932_73bd.png filter=lfs diff=lfs merge=lfs -text
|
| 92 |
+
cache/GRADIO_TEMP_DIR/7bb7366e4ee283ebd19fca4f8866abcd0118d1c4082a961032d2d27759ed080c/174427_026c.png filter=lfs diff=lfs merge=lfs -text
|
| 93 |
+
cache/GRADIO_TEMP_DIR/7ce8afffd4164ec53eae38def96ab0ab0aecf5740b98ef367a4513535862de43/image_235656_313ac3ec.png filter=lfs diff=lfs merge=lfs -text
|
| 94 |
+
cache/GRADIO_TEMP_DIR/80fa2d35192ee203c208228ced366a62942ee43299d805ead746a7babba243b8/173055_9027.png filter=lfs diff=lfs merge=lfs -text
|
| 95 |
+
cache/GRADIO_TEMP_DIR/82584677e390c6b1e631a2f3aa09593c554bb1f9d814c87a873f399f117d8021/212625_ffff.png filter=lfs diff=lfs merge=lfs -text
|
| 96 |
+
cache/GRADIO_TEMP_DIR/8600377e1d617099c004bee769e5277d83c81224a2666ce6bedb9aec8e44719d/184216_652e.png filter=lfs diff=lfs merge=lfs -text
|
| 97 |
+
cache/GRADIO_TEMP_DIR/88098f462101fcaba9f43b9964bfe86ea774e428db93ae0a17cb115f37f0bee1/background.png filter=lfs diff=lfs merge=lfs -text
|
| 98 |
+
cache/GRADIO_TEMP_DIR/8bc420f6018d03774a50f6b6024ea8dc391b186204a1a03c420b4db129a24f96/composite.png filter=lfs diff=lfs merge=lfs -text
|
| 99 |
+
cache/GRADIO_TEMP_DIR/90094265faa035aa3f7c73bf36190fb7a5e673a081764bec856bca319da1bda3/composite.png filter=lfs diff=lfs merge=lfs -text
|
| 100 |
+
cache/GRADIO_TEMP_DIR/9482c3341cc290e034761d3233c1d16cc226d029961d57683519f92174854bd3/193438_a1ee.png filter=lfs diff=lfs merge=lfs -text
|
| 101 |
+
cache/GRADIO_TEMP_DIR/9496fb6dcc061cad6ad2e85f54031653c511e8c3be22010db1bf8d34550c3dce/image_20251218_222807_672f094d.png filter=lfs diff=lfs merge=lfs -text
|
| 102 |
+
cache/GRADIO_TEMP_DIR/9741f9066e124f0ab755d60cba06cd3c1c2225942235d4e57b43f270fb429f7d/184933_cae6.png filter=lfs diff=lfs merge=lfs -text
|
| 103 |
+
cache/GRADIO_TEMP_DIR/99a68ec3226f3adf12302081ec81fc7d4c1b159a5b400c646e37d9eecfcb9df5/i2i_133331_bd3f.png filter=lfs diff=lfs merge=lfs -text
|
| 104 |
+
cache/GRADIO_TEMP_DIR/9d88f78acbbaba9012a69cbdb345a1a04772aa08b95de90503ea7b5b0a7909b5/111425_13dc.png filter=lfs diff=lfs merge=lfs -text
|
| 105 |
+
cache/GRADIO_TEMP_DIR/a310f1f579e215476409d71940c213a4669deb2ad87d93c9cdaa6472ae3ff07f/image_192955_d771b409.png filter=lfs diff=lfs merge=lfs -text
|
| 106 |
+
cache/GRADIO_TEMP_DIR/a327c7e44941b87ce775f556197b5b61e3eb802a20d58157e9ae497291b02fd9/composite.png filter=lfs diff=lfs merge=lfs -text
|
| 107 |
+
cache/GRADIO_TEMP_DIR/a419b1307c2d8b2aae24cd649f67d04e6cbcc8b9bb06b401d02e82ebbf106309/composite.png filter=lfs diff=lfs merge=lfs -text
|
| 108 |
+
cache/GRADIO_TEMP_DIR/a5a2b9cd94e0b16826bad2340e7d415100509e7902b11b04e8e6c0065a59a67e/image_124156_4e4c3b01.png filter=lfs diff=lfs merge=lfs -text
|
| 109 |
+
cache/GRADIO_TEMP_DIR/a61f1a99c73b58c3185054b9691872f4faaf0785579e272d9d1506758ed18fa5/105143_2ebb.png filter=lfs diff=lfs merge=lfs -text
|
| 110 |
+
cache/GRADIO_TEMP_DIR/b224f120a3a95f6b21186549fe6ad1a9cf623a8e19df0b9321e505205c512801/193626_2c54.png filter=lfs diff=lfs merge=lfs -text
|
| 111 |
+
cache/GRADIO_TEMP_DIR/b229f7bc28193e011c40ff909130dba345f2c1ef310c0df20215c6d7a50d30b2/161342_bae4.png filter=lfs diff=lfs merge=lfs -text
|
| 112 |
+
cache/GRADIO_TEMP_DIR/b28b50325c1cf8c0c59528767652b8138426d4f049111cd040b0a8b6071c6cc7/background.png filter=lfs diff=lfs merge=lfs -text
|
| 113 |
+
cache/GRADIO_TEMP_DIR/b5e1abda68bdb09921d5fe24e19a2e4c6f98f27ce8f6fbaccba4e9046faf3fac/174047_6b5c.png filter=lfs diff=lfs merge=lfs -text
|
| 114 |
+
cache/GRADIO_TEMP_DIR/b9e91145e65105a2cdabde49b2c2f66b6dc10588f3724d3ee1c80397f2e0e46d/image_231332_7275a0ba.png filter=lfs diff=lfs merge=lfs -text
|
| 115 |
+
cache/GRADIO_TEMP_DIR/ba095bf3262363416c6b61d8e0a42ab21dda842620af26d2f533725874198e69/image_155912_1b051896.png filter=lfs diff=lfs merge=lfs -text
|
| 116 |
+
cache/GRADIO_TEMP_DIR/ba465b6c2b08d01bcbd889f52b94491df760457cd14e8645c25928ab7dc009ec/122427_380c.png filter=lfs diff=lfs merge=lfs -text
|
| 117 |
+
cache/GRADIO_TEMP_DIR/ba57eb2021b31bf9e58b8bbd2f9d06df8fbbca320d83dcda165d759733c2e407/background.png filter=lfs diff=lfs merge=lfs -text
|
| 118 |
+
cache/GRADIO_TEMP_DIR/ba57eb2021b31bf9e58b8bbd2f9d06df8fbbca320d83dcda165d759733c2e407/composite.png filter=lfs diff=lfs merge=lfs -text
|
| 119 |
+
cache/GRADIO_TEMP_DIR/ba66073accdf211821de826075fb88898d1503bd3d4ccb43b7ef2ae4adccce5b/122925_cb76.png filter=lfs diff=lfs merge=lfs -text
|
| 120 |
+
cache/GRADIO_TEMP_DIR/bf69fd4dbc0c7660c81d7a4d56f57357b4f72e23d6639a91cc4b271fc89441a8/104640_444b.png filter=lfs diff=lfs merge=lfs -text
|
| 121 |
+
cache/GRADIO_TEMP_DIR/bf992d41695be78a26c4c967a21428ecf54daa232524d8afabb1e35610c8ebf0/image_20251219_064526_8667a4f6.png filter=lfs diff=lfs merge=lfs -text
|
| 122 |
+
cache/GRADIO_TEMP_DIR/c83545d19a58472553923ef0830c8e191cf17049ca43d87dfdb46cb51126840f/111707_46ef.png filter=lfs diff=lfs merge=lfs -text
|
| 123 |
+
cache/GRADIO_TEMP_DIR/cab4e1ce944cbd8cb3f4b34c2088008d6e58123b139c604f7d50be376c7e5542/image_213744_925f86e3.png filter=lfs diff=lfs merge=lfs -text
|
| 124 |
+
cache/GRADIO_TEMP_DIR/d451bcbf620bf662e88d6818c1862e555dad7f9fc94d2e3753bc55d08490e571/image_215853_7f190bf1.png filter=lfs diff=lfs merge=lfs -text
|
| 125 |
+
cache/GRADIO_TEMP_DIR/d66d5cfbc1a9e8de88d1e2f4b9c0bc487888e6746a3ed2cb9a71b519b8478d2f/194110_e977.png filter=lfs diff=lfs merge=lfs -text
|
| 126 |
+
cache/GRADIO_TEMP_DIR/d88a6fd16b470f47e5f4d196e2e5ab4b08bee25518b873cc76c7691821509103/image_001837_2435da99.png filter=lfs diff=lfs merge=lfs -text
|
| 127 |
+
cache/GRADIO_TEMP_DIR/dbdd135cd6e0850542cbdeb8904c7088d25fb95407605086aea738e422fcc77d/174238_0267.png filter=lfs diff=lfs merge=lfs -text
|
| 128 |
+
cache/GRADIO_TEMP_DIR/dda7f0466f527a534776315dc588949e9f02727a121b8d06ba5cd2fa8a50052a/161137_1198.png filter=lfs diff=lfs merge=lfs -text
|
| 129 |
+
cache/GRADIO_TEMP_DIR/de579b6cd28da8cc5272e99cf06903d28dd563614e83460e54b027c96f3739de/image_074420_2ebe2973.png filter=lfs diff=lfs merge=lfs -text
|
| 130 |
+
cache/GRADIO_TEMP_DIR/e2faf61c821dd449470a0c5c052a3f598e84f92299b1173ca189a9a4a7b0d4c1/image_004017_cf393c0b.png filter=lfs diff=lfs merge=lfs -text
|
| 131 |
+
cache/GRADIO_TEMP_DIR/e88e22c91caa1107d3b3fc8adf430b6e61271d5e1066156b001e390bd6fc8592/image_133821_be3e1e18.png filter=lfs diff=lfs merge=lfs -text
|
| 132 |
+
cache/GRADIO_TEMP_DIR/e946e222a8e87fd9729a28c94c357e3f222e889a722970a727dfac424b325f1f/composite.png filter=lfs diff=lfs merge=lfs -text
|
| 133 |
+
cache/GRADIO_TEMP_DIR/eb27ebd4d6a857f78595a8907296ec35edb15e0dbfadc62e93217824871e59e2/173523_769d.png filter=lfs diff=lfs merge=lfs -text
|
| 134 |
+
cache/GRADIO_TEMP_DIR/eb2b854a5861e74ee982ddc5e4394aa2e1a345957e719b00eb1a2755389b2823/image_211636_9906c971.png filter=lfs diff=lfs merge=lfs -text
|
| 135 |
+
cache/GRADIO_TEMP_DIR/ec6d4ec205e70da65fdc19331e9b3d02336fe3893aca89f5a97f09154fb5302b/image_225150_27c33ecc.png filter=lfs diff=lfs merge=lfs -text
|
| 136 |
+
cache/GRADIO_TEMP_DIR/ee91807ad68539352d2125c84be145425c53067892649c95de0456ba649a246a/184401_9308.png filter=lfs diff=lfs merge=lfs -text
|
| 137 |
+
cache/GRADIO_TEMP_DIR/efe7381de79d01496132f63365c9442352a2888f7074b022a1db19be4882d55f/174143_0463.png filter=lfs diff=lfs merge=lfs -text
|
| 138 |
+
cache/GRADIO_TEMP_DIR/f215759df2dc00f8204147d4e00c059a8c1edebae52fe79a5273d3bb1f44ff80/i2i_133506_04ce.png filter=lfs diff=lfs merge=lfs -text
|
| 139 |
+
cache/GRADIO_TEMP_DIR/f86e040927a38966bed4f3b18487686c0793aca3fe2e48c517127f8e781edc1c/194021_11a6.png filter=lfs diff=lfs merge=lfs -text
|
| 140 |
+
cache/GRADIO_TEMP_DIR/f8e92be1443adbba50d91201763b6ce49233664ca9b311e815aee2df1e1efad5/160830_7b26.png filter=lfs diff=lfs merge=lfs -text
|
| 141 |
+
cache/GRADIO_TEMP_DIR/fc3de51408f1e65637461b53a0981a7f9c008f178fabbf0aadbd44e6caa4392c/image_010200_60704c43.png filter=lfs diff=lfs merge=lfs -text
|
| 142 |
+
cache/GRADIO_TEMP_DIR/fdb6ba5ae6edc725e2a6205d4bb1c535667ab0fdf5ba5da8c44d55f8b267cc5e/183514_9d21.png filter=lfs diff=lfs merge=lfs -text
|
| 143 |
+
cache/GRADIO_TEMP_DIR/fe921dea56812384a39b85df341f1199e27c1e95f15358a13964a3c2d37fbce0/image_150656_45c20713.png filter=lfs diff=lfs merge=lfs -text
|
| 144 |
+
cache/GRADIO_TEMP_DIR/fe99491cc85d1ad274a96752b812e88d23bb69729a89104b196847af7ae2f7da/i2i_205330_a263.png filter=lfs diff=lfs merge=lfs -text
|
| 145 |
+
cache/HF_HOME/hub/models--Tongyi-MAI--Z-Image-Turbo/snapshots/5f4b9cbb80cc95ba44fe6667dfd75710f7db2947/tokenizer/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
env/
|
| 8 |
+
venv/
|
| 9 |
+
ENV/
|
| 10 |
+
build/
|
| 11 |
+
develop-eggs/
|
| 12 |
+
dist/
|
| 13 |
+
downloads/
|
| 14 |
+
eggs/
|
| 15 |
+
.eggs/
|
| 16 |
+
lib/
|
| 17 |
+
lib64/
|
| 18 |
+
parts/
|
| 19 |
+
sdist/
|
| 20 |
+
var/
|
| 21 |
+
wheels/
|
| 22 |
+
*.egg-info/
|
| 23 |
+
.installed.cfg
|
| 24 |
+
*.egg
|
| 25 |
+
|
| 26 |
+
# Virtual environments
|
| 27 |
+
env/
|
| 28 |
+
venv/
|
| 29 |
+
ENV/
|
| 30 |
+
.venv
|
| 31 |
+
|
| 32 |
+
# IDEs
|
| 33 |
+
.vscode/
|
| 34 |
+
.idea/
|
| 35 |
+
*.swp
|
| 36 |
+
*.swo
|
| 37 |
+
*~
|
| 38 |
+
|
| 39 |
+
# OS
|
| 40 |
+
.DS_Store
|
| 41 |
+
Thumbs.db
|
| 42 |
+
desktop.ini
|
| 43 |
+
|
| 44 |
+
# Model cache and outputs
|
| 45 |
+
*.ckpt
|
| 46 |
+
*.safetensors
|
| 47 |
+
*.pth
|
| 48 |
+
*.bin
|
| 49 |
+
models/
|
| 50 |
+
checkpoints/
|
| 51 |
+
outputs/
|
| 52 |
+
*.jpg
|
| 53 |
+
*.jpeg
|
| 54 |
+
example.png
|
| 55 |
+
|
| 56 |
+
# Gradio
|
| 57 |
+
gradio_cached_examples/
|
| 58 |
+
flagged/
|
| 59 |
+
|
| 60 |
+
# Logs
|
| 61 |
+
*.log
|
| 62 |
+
logs/
|
| 63 |
+
|
| 64 |
+
# Temporary files
|
| 65 |
+
tmp/
|
| 66 |
+
temp/
|
| 67 |
+
*.tmp
|
| 68 |
+
|
| 69 |
+
#Custom MOD and Lora
|
| 70 |
+
MOD/
|
| 71 |
+
lora/
|
.windsurfrules
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Development Guide for Pinokio Projects
|
| 2 |
+
|
| 3 |
+
## Non-Negotiable Execution Workflow
|
| 4 |
+
|
| 5 |
+
To guarantee every contribution follows this guide precisely, obey this checklist **before any edits** and **again before finalizing**. Do not skip or reorder.
|
| 6 |
+
1. **AGENTS Snapshot:** Re-open this file and write down (in your working notes or response draft) the exact sections relevant to the requested task. No work begins until this snapshot exists.
|
| 7 |
+
2. **Example Lock-in:** Identify the closest matching script in `C:\pinokio\prototype\system\examples`. Record its path and keep it open while editing. Every launcher change must mirror that reference unless the user explicitly instructs otherwise.
|
| 8 |
+
3. **Pre-flight Checklist:** Convert the applicable rules from this document and `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md into a task-specific checklist (install/start/reset/update structure, regex patterns, menu defaults, log checks, etc.). Confirm each item is ticked **before** making changes.
|
| 9 |
+
4. **Mid-task Verification:** Any time you touch a Pinokio script, cross-check the corresponding example line to ensure syntax and structure match. Document the reference (example path + line) in your reasoning.
|
| 10 |
+
5. **Exit Checklist:** Before responding to the user, revisit the pre-flight checklist and explicitly confirm every item is satisfied. If anything diverges from the example or these rules, fix it first.
|
| 11 |
+
|
| 12 |
+
If any step cannot be completed, stop immediately and ask the user how to proceed. These five steps are mandatory for every session.
|
| 13 |
+
|
| 14 |
+
### Critical Pattern Lock: Capturing Web UI URLs
|
| 15 |
+
|
| 16 |
+
When writing `start.js` (or any script that needs to surface a web URL for a server):
|
| 17 |
+
|
| 18 |
+
1. **Always copy the capture block from an example such as `system/examples/mochi/start.js`.**
|
| 19 |
+
```javascript
|
| 20 |
+
on: [{
|
| 21 |
+
event: "/(http:\\/\\/[0-9.:]+)/",
|
| 22 |
+
done: true
|
| 23 |
+
}]
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
2. **Set the local variable using the captured match exactly as below (The regex capture object is passed in as `input.event`, so need to use the index 1 inside the parenthesis):**
|
| 27 |
+
```javascript
|
| 28 |
+
{
|
| 29 |
+
method: "local.set",
|
| 30 |
+
params: {
|
| 31 |
+
url: "{{input.event[1]}}"
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
3. Always try to come up with the most generic regex.
|
| 37 |
+
4. During the exit checklist, explicitly confirm that the `url` local variable is set via `local.set` API by using the captured regex object as passed in as `input.event` from the previous `shell.run` step.
|
| 38 |
+
|
| 39 |
+
Deviation from this pattern requires written approval from the user.
|
| 40 |
+
|
| 41 |
+
- Make sure to keep this entire document and `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md in memory with high priority before making any decision. Pinokio is a system that makes it easy to write launchers through scripting by providing various cross-platform APIs, so whenever possible you should prioritize using Pinokio API over lower level APIs.
|
| 42 |
+
- When writing pinokio scripts, ALWAYS check the examples folder (in C:\pinokio\prototype\system\examples folder) to see if there are existing example scripts you can imitate, instead of assuming syntax.
|
| 43 |
+
- When implementing pinokio script APIs and you cannot infer the syntax just based on the examples, always search the API documentation `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md to use the correct syntax instead of assuming the syntax.
|
| 44 |
+
- When trying to fix something or figure out what's going on, ALWAYS start by checking the `logs` folder before doing anything else, as mentioned in the "Troubleshooting with Logs" section.
|
| 45 |
+
- Finally, make sure to ALWAYS follow all the items in the "best practices" section below.
|
| 46 |
+
|
| 47 |
+
## Determine User Intent
|
| 48 |
+
If the initial prompt is simply a URL and nothing else, check the website content and determine the intent, and ask the user to confirm. For example a URL may point to
|
| 49 |
+
|
| 50 |
+
1. A Tutorial: the intent may be to implement a demo for the tutorial and build a launcher.
|
| 51 |
+
2. A Demo: the intent may be a 1-click launcher for the demo
|
| 52 |
+
3. Open source project: the intent may be a 1-click launcher for the project
|
| 53 |
+
4. Regular website: the intent may be to clone the website and a launcher.
|
| 54 |
+
5. There can be other cases, but try to guess.
|
| 55 |
+
|
| 56 |
+
## Project Structure
|
| 57 |
+
|
| 58 |
+
Pinokio projects normally follow a standardized structure with app logic separated from launcher scripts:
|
| 59 |
+
|
| 60 |
+
Pinokio projects follow a standardized structure with app logic separated from launcher scripts:
|
| 61 |
+
|
| 62 |
+
```
|
| 63 |
+
project-root/
|
| 64 |
+
├── app/ # Self-contained app logic (can be standalone repo)
|
| 65 |
+
│ ├── package.json # Node.js projects
|
| 66 |
+
│ ├── requirements.txt # Python projects
|
| 67 |
+
│ └── ... # Other language-specific files
|
| 68 |
+
├── README.md # Documentation
|
| 69 |
+
├── install.js # Installation script
|
| 70 |
+
├── start.js # Launch script
|
| 71 |
+
├── update.js # Update script (for updating the scripts and app logic to the latest)
|
| 72 |
+
├── reset.js # Reset dependencies script
|
| 73 |
+
├── pinokio.js # UI generator script
|
| 74 |
+
└── pinokio.json # Metadata (title, description, icon)
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
- Keep app code in `/app` folder only (never in root)
|
| 78 |
+
- Store all launcher files in project root (never in `/app`)
|
| 79 |
+
- `/app` folder should be self-contained and publishable
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
The only exceptions are serverless web apps---purely frontend only web applications that do NOT have a server component and connect to 3rd party API endpoints--in which case the folder structure looks like the following (No need for launcher scripts since the index.html will automatically launch. The only thing needed is the metadata file named pinokio.json):
|
| 83 |
+
|
| 84 |
+
```
|
| 85 |
+
project-root/
|
| 86 |
+
├── index.html # The serverless web app entry point
|
| 87 |
+
├── ...
|
| 88 |
+
├── README.md # Documentation
|
| 89 |
+
└── pinokio.json # Metadata (title, description, icon)
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
IMPORTANT: ALWAYS try to follow the best practices in the examples folder (C:\pinokio\prototype\system\examples) instead of trying to come up with your own structure. The examples have been optimized for the best user experience.
|
| 93 |
+
|
| 94 |
+
## Launcher Project Working Directory
|
| 95 |
+
|
| 96 |
+
- The project working directory for a script is always the same directory as the script location.
|
| 97 |
+
- For example, when you run `shell.run` API inside `pinokio/start.js`, the default path for shell execution is `pinokio`.
|
| 98 |
+
- If the launcher files are in the project root path, then the default path for shell execution is the project root.
|
| 99 |
+
- Therefore, it is important to specify the correct `path` attribute when running `shell.run` API commands.
|
| 100 |
+
|
| 101 |
+
Example: in the following project structure:
|
| 102 |
+
|
| 103 |
+
```
|
| 104 |
+
project-root/
|
| 105 |
+
├── pinokio/ # Pinokio launcher folder
|
| 106 |
+
│ ├── start.js # Launch script
|
| 107 |
+
│ ├── pinokio.js # UI generator script
|
| 108 |
+
│ └── pinokio.json # Metadata (title, description, icon)
|
| 109 |
+
└─── backend/
|
| 110 |
+
├── requirements.txt # App dependencies
|
| 111 |
+
└── app.py # App code
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
The `pinokio/start.js` should use the correct path `../backend` as the `path` attribute, as follows:
|
| 115 |
+
|
| 116 |
+
```
|
| 117 |
+
{
|
| 118 |
+
run: [{
|
| 119 |
+
...
|
| 120 |
+
}, {
|
| 121 |
+
method: "shell.run",
|
| 122 |
+
params: {
|
| 123 |
+
message: "python app.py",
|
| 124 |
+
venv: "env",
|
| 125 |
+
path: "../backend"
|
| 126 |
+
}
|
| 127 |
+
}, {
|
| 128 |
+
...
|
| 129 |
+
}]
|
| 130 |
+
}
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
## Development Workflow
|
| 134 |
+
|
| 135 |
+
### 1. Understanding the Project
|
| 136 |
+
- Check `SPEC.md` in project root. If the file exists, use that to learn about the project details (what and how to build)
|
| 137 |
+
- If no `SPEC.md` exists, build based on user requirements
|
| 138 |
+
### 2. Modifying Existing Launcher Projects
|
| 139 |
+
If we are starting with existing launcher script files, work with the existing files instead of coming up with your own.
|
| 140 |
+
- **Preserve existing functionality:** Only modify necessary parts
|
| 141 |
+
- **Don't touch working scripts:** Unless adding/updating specific commands
|
| 142 |
+
- **Follow existing conventions:** Match the style and structure already present
|
| 143 |
+
### 3. Try to adopt from examples as much as possible
|
| 144 |
+
- If starting from scratch, first determine what type of project you will be building, and then check the examples folder (C:\pinokio\prototype\system\examples) to see if you can adopt them instead of coming up everything from scratch.
|
| 145 |
+
- Even if there are no relevant examples, check the examples to get inspiration for how you would structure the script files even if you have to write from scratch.
|
| 146 |
+
### 4. Writing from scratch as a last resort
|
| 147 |
+
If there are relevant examples to adopt from, write the scripts from scratch, but just make sure to follow the requirements in the next section.
|
| 148 |
+
### 5. Debugging
|
| 149 |
+
When the user reports something is not working, ALWAYS inspect the logs folder to get all the execution logs. For more info on how this works, check the "Troubleshooting with Logs" section below.
|
| 150 |
+
|
| 151 |
+
## Script Requirements
|
| 152 |
+
|
| 153 |
+
### 1. 1-click launchable
|
| 154 |
+
- The main purpose of Pinokio is to provide an easy interface to invoke commands, which may include launching servers, installing programs, etc. Make sure the final product provides ways to install, launch, reset, and update whatever is needed.
|
| 155 |
+
|
| 156 |
+
### 2. Write Documentation
|
| 157 |
+
- ALWAYS write a documentation. A documentation must be stored as `README.md` in the project root folder, along with the rest of the pinokio launcher script files. A documentation file must contain:
|
| 158 |
+
- What the app does
|
| 159 |
+
- How to use the app
|
| 160 |
+
- API documentation for programmatically accessing the app's main features (Javascript, Python, and Curl)
|
| 161 |
+
|
| 162 |
+
## Types of launchers
|
| 163 |
+
## 1. Launching servers
|
| 164 |
+
- When an app requires launching a server, here are the commonly used scripts:
|
| 165 |
+
- `install.js`: a script to install the app
|
| 166 |
+
- `start.js`: a script to start the app
|
| 167 |
+
- `reset.js`: a script to reset all the dependencies installed in the `install.js` step. used if the user wants to restart from scratch
|
| 168 |
+
- `update.js`: a script to update the launcher AND the app in case there are new updates. Involves pulling in the relevant git repositories installed through `install.js` (often it's the script repo and some git repositories cloned through the install steps if any)
|
| 169 |
+
- `pinokio.js`: the launcher script that ties all of the above scripts together by providing a UI that links to these scripts.
|
| 170 |
+
- `pinokio.json`: For metadata
|
| 171 |
+
|
| 172 |
+
Here's a basic server launcher script example (`start.js`). Unless there's a special reason you need to use another pattern, this is the most recommended pattern. Use this or adopt it as needed, but NEVER try something else unless there's a good reason you should not take this approach:
|
| 173 |
+
|
| 174 |
+
```javascript
|
| 175 |
+
module.exports = {
|
| 176 |
+
// By setting daemon: true, the script keeps running even after all items in the `run` array finishes running. Mandatory for launching servers, since otherwise the shells running the server process will get killed after the scripts finish running.
|
| 177 |
+
daemon: true,
|
| 178 |
+
run: [
|
| 179 |
+
{
|
| 180 |
+
// The "shell.run" API for running a shell session
|
| 181 |
+
method: "shell.run",
|
| 182 |
+
params: {
|
| 183 |
+
// Edit 'venv' to customize the venv folder path
|
| 184 |
+
venv: "env",
|
| 185 |
+
// Edit 'env' to customize environment variables (see documentation)
|
| 186 |
+
env: { },
|
| 187 |
+
// Edit 'path' to customize the path to start the shell from
|
| 188 |
+
path: "app",
|
| 189 |
+
// Edit 'message' to customize the commands, or to run multiple commands
|
| 190 |
+
message: [
|
| 191 |
+
"python app.py",
|
| 192 |
+
],
|
| 193 |
+
on: [{
|
| 194 |
+
// The regular expression pattern to monitor.
|
| 195 |
+
// Whenever each "event" pattern occurs in the shell terminal, the shell will return,
|
| 196 |
+
// and the script will go onto the next step.
|
| 197 |
+
// The regular expression match object will be passed on to the next step as `input.event`
|
| 198 |
+
// Useful for capturing the URL at which the server is running (in case the server prints some message about where the server is running)
|
| 199 |
+
"event": "/(http:\/\/\\S+)/",
|
| 200 |
+
|
| 201 |
+
// Use "done": true to move to the next step while keeping the shell alive.
|
| 202 |
+
// Use "kill": true to move to the next step after killing the shell.
|
| 203 |
+
"done": true
|
| 204 |
+
}]
|
| 205 |
+
}
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
// This step sets the local variable 'url'.
|
| 209 |
+
// This local variable will be used in pinokio.js to display the "Open WebUI" tab when the value is set.
|
| 210 |
+
method: "local.set",
|
| 211 |
+
params: {
|
| 212 |
+
// the input.event is the regular expression match object from the previous step
|
| 213 |
+
// In this example, since the pattern was "/(http:\/\/\\S+)/", input.event[1] will include the exact http url match caputred by the parenthesis.
|
| 214 |
+
// Therefore setting the local variable 'url'
|
| 215 |
+
url: "{{input.event[1]}}"
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
]
|
| 219 |
+
}
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
## 2. Launching serverless web apps
|
| 223 |
+
|
| 224 |
+
- In case of purely static web apps WITHOUT servers or backends (for example an HTML based app that connects to 3rd party servers--either remote or localhost), we do NOT need the launcher scripts.
|
| 225 |
+
- In these cases, simply include `index.html` in the project root folder and everything should automatically work. No need for any of the pinokio launcher scripts. (Do
|
| 226 |
+
- You still need to include the metadata file so they show up properly on pinokio:
|
| 227 |
+
- `pinokio.json`: For metadata
|
| 228 |
+
|
| 229 |
+
## 3. Launching quick scripts without web UI
|
| 230 |
+
|
| 231 |
+
- In many cases, we may not even need a web UI, but instead just a simple way to run scripts.
|
| 232 |
+
- This may include TUI (Terminal User Interface) apps, a simple launcher
|
| 233 |
+
- In these cases, all we need is the launcher file `pinokio.js`, which may link to multiple scripts. In this case, there are no web apps (no serverless apsp, no servers), but instead just the default pinokio launcher UI that calls a bunch of scripts.
|
| 234 |
+
- Here are some examples:
|
| 235 |
+
- A pinokio script to toggle the desktop theme between dark and light
|
| 236 |
+
- Write some code (python or javascript or whatever)
|
| 237 |
+
- Write a `toggle.js` pinokio script that executes the code
|
| 238 |
+
- Write a `pinokio.js` launcher script to create a sidebar UI that displays the `toggle.js` so the user can simply click the "toggle" button to toggle back and forth between desktop themes
|
| 239 |
+
- A pinokio script to fetch some file
|
| 240 |
+
- Write some code (python or javascript or whatever)
|
| 241 |
+
- Write a `fetch.js` pinokio script that executes the code
|
| 242 |
+
- Write a `pinokio.js` launcher script to create a sidebar UI that displays the `fetch.js` so the user can simply click the "fetch" button to fetch some data.
|
| 243 |
+
- You still need to include the metadata file so they show up properly on pinokio:
|
| 244 |
+
- `pinokio.json`: For metadata
|
| 245 |
+
|
| 246 |
+
## API
|
| 247 |
+
|
| 248 |
+
This section lists all the script APIs available on Pinokio. To learn the details of how they are used, you can:
|
| 249 |
+
1. Check the examples in the C:\pinokio\prototype\system\examples folder
|
| 250 |
+
2. Read the `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md further documentation on the full syntax
|
| 251 |
+
|
| 252 |
+
### Script API
|
| 253 |
+
|
| 254 |
+
These APIs can be used to describe each step in a pinokio script:
|
| 255 |
+
- shell.run: run shell commands
|
| 256 |
+
- input: accept user input
|
| 257 |
+
- filepicker: accept file upload
|
| 258 |
+
- fs.write: write to file
|
| 259 |
+
- fs.read: read from file
|
| 260 |
+
- fs.copy: copy files
|
| 261 |
+
- fs.download: download files
|
| 262 |
+
- fs.link: create a symbolic link (or junction on windows) for folders
|
| 263 |
+
- fs.open: open the system file explorer at a given path
|
| 264 |
+
- fs.cat: print file contents
|
| 265 |
+
- jump: jump to a specific step
|
| 266 |
+
- local.set: set local variables for the currently running script
|
| 267 |
+
- json.set: update a json file
|
| 268 |
+
- json.rm: remove keys from a json file
|
| 269 |
+
- json.get: get values from a json file
|
| 270 |
+
- log: print to the web terminal
|
| 271 |
+
- net: make network requests
|
| 272 |
+
- notify: display a notification
|
| 273 |
+
- script.download: download a script from a git uri
|
| 274 |
+
- script.start: start a script
|
| 275 |
+
- script.stop: stop a script
|
| 276 |
+
- script.return: return values if the current script was called by a caller script, so the caller script can utilize the return value as `input`
|
| 277 |
+
- web.open: open a url in web browser
|
| 278 |
+
- hf.download: huggingfac-cli download API
|
| 279 |
+
### Template variables
|
| 280 |
+
The following variables are accessible inside template expressions (example `{{args.command}` in scripts, resulting in dynamic behaviors of scripts:
|
| 281 |
+
- input: An input is a variable that gets passed from one RPC call to the next
|
| 282 |
+
- args: args is the parameter object that gets passed into the script (via pinokio.js `params`). Unlike `input` which takes the value passed in from the immediately previous step, `args` is a global value that is the same through out the entire script execution.
|
| 283 |
+
- local: local variable object that can be set with `local.set` API
|
| 284 |
+
- self: refers to the script file itself (which is JSON or JavaScript). For example if `start.js` that's currently running has `daemon: true` set, `{{self.daemon}}` will evaluate to true.
|
| 285 |
+
- uri: The current script uri
|
| 286 |
+
- port: The next available port. Very useful when you need to launch an app at a specific port without port conflicts.
|
| 287 |
+
- cwd: The current script execution folder path
|
| 288 |
+
- platform: The current operating system. May be one of the following: `darwin`, `win32`, `linux`
|
| 289 |
+
- arch: The current system architecture. May be one of the following: x32, x64, arm, arm64, s390, s390x, mipsel, ia32, mips, ppc, ppc64
|
| 290 |
+
- gpus: array of available GPUs on the machine (example: `['apple']`, `['nvidia']`)
|
| 291 |
+
- gpu: the first available GPU (example: `nvidia`)
|
| 292 |
+
- current: The current variable points to the index of the currently executing instruction within the run array.
|
| 293 |
+
- next: The next variable points to the index of the next instruction to be executed. (null if the current instruction is the final instruction in the run array)
|
| 294 |
+
- envs: You can access the environment variables of the currently running process with envs object.
|
| 295 |
+
- which: Check whether a command exists (example: `{{which('winget')}}`. Can be used in the `when` attribute of a script step to run commands or install first.
|
| 296 |
+
- exists: Check whether a file or folder exists at the specified relative path (example: `"when": "{{!exists('app')}}"`). Can be used with the `when` attribute to determine a path's existence and trigger custom logic. Use relative paths and it will resolve automatically to the current execution folder.
|
| 297 |
+
- running: Check whether a script file is running (example: `"when": "{{!running('start.js')}}"`). Can be used with the `when` attribute to determine a path's existence and trigger custom logic. Use relative paths and it will resolve automatically to the current execution folder.
|
| 298 |
+
- os: Pinokio exposes the node.js os module through the os variable.
|
| 299 |
+
- path: Pinokio exposes the node.js path module through the os variable (example: `{{path.resolve(...)}}`
|
| 300 |
+
|
| 301 |
+
## System Capabilities
|
| 302 |
+
### Package Management (Use in Order of Preference)
|
| 303 |
+
The following package managers come pre-installed with Pinokio, so whenever you need to install a 3rd party binary, remember that these are available. Also, you can assume these are available and include the following package manager commands in Pinokio scripts:
|
| 304 |
+
1. **UV** - For Python packages (preferred over pip)
|
| 305 |
+
2. **NPM** - For Node.js packages
|
| 306 |
+
3. **Conda** - For cross-platform 3rd party binaries
|
| 307 |
+
4. **Brew** - Mac-only fallback when other options unavailable
|
| 308 |
+
5. **Git** - Full access to git is available.
|
| 309 |
+
**Important:** Include all install commands in the install script for reproducibility.
|
| 310 |
+
### HTTPS Proxy Support
|
| 311 |
+
- All HTTP servers automatically get HTTPS endpoints
|
| 312 |
+
- Convention: `http://localhost:<PORT>` → `https://<PORT>.localhost`
|
| 313 |
+
- Full proxy list available at: `http://localhost:2019/config/`
|
| 314 |
+
### Pterm Features:
|
| 315 |
+
- **Clipboard Access:** Read from or Write to system clipboard via pinokio Pterm CLI (`pterm clipboard` command.)
|
| 316 |
+
- **Notifications:** Send desktop alerts via pinokio pterm CLI (`pterm push` command.)
|
| 317 |
+
- **Script Testing:** Run launcher scripts via pinokio pterm CLI (`pterm start` command.)
|
| 318 |
+
- **File Selection:** Use built-in filepicker for user file/folder input (`pterm filepicker` command.)
|
| 319 |
+
- **Git Operations:** Clone repositories, push to GitHub
|
| 320 |
+
- **GitHub Integration:** Full GitHub CLI support (`gh` commands)
|
| 321 |
+
|
| 322 |
+
## Troubleshooting with Logs
|
| 323 |
+
Pinokio stores the logs for everything that happened in terminal at the following locations, so you can make use of them to determine what's going on:
|
| 324 |
+
|
| 325 |
+
### Log Structure
|
| 326 |
+
In case there is a `pinokio` folder in the project root folder, you should be able to find the logs folder here:
|
| 327 |
+
|
| 328 |
+
```
|
| 329 |
+
pinokio/
|
| 330 |
+
└── logs/ # Direct user interaction logs
|
| 331 |
+
├── api/ # Launcher script logs (install.js, start.js, etc.)
|
| 332 |
+
├── dev/ # AI coding tool logs (organized by tool)
|
| 333 |
+
└── shell/ # Direct user interaction logs
|
| 334 |
+
```
|
| 335 |
+
|
| 336 |
+
Otherwise, the `logs` folder should be found at project root:
|
| 337 |
+
|
| 338 |
+
```
|
| 339 |
+
logs/
|
| 340 |
+
├── api/ # Launcher script logs (install.js, start.js, etc.)
|
| 341 |
+
├── dev/ # AI coding tool logs (organized by tool)
|
| 342 |
+
└── shell/ # Direct user interaction logs
|
| 343 |
+
```
|
| 344 |
+
|
| 345 |
+
### Log File Naming
|
| 346 |
+
- Unix timestamps for each session
|
| 347 |
+
- Special "latest" file contains most recent session logs
|
| 348 |
+
- **Default:** Use "latest" files for current issues
|
| 349 |
+
- **Historical:** Use timestamped files for pattern analysis and the full history.
|
| 350 |
+
|
| 351 |
+
## Best practices
|
| 352 |
+
### 0. Always reference the logs when debugging
|
| 353 |
+
- When the user asks to fix something, ALWAYS check the logs folder first to check what went wrong. Check the "Troubleshooting with Logs" section.
|
| 354 |
+
### 1. Shell commands for launching programs
|
| 355 |
+
- Launch flags related
|
| 356 |
+
- Try as hard as possible to minimize launch flags and parameters when launching an app. For example, instead of `python app.py --port 8610`, try to do `python app.py` unless really necessary. The only exception is when the only way to launch the app is to specify the flags.
|
| 357 |
+
- Launch IP related
|
| 358 |
+
- Always try to find a way to launch servers at 127.0.0.1 or localhost, often by specifying launch flags or using environment variables. Some apps launch apps at 0.0.0.0 by default but we do not want this.
|
| 359 |
+
- Launch Port related
|
| 360 |
+
- In case the app itself automatically launches at the next available port by default (for example Gradio does this), do NOT specify port, since it's taken care of by the app itself. Always try to minimize the amount of code.
|
| 361 |
+
- If the install instruction says to launch at a specific port, don't use the hardcoded port they suggest since there's a risk of port conflicts. Instead, use Pinokio's `{{port}}` template expression to automatically get the next available port.
|
| 362 |
+
- For example, if the instruction says `python app.py --port 7860`, don't use that hardcoded port since there might be another app running at that port. Instead, automatically assign the next available port like this: `python app.py --port {{port}}`
|
| 363 |
+
- Note that the `{{port}}` expression always returns the next immediately available port for each step, so if you have multiple steps in a script and use `{{port}}` in multiple steps, the value will be different. So if you want to launch at the next available port and then later reuse that port, you will need to first use `{{port}}` to get the next available port, and save the value in local variable using `local.set`, and then use the `{{local.<variable_name>}}` expression later.
|
| 364 |
+
### 2. shell.run API
|
| 365 |
+
- When writing `shell.run` API requests, always use relative paths (no absolute paths) for the `path` field. For example, if you need to run a command from `app` folder, the `path` attribute should simply be `app`, instead of its full absolute path.
|
| 366 |
+
### 2. Package managers
|
| 367 |
+
- When installing python packages, try best to use `uv` instead of `pip` even if the install instruction says to use pip. Instead of `pip install -r requirements.txt`, you can simply use `uv pip install -r requirements.txt` for example. Even if the project's own README says use pip or poetry, first check if there's a way to use uv instead.
|
| 368 |
+
- When you need to install some global package, try to use `conda` as much as possible. Even on macs, `brew` should be only used if there are no `conda` options.
|
| 369 |
+
### 3. Minimal Always
|
| 370 |
+
- If you are starting with existing script files, before modifying, creating, or removing any script files, first look at `pinokio.js` to understand which script files are actually used in the launcher. The only script files used are the ones mentioned in the `pinokio.js` file. The `pinokio.js` file is the file that constructs the UI dynamically.
|
| 371 |
+
- Do not create a redundant script file that does something that already exists. Instead modify the existing script file for the feature. For example, do not create an `install.json` file for installation if `install.js` already exists. Instead, modify the `install.js` file.
|
| 372 |
+
- Pinokio accepts both JSON and JS script files, so when determining whether a script for a specific purpose already exists, check both JSON and JS files mentioned in the `pinokio.js` file. Do not create script files for rendundant purpose.
|
| 373 |
+
- When building launchers for existing projects cloned from a repository, try to stay away from modifying the project folder (the `C:\pinokio\api\Z-Image-Pinokio.git` folder), even if installations are failing. Instead, try to work around it by creating additional files in the launcher folder, and using those files IN ADDITION to the default project.
|
| 374 |
+
- The only exception when you may need to make changes to the project folder is when the user explicitly wants to modify the existing project. Otherwise if the purpose is to simply write a launcher, the app logic folder should never be touched.
|
| 375 |
+
- When running shell commands, take full advantage of the Pinokio `shell.run` API, which provides features like `env`, `venv`, `input`, `path`, `sudo`, `on`, etc. which can greatly reduce the amount of script code.
|
| 376 |
+
- Python apps: Always use virtual environments via `venv` attribute. This attribute automatically creates a venv or uses if it already exists.
|
| 377 |
+
### 4. Try to support Cross-platform as much as possible
|
| 378 |
+
- Use cross-platform shell commands only.
|
| 379 |
+
- This means, prefer to use commands that work on all platforms instead of the current platform.
|
| 380 |
+
- If there are no cross platform commands, use Pinokio's template expressions to conditionally use commands depending on `platform`, `arch`, etc.
|
| 381 |
+
- Also try to utilize Pinokio Pterm APIs for various cross-platform system features.
|
| 382 |
+
- If it is impossible to implement a cross platform solution (due to the nature of the project itself), set the `platform`, `arch`, and/or `gpu` attributes of the `pinokio.json` file to declare the limitation.
|
| 383 |
+
- Pinokio provides various APIs for cross-platform way of calling commonly used system functions, or lets you selectively run commands depending on `platform`, `arch`, etc.
|
| 384 |
+
### 5. Do not make assumptions about Pinokio API
|
| 385 |
+
- Do NOT make assumptions about which Pinokio APIs exist. Check the documentation.
|
| 386 |
+
- Do NOT make assumptions about the Pinokio API syntax. Follow the documentation.
|
| 387 |
+
### 6. Scripts must be able to replicate install and launch steps 100%
|
| 388 |
+
- The whole point of the scripts is for others to easily download and invoke them via Pinokio interface with one click. Therefore, do not assume the end user's system state, and make everything self-contained.
|
| 389 |
+
- When a 3rd party package needs to be installed, or a 3rd party repository needs to be downloaded, include them in the scripts.
|
| 390 |
+
### 7 Dynamic UI rendering
|
| 391 |
+
- The `pinokio.js` launcher script can change dynamically depending on the current state of the script execution. Which means, depending on what the file returns, it can determine what the sidebar looks like at any given moment of the script cycle.
|
| 392 |
+
- `info.exists(relative_path)`: The `info.exists` can be used to check whether a relative path (relative to the script root path) exists. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 393 |
+
- `info.running(relative_path)`: The `info.running` can be used to check whether a script at a relative path is currently running (relative to the script root path) exists. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 394 |
+
- `info.local(relative_path)`: The `info.local` can be used to return all the local variables tied to a script that's currently running. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 395 |
+
- `default`: set the `default` attribute on any menu item for whichever menu needs to be selected by default at a given step. Some example scenarios:
|
| 396 |
+
- during the install process, the `install.js` menu item needs to be set as the `default`, so it automatically executes the script
|
| 397 |
+
- when launching the `start.js` menu item needs to be set as the `default`, so it automatically executes the script
|
| 398 |
+
- after the app has launched, the `default` needs to be set on the web UI URL, so the user is sent to the actual app automatically.
|
| 399 |
+
- Check the examples in the C:\pinokio\prototype\system\examples folder to see how these are being used.
|
| 400 |
+
### 8. No need for stop scripts
|
| 401 |
+
- `pinokio.js` does NOT need a separate `stop` script. Every script that can be started can also be natively stopped through the Pinokio UI, therefore you do not need a separate stop script for start script
|
| 402 |
+
### 9. Writing launchers for existing projects
|
| 403 |
+
- When writing or modifying pinokio launcher scripts, figure out the install/launch steps by reading the project folder `app`.
|
| 404 |
+
- In most cases, the `README.md` file in the `C:\pinokio\api\Z-Image-Pinokio.git` folder contains the instructions needed to install and run the app, but if not, figure out by scanning the rest of the project files.
|
| 405 |
+
- Install scripts should work for each specific operating system, so ignore Docker related instructions. Instead use install/launch instructions for each platform.
|
| 406 |
+
### 10. Don't use Docker unless really necessary
|
| 407 |
+
- Some projects suggest docker as installation options. But even in these cases, try to find "development" options to launch the app without relying on Docker, as much as possible. We do not need Docker since we can automatically install and launch apps specifically for the user's platform, since we can write scripts that run cross platform.
|
| 408 |
+
### 11. pinokio.json
|
| 409 |
+
- Do not touch the `version` field since the version is the script schema version and the one pre-set in `pinokio.js` must be used.
|
| 410 |
+
- `icon`: It's best if we have a user friendly icon to represent the app, so try to get an image and link it from `pinokio.json`.
|
| 411 |
+
- If the git repository for the `C:\pinokio\api\Z-Image-Pinokio.git` folder points to GitHub (for example https://github.com/<USERNAME>/<REPO_NAME>`, ask the user if they want to download the icon from GitHub, and if approved, get the `avatar_url` by fetching `https://api.github.com/users/<USERNAME>`, and then download the image to the root folder as `icon.png`, and set `icon.png` as the `icon` field of the `pinokio.json`.
|
| 412 |
+
### 12. Gitignore
|
| 413 |
+
- When a launcher involves cloning 3rd party repositories, downloading files dynamically, or some files to be generated, these need to be included in the .gitignore file. This may include things like:
|
| 414 |
+
- Cloning git repositories
|
| 415 |
+
- Downloading files
|
| 416 |
+
- Dynamically creating files during installation or running, such as Sqlite Databases, or environment variables, or anything specific to the user.
|
| 417 |
+
- Make sure these file paths are included in the .gitignore file, and if not, include them in .gitignore.
|
| 418 |
+
|
| 419 |
+
## AI Libraries (Pytorch, Xformers, Triton, Sageattention, etc.)
|
| 420 |
+
If the launcher has a dedicated built-in script named `torch.js`, it can be used as follows:
|
| 421 |
+
|
| 422 |
+
```
|
| 423 |
+
// install.js
|
| 424 |
+
module.exports = {
|
| 425 |
+
run: [
|
| 426 |
+
// Delete this step if your project does not use torch
|
| 427 |
+
{
|
| 428 |
+
method: "script.start",
|
| 429 |
+
params: {
|
| 430 |
+
uri: "torch.js",
|
| 431 |
+
params: {
|
| 432 |
+
path: "app",
|
| 433 |
+
venv: "venv", // Edit this to customize the venv folder path
|
| 434 |
+
// xformers: true // uncomment this line if your project requires xformers
|
| 435 |
+
// triton: true // uncomment this line if your project requires triton
|
| 436 |
+
// sageattention: true // uncomment this line if your project requires sageattention
|
| 437 |
+
}
|
| 438 |
+
}
|
| 439 |
+
},
|
| 440 |
+
// Edit this step with your custom install commands
|
| 441 |
+
{
|
| 442 |
+
method: "shell.run",
|
| 443 |
+
params: {
|
| 444 |
+
venv: "venv", // Edit this to customize the venv folder path
|
| 445 |
+
path: "app",
|
| 446 |
+
message: [
|
| 447 |
+
"uv pip install -r requirements.txt"
|
| 448 |
+
],
|
| 449 |
+
}
|
| 450 |
+
},
|
| 451 |
+
]
|
| 452 |
+
}
|
| 453 |
+
```
|
| 454 |
+
|
| 455 |
+
The `torch.js` script also includes ways to install pytorch dependent libraries such as xformers, triton, sagetattention. If any of these libraries need to be installed, use the torch.js to install in order to install them cross platform.
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
## Quick Reference
|
| 459 |
+
### Essential Documentation
|
| 460 |
+
- **Pinokio Programming:** See `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md → "Programming Pinokio" section
|
| 461 |
+
- **Dynamic Menus:** See `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md → "Dynamic menu rendering" section
|
| 462 |
+
- **CLI Commands:** See `PTERM.md` at C:\pinokio\prototype\PTERM.md
|
| 463 |
+
### Common Patterns
|
| 464 |
+
- **Python Virtual Env:** `shell.run` with `venv` attribute
|
| 465 |
+
- **Cross-platform Commands:** Always test on multiple platforms
|
| 466 |
+
- **Error Handling:** Check logs/api for launcher issues
|
| 467 |
+
- **GitHub Operations:** Use `gh` CLI for advanced GitHub features
|
| 468 |
+
## Development Principles
|
| 469 |
+
1. **Minimize Shell Usage:** Leverage API parameters instead of raw commands
|
| 470 |
+
2. **Maintain Separation:** Keep app logic and launchers separate
|
| 471 |
+
3. **Follow Conventions:** Match existing project patterns
|
| 472 |
+
4. **Test Thoroughly:** Use CLI to verify launcher functionality
|
| 473 |
+
5. **Document Changes:** Update relevant metadata and documentation
|
AGENTS.md
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Development Guide for Pinokio Projects
|
| 2 |
+
|
| 3 |
+
## Non-Negotiable Execution Workflow
|
| 4 |
+
|
| 5 |
+
To guarantee every contribution follows this guide precisely, obey this checklist **before any edits** and **again before finalizing**. Do not skip or reorder.
|
| 6 |
+
1. **AGENTS Snapshot:** Re-open this file and write down (in your working notes or response draft) the exact sections relevant to the requested task. No work begins until this snapshot exists.
|
| 7 |
+
2. **Example Lock-in:** Identify the closest matching script in `C:\pinokio\prototype\system\examples`. Record its path and keep it open while editing. Every launcher change must mirror that reference unless the user explicitly instructs otherwise.
|
| 8 |
+
3. **Pre-flight Checklist:** Convert the applicable rules from this document and `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md into a task-specific checklist (install/start/reset/update structure, regex patterns, menu defaults, log checks, etc.). Confirm each item is ticked **before** making changes.
|
| 9 |
+
4. **Mid-task Verification:** Any time you touch a Pinokio script, cross-check the corresponding example line to ensure syntax and structure match. Document the reference (example path + line) in your reasoning.
|
| 10 |
+
5. **Exit Checklist:** Before responding to the user, revisit the pre-flight checklist and explicitly confirm every item is satisfied. If anything diverges from the example or these rules, fix it first.
|
| 11 |
+
|
| 12 |
+
If any step cannot be completed, stop immediately and ask the user how to proceed. These five steps are mandatory for every session.
|
| 13 |
+
|
| 14 |
+
### Critical Pattern Lock: Capturing Web UI URLs
|
| 15 |
+
|
| 16 |
+
When writing `start.js` (or any script that needs to surface a web URL for a server):
|
| 17 |
+
|
| 18 |
+
1. **Always copy the capture block from an example such as `system/examples/mochi/start.js`.**
|
| 19 |
+
```javascript
|
| 20 |
+
on: [{
|
| 21 |
+
event: "/(http:\\/\\/[0-9.:]+)/",
|
| 22 |
+
done: true
|
| 23 |
+
}]
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
2. **Set the local variable using the captured match exactly as below (The regex capture object is passed in as `input.event`, so need to use the index 1 inside the parenthesis):**
|
| 27 |
+
```javascript
|
| 28 |
+
{
|
| 29 |
+
method: "local.set",
|
| 30 |
+
params: {
|
| 31 |
+
url: "{{input.event[1]}}"
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
3. Always try to come up with the most generic regex.
|
| 37 |
+
4. During the exit checklist, explicitly confirm that the `url` local variable is set via `local.set` API by using the captured regex object as passed in as `input.event` from the previous `shell.run` step.
|
| 38 |
+
|
| 39 |
+
Deviation from this pattern requires written approval from the user.
|
| 40 |
+
|
| 41 |
+
- Make sure to keep this entire document and `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md in memory with high priority before making any decision. Pinokio is a system that makes it easy to write launchers through scripting by providing various cross-platform APIs, so whenever possible you should prioritize using Pinokio API over lower level APIs.
|
| 42 |
+
- When writing pinokio scripts, ALWAYS check the examples folder (in C:\pinokio\prototype\system\examples folder) to see if there are existing example scripts you can imitate, instead of assuming syntax.
|
| 43 |
+
- When implementing pinokio script APIs and you cannot infer the syntax just based on the examples, always search the API documentation `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md to use the correct syntax instead of assuming the syntax.
|
| 44 |
+
- When trying to fix something or figure out what's going on, ALWAYS start by checking the `logs` folder before doing anything else, as mentioned in the "Troubleshooting with Logs" section.
|
| 45 |
+
- Finally, make sure to ALWAYS follow all the items in the "best practices" section below.
|
| 46 |
+
|
| 47 |
+
## Determine User Intent
|
| 48 |
+
If the initial prompt is simply a URL and nothing else, check the website content and determine the intent, and ask the user to confirm. For example a URL may point to
|
| 49 |
+
|
| 50 |
+
1. A Tutorial: the intent may be to implement a demo for the tutorial and build a launcher.
|
| 51 |
+
2. A Demo: the intent may be a 1-click launcher for the demo
|
| 52 |
+
3. Open source project: the intent may be a 1-click launcher for the project
|
| 53 |
+
4. Regular website: the intent may be to clone the website and a launcher.
|
| 54 |
+
5. There can be other cases, but try to guess.
|
| 55 |
+
|
| 56 |
+
## Project Structure
|
| 57 |
+
|
| 58 |
+
Pinokio projects normally follow a standardized structure with app logic separated from launcher scripts:
|
| 59 |
+
|
| 60 |
+
Pinokio projects follow a standardized structure with app logic separated from launcher scripts:
|
| 61 |
+
|
| 62 |
+
```
|
| 63 |
+
project-root/
|
| 64 |
+
├── app/ # Self-contained app logic (can be standalone repo)
|
| 65 |
+
│ ├── package.json # Node.js projects
|
| 66 |
+
│ ├── requirements.txt # Python projects
|
| 67 |
+
│ └── ... # Other language-specific files
|
| 68 |
+
├── README.md # Documentation
|
| 69 |
+
├── install.js # Installation script
|
| 70 |
+
├── start.js # Launch script
|
| 71 |
+
├── update.js # Update script (for updating the scripts and app logic to the latest)
|
| 72 |
+
├── reset.js # Reset dependencies script
|
| 73 |
+
├── pinokio.js # UI generator script
|
| 74 |
+
└── pinokio.json # Metadata (title, description, icon)
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
- Keep app code in `/app` folder only (never in root)
|
| 78 |
+
- Store all launcher files in project root (never in `/app`)
|
| 79 |
+
- `/app` folder should be self-contained and publishable
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
The only exceptions are serverless web apps---purely frontend only web applications that do NOT have a server component and connect to 3rd party API endpoints--in which case the folder structure looks like the following (No need for launcher scripts since the index.html will automatically launch. The only thing needed is the metadata file named pinokio.json):
|
| 83 |
+
|
| 84 |
+
```
|
| 85 |
+
project-root/
|
| 86 |
+
├── index.html # The serverless web app entry point
|
| 87 |
+
├── ...
|
| 88 |
+
├── README.md # Documentation
|
| 89 |
+
└── pinokio.json # Metadata (title, description, icon)
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
IMPORTANT: ALWAYS try to follow the best practices in the examples folder (C:\pinokio\prototype\system\examples) instead of trying to come up with your own structure. The examples have been optimized for the best user experience.
|
| 93 |
+
|
| 94 |
+
## Launcher Project Working Directory
|
| 95 |
+
|
| 96 |
+
- The project working directory for a script is always the same directory as the script location.
|
| 97 |
+
- For example, when you run `shell.run` API inside `pinokio/start.js`, the default path for shell execution is `pinokio`.
|
| 98 |
+
- If the launcher files are in the project root path, then the default path for shell execution is the project root.
|
| 99 |
+
- Therefore, it is important to specify the correct `path` attribute when running `shell.run` API commands.
|
| 100 |
+
|
| 101 |
+
Example: in the following project structure:
|
| 102 |
+
|
| 103 |
+
```
|
| 104 |
+
project-root/
|
| 105 |
+
├── pinokio/ # Pinokio launcher folder
|
| 106 |
+
│ ├── start.js # Launch script
|
| 107 |
+
│ ├── pinokio.js # UI generator script
|
| 108 |
+
│ └── pinokio.json # Metadata (title, description, icon)
|
| 109 |
+
└─── backend/
|
| 110 |
+
├── requirements.txt # App dependencies
|
| 111 |
+
└── app.py # App code
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
The `pinokio/start.js` should use the correct path `../backend` as the `path` attribute, as follows:
|
| 115 |
+
|
| 116 |
+
```
|
| 117 |
+
{
|
| 118 |
+
run: [{
|
| 119 |
+
...
|
| 120 |
+
}, {
|
| 121 |
+
method: "shell.run",
|
| 122 |
+
params: {
|
| 123 |
+
message: "python app.py",
|
| 124 |
+
venv: "env",
|
| 125 |
+
path: "../backend"
|
| 126 |
+
}
|
| 127 |
+
}, {
|
| 128 |
+
...
|
| 129 |
+
}]
|
| 130 |
+
}
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
## Development Workflow
|
| 134 |
+
|
| 135 |
+
### 1. Understanding the Project
|
| 136 |
+
- Check `SPEC.md` in project root. If the file exists, use that to learn about the project details (what and how to build)
|
| 137 |
+
- If no `SPEC.md` exists, build based on user requirements
|
| 138 |
+
### 2. Modifying Existing Launcher Projects
|
| 139 |
+
If we are starting with existing launcher script files, work with the existing files instead of coming up with your own.
|
| 140 |
+
- **Preserve existing functionality:** Only modify necessary parts
|
| 141 |
+
- **Don't touch working scripts:** Unless adding/updating specific commands
|
| 142 |
+
- **Follow existing conventions:** Match the style and structure already present
|
| 143 |
+
### 3. Try to adopt from examples as much as possible
|
| 144 |
+
- If starting from scratch, first determine what type of project you will be building, and then check the examples folder (C:\pinokio\prototype\system\examples) to see if you can adopt them instead of coming up everything from scratch.
|
| 145 |
+
- Even if there are no relevant examples, check the examples to get inspiration for how you would structure the script files even if you have to write from scratch.
|
| 146 |
+
### 4. Writing from scratch as a last resort
|
| 147 |
+
If there are relevant examples to adopt from, write the scripts from scratch, but just make sure to follow the requirements in the next section.
|
| 148 |
+
### 5. Debugging
|
| 149 |
+
When the user reports something is not working, ALWAYS inspect the logs folder to get all the execution logs. For more info on how this works, check the "Troubleshooting with Logs" section below.
|
| 150 |
+
|
| 151 |
+
## Script Requirements
|
| 152 |
+
|
| 153 |
+
### 1. 1-click launchable
|
| 154 |
+
- The main purpose of Pinokio is to provide an easy interface to invoke commands, which may include launching servers, installing programs, etc. Make sure the final product provides ways to install, launch, reset, and update whatever is needed.
|
| 155 |
+
|
| 156 |
+
### 2. Write Documentation
|
| 157 |
+
- ALWAYS write a documentation. A documentation must be stored as `README.md` in the project root folder, along with the rest of the pinokio launcher script files. A documentation file must contain:
|
| 158 |
+
- What the app does
|
| 159 |
+
- How to use the app
|
| 160 |
+
- API documentation for programmatically accessing the app's main features (Javascript, Python, and Curl)
|
| 161 |
+
|
| 162 |
+
## Types of launchers
|
| 163 |
+
## 1. Launching servers
|
| 164 |
+
- When an app requires launching a server, here are the commonly used scripts:
|
| 165 |
+
- `install.js`: a script to install the app
|
| 166 |
+
- `start.js`: a script to start the app
|
| 167 |
+
- `reset.js`: a script to reset all the dependencies installed in the `install.js` step. used if the user wants to restart from scratch
|
| 168 |
+
- `update.js`: a script to update the launcher AND the app in case there are new updates. Involves pulling in the relevant git repositories installed through `install.js` (often it's the script repo and some git repositories cloned through the install steps if any)
|
| 169 |
+
- `pinokio.js`: the launcher script that ties all of the above scripts together by providing a UI that links to these scripts.
|
| 170 |
+
- `pinokio.json`: For metadata
|
| 171 |
+
|
| 172 |
+
Here's a basic server launcher script example (`start.js`). Unless there's a special reason you need to use another pattern, this is the most recommended pattern. Use this or adopt it as needed, but NEVER try something else unless there's a good reason you should not take this approach:
|
| 173 |
+
|
| 174 |
+
```javascript
|
| 175 |
+
module.exports = {
|
| 176 |
+
// By setting daemon: true, the script keeps running even after all items in the `run` array finishes running. Mandatory for launching servers, since otherwise the shells running the server process will get killed after the scripts finish running.
|
| 177 |
+
daemon: true,
|
| 178 |
+
run: [
|
| 179 |
+
{
|
| 180 |
+
// The "shell.run" API for running a shell session
|
| 181 |
+
method: "shell.run",
|
| 182 |
+
params: {
|
| 183 |
+
// Edit 'venv' to customize the venv folder path
|
| 184 |
+
venv: "env",
|
| 185 |
+
// Edit 'env' to customize environment variables (see documentation)
|
| 186 |
+
env: { },
|
| 187 |
+
// Edit 'path' to customize the path to start the shell from
|
| 188 |
+
path: "app",
|
| 189 |
+
// Edit 'message' to customize the commands, or to run multiple commands
|
| 190 |
+
message: [
|
| 191 |
+
"python app.py",
|
| 192 |
+
],
|
| 193 |
+
on: [{
|
| 194 |
+
// The regular expression pattern to monitor.
|
| 195 |
+
// Whenever each "event" pattern occurs in the shell terminal, the shell will return,
|
| 196 |
+
// and the script will go onto the next step.
|
| 197 |
+
// The regular expression match object will be passed on to the next step as `input.event`
|
| 198 |
+
// Useful for capturing the URL at which the server is running (in case the server prints some message about where the server is running)
|
| 199 |
+
"event": "/(http:\/\/\\S+)/",
|
| 200 |
+
|
| 201 |
+
// Use "done": true to move to the next step while keeping the shell alive.
|
| 202 |
+
// Use "kill": true to move to the next step after killing the shell.
|
| 203 |
+
"done": true
|
| 204 |
+
}]
|
| 205 |
+
}
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
// This step sets the local variable 'url'.
|
| 209 |
+
// This local variable will be used in pinokio.js to display the "Open WebUI" tab when the value is set.
|
| 210 |
+
method: "local.set",
|
| 211 |
+
params: {
|
| 212 |
+
// the input.event is the regular expression match object from the previous step
|
| 213 |
+
// In this example, since the pattern was "/(http:\/\/\\S+)/", input.event[1] will include the exact http url match caputred by the parenthesis.
|
| 214 |
+
// Therefore setting the local variable 'url'
|
| 215 |
+
url: "{{input.event[1]}}"
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
]
|
| 219 |
+
}
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
## 2. Launching serverless web apps
|
| 223 |
+
|
| 224 |
+
- In case of purely static web apps WITHOUT servers or backends (for example an HTML based app that connects to 3rd party servers--either remote or localhost), we do NOT need the launcher scripts.
|
| 225 |
+
- In these cases, simply include `index.html` in the project root folder and everything should automatically work. No need for any of the pinokio launcher scripts. (Do
|
| 226 |
+
- You still need to include the metadata file so they show up properly on pinokio:
|
| 227 |
+
- `pinokio.json`: For metadata
|
| 228 |
+
|
| 229 |
+
## 3. Launching quick scripts without web UI
|
| 230 |
+
|
| 231 |
+
- In many cases, we may not even need a web UI, but instead just a simple way to run scripts.
|
| 232 |
+
- This may include TUI (Terminal User Interface) apps, a simple launcher
|
| 233 |
+
- In these cases, all we need is the launcher file `pinokio.js`, which may link to multiple scripts. In this case, there are no web apps (no serverless apsp, no servers), but instead just the default pinokio launcher UI that calls a bunch of scripts.
|
| 234 |
+
- Here are some examples:
|
| 235 |
+
- A pinokio script to toggle the desktop theme between dark and light
|
| 236 |
+
- Write some code (python or javascript or whatever)
|
| 237 |
+
- Write a `toggle.js` pinokio script that executes the code
|
| 238 |
+
- Write a `pinokio.js` launcher script to create a sidebar UI that displays the `toggle.js` so the user can simply click the "toggle" button to toggle back and forth between desktop themes
|
| 239 |
+
- A pinokio script to fetch some file
|
| 240 |
+
- Write some code (python or javascript or whatever)
|
| 241 |
+
- Write a `fetch.js` pinokio script that executes the code
|
| 242 |
+
- Write a `pinokio.js` launcher script to create a sidebar UI that displays the `fetch.js` so the user can simply click the "fetch" button to fetch some data.
|
| 243 |
+
- You still need to include the metadata file so they show up properly on pinokio:
|
| 244 |
+
- `pinokio.json`: For metadata
|
| 245 |
+
|
| 246 |
+
## API
|
| 247 |
+
|
| 248 |
+
This section lists all the script APIs available on Pinokio. To learn the details of how they are used, you can:
|
| 249 |
+
1. Check the examples in the C:\pinokio\prototype\system\examples folder
|
| 250 |
+
2. Read the `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md further documentation on the full syntax
|
| 251 |
+
|
| 252 |
+
### Script API
|
| 253 |
+
|
| 254 |
+
These APIs can be used to describe each step in a pinokio script:
|
| 255 |
+
- shell.run: run shell commands
|
| 256 |
+
- input: accept user input
|
| 257 |
+
- filepicker: accept file upload
|
| 258 |
+
- fs.write: write to file
|
| 259 |
+
- fs.read: read from file
|
| 260 |
+
- fs.copy: copy files
|
| 261 |
+
- fs.download: download files
|
| 262 |
+
- fs.link: create a symbolic link (or junction on windows) for folders
|
| 263 |
+
- fs.open: open the system file explorer at a given path
|
| 264 |
+
- fs.cat: print file contents
|
| 265 |
+
- jump: jump to a specific step
|
| 266 |
+
- local.set: set local variables for the currently running script
|
| 267 |
+
- json.set: update a json file
|
| 268 |
+
- json.rm: remove keys from a json file
|
| 269 |
+
- json.get: get values from a json file
|
| 270 |
+
- log: print to the web terminal
|
| 271 |
+
- net: make network requests
|
| 272 |
+
- notify: display a notification
|
| 273 |
+
- script.download: download a script from a git uri
|
| 274 |
+
- script.start: start a script
|
| 275 |
+
- script.stop: stop a script
|
| 276 |
+
- script.return: return values if the current script was called by a caller script, so the caller script can utilize the return value as `input`
|
| 277 |
+
- web.open: open a url in web browser
|
| 278 |
+
- hf.download: huggingfac-cli download API
|
| 279 |
+
### Template variables
|
| 280 |
+
The following variables are accessible inside template expressions (example `{{args.command}` in scripts, resulting in dynamic behaviors of scripts:
|
| 281 |
+
- input: An input is a variable that gets passed from one RPC call to the next
|
| 282 |
+
- args: args is the parameter object that gets passed into the script (via pinokio.js `params`). Unlike `input` which takes the value passed in from the immediately previous step, `args` is a global value that is the same through out the entire script execution.
|
| 283 |
+
- local: local variable object that can be set with `local.set` API
|
| 284 |
+
- self: refers to the script file itself (which is JSON or JavaScript). For example if `start.js` that's currently running has `daemon: true` set, `{{self.daemon}}` will evaluate to true.
|
| 285 |
+
- uri: The current script uri
|
| 286 |
+
- port: The next available port. Very useful when you need to launch an app at a specific port without port conflicts.
|
| 287 |
+
- cwd: The current script execution folder path
|
| 288 |
+
- platform: The current operating system. May be one of the following: `darwin`, `win32`, `linux`
|
| 289 |
+
- arch: The current system architecture. May be one of the following: x32, x64, arm, arm64, s390, s390x, mipsel, ia32, mips, ppc, ppc64
|
| 290 |
+
- gpus: array of available GPUs on the machine (example: `['apple']`, `['nvidia']`)
|
| 291 |
+
- gpu: the first available GPU (example: `nvidia`)
|
| 292 |
+
- current: The current variable points to the index of the currently executing instruction within the run array.
|
| 293 |
+
- next: The next variable points to the index of the next instruction to be executed. (null if the current instruction is the final instruction in the run array)
|
| 294 |
+
- envs: You can access the environment variables of the currently running process with envs object.
|
| 295 |
+
- which: Check whether a command exists (example: `{{which('winget')}}`. Can be used in the `when` attribute of a script step to run commands or install first.
|
| 296 |
+
- exists: Check whether a file or folder exists at the specified relative path (example: `"when": "{{!exists('app')}}"`). Can be used with the `when` attribute to determine a path's existence and trigger custom logic. Use relative paths and it will resolve automatically to the current execution folder.
|
| 297 |
+
- running: Check whether a script file is running (example: `"when": "{{!running('start.js')}}"`). Can be used with the `when` attribute to determine a path's existence and trigger custom logic. Use relative paths and it will resolve automatically to the current execution folder.
|
| 298 |
+
- os: Pinokio exposes the node.js os module through the os variable.
|
| 299 |
+
- path: Pinokio exposes the node.js path module through the os variable (example: `{{path.resolve(...)}}`
|
| 300 |
+
|
| 301 |
+
## System Capabilities
|
| 302 |
+
### Package Management (Use in Order of Preference)
|
| 303 |
+
The following package managers come pre-installed with Pinokio, so whenever you need to install a 3rd party binary, remember that these are available. Also, you can assume these are available and include the following package manager commands in Pinokio scripts:
|
| 304 |
+
1. **UV** - For Python packages (preferred over pip)
|
| 305 |
+
2. **NPM** - For Node.js packages
|
| 306 |
+
3. **Conda** - For cross-platform 3rd party binaries
|
| 307 |
+
4. **Brew** - Mac-only fallback when other options unavailable
|
| 308 |
+
5. **Git** - Full access to git is available.
|
| 309 |
+
**Important:** Include all install commands in the install script for reproducibility.
|
| 310 |
+
### HTTPS Proxy Support
|
| 311 |
+
- All HTTP servers automatically get HTTPS endpoints
|
| 312 |
+
- Convention: `http://localhost:<PORT>` → `https://<PORT>.localhost`
|
| 313 |
+
- Full proxy list available at: `http://localhost:2019/config/`
|
| 314 |
+
### Pterm Features:
|
| 315 |
+
- **Clipboard Access:** Read from or Write to system clipboard via pinokio Pterm CLI (`pterm clipboard` command.)
|
| 316 |
+
- **Notifications:** Send desktop alerts via pinokio pterm CLI (`pterm push` command.)
|
| 317 |
+
- **Script Testing:** Run launcher scripts via pinokio pterm CLI (`pterm start` command.)
|
| 318 |
+
- **File Selection:** Use built-in filepicker for user file/folder input (`pterm filepicker` command.)
|
| 319 |
+
- **Git Operations:** Clone repositories, push to GitHub
|
| 320 |
+
- **GitHub Integration:** Full GitHub CLI support (`gh` commands)
|
| 321 |
+
|
| 322 |
+
## Troubleshooting with Logs
|
| 323 |
+
Pinokio stores the logs for everything that happened in terminal at the following locations, so you can make use of them to determine what's going on:
|
| 324 |
+
|
| 325 |
+
### Log Structure
|
| 326 |
+
In case there is a `pinokio` folder in the project root folder, you should be able to find the logs folder here:
|
| 327 |
+
|
| 328 |
+
```
|
| 329 |
+
pinokio/
|
| 330 |
+
└── logs/ # Direct user interaction logs
|
| 331 |
+
├── api/ # Launcher script logs (install.js, start.js, etc.)
|
| 332 |
+
├── dev/ # AI coding tool logs (organized by tool)
|
| 333 |
+
└── shell/ # Direct user interaction logs
|
| 334 |
+
```
|
| 335 |
+
|
| 336 |
+
Otherwise, the `logs` folder should be found at project root:
|
| 337 |
+
|
| 338 |
+
```
|
| 339 |
+
logs/
|
| 340 |
+
├── api/ # Launcher script logs (install.js, start.js, etc.)
|
| 341 |
+
├── dev/ # AI coding tool logs (organized by tool)
|
| 342 |
+
└── shell/ # Direct user interaction logs
|
| 343 |
+
```
|
| 344 |
+
|
| 345 |
+
### Log File Naming
|
| 346 |
+
- Unix timestamps for each session
|
| 347 |
+
- Special "latest" file contains most recent session logs
|
| 348 |
+
- **Default:** Use "latest" files for current issues
|
| 349 |
+
- **Historical:** Use timestamped files for pattern analysis and the full history.
|
| 350 |
+
|
| 351 |
+
## Best practices
|
| 352 |
+
### 0. Always reference the logs when debugging
|
| 353 |
+
- When the user asks to fix something, ALWAYS check the logs folder first to check what went wrong. Check the "Troubleshooting with Logs" section.
|
| 354 |
+
### 1. Shell commands for launching programs
|
| 355 |
+
- Launch flags related
|
| 356 |
+
- Try as hard as possible to minimize launch flags and parameters when launching an app. For example, instead of `python app.py --port 8610`, try to do `python app.py` unless really necessary. The only exception is when the only way to launch the app is to specify the flags.
|
| 357 |
+
- Launch IP related
|
| 358 |
+
- Always try to find a way to launch servers at 127.0.0.1 or localhost, often by specifying launch flags or using environment variables. Some apps launch apps at 0.0.0.0 by default but we do not want this.
|
| 359 |
+
- Launch Port related
|
| 360 |
+
- In case the app itself automatically launches at the next available port by default (for example Gradio does this), do NOT specify port, since it's taken care of by the app itself. Always try to minimize the amount of code.
|
| 361 |
+
- If the install instruction says to launch at a specific port, don't use the hardcoded port they suggest since there's a risk of port conflicts. Instead, use Pinokio's `{{port}}` template expression to automatically get the next available port.
|
| 362 |
+
- For example, if the instruction says `python app.py --port 7860`, don't use that hardcoded port since there might be another app running at that port. Instead, automatically assign the next available port like this: `python app.py --port {{port}}`
|
| 363 |
+
- Note that the `{{port}}` expression always returns the next immediately available port for each step, so if you have multiple steps in a script and use `{{port}}` in multiple steps, the value will be different. So if you want to launch at the next available port and then later reuse that port, you will need to first use `{{port}}` to get the next available port, and save the value in local variable using `local.set`, and then use the `{{local.<variable_name>}}` expression later.
|
| 364 |
+
### 2. shell.run API
|
| 365 |
+
- When writing `shell.run` API requests, always use relative paths (no absolute paths) for the `path` field. For example, if you need to run a command from `app` folder, the `path` attribute should simply be `app`, instead of its full absolute path.
|
| 366 |
+
### 2. Package managers
|
| 367 |
+
- When installing python packages, try best to use `uv` instead of `pip` even if the install instruction says to use pip. Instead of `pip install -r requirements.txt`, you can simply use `uv pip install -r requirements.txt` for example. Even if the project's own README says use pip or poetry, first check if there's a way to use uv instead.
|
| 368 |
+
- When you need to install some global package, try to use `conda` as much as possible. Even on macs, `brew` should be only used if there are no `conda` options.
|
| 369 |
+
### 3. Minimal Always
|
| 370 |
+
- If you are starting with existing script files, before modifying, creating, or removing any script files, first look at `pinokio.js` to understand which script files are actually used in the launcher. The only script files used are the ones mentioned in the `pinokio.js` file. The `pinokio.js` file is the file that constructs the UI dynamically.
|
| 371 |
+
- Do not create a redundant script file that does something that already exists. Instead modify the existing script file for the feature. For example, do not create an `install.json` file for installation if `install.js` already exists. Instead, modify the `install.js` file.
|
| 372 |
+
- Pinokio accepts both JSON and JS script files, so when determining whether a script for a specific purpose already exists, check both JSON and JS files mentioned in the `pinokio.js` file. Do not create script files for rendundant purpose.
|
| 373 |
+
- When building launchers for existing projects cloned from a repository, try to stay away from modifying the project folder (the `C:\pinokio\api\Z-Image-Pinokio.git` folder), even if installations are failing. Instead, try to work around it by creating additional files in the launcher folder, and using those files IN ADDITION to the default project.
|
| 374 |
+
- The only exception when you may need to make changes to the project folder is when the user explicitly wants to modify the existing project. Otherwise if the purpose is to simply write a launcher, the app logic folder should never be touched.
|
| 375 |
+
- When running shell commands, take full advantage of the Pinokio `shell.run` API, which provides features like `env`, `venv`, `input`, `path`, `sudo`, `on`, etc. which can greatly reduce the amount of script code.
|
| 376 |
+
- Python apps: Always use virtual environments via `venv` attribute. This attribute automatically creates a venv or uses if it already exists.
|
| 377 |
+
### 4. Try to support Cross-platform as much as possible
|
| 378 |
+
- Use cross-platform shell commands only.
|
| 379 |
+
- This means, prefer to use commands that work on all platforms instead of the current platform.
|
| 380 |
+
- If there are no cross platform commands, use Pinokio's template expressions to conditionally use commands depending on `platform`, `arch`, etc.
|
| 381 |
+
- Also try to utilize Pinokio Pterm APIs for various cross-platform system features.
|
| 382 |
+
- If it is impossible to implement a cross platform solution (due to the nature of the project itself), set the `platform`, `arch`, and/or `gpu` attributes of the `pinokio.json` file to declare the limitation.
|
| 383 |
+
- Pinokio provides various APIs for cross-platform way of calling commonly used system functions, or lets you selectively run commands depending on `platform`, `arch`, etc.
|
| 384 |
+
### 5. Do not make assumptions about Pinokio API
|
| 385 |
+
- Do NOT make assumptions about which Pinokio APIs exist. Check the documentation.
|
| 386 |
+
- Do NOT make assumptions about the Pinokio API syntax. Follow the documentation.
|
| 387 |
+
### 6. Scripts must be able to replicate install and launch steps 100%
|
| 388 |
+
- The whole point of the scripts is for others to easily download and invoke them via Pinokio interface with one click. Therefore, do not assume the end user's system state, and make everything self-contained.
|
| 389 |
+
- When a 3rd party package needs to be installed, or a 3rd party repository needs to be downloaded, include them in the scripts.
|
| 390 |
+
### 7 Dynamic UI rendering
|
| 391 |
+
- The `pinokio.js` launcher script can change dynamically depending on the current state of the script execution. Which means, depending on what the file returns, it can determine what the sidebar looks like at any given moment of the script cycle.
|
| 392 |
+
- `info.exists(relative_path)`: The `info.exists` can be used to check whether a relative path (relative to the script root path) exists. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 393 |
+
- `info.running(relative_path)`: The `info.running` can be used to check whether a script at a relative path is currently running (relative to the script root path) exists. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 394 |
+
- `info.local(relative_path)`: The `info.local` can be used to return all the local variables tied to a script that's currently running. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 395 |
+
- `default`: set the `default` attribute on any menu item for whichever menu needs to be selected by default at a given step. Some example scenarios:
|
| 396 |
+
- during the install process, the `install.js` menu item needs to be set as the `default`, so it automatically executes the script
|
| 397 |
+
- when launching the `start.js` menu item needs to be set as the `default`, so it automatically executes the script
|
| 398 |
+
- after the app has launched, the `default` needs to be set on the web UI URL, so the user is sent to the actual app automatically.
|
| 399 |
+
- Check the examples in the C:\pinokio\prototype\system\examples folder to see how these are being used.
|
| 400 |
+
### 8. No need for stop scripts
|
| 401 |
+
- `pinokio.js` does NOT need a separate `stop` script. Every script that can be started can also be natively stopped through the Pinokio UI, therefore you do not need a separate stop script for start script
|
| 402 |
+
### 9. Writing launchers for existing projects
|
| 403 |
+
- When writing or modifying pinokio launcher scripts, figure out the install/launch steps by reading the project folder `app`.
|
| 404 |
+
- In most cases, the `README.md` file in the `C:\pinokio\api\Z-Image-Pinokio.git` folder contains the instructions needed to install and run the app, but if not, figure out by scanning the rest of the project files.
|
| 405 |
+
- Install scripts should work for each specific operating system, so ignore Docker related instructions. Instead use install/launch instructions for each platform.
|
| 406 |
+
### 10. Don't use Docker unless really necessary
|
| 407 |
+
- Some projects suggest docker as installation options. But even in these cases, try to find "development" options to launch the app without relying on Docker, as much as possible. We do not need Docker since we can automatically install and launch apps specifically for the user's platform, since we can write scripts that run cross platform.
|
| 408 |
+
### 11. pinokio.json
|
| 409 |
+
- Do not touch the `version` field since the version is the script schema version and the one pre-set in `pinokio.js` must be used.
|
| 410 |
+
- `icon`: It's best if we have a user friendly icon to represent the app, so try to get an image and link it from `pinokio.json`.
|
| 411 |
+
- If the git repository for the `C:\pinokio\api\Z-Image-Pinokio.git` folder points to GitHub (for example https://github.com/<USERNAME>/<REPO_NAME>`, ask the user if they want to download the icon from GitHub, and if approved, get the `avatar_url` by fetching `https://api.github.com/users/<USERNAME>`, and then download the image to the root folder as `icon.png`, and set `icon.png` as the `icon` field of the `pinokio.json`.
|
| 412 |
+
### 12. Gitignore
|
| 413 |
+
- When a launcher involves cloning 3rd party repositories, downloading files dynamically, or some files to be generated, these need to be included in the .gitignore file. This may include things like:
|
| 414 |
+
- Cloning git repositories
|
| 415 |
+
- Downloading files
|
| 416 |
+
- Dynamically creating files during installation or running, such as Sqlite Databases, or environment variables, or anything specific to the user.
|
| 417 |
+
- Make sure these file paths are included in the .gitignore file, and if not, include them in .gitignore.
|
| 418 |
+
|
| 419 |
+
## AI Libraries (Pytorch, Xformers, Triton, Sageattention, etc.)
|
| 420 |
+
If the launcher has a dedicated built-in script named `torch.js`, it can be used as follows:
|
| 421 |
+
|
| 422 |
+
```
|
| 423 |
+
// install.js
|
| 424 |
+
module.exports = {
|
| 425 |
+
run: [
|
| 426 |
+
// Delete this step if your project does not use torch
|
| 427 |
+
{
|
| 428 |
+
method: "script.start",
|
| 429 |
+
params: {
|
| 430 |
+
uri: "torch.js",
|
| 431 |
+
params: {
|
| 432 |
+
path: "app",
|
| 433 |
+
venv: "venv", // Edit this to customize the venv folder path
|
| 434 |
+
// xformers: true // uncomment this line if your project requires xformers
|
| 435 |
+
// triton: true // uncomment this line if your project requires triton
|
| 436 |
+
// sageattention: true // uncomment this line if your project requires sageattention
|
| 437 |
+
}
|
| 438 |
+
}
|
| 439 |
+
},
|
| 440 |
+
// Edit this step with your custom install commands
|
| 441 |
+
{
|
| 442 |
+
method: "shell.run",
|
| 443 |
+
params: {
|
| 444 |
+
venv: "venv", // Edit this to customize the venv folder path
|
| 445 |
+
path: "app",
|
| 446 |
+
message: [
|
| 447 |
+
"uv pip install -r requirements.txt"
|
| 448 |
+
],
|
| 449 |
+
}
|
| 450 |
+
},
|
| 451 |
+
]
|
| 452 |
+
}
|
| 453 |
+
```
|
| 454 |
+
|
| 455 |
+
The `torch.js` script also includes ways to install pytorch dependent libraries such as xformers, triton, sagetattention. If any of these libraries need to be installed, use the torch.js to install in order to install them cross platform.
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
## Quick Reference
|
| 459 |
+
### Essential Documentation
|
| 460 |
+
- **Pinokio Programming:** See `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md → "Programming Pinokio" section
|
| 461 |
+
- **Dynamic Menus:** See `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md → "Dynamic menu rendering" section
|
| 462 |
+
- **CLI Commands:** See `PTERM.md` at C:\pinokio\prototype\PTERM.md
|
| 463 |
+
### Common Patterns
|
| 464 |
+
- **Python Virtual Env:** `shell.run` with `venv` attribute
|
| 465 |
+
- **Cross-platform Commands:** Always test on multiple platforms
|
| 466 |
+
- **Error Handling:** Check logs/api for launcher issues
|
| 467 |
+
- **GitHub Operations:** Use `gh` CLI for advanced GitHub features
|
| 468 |
+
## Development Principles
|
| 469 |
+
1. **Minimize Shell Usage:** Leverage API parameters instead of raw commands
|
| 470 |
+
2. **Maintain Separation:** Keep app logic and launchers separate
|
| 471 |
+
3. **Follow Conventions:** Match existing project patterns
|
| 472 |
+
4. **Test Thoroughly:** Use CLI to verify launcher functionality
|
| 473 |
+
5. **Document Changes:** Update relevant metadata and documentation
|
CLAUDE.md
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Development Guide for Pinokio Projects
|
| 2 |
+
|
| 3 |
+
## Non-Negotiable Execution Workflow
|
| 4 |
+
|
| 5 |
+
To guarantee every contribution follows this guide precisely, obey this checklist **before any edits** and **again before finalizing**. Do not skip or reorder.
|
| 6 |
+
1. **AGENTS Snapshot:** Re-open this file and write down (in your working notes or response draft) the exact sections relevant to the requested task. No work begins until this snapshot exists.
|
| 7 |
+
2. **Example Lock-in:** Identify the closest matching script in `C:\pinokio\prototype\system\examples`. Record its path and keep it open while editing. Every launcher change must mirror that reference unless the user explicitly instructs otherwise.
|
| 8 |
+
3. **Pre-flight Checklist:** Convert the applicable rules from this document and `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md into a task-specific checklist (install/start/reset/update structure, regex patterns, menu defaults, log checks, etc.). Confirm each item is ticked **before** making changes.
|
| 9 |
+
4. **Mid-task Verification:** Any time you touch a Pinokio script, cross-check the corresponding example line to ensure syntax and structure match. Document the reference (example path + line) in your reasoning.
|
| 10 |
+
5. **Exit Checklist:** Before responding to the user, revisit the pre-flight checklist and explicitly confirm every item is satisfied. If anything diverges from the example or these rules, fix it first.
|
| 11 |
+
|
| 12 |
+
If any step cannot be completed, stop immediately and ask the user how to proceed. These five steps are mandatory for every session.
|
| 13 |
+
|
| 14 |
+
### Critical Pattern Lock: Capturing Web UI URLs
|
| 15 |
+
|
| 16 |
+
When writing `start.js` (or any script that needs to surface a web URL for a server):
|
| 17 |
+
|
| 18 |
+
1. **Always copy the capture block from an example such as `system/examples/mochi/start.js`.**
|
| 19 |
+
```javascript
|
| 20 |
+
on: [{
|
| 21 |
+
event: "/(http:\\/\\/[0-9.:]+)/",
|
| 22 |
+
done: true
|
| 23 |
+
}]
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
2. **Set the local variable using the captured match exactly as below (The regex capture object is passed in as `input.event`, so need to use the index 1 inside the parenthesis):**
|
| 27 |
+
```javascript
|
| 28 |
+
{
|
| 29 |
+
method: "local.set",
|
| 30 |
+
params: {
|
| 31 |
+
url: "{{input.event[1]}}"
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
3. Always try to come up with the most generic regex.
|
| 37 |
+
4. During the exit checklist, explicitly confirm that the `url` local variable is set via `local.set` API by using the captured regex object as passed in as `input.event` from the previous `shell.run` step.
|
| 38 |
+
|
| 39 |
+
Deviation from this pattern requires written approval from the user.
|
| 40 |
+
|
| 41 |
+
- Make sure to keep this entire document and `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md in memory with high priority before making any decision. Pinokio is a system that makes it easy to write launchers through scripting by providing various cross-platform APIs, so whenever possible you should prioritize using Pinokio API over lower level APIs.
|
| 42 |
+
- When writing pinokio scripts, ALWAYS check the examples folder (in C:\pinokio\prototype\system\examples folder) to see if there are existing example scripts you can imitate, instead of assuming syntax.
|
| 43 |
+
- When implementing pinokio script APIs and you cannot infer the syntax just based on the examples, always search the API documentation `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md to use the correct syntax instead of assuming the syntax.
|
| 44 |
+
- When trying to fix something or figure out what's going on, ALWAYS start by checking the `logs` folder before doing anything else, as mentioned in the "Troubleshooting with Logs" section.
|
| 45 |
+
- Finally, make sure to ALWAYS follow all the items in the "best practices" section below.
|
| 46 |
+
|
| 47 |
+
## Determine User Intent
|
| 48 |
+
If the initial prompt is simply a URL and nothing else, check the website content and determine the intent, and ask the user to confirm. For example a URL may point to
|
| 49 |
+
|
| 50 |
+
1. A Tutorial: the intent may be to implement a demo for the tutorial and build a launcher.
|
| 51 |
+
2. A Demo: the intent may be a 1-click launcher for the demo
|
| 52 |
+
3. Open source project: the intent may be a 1-click launcher for the project
|
| 53 |
+
4. Regular website: the intent may be to clone the website and a launcher.
|
| 54 |
+
5. There can be other cases, but try to guess.
|
| 55 |
+
|
| 56 |
+
## Project Structure
|
| 57 |
+
|
| 58 |
+
Pinokio projects normally follow a standardized structure with app logic separated from launcher scripts:
|
| 59 |
+
|
| 60 |
+
Pinokio projects follow a standardized structure with app logic separated from launcher scripts:
|
| 61 |
+
|
| 62 |
+
```
|
| 63 |
+
project-root/
|
| 64 |
+
├── app/ # Self-contained app logic (can be standalone repo)
|
| 65 |
+
│ ├── package.json # Node.js projects
|
| 66 |
+
│ ├── requirements.txt # Python projects
|
| 67 |
+
│ └── ... # Other language-specific files
|
| 68 |
+
├── README.md # Documentation
|
| 69 |
+
├── install.js # Installation script
|
| 70 |
+
├── start.js # Launch script
|
| 71 |
+
├── update.js # Update script (for updating the scripts and app logic to the latest)
|
| 72 |
+
├── reset.js # Reset dependencies script
|
| 73 |
+
├── pinokio.js # UI generator script
|
| 74 |
+
└── pinokio.json # Metadata (title, description, icon)
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
- Keep app code in `/app` folder only (never in root)
|
| 78 |
+
- Store all launcher files in project root (never in `/app`)
|
| 79 |
+
- `/app` folder should be self-contained and publishable
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
The only exceptions are serverless web apps---purely frontend only web applications that do NOT have a server component and connect to 3rd party API endpoints--in which case the folder structure looks like the following (No need for launcher scripts since the index.html will automatically launch. The only thing needed is the metadata file named pinokio.json):
|
| 83 |
+
|
| 84 |
+
```
|
| 85 |
+
project-root/
|
| 86 |
+
├── index.html # The serverless web app entry point
|
| 87 |
+
├── ...
|
| 88 |
+
├── README.md # Documentation
|
| 89 |
+
└── pinokio.json # Metadata (title, description, icon)
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
IMPORTANT: ALWAYS try to follow the best practices in the examples folder (C:\pinokio\prototype\system\examples) instead of trying to come up with your own structure. The examples have been optimized for the best user experience.
|
| 93 |
+
|
| 94 |
+
## Launcher Project Working Directory
|
| 95 |
+
|
| 96 |
+
- The project working directory for a script is always the same directory as the script location.
|
| 97 |
+
- For example, when you run `shell.run` API inside `pinokio/start.js`, the default path for shell execution is `pinokio`.
|
| 98 |
+
- If the launcher files are in the project root path, then the default path for shell execution is the project root.
|
| 99 |
+
- Therefore, it is important to specify the correct `path` attribute when running `shell.run` API commands.
|
| 100 |
+
|
| 101 |
+
Example: in the following project structure:
|
| 102 |
+
|
| 103 |
+
```
|
| 104 |
+
project-root/
|
| 105 |
+
├── pinokio/ # Pinokio launcher folder
|
| 106 |
+
│ ├── start.js # Launch script
|
| 107 |
+
│ ├── pinokio.js # UI generator script
|
| 108 |
+
│ └── pinokio.json # Metadata (title, description, icon)
|
| 109 |
+
└─── backend/
|
| 110 |
+
├── requirements.txt # App dependencies
|
| 111 |
+
└── app.py # App code
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
The `pinokio/start.js` should use the correct path `../backend` as the `path` attribute, as follows:
|
| 115 |
+
|
| 116 |
+
```
|
| 117 |
+
{
|
| 118 |
+
run: [{
|
| 119 |
+
...
|
| 120 |
+
}, {
|
| 121 |
+
method: "shell.run",
|
| 122 |
+
params: {
|
| 123 |
+
message: "python app.py",
|
| 124 |
+
venv: "env",
|
| 125 |
+
path: "../backend"
|
| 126 |
+
}
|
| 127 |
+
}, {
|
| 128 |
+
...
|
| 129 |
+
}]
|
| 130 |
+
}
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
## Development Workflow
|
| 134 |
+
|
| 135 |
+
### 1. Understanding the Project
|
| 136 |
+
- Check `SPEC.md` in project root. If the file exists, use that to learn about the project details (what and how to build)
|
| 137 |
+
- If no `SPEC.md` exists, build based on user requirements
|
| 138 |
+
### 2. Modifying Existing Launcher Projects
|
| 139 |
+
If we are starting with existing launcher script files, work with the existing files instead of coming up with your own.
|
| 140 |
+
- **Preserve existing functionality:** Only modify necessary parts
|
| 141 |
+
- **Don't touch working scripts:** Unless adding/updating specific commands
|
| 142 |
+
- **Follow existing conventions:** Match the style and structure already present
|
| 143 |
+
### 3. Try to adopt from examples as much as possible
|
| 144 |
+
- If starting from scratch, first determine what type of project you will be building, and then check the examples folder (C:\pinokio\prototype\system\examples) to see if you can adopt them instead of coming up everything from scratch.
|
| 145 |
+
- Even if there are no relevant examples, check the examples to get inspiration for how you would structure the script files even if you have to write from scratch.
|
| 146 |
+
### 4. Writing from scratch as a last resort
|
| 147 |
+
If there are relevant examples to adopt from, write the scripts from scratch, but just make sure to follow the requirements in the next section.
|
| 148 |
+
### 5. Debugging
|
| 149 |
+
When the user reports something is not working, ALWAYS inspect the logs folder to get all the execution logs. For more info on how this works, check the "Troubleshooting with Logs" section below.
|
| 150 |
+
|
| 151 |
+
## Script Requirements
|
| 152 |
+
|
| 153 |
+
### 1. 1-click launchable
|
| 154 |
+
- The main purpose of Pinokio is to provide an easy interface to invoke commands, which may include launching servers, installing programs, etc. Make sure the final product provides ways to install, launch, reset, and update whatever is needed.
|
| 155 |
+
|
| 156 |
+
### 2. Write Documentation
|
| 157 |
+
- ALWAYS write a documentation. A documentation must be stored as `README.md` in the project root folder, along with the rest of the pinokio launcher script files. A documentation file must contain:
|
| 158 |
+
- What the app does
|
| 159 |
+
- How to use the app
|
| 160 |
+
- API documentation for programmatically accessing the app's main features (Javascript, Python, and Curl)
|
| 161 |
+
|
| 162 |
+
## Types of launchers
|
| 163 |
+
## 1. Launching servers
|
| 164 |
+
- When an app requires launching a server, here are the commonly used scripts:
|
| 165 |
+
- `install.js`: a script to install the app
|
| 166 |
+
- `start.js`: a script to start the app
|
| 167 |
+
- `reset.js`: a script to reset all the dependencies installed in the `install.js` step. used if the user wants to restart from scratch
|
| 168 |
+
- `update.js`: a script to update the launcher AND the app in case there are new updates. Involves pulling in the relevant git repositories installed through `install.js` (often it's the script repo and some git repositories cloned through the install steps if any)
|
| 169 |
+
- `pinokio.js`: the launcher script that ties all of the above scripts together by providing a UI that links to these scripts.
|
| 170 |
+
- `pinokio.json`: For metadata
|
| 171 |
+
|
| 172 |
+
Here's a basic server launcher script example (`start.js`). Unless there's a special reason you need to use another pattern, this is the most recommended pattern. Use this or adopt it as needed, but NEVER try something else unless there's a good reason you should not take this approach:
|
| 173 |
+
|
| 174 |
+
```javascript
|
| 175 |
+
module.exports = {
|
| 176 |
+
// By setting daemon: true, the script keeps running even after all items in the `run` array finishes running. Mandatory for launching servers, since otherwise the shells running the server process will get killed after the scripts finish running.
|
| 177 |
+
daemon: true,
|
| 178 |
+
run: [
|
| 179 |
+
{
|
| 180 |
+
// The "shell.run" API for running a shell session
|
| 181 |
+
method: "shell.run",
|
| 182 |
+
params: {
|
| 183 |
+
// Edit 'venv' to customize the venv folder path
|
| 184 |
+
venv: "env",
|
| 185 |
+
// Edit 'env' to customize environment variables (see documentation)
|
| 186 |
+
env: { },
|
| 187 |
+
// Edit 'path' to customize the path to start the shell from
|
| 188 |
+
path: "app",
|
| 189 |
+
// Edit 'message' to customize the commands, or to run multiple commands
|
| 190 |
+
message: [
|
| 191 |
+
"python app.py",
|
| 192 |
+
],
|
| 193 |
+
on: [{
|
| 194 |
+
// The regular expression pattern to monitor.
|
| 195 |
+
// Whenever each "event" pattern occurs in the shell terminal, the shell will return,
|
| 196 |
+
// and the script will go onto the next step.
|
| 197 |
+
// The regular expression match object will be passed on to the next step as `input.event`
|
| 198 |
+
// Useful for capturing the URL at which the server is running (in case the server prints some message about where the server is running)
|
| 199 |
+
"event": "/(http:\/\/\\S+)/",
|
| 200 |
+
|
| 201 |
+
// Use "done": true to move to the next step while keeping the shell alive.
|
| 202 |
+
// Use "kill": true to move to the next step after killing the shell.
|
| 203 |
+
"done": true
|
| 204 |
+
}]
|
| 205 |
+
}
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
// This step sets the local variable 'url'.
|
| 209 |
+
// This local variable will be used in pinokio.js to display the "Open WebUI" tab when the value is set.
|
| 210 |
+
method: "local.set",
|
| 211 |
+
params: {
|
| 212 |
+
// the input.event is the regular expression match object from the previous step
|
| 213 |
+
// In this example, since the pattern was "/(http:\/\/\\S+)/", input.event[1] will include the exact http url match caputred by the parenthesis.
|
| 214 |
+
// Therefore setting the local variable 'url'
|
| 215 |
+
url: "{{input.event[1]}}"
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
]
|
| 219 |
+
}
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
## 2. Launching serverless web apps
|
| 223 |
+
|
| 224 |
+
- In case of purely static web apps WITHOUT servers or backends (for example an HTML based app that connects to 3rd party servers--either remote or localhost), we do NOT need the launcher scripts.
|
| 225 |
+
- In these cases, simply include `index.html` in the project root folder and everything should automatically work. No need for any of the pinokio launcher scripts. (Do
|
| 226 |
+
- You still need to include the metadata file so they show up properly on pinokio:
|
| 227 |
+
- `pinokio.json`: For metadata
|
| 228 |
+
|
| 229 |
+
## 3. Launching quick scripts without web UI
|
| 230 |
+
|
| 231 |
+
- In many cases, we may not even need a web UI, but instead just a simple way to run scripts.
|
| 232 |
+
- This may include TUI (Terminal User Interface) apps, a simple launcher
|
| 233 |
+
- In these cases, all we need is the launcher file `pinokio.js`, which may link to multiple scripts. In this case, there are no web apps (no serverless apsp, no servers), but instead just the default pinokio launcher UI that calls a bunch of scripts.
|
| 234 |
+
- Here are some examples:
|
| 235 |
+
- A pinokio script to toggle the desktop theme between dark and light
|
| 236 |
+
- Write some code (python or javascript or whatever)
|
| 237 |
+
- Write a `toggle.js` pinokio script that executes the code
|
| 238 |
+
- Write a `pinokio.js` launcher script to create a sidebar UI that displays the `toggle.js` so the user can simply click the "toggle" button to toggle back and forth between desktop themes
|
| 239 |
+
- A pinokio script to fetch some file
|
| 240 |
+
- Write some code (python or javascript or whatever)
|
| 241 |
+
- Write a `fetch.js` pinokio script that executes the code
|
| 242 |
+
- Write a `pinokio.js` launcher script to create a sidebar UI that displays the `fetch.js` so the user can simply click the "fetch" button to fetch some data.
|
| 243 |
+
- You still need to include the metadata file so they show up properly on pinokio:
|
| 244 |
+
- `pinokio.json`: For metadata
|
| 245 |
+
|
| 246 |
+
## API
|
| 247 |
+
|
| 248 |
+
This section lists all the script APIs available on Pinokio. To learn the details of how they are used, you can:
|
| 249 |
+
1. Check the examples in the C:\pinokio\prototype\system\examples folder
|
| 250 |
+
2. Read the `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md further documentation on the full syntax
|
| 251 |
+
|
| 252 |
+
### Script API
|
| 253 |
+
|
| 254 |
+
These APIs can be used to describe each step in a pinokio script:
|
| 255 |
+
- shell.run: run shell commands
|
| 256 |
+
- input: accept user input
|
| 257 |
+
- filepicker: accept file upload
|
| 258 |
+
- fs.write: write to file
|
| 259 |
+
- fs.read: read from file
|
| 260 |
+
- fs.copy: copy files
|
| 261 |
+
- fs.download: download files
|
| 262 |
+
- fs.link: create a symbolic link (or junction on windows) for folders
|
| 263 |
+
- fs.open: open the system file explorer at a given path
|
| 264 |
+
- fs.cat: print file contents
|
| 265 |
+
- jump: jump to a specific step
|
| 266 |
+
- local.set: set local variables for the currently running script
|
| 267 |
+
- json.set: update a json file
|
| 268 |
+
- json.rm: remove keys from a json file
|
| 269 |
+
- json.get: get values from a json file
|
| 270 |
+
- log: print to the web terminal
|
| 271 |
+
- net: make network requests
|
| 272 |
+
- notify: display a notification
|
| 273 |
+
- script.download: download a script from a git uri
|
| 274 |
+
- script.start: start a script
|
| 275 |
+
- script.stop: stop a script
|
| 276 |
+
- script.return: return values if the current script was called by a caller script, so the caller script can utilize the return value as `input`
|
| 277 |
+
- web.open: open a url in web browser
|
| 278 |
+
- hf.download: huggingfac-cli download API
|
| 279 |
+
### Template variables
|
| 280 |
+
The following variables are accessible inside template expressions (example `{{args.command}` in scripts, resulting in dynamic behaviors of scripts:
|
| 281 |
+
- input: An input is a variable that gets passed from one RPC call to the next
|
| 282 |
+
- args: args is the parameter object that gets passed into the script (via pinokio.js `params`). Unlike `input` which takes the value passed in from the immediately previous step, `args` is a global value that is the same through out the entire script execution.
|
| 283 |
+
- local: local variable object that can be set with `local.set` API
|
| 284 |
+
- self: refers to the script file itself (which is JSON or JavaScript). For example if `start.js` that's currently running has `daemon: true` set, `{{self.daemon}}` will evaluate to true.
|
| 285 |
+
- uri: The current script uri
|
| 286 |
+
- port: The next available port. Very useful when you need to launch an app at a specific port without port conflicts.
|
| 287 |
+
- cwd: The current script execution folder path
|
| 288 |
+
- platform: The current operating system. May be one of the following: `darwin`, `win32`, `linux`
|
| 289 |
+
- arch: The current system architecture. May be one of the following: x32, x64, arm, arm64, s390, s390x, mipsel, ia32, mips, ppc, ppc64
|
| 290 |
+
- gpus: array of available GPUs on the machine (example: `['apple']`, `['nvidia']`)
|
| 291 |
+
- gpu: the first available GPU (example: `nvidia`)
|
| 292 |
+
- current: The current variable points to the index of the currently executing instruction within the run array.
|
| 293 |
+
- next: The next variable points to the index of the next instruction to be executed. (null if the current instruction is the final instruction in the run array)
|
| 294 |
+
- envs: You can access the environment variables of the currently running process with envs object.
|
| 295 |
+
- which: Check whether a command exists (example: `{{which('winget')}}`. Can be used in the `when` attribute of a script step to run commands or install first.
|
| 296 |
+
- exists: Check whether a file or folder exists at the specified relative path (example: `"when": "{{!exists('app')}}"`). Can be used with the `when` attribute to determine a path's existence and trigger custom logic. Use relative paths and it will resolve automatically to the current execution folder.
|
| 297 |
+
- running: Check whether a script file is running (example: `"when": "{{!running('start.js')}}"`). Can be used with the `when` attribute to determine a path's existence and trigger custom logic. Use relative paths and it will resolve automatically to the current execution folder.
|
| 298 |
+
- os: Pinokio exposes the node.js os module through the os variable.
|
| 299 |
+
- path: Pinokio exposes the node.js path module through the os variable (example: `{{path.resolve(...)}}`
|
| 300 |
+
|
| 301 |
+
## System Capabilities
|
| 302 |
+
### Package Management (Use in Order of Preference)
|
| 303 |
+
The following package managers come pre-installed with Pinokio, so whenever you need to install a 3rd party binary, remember that these are available. Also, you can assume these are available and include the following package manager commands in Pinokio scripts:
|
| 304 |
+
1. **UV** - For Python packages (preferred over pip)
|
| 305 |
+
2. **NPM** - For Node.js packages
|
| 306 |
+
3. **Conda** - For cross-platform 3rd party binaries
|
| 307 |
+
4. **Brew** - Mac-only fallback when other options unavailable
|
| 308 |
+
5. **Git** - Full access to git is available.
|
| 309 |
+
**Important:** Include all install commands in the install script for reproducibility.
|
| 310 |
+
### HTTPS Proxy Support
|
| 311 |
+
- All HTTP servers automatically get HTTPS endpoints
|
| 312 |
+
- Convention: `http://localhost:<PORT>` → `https://<PORT>.localhost`
|
| 313 |
+
- Full proxy list available at: `http://localhost:2019/config/`
|
| 314 |
+
### Pterm Features:
|
| 315 |
+
- **Clipboard Access:** Read from or Write to system clipboard via pinokio Pterm CLI (`pterm clipboard` command.)
|
| 316 |
+
- **Notifications:** Send desktop alerts via pinokio pterm CLI (`pterm push` command.)
|
| 317 |
+
- **Script Testing:** Run launcher scripts via pinokio pterm CLI (`pterm start` command.)
|
| 318 |
+
- **File Selection:** Use built-in filepicker for user file/folder input (`pterm filepicker` command.)
|
| 319 |
+
- **Git Operations:** Clone repositories, push to GitHub
|
| 320 |
+
- **GitHub Integration:** Full GitHub CLI support (`gh` commands)
|
| 321 |
+
|
| 322 |
+
## Troubleshooting with Logs
|
| 323 |
+
Pinokio stores the logs for everything that happened in terminal at the following locations, so you can make use of them to determine what's going on:
|
| 324 |
+
|
| 325 |
+
### Log Structure
|
| 326 |
+
In case there is a `pinokio` folder in the project root folder, you should be able to find the logs folder here:
|
| 327 |
+
|
| 328 |
+
```
|
| 329 |
+
pinokio/
|
| 330 |
+
└── logs/ # Direct user interaction logs
|
| 331 |
+
├── api/ # Launcher script logs (install.js, start.js, etc.)
|
| 332 |
+
├── dev/ # AI coding tool logs (organized by tool)
|
| 333 |
+
└── shell/ # Direct user interaction logs
|
| 334 |
+
```
|
| 335 |
+
|
| 336 |
+
Otherwise, the `logs` folder should be found at project root:
|
| 337 |
+
|
| 338 |
+
```
|
| 339 |
+
logs/
|
| 340 |
+
├── api/ # Launcher script logs (install.js, start.js, etc.)
|
| 341 |
+
├── dev/ # AI coding tool logs (organized by tool)
|
| 342 |
+
└── shell/ # Direct user interaction logs
|
| 343 |
+
```
|
| 344 |
+
|
| 345 |
+
### Log File Naming
|
| 346 |
+
- Unix timestamps for each session
|
| 347 |
+
- Special "latest" file contains most recent session logs
|
| 348 |
+
- **Default:** Use "latest" files for current issues
|
| 349 |
+
- **Historical:** Use timestamped files for pattern analysis and the full history.
|
| 350 |
+
|
| 351 |
+
## Best practices
|
| 352 |
+
### 0. Always reference the logs when debugging
|
| 353 |
+
- When the user asks to fix something, ALWAYS check the logs folder first to check what went wrong. Check the "Troubleshooting with Logs" section.
|
| 354 |
+
### 1. Shell commands for launching programs
|
| 355 |
+
- Launch flags related
|
| 356 |
+
- Try as hard as possible to minimize launch flags and parameters when launching an app. For example, instead of `python app.py --port 8610`, try to do `python app.py` unless really necessary. The only exception is when the only way to launch the app is to specify the flags.
|
| 357 |
+
- Launch IP related
|
| 358 |
+
- Always try to find a way to launch servers at 127.0.0.1 or localhost, often by specifying launch flags or using environment variables. Some apps launch apps at 0.0.0.0 by default but we do not want this.
|
| 359 |
+
- Launch Port related
|
| 360 |
+
- In case the app itself automatically launches at the next available port by default (for example Gradio does this), do NOT specify port, since it's taken care of by the app itself. Always try to minimize the amount of code.
|
| 361 |
+
- If the install instruction says to launch at a specific port, don't use the hardcoded port they suggest since there's a risk of port conflicts. Instead, use Pinokio's `{{port}}` template expression to automatically get the next available port.
|
| 362 |
+
- For example, if the instruction says `python app.py --port 7860`, don't use that hardcoded port since there might be another app running at that port. Instead, automatically assign the next available port like this: `python app.py --port {{port}}`
|
| 363 |
+
- Note that the `{{port}}` expression always returns the next immediately available port for each step, so if you have multiple steps in a script and use `{{port}}` in multiple steps, the value will be different. So if you want to launch at the next available port and then later reuse that port, you will need to first use `{{port}}` to get the next available port, and save the value in local variable using `local.set`, and then use the `{{local.<variable_name>}}` expression later.
|
| 364 |
+
### 2. shell.run API
|
| 365 |
+
- When writing `shell.run` API requests, always use relative paths (no absolute paths) for the `path` field. For example, if you need to run a command from `app` folder, the `path` attribute should simply be `app`, instead of its full absolute path.
|
| 366 |
+
### 2. Package managers
|
| 367 |
+
- When installing python packages, try best to use `uv` instead of `pip` even if the install instruction says to use pip. Instead of `pip install -r requirements.txt`, you can simply use `uv pip install -r requirements.txt` for example. Even if the project's own README says use pip or poetry, first check if there's a way to use uv instead.
|
| 368 |
+
- When you need to install some global package, try to use `conda` as much as possible. Even on macs, `brew` should be only used if there are no `conda` options.
|
| 369 |
+
### 3. Minimal Always
|
| 370 |
+
- If you are starting with existing script files, before modifying, creating, or removing any script files, first look at `pinokio.js` to understand which script files are actually used in the launcher. The only script files used are the ones mentioned in the `pinokio.js` file. The `pinokio.js` file is the file that constructs the UI dynamically.
|
| 371 |
+
- Do not create a redundant script file that does something that already exists. Instead modify the existing script file for the feature. For example, do not create an `install.json` file for installation if `install.js` already exists. Instead, modify the `install.js` file.
|
| 372 |
+
- Pinokio accepts both JSON and JS script files, so when determining whether a script for a specific purpose already exists, check both JSON and JS files mentioned in the `pinokio.js` file. Do not create script files for rendundant purpose.
|
| 373 |
+
- When building launchers for existing projects cloned from a repository, try to stay away from modifying the project folder (the `C:\pinokio\api\Z-Image-Pinokio.git` folder), even if installations are failing. Instead, try to work around it by creating additional files in the launcher folder, and using those files IN ADDITION to the default project.
|
| 374 |
+
- The only exception when you may need to make changes to the project folder is when the user explicitly wants to modify the existing project. Otherwise if the purpose is to simply write a launcher, the app logic folder should never be touched.
|
| 375 |
+
- When running shell commands, take full advantage of the Pinokio `shell.run` API, which provides features like `env`, `venv`, `input`, `path`, `sudo`, `on`, etc. which can greatly reduce the amount of script code.
|
| 376 |
+
- Python apps: Always use virtual environments via `venv` attribute. This attribute automatically creates a venv or uses if it already exists.
|
| 377 |
+
### 4. Try to support Cross-platform as much as possible
|
| 378 |
+
- Use cross-platform shell commands only.
|
| 379 |
+
- This means, prefer to use commands that work on all platforms instead of the current platform.
|
| 380 |
+
- If there are no cross platform commands, use Pinokio's template expressions to conditionally use commands depending on `platform`, `arch`, etc.
|
| 381 |
+
- Also try to utilize Pinokio Pterm APIs for various cross-platform system features.
|
| 382 |
+
- If it is impossible to implement a cross platform solution (due to the nature of the project itself), set the `platform`, `arch`, and/or `gpu` attributes of the `pinokio.json` file to declare the limitation.
|
| 383 |
+
- Pinokio provides various APIs for cross-platform way of calling commonly used system functions, or lets you selectively run commands depending on `platform`, `arch`, etc.
|
| 384 |
+
### 5. Do not make assumptions about Pinokio API
|
| 385 |
+
- Do NOT make assumptions about which Pinokio APIs exist. Check the documentation.
|
| 386 |
+
- Do NOT make assumptions about the Pinokio API syntax. Follow the documentation.
|
| 387 |
+
### 6. Scripts must be able to replicate install and launch steps 100%
|
| 388 |
+
- The whole point of the scripts is for others to easily download and invoke them via Pinokio interface with one click. Therefore, do not assume the end user's system state, and make everything self-contained.
|
| 389 |
+
- When a 3rd party package needs to be installed, or a 3rd party repository needs to be downloaded, include them in the scripts.
|
| 390 |
+
### 7 Dynamic UI rendering
|
| 391 |
+
- The `pinokio.js` launcher script can change dynamically depending on the current state of the script execution. Which means, depending on what the file returns, it can determine what the sidebar looks like at any given moment of the script cycle.
|
| 392 |
+
- `info.exists(relative_path)`: The `info.exists` can be used to check whether a relative path (relative to the script root path) exists. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 393 |
+
- `info.running(relative_path)`: The `info.running` can be used to check whether a script at a relative path is currently running (relative to the script root path) exists. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 394 |
+
- `info.local(relative_path)`: The `info.local` can be used to return all the local variables tied to a script that's currently running. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 395 |
+
- `default`: set the `default` attribute on any menu item for whichever menu needs to be selected by default at a given step. Some example scenarios:
|
| 396 |
+
- during the install process, the `install.js` menu item needs to be set as the `default`, so it automatically executes the script
|
| 397 |
+
- when launching the `start.js` menu item needs to be set as the `default`, so it automatically executes the script
|
| 398 |
+
- after the app has launched, the `default` needs to be set on the web UI URL, so the user is sent to the actual app automatically.
|
| 399 |
+
- Check the examples in the C:\pinokio\prototype\system\examples folder to see how these are being used.
|
| 400 |
+
### 8. No need for stop scripts
|
| 401 |
+
- `pinokio.js` does NOT need a separate `stop` script. Every script that can be started can also be natively stopped through the Pinokio UI, therefore you do not need a separate stop script for start script
|
| 402 |
+
### 9. Writing launchers for existing projects
|
| 403 |
+
- When writing or modifying pinokio launcher scripts, figure out the install/launch steps by reading the project folder `app`.
|
| 404 |
+
- In most cases, the `README.md` file in the `C:\pinokio\api\Z-Image-Pinokio.git` folder contains the instructions needed to install and run the app, but if not, figure out by scanning the rest of the project files.
|
| 405 |
+
- Install scripts should work for each specific operating system, so ignore Docker related instructions. Instead use install/launch instructions for each platform.
|
| 406 |
+
### 10. Don't use Docker unless really necessary
|
| 407 |
+
- Some projects suggest docker as installation options. But even in these cases, try to find "development" options to launch the app without relying on Docker, as much as possible. We do not need Docker since we can automatically install and launch apps specifically for the user's platform, since we can write scripts that run cross platform.
|
| 408 |
+
### 11. pinokio.json
|
| 409 |
+
- Do not touch the `version` field since the version is the script schema version and the one pre-set in `pinokio.js` must be used.
|
| 410 |
+
- `icon`: It's best if we have a user friendly icon to represent the app, so try to get an image and link it from `pinokio.json`.
|
| 411 |
+
- If the git repository for the `C:\pinokio\api\Z-Image-Pinokio.git` folder points to GitHub (for example https://github.com/<USERNAME>/<REPO_NAME>`, ask the user if they want to download the icon from GitHub, and if approved, get the `avatar_url` by fetching `https://api.github.com/users/<USERNAME>`, and then download the image to the root folder as `icon.png`, and set `icon.png` as the `icon` field of the `pinokio.json`.
|
| 412 |
+
### 12. Gitignore
|
| 413 |
+
- When a launcher involves cloning 3rd party repositories, downloading files dynamically, or some files to be generated, these need to be included in the .gitignore file. This may include things like:
|
| 414 |
+
- Cloning git repositories
|
| 415 |
+
- Downloading files
|
| 416 |
+
- Dynamically creating files during installation or running, such as Sqlite Databases, or environment variables, or anything specific to the user.
|
| 417 |
+
- Make sure these file paths are included in the .gitignore file, and if not, include them in .gitignore.
|
| 418 |
+
|
| 419 |
+
## AI Libraries (Pytorch, Xformers, Triton, Sageattention, etc.)
|
| 420 |
+
If the launcher has a dedicated built-in script named `torch.js`, it can be used as follows:
|
| 421 |
+
|
| 422 |
+
```
|
| 423 |
+
// install.js
|
| 424 |
+
module.exports = {
|
| 425 |
+
run: [
|
| 426 |
+
// Delete this step if your project does not use torch
|
| 427 |
+
{
|
| 428 |
+
method: "script.start",
|
| 429 |
+
params: {
|
| 430 |
+
uri: "torch.js",
|
| 431 |
+
params: {
|
| 432 |
+
path: "app",
|
| 433 |
+
venv: "venv", // Edit this to customize the venv folder path
|
| 434 |
+
// xformers: true // uncomment this line if your project requires xformers
|
| 435 |
+
// triton: true // uncomment this line if your project requires triton
|
| 436 |
+
// sageattention: true // uncomment this line if your project requires sageattention
|
| 437 |
+
}
|
| 438 |
+
}
|
| 439 |
+
},
|
| 440 |
+
// Edit this step with your custom install commands
|
| 441 |
+
{
|
| 442 |
+
method: "shell.run",
|
| 443 |
+
params: {
|
| 444 |
+
venv: "venv", // Edit this to customize the venv folder path
|
| 445 |
+
path: "app",
|
| 446 |
+
message: [
|
| 447 |
+
"uv pip install -r requirements.txt"
|
| 448 |
+
],
|
| 449 |
+
}
|
| 450 |
+
},
|
| 451 |
+
]
|
| 452 |
+
}
|
| 453 |
+
```
|
| 454 |
+
|
| 455 |
+
The `torch.js` script also includes ways to install pytorch dependent libraries such as xformers, triton, sagetattention. If any of these libraries need to be installed, use the torch.js to install in order to install them cross platform.
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
## Quick Reference
|
| 459 |
+
### Essential Documentation
|
| 460 |
+
- **Pinokio Programming:** See `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md → "Programming Pinokio" section
|
| 461 |
+
- **Dynamic Menus:** See `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md → "Dynamic menu rendering" section
|
| 462 |
+
- **CLI Commands:** See `PTERM.md` at C:\pinokio\prototype\PTERM.md
|
| 463 |
+
### Common Patterns
|
| 464 |
+
- **Python Virtual Env:** `shell.run` with `venv` attribute
|
| 465 |
+
- **Cross-platform Commands:** Always test on multiple platforms
|
| 466 |
+
- **Error Handling:** Check logs/api for launcher issues
|
| 467 |
+
- **GitHub Operations:** Use `gh` CLI for advanced GitHub features
|
| 468 |
+
## Development Principles
|
| 469 |
+
1. **Minimize Shell Usage:** Leverage API parameters instead of raw commands
|
| 470 |
+
2. **Maintain Separation:** Keep app logic and launchers separate
|
| 471 |
+
3. **Follow Conventions:** Match existing project patterns
|
| 472 |
+
4. **Test Thoroughly:** Use CLI to verify launcher functionality
|
| 473 |
+
5. **Document Changes:** Update relevant metadata and documentation
|
ENVIRONMENT
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
##########################################################################
|
| 3 |
+
#
|
| 4 |
+
# PINOKIO_SCRIPT_AUTOLAUNCH
|
| 5 |
+
# the relative file path for auto launching any script
|
| 6 |
+
# the specified script will automatically run when pinokio first launches
|
| 7 |
+
#
|
| 8 |
+
##########################################################################
|
| 9 |
+
PINOKIO_SCRIPT_AUTOLAUNCH=
|
| 10 |
+
|
| 11 |
+
##########################################################################
|
| 12 |
+
#
|
| 13 |
+
# PINOKIO_SHARE_CLOUDFLARE
|
| 14 |
+
# Set this variable to share the app publicly via cloudflare tunnel.
|
| 15 |
+
#
|
| 16 |
+
##########################################################################
|
| 17 |
+
PINOKIO_SHARE_CLOUDFLARE=false
|
| 18 |
+
|
| 19 |
+
##########################################################################
|
| 20 |
+
#
|
| 21 |
+
# PINOKIO_SHARE_PASSCODE
|
| 22 |
+
#
|
| 23 |
+
# By default, your publicly shared app will be 100% open to anyone
|
| 24 |
+
# with the link via Cloudflare.
|
| 25 |
+
#
|
| 26 |
+
# You can add authorization by protecting it with a passcode.
|
| 27 |
+
# Set this value, and any access to the app will require a pass code input
|
| 28 |
+
#
|
| 29 |
+
##########################################################################
|
| 30 |
+
PINOKIO_SHARE_PASSCODE=
|
| 31 |
+
|
| 32 |
+
##########################################################################
|
| 33 |
+
#
|
| 34 |
+
# PINOKIO_SCRIPT_DEFAULT
|
| 35 |
+
# If this variable is false, 'default': true menu items in pinokio.js
|
| 36 |
+
# will NOT automatically run
|
| 37 |
+
#
|
| 38 |
+
##########################################################################
|
| 39 |
+
PINOKIO_SCRIPT_DEFAULT=true
|
| 40 |
+
|
| 41 |
+
##########################################################################
|
| 42 |
+
#
|
| 43 |
+
# GRADIO_TEMP_DIR
|
| 44 |
+
# All the files uploaded through gradio goes here.
|
| 45 |
+
#
|
| 46 |
+
# Delete this line to store the files under PINOKIO_HOME/cache/GRADIO_TEMPDIR
|
| 47 |
+
# or change the path if you want to use a different path
|
| 48 |
+
#
|
| 49 |
+
##########################################################################
|
| 50 |
+
GRADIO_TEMP_DIR=./cache/GRADIO_TEMP_DIR
|
| 51 |
+
|
| 52 |
+
##########################################################################
|
| 53 |
+
#
|
| 54 |
+
# HF_HOME
|
| 55 |
+
#
|
| 56 |
+
# Huggingface cache
|
| 57 |
+
# All the model files automatically downloaded through libraries like
|
| 58 |
+
# diffusers, transformers, etc. will be stored under this path
|
| 59 |
+
#
|
| 60 |
+
# You can save disk space by deleting this line, which will store all
|
| 61 |
+
# huggingface files under PINOKIO_HOME/cache/HF_HOME without redundancy.
|
| 62 |
+
#
|
| 63 |
+
##########################################################################
|
| 64 |
+
HF_HOME=./cache/HF_HOME
|
| 65 |
+
|
| 66 |
+
##########################################################################
|
| 67 |
+
#
|
| 68 |
+
# TORCH_HOME
|
| 69 |
+
#
|
| 70 |
+
# Torch hub cache
|
| 71 |
+
# All the files automatically downloaded by pytorch will be stored here
|
| 72 |
+
#
|
| 73 |
+
# You can save disk space by deleting this line, which will store all
|
| 74 |
+
# torch hub files under PINOKIO_HOME/cache/TORCH_HOME without redundancy.
|
| 75 |
+
#
|
| 76 |
+
##########################################################################
|
| 77 |
+
TORCH_HOME=./cache/TORCH_HOME
|
| 78 |
+
|
| 79 |
+
##########################################################################
|
| 80 |
+
#
|
| 81 |
+
# PINOKIO_SHARE_LOCAL
|
| 82 |
+
# Set this variable to true to share the app on the local network.
|
| 83 |
+
#
|
| 84 |
+
##########################################################################
|
| 85 |
+
PINOKIO_SHARE_LOCAL=false
|
| 86 |
+
|
| 87 |
+
##########################################################################
|
| 88 |
+
#
|
| 89 |
+
# PINOKIO_SHARE_LOCAL_PORT
|
| 90 |
+
# Set this variable to use fixed port for the local network sharing feature
|
| 91 |
+
# If not specified, a random port will be assigned to the local proxy used
|
| 92 |
+
# for local sharing.
|
| 93 |
+
#
|
| 94 |
+
##########################################################################
|
| 95 |
+
PINOKIO_SHARE_LOCAL_PORT=
|
GEMINI.md
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Development Guide for Pinokio Projects
|
| 2 |
+
|
| 3 |
+
## Non-Negotiable Execution Workflow
|
| 4 |
+
|
| 5 |
+
To guarantee every contribution follows this guide precisely, obey this checklist **before any edits** and **again before finalizing**. Do not skip or reorder.
|
| 6 |
+
1. **AGENTS Snapshot:** Re-open this file and write down (in your working notes or response draft) the exact sections relevant to the requested task. No work begins until this snapshot exists.
|
| 7 |
+
2. **Example Lock-in:** Identify the closest matching script in `C:\pinokio\prototype\system\examples`. Record its path and keep it open while editing. Every launcher change must mirror that reference unless the user explicitly instructs otherwise.
|
| 8 |
+
3. **Pre-flight Checklist:** Convert the applicable rules from this document and `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md into a task-specific checklist (install/start/reset/update structure, regex patterns, menu defaults, log checks, etc.). Confirm each item is ticked **before** making changes.
|
| 9 |
+
4. **Mid-task Verification:** Any time you touch a Pinokio script, cross-check the corresponding example line to ensure syntax and structure match. Document the reference (example path + line) in your reasoning.
|
| 10 |
+
5. **Exit Checklist:** Before responding to the user, revisit the pre-flight checklist and explicitly confirm every item is satisfied. If anything diverges from the example or these rules, fix it first.
|
| 11 |
+
|
| 12 |
+
If any step cannot be completed, stop immediately and ask the user how to proceed. These five steps are mandatory for every session.
|
| 13 |
+
|
| 14 |
+
### Critical Pattern Lock: Capturing Web UI URLs
|
| 15 |
+
|
| 16 |
+
When writing `start.js` (or any script that needs to surface a web URL for a server):
|
| 17 |
+
|
| 18 |
+
1. **Always copy the capture block from an example such as `system/examples/mochi/start.js`.**
|
| 19 |
+
```javascript
|
| 20 |
+
on: [{
|
| 21 |
+
event: "/(http:\\/\\/[0-9.:]+)/",
|
| 22 |
+
done: true
|
| 23 |
+
}]
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
2. **Set the local variable using the captured match exactly as below (The regex capture object is passed in as `input.event`, so need to use the index 1 inside the parenthesis):**
|
| 27 |
+
```javascript
|
| 28 |
+
{
|
| 29 |
+
method: "local.set",
|
| 30 |
+
params: {
|
| 31 |
+
url: "{{input.event[1]}}"
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
3. Always try to come up with the most generic regex.
|
| 37 |
+
4. During the exit checklist, explicitly confirm that the `url` local variable is set via `local.set` API by using the captured regex object as passed in as `input.event` from the previous `shell.run` step.
|
| 38 |
+
|
| 39 |
+
Deviation from this pattern requires written approval from the user.
|
| 40 |
+
|
| 41 |
+
- Make sure to keep this entire document and `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md in memory with high priority before making any decision. Pinokio is a system that makes it easy to write launchers through scripting by providing various cross-platform APIs, so whenever possible you should prioritize using Pinokio API over lower level APIs.
|
| 42 |
+
- When writing pinokio scripts, ALWAYS check the examples folder (in C:\pinokio\prototype\system\examples folder) to see if there are existing example scripts you can imitate, instead of assuming syntax.
|
| 43 |
+
- When implementing pinokio script APIs and you cannot infer the syntax just based on the examples, always search the API documentation `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md to use the correct syntax instead of assuming the syntax.
|
| 44 |
+
- When trying to fix something or figure out what's going on, ALWAYS start by checking the `logs` folder before doing anything else, as mentioned in the "Troubleshooting with Logs" section.
|
| 45 |
+
- Finally, make sure to ALWAYS follow all the items in the "best practices" section below.
|
| 46 |
+
|
| 47 |
+
## Determine User Intent
|
| 48 |
+
If the initial prompt is simply a URL and nothing else, check the website content and determine the intent, and ask the user to confirm. For example a URL may point to
|
| 49 |
+
|
| 50 |
+
1. A Tutorial: the intent may be to implement a demo for the tutorial and build a launcher.
|
| 51 |
+
2. A Demo: the intent may be a 1-click launcher for the demo
|
| 52 |
+
3. Open source project: the intent may be a 1-click launcher for the project
|
| 53 |
+
4. Regular website: the intent may be to clone the website and a launcher.
|
| 54 |
+
5. There can be other cases, but try to guess.
|
| 55 |
+
|
| 56 |
+
## Project Structure
|
| 57 |
+
|
| 58 |
+
Pinokio projects normally follow a standardized structure with app logic separated from launcher scripts:
|
| 59 |
+
|
| 60 |
+
Pinokio projects follow a standardized structure with app logic separated from launcher scripts:
|
| 61 |
+
|
| 62 |
+
```
|
| 63 |
+
project-root/
|
| 64 |
+
├── app/ # Self-contained app logic (can be standalone repo)
|
| 65 |
+
│ ├── package.json # Node.js projects
|
| 66 |
+
│ ├── requirements.txt # Python projects
|
| 67 |
+
│ └── ... # Other language-specific files
|
| 68 |
+
├── README.md # Documentation
|
| 69 |
+
├── install.js # Installation script
|
| 70 |
+
├── start.js # Launch script
|
| 71 |
+
├── update.js # Update script (for updating the scripts and app logic to the latest)
|
| 72 |
+
├── reset.js # Reset dependencies script
|
| 73 |
+
├── pinokio.js # UI generator script
|
| 74 |
+
└── pinokio.json # Metadata (title, description, icon)
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
- Keep app code in `/app` folder only (never in root)
|
| 78 |
+
- Store all launcher files in project root (never in `/app`)
|
| 79 |
+
- `/app` folder should be self-contained and publishable
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
The only exceptions are serverless web apps---purely frontend only web applications that do NOT have a server component and connect to 3rd party API endpoints--in which case the folder structure looks like the following (No need for launcher scripts since the index.html will automatically launch. The only thing needed is the metadata file named pinokio.json):
|
| 83 |
+
|
| 84 |
+
```
|
| 85 |
+
project-root/
|
| 86 |
+
├── index.html # The serverless web app entry point
|
| 87 |
+
├── ...
|
| 88 |
+
├── README.md # Documentation
|
| 89 |
+
└── pinokio.json # Metadata (title, description, icon)
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
IMPORTANT: ALWAYS try to follow the best practices in the examples folder (C:\pinokio\prototype\system\examples) instead of trying to come up with your own structure. The examples have been optimized for the best user experience.
|
| 93 |
+
|
| 94 |
+
## Launcher Project Working Directory
|
| 95 |
+
|
| 96 |
+
- The project working directory for a script is always the same directory as the script location.
|
| 97 |
+
- For example, when you run `shell.run` API inside `pinokio/start.js`, the default path for shell execution is `pinokio`.
|
| 98 |
+
- If the launcher files are in the project root path, then the default path for shell execution is the project root.
|
| 99 |
+
- Therefore, it is important to specify the correct `path` attribute when running `shell.run` API commands.
|
| 100 |
+
|
| 101 |
+
Example: in the following project structure:
|
| 102 |
+
|
| 103 |
+
```
|
| 104 |
+
project-root/
|
| 105 |
+
├── pinokio/ # Pinokio launcher folder
|
| 106 |
+
│ ├── start.js # Launch script
|
| 107 |
+
│ ├── pinokio.js # UI generator script
|
| 108 |
+
│ └── pinokio.json # Metadata (title, description, icon)
|
| 109 |
+
└─── backend/
|
| 110 |
+
├── requirements.txt # App dependencies
|
| 111 |
+
└── app.py # App code
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
The `pinokio/start.js` should use the correct path `../backend` as the `path` attribute, as follows:
|
| 115 |
+
|
| 116 |
+
```
|
| 117 |
+
{
|
| 118 |
+
run: [{
|
| 119 |
+
...
|
| 120 |
+
}, {
|
| 121 |
+
method: "shell.run",
|
| 122 |
+
params: {
|
| 123 |
+
message: "python app.py",
|
| 124 |
+
venv: "env",
|
| 125 |
+
path: "../backend"
|
| 126 |
+
}
|
| 127 |
+
}, {
|
| 128 |
+
...
|
| 129 |
+
}]
|
| 130 |
+
}
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
## Development Workflow
|
| 134 |
+
|
| 135 |
+
### 1. Understanding the Project
|
| 136 |
+
- Check `SPEC.md` in project root. If the file exists, use that to learn about the project details (what and how to build)
|
| 137 |
+
- If no `SPEC.md` exists, build based on user requirements
|
| 138 |
+
### 2. Modifying Existing Launcher Projects
|
| 139 |
+
If we are starting with existing launcher script files, work with the existing files instead of coming up with your own.
|
| 140 |
+
- **Preserve existing functionality:** Only modify necessary parts
|
| 141 |
+
- **Don't touch working scripts:** Unless adding/updating specific commands
|
| 142 |
+
- **Follow existing conventions:** Match the style and structure already present
|
| 143 |
+
### 3. Try to adopt from examples as much as possible
|
| 144 |
+
- If starting from scratch, first determine what type of project you will be building, and then check the examples folder (C:\pinokio\prototype\system\examples) to see if you can adopt them instead of coming up everything from scratch.
|
| 145 |
+
- Even if there are no relevant examples, check the examples to get inspiration for how you would structure the script files even if you have to write from scratch.
|
| 146 |
+
### 4. Writing from scratch as a last resort
|
| 147 |
+
If there are relevant examples to adopt from, write the scripts from scratch, but just make sure to follow the requirements in the next section.
|
| 148 |
+
### 5. Debugging
|
| 149 |
+
When the user reports something is not working, ALWAYS inspect the logs folder to get all the execution logs. For more info on how this works, check the "Troubleshooting with Logs" section below.
|
| 150 |
+
|
| 151 |
+
## Script Requirements
|
| 152 |
+
|
| 153 |
+
### 1. 1-click launchable
|
| 154 |
+
- The main purpose of Pinokio is to provide an easy interface to invoke commands, which may include launching servers, installing programs, etc. Make sure the final product provides ways to install, launch, reset, and update whatever is needed.
|
| 155 |
+
|
| 156 |
+
### 2. Write Documentation
|
| 157 |
+
- ALWAYS write a documentation. A documentation must be stored as `README.md` in the project root folder, along with the rest of the pinokio launcher script files. A documentation file must contain:
|
| 158 |
+
- What the app does
|
| 159 |
+
- How to use the app
|
| 160 |
+
- API documentation for programmatically accessing the app's main features (Javascript, Python, and Curl)
|
| 161 |
+
|
| 162 |
+
## Types of launchers
|
| 163 |
+
## 1. Launching servers
|
| 164 |
+
- When an app requires launching a server, here are the commonly used scripts:
|
| 165 |
+
- `install.js`: a script to install the app
|
| 166 |
+
- `start.js`: a script to start the app
|
| 167 |
+
- `reset.js`: a script to reset all the dependencies installed in the `install.js` step. used if the user wants to restart from scratch
|
| 168 |
+
- `update.js`: a script to update the launcher AND the app in case there are new updates. Involves pulling in the relevant git repositories installed through `install.js` (often it's the script repo and some git repositories cloned through the install steps if any)
|
| 169 |
+
- `pinokio.js`: the launcher script that ties all of the above scripts together by providing a UI that links to these scripts.
|
| 170 |
+
- `pinokio.json`: For metadata
|
| 171 |
+
|
| 172 |
+
Here's a basic server launcher script example (`start.js`). Unless there's a special reason you need to use another pattern, this is the most recommended pattern. Use this or adopt it as needed, but NEVER try something else unless there's a good reason you should not take this approach:
|
| 173 |
+
|
| 174 |
+
```javascript
|
| 175 |
+
module.exports = {
|
| 176 |
+
// By setting daemon: true, the script keeps running even after all items in the `run` array finishes running. Mandatory for launching servers, since otherwise the shells running the server process will get killed after the scripts finish running.
|
| 177 |
+
daemon: true,
|
| 178 |
+
run: [
|
| 179 |
+
{
|
| 180 |
+
// The "shell.run" API for running a shell session
|
| 181 |
+
method: "shell.run",
|
| 182 |
+
params: {
|
| 183 |
+
// Edit 'venv' to customize the venv folder path
|
| 184 |
+
venv: "env",
|
| 185 |
+
// Edit 'env' to customize environment variables (see documentation)
|
| 186 |
+
env: { },
|
| 187 |
+
// Edit 'path' to customize the path to start the shell from
|
| 188 |
+
path: "app",
|
| 189 |
+
// Edit 'message' to customize the commands, or to run multiple commands
|
| 190 |
+
message: [
|
| 191 |
+
"python app.py",
|
| 192 |
+
],
|
| 193 |
+
on: [{
|
| 194 |
+
// The regular expression pattern to monitor.
|
| 195 |
+
// Whenever each "event" pattern occurs in the shell terminal, the shell will return,
|
| 196 |
+
// and the script will go onto the next step.
|
| 197 |
+
// The regular expression match object will be passed on to the next step as `input.event`
|
| 198 |
+
// Useful for capturing the URL at which the server is running (in case the server prints some message about where the server is running)
|
| 199 |
+
"event": "/(http:\/\/\\S+)/",
|
| 200 |
+
|
| 201 |
+
// Use "done": true to move to the next step while keeping the shell alive.
|
| 202 |
+
// Use "kill": true to move to the next step after killing the shell.
|
| 203 |
+
"done": true
|
| 204 |
+
}]
|
| 205 |
+
}
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
// This step sets the local variable 'url'.
|
| 209 |
+
// This local variable will be used in pinokio.js to display the "Open WebUI" tab when the value is set.
|
| 210 |
+
method: "local.set",
|
| 211 |
+
params: {
|
| 212 |
+
// the input.event is the regular expression match object from the previous step
|
| 213 |
+
// In this example, since the pattern was "/(http:\/\/\\S+)/", input.event[1] will include the exact http url match caputred by the parenthesis.
|
| 214 |
+
// Therefore setting the local variable 'url'
|
| 215 |
+
url: "{{input.event[1]}}"
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
]
|
| 219 |
+
}
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
## 2. Launching serverless web apps
|
| 223 |
+
|
| 224 |
+
- In case of purely static web apps WITHOUT servers or backends (for example an HTML based app that connects to 3rd party servers--either remote or localhost), we do NOT need the launcher scripts.
|
| 225 |
+
- In these cases, simply include `index.html` in the project root folder and everything should automatically work. No need for any of the pinokio launcher scripts. (Do
|
| 226 |
+
- You still need to include the metadata file so they show up properly on pinokio:
|
| 227 |
+
- `pinokio.json`: For metadata
|
| 228 |
+
|
| 229 |
+
## 3. Launching quick scripts without web UI
|
| 230 |
+
|
| 231 |
+
- In many cases, we may not even need a web UI, but instead just a simple way to run scripts.
|
| 232 |
+
- This may include TUI (Terminal User Interface) apps, a simple launcher
|
| 233 |
+
- In these cases, all we need is the launcher file `pinokio.js`, which may link to multiple scripts. In this case, there are no web apps (no serverless apsp, no servers), but instead just the default pinokio launcher UI that calls a bunch of scripts.
|
| 234 |
+
- Here are some examples:
|
| 235 |
+
- A pinokio script to toggle the desktop theme between dark and light
|
| 236 |
+
- Write some code (python or javascript or whatever)
|
| 237 |
+
- Write a `toggle.js` pinokio script that executes the code
|
| 238 |
+
- Write a `pinokio.js` launcher script to create a sidebar UI that displays the `toggle.js` so the user can simply click the "toggle" button to toggle back and forth between desktop themes
|
| 239 |
+
- A pinokio script to fetch some file
|
| 240 |
+
- Write some code (python or javascript or whatever)
|
| 241 |
+
- Write a `fetch.js` pinokio script that executes the code
|
| 242 |
+
- Write a `pinokio.js` launcher script to create a sidebar UI that displays the `fetch.js` so the user can simply click the "fetch" button to fetch some data.
|
| 243 |
+
- You still need to include the metadata file so they show up properly on pinokio:
|
| 244 |
+
- `pinokio.json`: For metadata
|
| 245 |
+
|
| 246 |
+
## API
|
| 247 |
+
|
| 248 |
+
This section lists all the script APIs available on Pinokio. To learn the details of how they are used, you can:
|
| 249 |
+
1. Check the examples in the C:\pinokio\prototype\system\examples folder
|
| 250 |
+
2. Read the `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md further documentation on the full syntax
|
| 251 |
+
|
| 252 |
+
### Script API
|
| 253 |
+
|
| 254 |
+
These APIs can be used to describe each step in a pinokio script:
|
| 255 |
+
- shell.run: run shell commands
|
| 256 |
+
- input: accept user input
|
| 257 |
+
- filepicker: accept file upload
|
| 258 |
+
- fs.write: write to file
|
| 259 |
+
- fs.read: read from file
|
| 260 |
+
- fs.copy: copy files
|
| 261 |
+
- fs.download: download files
|
| 262 |
+
- fs.link: create a symbolic link (or junction on windows) for folders
|
| 263 |
+
- fs.open: open the system file explorer at a given path
|
| 264 |
+
- fs.cat: print file contents
|
| 265 |
+
- jump: jump to a specific step
|
| 266 |
+
- local.set: set local variables for the currently running script
|
| 267 |
+
- json.set: update a json file
|
| 268 |
+
- json.rm: remove keys from a json file
|
| 269 |
+
- json.get: get values from a json file
|
| 270 |
+
- log: print to the web terminal
|
| 271 |
+
- net: make network requests
|
| 272 |
+
- notify: display a notification
|
| 273 |
+
- script.download: download a script from a git uri
|
| 274 |
+
- script.start: start a script
|
| 275 |
+
- script.stop: stop a script
|
| 276 |
+
- script.return: return values if the current script was called by a caller script, so the caller script can utilize the return value as `input`
|
| 277 |
+
- web.open: open a url in web browser
|
| 278 |
+
- hf.download: huggingfac-cli download API
|
| 279 |
+
### Template variables
|
| 280 |
+
The following variables are accessible inside template expressions (example `{{args.command}` in scripts, resulting in dynamic behaviors of scripts:
|
| 281 |
+
- input: An input is a variable that gets passed from one RPC call to the next
|
| 282 |
+
- args: args is the parameter object that gets passed into the script (via pinokio.js `params`). Unlike `input` which takes the value passed in from the immediately previous step, `args` is a global value that is the same through out the entire script execution.
|
| 283 |
+
- local: local variable object that can be set with `local.set` API
|
| 284 |
+
- self: refers to the script file itself (which is JSON or JavaScript). For example if `start.js` that's currently running has `daemon: true` set, `{{self.daemon}}` will evaluate to true.
|
| 285 |
+
- uri: The current script uri
|
| 286 |
+
- port: The next available port. Very useful when you need to launch an app at a specific port without port conflicts.
|
| 287 |
+
- cwd: The current script execution folder path
|
| 288 |
+
- platform: The current operating system. May be one of the following: `darwin`, `win32`, `linux`
|
| 289 |
+
- arch: The current system architecture. May be one of the following: x32, x64, arm, arm64, s390, s390x, mipsel, ia32, mips, ppc, ppc64
|
| 290 |
+
- gpus: array of available GPUs on the machine (example: `['apple']`, `['nvidia']`)
|
| 291 |
+
- gpu: the first available GPU (example: `nvidia`)
|
| 292 |
+
- current: The current variable points to the index of the currently executing instruction within the run array.
|
| 293 |
+
- next: The next variable points to the index of the next instruction to be executed. (null if the current instruction is the final instruction in the run array)
|
| 294 |
+
- envs: You can access the environment variables of the currently running process with envs object.
|
| 295 |
+
- which: Check whether a command exists (example: `{{which('winget')}}`. Can be used in the `when` attribute of a script step to run commands or install first.
|
| 296 |
+
- exists: Check whether a file or folder exists at the specified relative path (example: `"when": "{{!exists('app')}}"`). Can be used with the `when` attribute to determine a path's existence and trigger custom logic. Use relative paths and it will resolve automatically to the current execution folder.
|
| 297 |
+
- running: Check whether a script file is running (example: `"when": "{{!running('start.js')}}"`). Can be used with the `when` attribute to determine a path's existence and trigger custom logic. Use relative paths and it will resolve automatically to the current execution folder.
|
| 298 |
+
- os: Pinokio exposes the node.js os module through the os variable.
|
| 299 |
+
- path: Pinokio exposes the node.js path module through the os variable (example: `{{path.resolve(...)}}`
|
| 300 |
+
|
| 301 |
+
## System Capabilities
|
| 302 |
+
### Package Management (Use in Order of Preference)
|
| 303 |
+
The following package managers come pre-installed with Pinokio, so whenever you need to install a 3rd party binary, remember that these are available. Also, you can assume these are available and include the following package manager commands in Pinokio scripts:
|
| 304 |
+
1. **UV** - For Python packages (preferred over pip)
|
| 305 |
+
2. **NPM** - For Node.js packages
|
| 306 |
+
3. **Conda** - For cross-platform 3rd party binaries
|
| 307 |
+
4. **Brew** - Mac-only fallback when other options unavailable
|
| 308 |
+
5. **Git** - Full access to git is available.
|
| 309 |
+
**Important:** Include all install commands in the install script for reproducibility.
|
| 310 |
+
### HTTPS Proxy Support
|
| 311 |
+
- All HTTP servers automatically get HTTPS endpoints
|
| 312 |
+
- Convention: `http://localhost:<PORT>` → `https://<PORT>.localhost`
|
| 313 |
+
- Full proxy list available at: `http://localhost:2019/config/`
|
| 314 |
+
### Pterm Features:
|
| 315 |
+
- **Clipboard Access:** Read from or Write to system clipboard via pinokio Pterm CLI (`pterm clipboard` command.)
|
| 316 |
+
- **Notifications:** Send desktop alerts via pinokio pterm CLI (`pterm push` command.)
|
| 317 |
+
- **Script Testing:** Run launcher scripts via pinokio pterm CLI (`pterm start` command.)
|
| 318 |
+
- **File Selection:** Use built-in filepicker for user file/folder input (`pterm filepicker` command.)
|
| 319 |
+
- **Git Operations:** Clone repositories, push to GitHub
|
| 320 |
+
- **GitHub Integration:** Full GitHub CLI support (`gh` commands)
|
| 321 |
+
|
| 322 |
+
## Troubleshooting with Logs
|
| 323 |
+
Pinokio stores the logs for everything that happened in terminal at the following locations, so you can make use of them to determine what's going on:
|
| 324 |
+
|
| 325 |
+
### Log Structure
|
| 326 |
+
In case there is a `pinokio` folder in the project root folder, you should be able to find the logs folder here:
|
| 327 |
+
|
| 328 |
+
```
|
| 329 |
+
pinokio/
|
| 330 |
+
└── logs/ # Direct user interaction logs
|
| 331 |
+
├── api/ # Launcher script logs (install.js, start.js, etc.)
|
| 332 |
+
├── dev/ # AI coding tool logs (organized by tool)
|
| 333 |
+
└── shell/ # Direct user interaction logs
|
| 334 |
+
```
|
| 335 |
+
|
| 336 |
+
Otherwise, the `logs` folder should be found at project root:
|
| 337 |
+
|
| 338 |
+
```
|
| 339 |
+
logs/
|
| 340 |
+
├── api/ # Launcher script logs (install.js, start.js, etc.)
|
| 341 |
+
├── dev/ # AI coding tool logs (organized by tool)
|
| 342 |
+
└── shell/ # Direct user interaction logs
|
| 343 |
+
```
|
| 344 |
+
|
| 345 |
+
### Log File Naming
|
| 346 |
+
- Unix timestamps for each session
|
| 347 |
+
- Special "latest" file contains most recent session logs
|
| 348 |
+
- **Default:** Use "latest" files for current issues
|
| 349 |
+
- **Historical:** Use timestamped files for pattern analysis and the full history.
|
| 350 |
+
|
| 351 |
+
## Best practices
|
| 352 |
+
### 0. Always reference the logs when debugging
|
| 353 |
+
- When the user asks to fix something, ALWAYS check the logs folder first to check what went wrong. Check the "Troubleshooting with Logs" section.
|
| 354 |
+
### 1. Shell commands for launching programs
|
| 355 |
+
- Launch flags related
|
| 356 |
+
- Try as hard as possible to minimize launch flags and parameters when launching an app. For example, instead of `python app.py --port 8610`, try to do `python app.py` unless really necessary. The only exception is when the only way to launch the app is to specify the flags.
|
| 357 |
+
- Launch IP related
|
| 358 |
+
- Always try to find a way to launch servers at 127.0.0.1 or localhost, often by specifying launch flags or using environment variables. Some apps launch apps at 0.0.0.0 by default but we do not want this.
|
| 359 |
+
- Launch Port related
|
| 360 |
+
- In case the app itself automatically launches at the next available port by default (for example Gradio does this), do NOT specify port, since it's taken care of by the app itself. Always try to minimize the amount of code.
|
| 361 |
+
- If the install instruction says to launch at a specific port, don't use the hardcoded port they suggest since there's a risk of port conflicts. Instead, use Pinokio's `{{port}}` template expression to automatically get the next available port.
|
| 362 |
+
- For example, if the instruction says `python app.py --port 7860`, don't use that hardcoded port since there might be another app running at that port. Instead, automatically assign the next available port like this: `python app.py --port {{port}}`
|
| 363 |
+
- Note that the `{{port}}` expression always returns the next immediately available port for each step, so if you have multiple steps in a script and use `{{port}}` in multiple steps, the value will be different. So if you want to launch at the next available port and then later reuse that port, you will need to first use `{{port}}` to get the next available port, and save the value in local variable using `local.set`, and then use the `{{local.<variable_name>}}` expression later.
|
| 364 |
+
### 2. shell.run API
|
| 365 |
+
- When writing `shell.run` API requests, always use relative paths (no absolute paths) for the `path` field. For example, if you need to run a command from `app` folder, the `path` attribute should simply be `app`, instead of its full absolute path.
|
| 366 |
+
### 2. Package managers
|
| 367 |
+
- When installing python packages, try best to use `uv` instead of `pip` even if the install instruction says to use pip. Instead of `pip install -r requirements.txt`, you can simply use `uv pip install -r requirements.txt` for example. Even if the project's own README says use pip or poetry, first check if there's a way to use uv instead.
|
| 368 |
+
- When you need to install some global package, try to use `conda` as much as possible. Even on macs, `brew` should be only used if there are no `conda` options.
|
| 369 |
+
### 3. Minimal Always
|
| 370 |
+
- If you are starting with existing script files, before modifying, creating, or removing any script files, first look at `pinokio.js` to understand which script files are actually used in the launcher. The only script files used are the ones mentioned in the `pinokio.js` file. The `pinokio.js` file is the file that constructs the UI dynamically.
|
| 371 |
+
- Do not create a redundant script file that does something that already exists. Instead modify the existing script file for the feature. For example, do not create an `install.json` file for installation if `install.js` already exists. Instead, modify the `install.js` file.
|
| 372 |
+
- Pinokio accepts both JSON and JS script files, so when determining whether a script for a specific purpose already exists, check both JSON and JS files mentioned in the `pinokio.js` file. Do not create script files for rendundant purpose.
|
| 373 |
+
- When building launchers for existing projects cloned from a repository, try to stay away from modifying the project folder (the `C:\pinokio\api\Z-Image-Pinokio.git` folder), even if installations are failing. Instead, try to work around it by creating additional files in the launcher folder, and using those files IN ADDITION to the default project.
|
| 374 |
+
- The only exception when you may need to make changes to the project folder is when the user explicitly wants to modify the existing project. Otherwise if the purpose is to simply write a launcher, the app logic folder should never be touched.
|
| 375 |
+
- When running shell commands, take full advantage of the Pinokio `shell.run` API, which provides features like `env`, `venv`, `input`, `path`, `sudo`, `on`, etc. which can greatly reduce the amount of script code.
|
| 376 |
+
- Python apps: Always use virtual environments via `venv` attribute. This attribute automatically creates a venv or uses if it already exists.
|
| 377 |
+
### 4. Try to support Cross-platform as much as possible
|
| 378 |
+
- Use cross-platform shell commands only.
|
| 379 |
+
- This means, prefer to use commands that work on all platforms instead of the current platform.
|
| 380 |
+
- If there are no cross platform commands, use Pinokio's template expressions to conditionally use commands depending on `platform`, `arch`, etc.
|
| 381 |
+
- Also try to utilize Pinokio Pterm APIs for various cross-platform system features.
|
| 382 |
+
- If it is impossible to implement a cross platform solution (due to the nature of the project itself), set the `platform`, `arch`, and/or `gpu` attributes of the `pinokio.json` file to declare the limitation.
|
| 383 |
+
- Pinokio provides various APIs for cross-platform way of calling commonly used system functions, or lets you selectively run commands depending on `platform`, `arch`, etc.
|
| 384 |
+
### 5. Do not make assumptions about Pinokio API
|
| 385 |
+
- Do NOT make assumptions about which Pinokio APIs exist. Check the documentation.
|
| 386 |
+
- Do NOT make assumptions about the Pinokio API syntax. Follow the documentation.
|
| 387 |
+
### 6. Scripts must be able to replicate install and launch steps 100%
|
| 388 |
+
- The whole point of the scripts is for others to easily download and invoke them via Pinokio interface with one click. Therefore, do not assume the end user's system state, and make everything self-contained.
|
| 389 |
+
- When a 3rd party package needs to be installed, or a 3rd party repository needs to be downloaded, include them in the scripts.
|
| 390 |
+
### 7 Dynamic UI rendering
|
| 391 |
+
- The `pinokio.js` launcher script can change dynamically depending on the current state of the script execution. Which means, depending on what the file returns, it can determine what the sidebar looks like at any given moment of the script cycle.
|
| 392 |
+
- `info.exists(relative_path)`: The `info.exists` can be used to check whether a relative path (relative to the script root path) exists. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 393 |
+
- `info.running(relative_path)`: The `info.running` can be used to check whether a script at a relative path is currently running (relative to the script root path) exists. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 394 |
+
- `info.local(relative_path)`: The `info.local` can be used to return all the local variables tied to a script that's currently running. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 395 |
+
- `default`: set the `default` attribute on any menu item for whichever menu needs to be selected by default at a given step. Some example scenarios:
|
| 396 |
+
- during the install process, the `install.js` menu item needs to be set as the `default`, so it automatically executes the script
|
| 397 |
+
- when launching the `start.js` menu item needs to be set as the `default`, so it automatically executes the script
|
| 398 |
+
- after the app has launched, the `default` needs to be set on the web UI URL, so the user is sent to the actual app automatically.
|
| 399 |
+
- Check the examples in the C:\pinokio\prototype\system\examples folder to see how these are being used.
|
| 400 |
+
### 8. No need for stop scripts
|
| 401 |
+
- `pinokio.js` does NOT need a separate `stop` script. Every script that can be started can also be natively stopped through the Pinokio UI, therefore you do not need a separate stop script for start script
|
| 402 |
+
### 9. Writing launchers for existing projects
|
| 403 |
+
- When writing or modifying pinokio launcher scripts, figure out the install/launch steps by reading the project folder `app`.
|
| 404 |
+
- In most cases, the `README.md` file in the `C:\pinokio\api\Z-Image-Pinokio.git` folder contains the instructions needed to install and run the app, but if not, figure out by scanning the rest of the project files.
|
| 405 |
+
- Install scripts should work for each specific operating system, so ignore Docker related instructions. Instead use install/launch instructions for each platform.
|
| 406 |
+
### 10. Don't use Docker unless really necessary
|
| 407 |
+
- Some projects suggest docker as installation options. But even in these cases, try to find "development" options to launch the app without relying on Docker, as much as possible. We do not need Docker since we can automatically install and launch apps specifically for the user's platform, since we can write scripts that run cross platform.
|
| 408 |
+
### 11. pinokio.json
|
| 409 |
+
- Do not touch the `version` field since the version is the script schema version and the one pre-set in `pinokio.js` must be used.
|
| 410 |
+
- `icon`: It's best if we have a user friendly icon to represent the app, so try to get an image and link it from `pinokio.json`.
|
| 411 |
+
- If the git repository for the `C:\pinokio\api\Z-Image-Pinokio.git` folder points to GitHub (for example https://github.com/<USERNAME>/<REPO_NAME>`, ask the user if they want to download the icon from GitHub, and if approved, get the `avatar_url` by fetching `https://api.github.com/users/<USERNAME>`, and then download the image to the root folder as `icon.png`, and set `icon.png` as the `icon` field of the `pinokio.json`.
|
| 412 |
+
### 12. Gitignore
|
| 413 |
+
- When a launcher involves cloning 3rd party repositories, downloading files dynamically, or some files to be generated, these need to be included in the .gitignore file. This may include things like:
|
| 414 |
+
- Cloning git repositories
|
| 415 |
+
- Downloading files
|
| 416 |
+
- Dynamically creating files during installation or running, such as Sqlite Databases, or environment variables, or anything specific to the user.
|
| 417 |
+
- Make sure these file paths are included in the .gitignore file, and if not, include them in .gitignore.
|
| 418 |
+
|
| 419 |
+
## AI Libraries (Pytorch, Xformers, Triton, Sageattention, etc.)
|
| 420 |
+
If the launcher has a dedicated built-in script named `torch.js`, it can be used as follows:
|
| 421 |
+
|
| 422 |
+
```
|
| 423 |
+
// install.js
|
| 424 |
+
module.exports = {
|
| 425 |
+
run: [
|
| 426 |
+
// Delete this step if your project does not use torch
|
| 427 |
+
{
|
| 428 |
+
method: "script.start",
|
| 429 |
+
params: {
|
| 430 |
+
uri: "torch.js",
|
| 431 |
+
params: {
|
| 432 |
+
path: "app",
|
| 433 |
+
venv: "venv", // Edit this to customize the venv folder path
|
| 434 |
+
// xformers: true // uncomment this line if your project requires xformers
|
| 435 |
+
// triton: true // uncomment this line if your project requires triton
|
| 436 |
+
// sageattention: true // uncomment this line if your project requires sageattention
|
| 437 |
+
}
|
| 438 |
+
}
|
| 439 |
+
},
|
| 440 |
+
// Edit this step with your custom install commands
|
| 441 |
+
{
|
| 442 |
+
method: "shell.run",
|
| 443 |
+
params: {
|
| 444 |
+
venv: "venv", // Edit this to customize the venv folder path
|
| 445 |
+
path: "app",
|
| 446 |
+
message: [
|
| 447 |
+
"uv pip install -r requirements.txt"
|
| 448 |
+
],
|
| 449 |
+
}
|
| 450 |
+
},
|
| 451 |
+
]
|
| 452 |
+
}
|
| 453 |
+
```
|
| 454 |
+
|
| 455 |
+
The `torch.js` script also includes ways to install pytorch dependent libraries such as xformers, triton, sagetattention. If any of these libraries need to be installed, use the torch.js to install in order to install them cross platform.
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
## Quick Reference
|
| 459 |
+
### Essential Documentation
|
| 460 |
+
- **Pinokio Programming:** See `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md → "Programming Pinokio" section
|
| 461 |
+
- **Dynamic Menus:** See `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md → "Dynamic menu rendering" section
|
| 462 |
+
- **CLI Commands:** See `PTERM.md` at C:\pinokio\prototype\PTERM.md
|
| 463 |
+
### Common Patterns
|
| 464 |
+
- **Python Virtual Env:** `shell.run` with `venv` attribute
|
| 465 |
+
- **Cross-platform Commands:** Always test on multiple platforms
|
| 466 |
+
- **Error Handling:** Check logs/api for launcher issues
|
| 467 |
+
- **GitHub Operations:** Use `gh` CLI for advanced GitHub features
|
| 468 |
+
## Development Principles
|
| 469 |
+
1. **Minimize Shell Usage:** Leverage API parameters instead of raw commands
|
| 470 |
+
2. **Maintain Separation:** Keep app logic and launchers separate
|
| 471 |
+
3. **Follow Conventions:** Match existing project patterns
|
| 472 |
+
4. **Test Thoroughly:** Use CLI to verify launcher functionality
|
| 473 |
+
5. **Document Changes:** Update relevant metadata and documentation
|
QWEN.md
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Development Guide for Pinokio Projects
|
| 2 |
+
|
| 3 |
+
## Non-Negotiable Execution Workflow
|
| 4 |
+
|
| 5 |
+
To guarantee every contribution follows this guide precisely, obey this checklist **before any edits** and **again before finalizing**. Do not skip or reorder.
|
| 6 |
+
1. **AGENTS Snapshot:** Re-open this file and write down (in your working notes or response draft) the exact sections relevant to the requested task. No work begins until this snapshot exists.
|
| 7 |
+
2. **Example Lock-in:** Identify the closest matching script in `C:\pinokio\prototype\system\examples`. Record its path and keep it open while editing. Every launcher change must mirror that reference unless the user explicitly instructs otherwise.
|
| 8 |
+
3. **Pre-flight Checklist:** Convert the applicable rules from this document and `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md into a task-specific checklist (install/start/reset/update structure, regex patterns, menu defaults, log checks, etc.). Confirm each item is ticked **before** making changes.
|
| 9 |
+
4. **Mid-task Verification:** Any time you touch a Pinokio script, cross-check the corresponding example line to ensure syntax and structure match. Document the reference (example path + line) in your reasoning.
|
| 10 |
+
5. **Exit Checklist:** Before responding to the user, revisit the pre-flight checklist and explicitly confirm every item is satisfied. If anything diverges from the example or these rules, fix it first.
|
| 11 |
+
|
| 12 |
+
If any step cannot be completed, stop immediately and ask the user how to proceed. These five steps are mandatory for every session.
|
| 13 |
+
|
| 14 |
+
### Critical Pattern Lock: Capturing Web UI URLs
|
| 15 |
+
|
| 16 |
+
When writing `start.js` (or any script that needs to surface a web URL for a server):
|
| 17 |
+
|
| 18 |
+
1. **Always copy the capture block from an example such as `system/examples/mochi/start.js`.**
|
| 19 |
+
```javascript
|
| 20 |
+
on: [{
|
| 21 |
+
event: "/(http:\\/\\/[0-9.:]+)/",
|
| 22 |
+
done: true
|
| 23 |
+
}]
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
2. **Set the local variable using the captured match exactly as below (The regex capture object is passed in as `input.event`, so need to use the index 1 inside the parenthesis):**
|
| 27 |
+
```javascript
|
| 28 |
+
{
|
| 29 |
+
method: "local.set",
|
| 30 |
+
params: {
|
| 31 |
+
url: "{{input.event[1]}}"
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
3. Always try to come up with the most generic regex.
|
| 37 |
+
4. During the exit checklist, explicitly confirm that the `url` local variable is set via `local.set` API by using the captured regex object as passed in as `input.event` from the previous `shell.run` step.
|
| 38 |
+
|
| 39 |
+
Deviation from this pattern requires written approval from the user.
|
| 40 |
+
|
| 41 |
+
- Make sure to keep this entire document and `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md in memory with high priority before making any decision. Pinokio is a system that makes it easy to write launchers through scripting by providing various cross-platform APIs, so whenever possible you should prioritize using Pinokio API over lower level APIs.
|
| 42 |
+
- When writing pinokio scripts, ALWAYS check the examples folder (in C:\pinokio\prototype\system\examples folder) to see if there are existing example scripts you can imitate, instead of assuming syntax.
|
| 43 |
+
- When implementing pinokio script APIs and you cannot infer the syntax just based on the examples, always search the API documentation `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md to use the correct syntax instead of assuming the syntax.
|
| 44 |
+
- When trying to fix something or figure out what's going on, ALWAYS start by checking the `logs` folder before doing anything else, as mentioned in the "Troubleshooting with Logs" section.
|
| 45 |
+
- Finally, make sure to ALWAYS follow all the items in the "best practices" section below.
|
| 46 |
+
|
| 47 |
+
## Determine User Intent
|
| 48 |
+
If the initial prompt is simply a URL and nothing else, check the website content and determine the intent, and ask the user to confirm. For example a URL may point to
|
| 49 |
+
|
| 50 |
+
1. A Tutorial: the intent may be to implement a demo for the tutorial and build a launcher.
|
| 51 |
+
2. A Demo: the intent may be a 1-click launcher for the demo
|
| 52 |
+
3. Open source project: the intent may be a 1-click launcher for the project
|
| 53 |
+
4. Regular website: the intent may be to clone the website and a launcher.
|
| 54 |
+
5. There can be other cases, but try to guess.
|
| 55 |
+
|
| 56 |
+
## Project Structure
|
| 57 |
+
|
| 58 |
+
Pinokio projects normally follow a standardized structure with app logic separated from launcher scripts:
|
| 59 |
+
|
| 60 |
+
Pinokio projects follow a standardized structure with app logic separated from launcher scripts:
|
| 61 |
+
|
| 62 |
+
```
|
| 63 |
+
project-root/
|
| 64 |
+
├── app/ # Self-contained app logic (can be standalone repo)
|
| 65 |
+
│ ├── package.json # Node.js projects
|
| 66 |
+
│ ├── requirements.txt # Python projects
|
| 67 |
+
│ └── ... # Other language-specific files
|
| 68 |
+
├── README.md # Documentation
|
| 69 |
+
├── install.js # Installation script
|
| 70 |
+
├── start.js # Launch script
|
| 71 |
+
├── update.js # Update script (for updating the scripts and app logic to the latest)
|
| 72 |
+
├── reset.js # Reset dependencies script
|
| 73 |
+
├── pinokio.js # UI generator script
|
| 74 |
+
└── pinokio.json # Metadata (title, description, icon)
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
- Keep app code in `/app` folder only (never in root)
|
| 78 |
+
- Store all launcher files in project root (never in `/app`)
|
| 79 |
+
- `/app` folder should be self-contained and publishable
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
The only exceptions are serverless web apps---purely frontend only web applications that do NOT have a server component and connect to 3rd party API endpoints--in which case the folder structure looks like the following (No need for launcher scripts since the index.html will automatically launch. The only thing needed is the metadata file named pinokio.json):
|
| 83 |
+
|
| 84 |
+
```
|
| 85 |
+
project-root/
|
| 86 |
+
├── index.html # The serverless web app entry point
|
| 87 |
+
├── ...
|
| 88 |
+
├── README.md # Documentation
|
| 89 |
+
└── pinokio.json # Metadata (title, description, icon)
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
IMPORTANT: ALWAYS try to follow the best practices in the examples folder (C:\pinokio\prototype\system\examples) instead of trying to come up with your own structure. The examples have been optimized for the best user experience.
|
| 93 |
+
|
| 94 |
+
## Launcher Project Working Directory
|
| 95 |
+
|
| 96 |
+
- The project working directory for a script is always the same directory as the script location.
|
| 97 |
+
- For example, when you run `shell.run` API inside `pinokio/start.js`, the default path for shell execution is `pinokio`.
|
| 98 |
+
- If the launcher files are in the project root path, then the default path for shell execution is the project root.
|
| 99 |
+
- Therefore, it is important to specify the correct `path` attribute when running `shell.run` API commands.
|
| 100 |
+
|
| 101 |
+
Example: in the following project structure:
|
| 102 |
+
|
| 103 |
+
```
|
| 104 |
+
project-root/
|
| 105 |
+
├── pinokio/ # Pinokio launcher folder
|
| 106 |
+
│ ├── start.js # Launch script
|
| 107 |
+
│ ├── pinokio.js # UI generator script
|
| 108 |
+
│ └── pinokio.json # Metadata (title, description, icon)
|
| 109 |
+
└─── backend/
|
| 110 |
+
├── requirements.txt # App dependencies
|
| 111 |
+
└── app.py # App code
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
The `pinokio/start.js` should use the correct path `../backend` as the `path` attribute, as follows:
|
| 115 |
+
|
| 116 |
+
```
|
| 117 |
+
{
|
| 118 |
+
run: [{
|
| 119 |
+
...
|
| 120 |
+
}, {
|
| 121 |
+
method: "shell.run",
|
| 122 |
+
params: {
|
| 123 |
+
message: "python app.py",
|
| 124 |
+
venv: "env",
|
| 125 |
+
path: "../backend"
|
| 126 |
+
}
|
| 127 |
+
}, {
|
| 128 |
+
...
|
| 129 |
+
}]
|
| 130 |
+
}
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
## Development Workflow
|
| 134 |
+
|
| 135 |
+
### 1. Understanding the Project
|
| 136 |
+
- Check `SPEC.md` in project root. If the file exists, use that to learn about the project details (what and how to build)
|
| 137 |
+
- If no `SPEC.md` exists, build based on user requirements
|
| 138 |
+
### 2. Modifying Existing Launcher Projects
|
| 139 |
+
If we are starting with existing launcher script files, work with the existing files instead of coming up with your own.
|
| 140 |
+
- **Preserve existing functionality:** Only modify necessary parts
|
| 141 |
+
- **Don't touch working scripts:** Unless adding/updating specific commands
|
| 142 |
+
- **Follow existing conventions:** Match the style and structure already present
|
| 143 |
+
### 3. Try to adopt from examples as much as possible
|
| 144 |
+
- If starting from scratch, first determine what type of project you will be building, and then check the examples folder (C:\pinokio\prototype\system\examples) to see if you can adopt them instead of coming up everything from scratch.
|
| 145 |
+
- Even if there are no relevant examples, check the examples to get inspiration for how you would structure the script files even if you have to write from scratch.
|
| 146 |
+
### 4. Writing from scratch as a last resort
|
| 147 |
+
If there are relevant examples to adopt from, write the scripts from scratch, but just make sure to follow the requirements in the next section.
|
| 148 |
+
### 5. Debugging
|
| 149 |
+
When the user reports something is not working, ALWAYS inspect the logs folder to get all the execution logs. For more info on how this works, check the "Troubleshooting with Logs" section below.
|
| 150 |
+
|
| 151 |
+
## Script Requirements
|
| 152 |
+
|
| 153 |
+
### 1. 1-click launchable
|
| 154 |
+
- The main purpose of Pinokio is to provide an easy interface to invoke commands, which may include launching servers, installing programs, etc. Make sure the final product provides ways to install, launch, reset, and update whatever is needed.
|
| 155 |
+
|
| 156 |
+
### 2. Write Documentation
|
| 157 |
+
- ALWAYS write a documentation. A documentation must be stored as `README.md` in the project root folder, along with the rest of the pinokio launcher script files. A documentation file must contain:
|
| 158 |
+
- What the app does
|
| 159 |
+
- How to use the app
|
| 160 |
+
- API documentation for programmatically accessing the app's main features (Javascript, Python, and Curl)
|
| 161 |
+
|
| 162 |
+
## Types of launchers
|
| 163 |
+
## 1. Launching servers
|
| 164 |
+
- When an app requires launching a server, here are the commonly used scripts:
|
| 165 |
+
- `install.js`: a script to install the app
|
| 166 |
+
- `start.js`: a script to start the app
|
| 167 |
+
- `reset.js`: a script to reset all the dependencies installed in the `install.js` step. used if the user wants to restart from scratch
|
| 168 |
+
- `update.js`: a script to update the launcher AND the app in case there are new updates. Involves pulling in the relevant git repositories installed through `install.js` (often it's the script repo and some git repositories cloned through the install steps if any)
|
| 169 |
+
- `pinokio.js`: the launcher script that ties all of the above scripts together by providing a UI that links to these scripts.
|
| 170 |
+
- `pinokio.json`: For metadata
|
| 171 |
+
|
| 172 |
+
Here's a basic server launcher script example (`start.js`). Unless there's a special reason you need to use another pattern, this is the most recommended pattern. Use this or adopt it as needed, but NEVER try something else unless there's a good reason you should not take this approach:
|
| 173 |
+
|
| 174 |
+
```javascript
|
| 175 |
+
module.exports = {
|
| 176 |
+
// By setting daemon: true, the script keeps running even after all items in the `run` array finishes running. Mandatory for launching servers, since otherwise the shells running the server process will get killed after the scripts finish running.
|
| 177 |
+
daemon: true,
|
| 178 |
+
run: [
|
| 179 |
+
{
|
| 180 |
+
// The "shell.run" API for running a shell session
|
| 181 |
+
method: "shell.run",
|
| 182 |
+
params: {
|
| 183 |
+
// Edit 'venv' to customize the venv folder path
|
| 184 |
+
venv: "env",
|
| 185 |
+
// Edit 'env' to customize environment variables (see documentation)
|
| 186 |
+
env: { },
|
| 187 |
+
// Edit 'path' to customize the path to start the shell from
|
| 188 |
+
path: "app",
|
| 189 |
+
// Edit 'message' to customize the commands, or to run multiple commands
|
| 190 |
+
message: [
|
| 191 |
+
"python app.py",
|
| 192 |
+
],
|
| 193 |
+
on: [{
|
| 194 |
+
// The regular expression pattern to monitor.
|
| 195 |
+
// Whenever each "event" pattern occurs in the shell terminal, the shell will return,
|
| 196 |
+
// and the script will go onto the next step.
|
| 197 |
+
// The regular expression match object will be passed on to the next step as `input.event`
|
| 198 |
+
// Useful for capturing the URL at which the server is running (in case the server prints some message about where the server is running)
|
| 199 |
+
"event": "/(http:\/\/\\S+)/",
|
| 200 |
+
|
| 201 |
+
// Use "done": true to move to the next step while keeping the shell alive.
|
| 202 |
+
// Use "kill": true to move to the next step after killing the shell.
|
| 203 |
+
"done": true
|
| 204 |
+
}]
|
| 205 |
+
}
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
// This step sets the local variable 'url'.
|
| 209 |
+
// This local variable will be used in pinokio.js to display the "Open WebUI" tab when the value is set.
|
| 210 |
+
method: "local.set",
|
| 211 |
+
params: {
|
| 212 |
+
// the input.event is the regular expression match object from the previous step
|
| 213 |
+
// In this example, since the pattern was "/(http:\/\/\\S+)/", input.event[1] will include the exact http url match caputred by the parenthesis.
|
| 214 |
+
// Therefore setting the local variable 'url'
|
| 215 |
+
url: "{{input.event[1]}}"
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
]
|
| 219 |
+
}
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
## 2. Launching serverless web apps
|
| 223 |
+
|
| 224 |
+
- In case of purely static web apps WITHOUT servers or backends (for example an HTML based app that connects to 3rd party servers--either remote or localhost), we do NOT need the launcher scripts.
|
| 225 |
+
- In these cases, simply include `index.html` in the project root folder and everything should automatically work. No need for any of the pinokio launcher scripts. (Do
|
| 226 |
+
- You still need to include the metadata file so they show up properly on pinokio:
|
| 227 |
+
- `pinokio.json`: For metadata
|
| 228 |
+
|
| 229 |
+
## 3. Launching quick scripts without web UI
|
| 230 |
+
|
| 231 |
+
- In many cases, we may not even need a web UI, but instead just a simple way to run scripts.
|
| 232 |
+
- This may include TUI (Terminal User Interface) apps, a simple launcher
|
| 233 |
+
- In these cases, all we need is the launcher file `pinokio.js`, which may link to multiple scripts. In this case, there are no web apps (no serverless apsp, no servers), but instead just the default pinokio launcher UI that calls a bunch of scripts.
|
| 234 |
+
- Here are some examples:
|
| 235 |
+
- A pinokio script to toggle the desktop theme between dark and light
|
| 236 |
+
- Write some code (python or javascript or whatever)
|
| 237 |
+
- Write a `toggle.js` pinokio script that executes the code
|
| 238 |
+
- Write a `pinokio.js` launcher script to create a sidebar UI that displays the `toggle.js` so the user can simply click the "toggle" button to toggle back and forth between desktop themes
|
| 239 |
+
- A pinokio script to fetch some file
|
| 240 |
+
- Write some code (python or javascript or whatever)
|
| 241 |
+
- Write a `fetch.js` pinokio script that executes the code
|
| 242 |
+
- Write a `pinokio.js` launcher script to create a sidebar UI that displays the `fetch.js` so the user can simply click the "fetch" button to fetch some data.
|
| 243 |
+
- You still need to include the metadata file so they show up properly on pinokio:
|
| 244 |
+
- `pinokio.json`: For metadata
|
| 245 |
+
|
| 246 |
+
## API
|
| 247 |
+
|
| 248 |
+
This section lists all the script APIs available on Pinokio. To learn the details of how they are used, you can:
|
| 249 |
+
1. Check the examples in the C:\pinokio\prototype\system\examples folder
|
| 250 |
+
2. Read the `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md further documentation on the full syntax
|
| 251 |
+
|
| 252 |
+
### Script API
|
| 253 |
+
|
| 254 |
+
These APIs can be used to describe each step in a pinokio script:
|
| 255 |
+
- shell.run: run shell commands
|
| 256 |
+
- input: accept user input
|
| 257 |
+
- filepicker: accept file upload
|
| 258 |
+
- fs.write: write to file
|
| 259 |
+
- fs.read: read from file
|
| 260 |
+
- fs.copy: copy files
|
| 261 |
+
- fs.download: download files
|
| 262 |
+
- fs.link: create a symbolic link (or junction on windows) for folders
|
| 263 |
+
- fs.open: open the system file explorer at a given path
|
| 264 |
+
- fs.cat: print file contents
|
| 265 |
+
- jump: jump to a specific step
|
| 266 |
+
- local.set: set local variables for the currently running script
|
| 267 |
+
- json.set: update a json file
|
| 268 |
+
- json.rm: remove keys from a json file
|
| 269 |
+
- json.get: get values from a json file
|
| 270 |
+
- log: print to the web terminal
|
| 271 |
+
- net: make network requests
|
| 272 |
+
- notify: display a notification
|
| 273 |
+
- script.download: download a script from a git uri
|
| 274 |
+
- script.start: start a script
|
| 275 |
+
- script.stop: stop a script
|
| 276 |
+
- script.return: return values if the current script was called by a caller script, so the caller script can utilize the return value as `input`
|
| 277 |
+
- web.open: open a url in web browser
|
| 278 |
+
- hf.download: huggingfac-cli download API
|
| 279 |
+
### Template variables
|
| 280 |
+
The following variables are accessible inside template expressions (example `{{args.command}` in scripts, resulting in dynamic behaviors of scripts:
|
| 281 |
+
- input: An input is a variable that gets passed from one RPC call to the next
|
| 282 |
+
- args: args is the parameter object that gets passed into the script (via pinokio.js `params`). Unlike `input` which takes the value passed in from the immediately previous step, `args` is a global value that is the same through out the entire script execution.
|
| 283 |
+
- local: local variable object that can be set with `local.set` API
|
| 284 |
+
- self: refers to the script file itself (which is JSON or JavaScript). For example if `start.js` that's currently running has `daemon: true` set, `{{self.daemon}}` will evaluate to true.
|
| 285 |
+
- uri: The current script uri
|
| 286 |
+
- port: The next available port. Very useful when you need to launch an app at a specific port without port conflicts.
|
| 287 |
+
- cwd: The current script execution folder path
|
| 288 |
+
- platform: The current operating system. May be one of the following: `darwin`, `win32`, `linux`
|
| 289 |
+
- arch: The current system architecture. May be one of the following: x32, x64, arm, arm64, s390, s390x, mipsel, ia32, mips, ppc, ppc64
|
| 290 |
+
- gpus: array of available GPUs on the machine (example: `['apple']`, `['nvidia']`)
|
| 291 |
+
- gpu: the first available GPU (example: `nvidia`)
|
| 292 |
+
- current: The current variable points to the index of the currently executing instruction within the run array.
|
| 293 |
+
- next: The next variable points to the index of the next instruction to be executed. (null if the current instruction is the final instruction in the run array)
|
| 294 |
+
- envs: You can access the environment variables of the currently running process with envs object.
|
| 295 |
+
- which: Check whether a command exists (example: `{{which('winget')}}`. Can be used in the `when` attribute of a script step to run commands or install first.
|
| 296 |
+
- exists: Check whether a file or folder exists at the specified relative path (example: `"when": "{{!exists('app')}}"`). Can be used with the `when` attribute to determine a path's existence and trigger custom logic. Use relative paths and it will resolve automatically to the current execution folder.
|
| 297 |
+
- running: Check whether a script file is running (example: `"when": "{{!running('start.js')}}"`). Can be used with the `when` attribute to determine a path's existence and trigger custom logic. Use relative paths and it will resolve automatically to the current execution folder.
|
| 298 |
+
- os: Pinokio exposes the node.js os module through the os variable.
|
| 299 |
+
- path: Pinokio exposes the node.js path module through the os variable (example: `{{path.resolve(...)}}`
|
| 300 |
+
|
| 301 |
+
## System Capabilities
|
| 302 |
+
### Package Management (Use in Order of Preference)
|
| 303 |
+
The following package managers come pre-installed with Pinokio, so whenever you need to install a 3rd party binary, remember that these are available. Also, you can assume these are available and include the following package manager commands in Pinokio scripts:
|
| 304 |
+
1. **UV** - For Python packages (preferred over pip)
|
| 305 |
+
2. **NPM** - For Node.js packages
|
| 306 |
+
3. **Conda** - For cross-platform 3rd party binaries
|
| 307 |
+
4. **Brew** - Mac-only fallback when other options unavailable
|
| 308 |
+
5. **Git** - Full access to git is available.
|
| 309 |
+
**Important:** Include all install commands in the install script for reproducibility.
|
| 310 |
+
### HTTPS Proxy Support
|
| 311 |
+
- All HTTP servers automatically get HTTPS endpoints
|
| 312 |
+
- Convention: `http://localhost:<PORT>` → `https://<PORT>.localhost`
|
| 313 |
+
- Full proxy list available at: `http://localhost:2019/config/`
|
| 314 |
+
### Pterm Features:
|
| 315 |
+
- **Clipboard Access:** Read from or Write to system clipboard via pinokio Pterm CLI (`pterm clipboard` command.)
|
| 316 |
+
- **Notifications:** Send desktop alerts via pinokio pterm CLI (`pterm push` command.)
|
| 317 |
+
- **Script Testing:** Run launcher scripts via pinokio pterm CLI (`pterm start` command.)
|
| 318 |
+
- **File Selection:** Use built-in filepicker for user file/folder input (`pterm filepicker` command.)
|
| 319 |
+
- **Git Operations:** Clone repositories, push to GitHub
|
| 320 |
+
- **GitHub Integration:** Full GitHub CLI support (`gh` commands)
|
| 321 |
+
|
| 322 |
+
## Troubleshooting with Logs
|
| 323 |
+
Pinokio stores the logs for everything that happened in terminal at the following locations, so you can make use of them to determine what's going on:
|
| 324 |
+
|
| 325 |
+
### Log Structure
|
| 326 |
+
In case there is a `pinokio` folder in the project root folder, you should be able to find the logs folder here:
|
| 327 |
+
|
| 328 |
+
```
|
| 329 |
+
pinokio/
|
| 330 |
+
└── logs/ # Direct user interaction logs
|
| 331 |
+
├── api/ # Launcher script logs (install.js, start.js, etc.)
|
| 332 |
+
├── dev/ # AI coding tool logs (organized by tool)
|
| 333 |
+
└── shell/ # Direct user interaction logs
|
| 334 |
+
```
|
| 335 |
+
|
| 336 |
+
Otherwise, the `logs` folder should be found at project root:
|
| 337 |
+
|
| 338 |
+
```
|
| 339 |
+
logs/
|
| 340 |
+
├── api/ # Launcher script logs (install.js, start.js, etc.)
|
| 341 |
+
├── dev/ # AI coding tool logs (organized by tool)
|
| 342 |
+
└── shell/ # Direct user interaction logs
|
| 343 |
+
```
|
| 344 |
+
|
| 345 |
+
### Log File Naming
|
| 346 |
+
- Unix timestamps for each session
|
| 347 |
+
- Special "latest" file contains most recent session logs
|
| 348 |
+
- **Default:** Use "latest" files for current issues
|
| 349 |
+
- **Historical:** Use timestamped files for pattern analysis and the full history.
|
| 350 |
+
|
| 351 |
+
## Best practices
|
| 352 |
+
### 0. Always reference the logs when debugging
|
| 353 |
+
- When the user asks to fix something, ALWAYS check the logs folder first to check what went wrong. Check the "Troubleshooting with Logs" section.
|
| 354 |
+
### 1. Shell commands for launching programs
|
| 355 |
+
- Launch flags related
|
| 356 |
+
- Try as hard as possible to minimize launch flags and parameters when launching an app. For example, instead of `python app.py --port 8610`, try to do `python app.py` unless really necessary. The only exception is when the only way to launch the app is to specify the flags.
|
| 357 |
+
- Launch IP related
|
| 358 |
+
- Always try to find a way to launch servers at 127.0.0.1 or localhost, often by specifying launch flags or using environment variables. Some apps launch apps at 0.0.0.0 by default but we do not want this.
|
| 359 |
+
- Launch Port related
|
| 360 |
+
- In case the app itself automatically launches at the next available port by default (for example Gradio does this), do NOT specify port, since it's taken care of by the app itself. Always try to minimize the amount of code.
|
| 361 |
+
- If the install instruction says to launch at a specific port, don't use the hardcoded port they suggest since there's a risk of port conflicts. Instead, use Pinokio's `{{port}}` template expression to automatically get the next available port.
|
| 362 |
+
- For example, if the instruction says `python app.py --port 7860`, don't use that hardcoded port since there might be another app running at that port. Instead, automatically assign the next available port like this: `python app.py --port {{port}}`
|
| 363 |
+
- Note that the `{{port}}` expression always returns the next immediately available port for each step, so if you have multiple steps in a script and use `{{port}}` in multiple steps, the value will be different. So if you want to launch at the next available port and then later reuse that port, you will need to first use `{{port}}` to get the next available port, and save the value in local variable using `local.set`, and then use the `{{local.<variable_name>}}` expression later.
|
| 364 |
+
### 2. shell.run API
|
| 365 |
+
- When writing `shell.run` API requests, always use relative paths (no absolute paths) for the `path` field. For example, if you need to run a command from `app` folder, the `path` attribute should simply be `app`, instead of its full absolute path.
|
| 366 |
+
### 2. Package managers
|
| 367 |
+
- When installing python packages, try best to use `uv` instead of `pip` even if the install instruction says to use pip. Instead of `pip install -r requirements.txt`, you can simply use `uv pip install -r requirements.txt` for example. Even if the project's own README says use pip or poetry, first check if there's a way to use uv instead.
|
| 368 |
+
- When you need to install some global package, try to use `conda` as much as possible. Even on macs, `brew` should be only used if there are no `conda` options.
|
| 369 |
+
### 3. Minimal Always
|
| 370 |
+
- If you are starting with existing script files, before modifying, creating, or removing any script files, first look at `pinokio.js` to understand which script files are actually used in the launcher. The only script files used are the ones mentioned in the `pinokio.js` file. The `pinokio.js` file is the file that constructs the UI dynamically.
|
| 371 |
+
- Do not create a redundant script file that does something that already exists. Instead modify the existing script file for the feature. For example, do not create an `install.json` file for installation if `install.js` already exists. Instead, modify the `install.js` file.
|
| 372 |
+
- Pinokio accepts both JSON and JS script files, so when determining whether a script for a specific purpose already exists, check both JSON and JS files mentioned in the `pinokio.js` file. Do not create script files for rendundant purpose.
|
| 373 |
+
- When building launchers for existing projects cloned from a repository, try to stay away from modifying the project folder (the `C:\pinokio\api\Z-Image-Pinokio.git` folder), even if installations are failing. Instead, try to work around it by creating additional files in the launcher folder, and using those files IN ADDITION to the default project.
|
| 374 |
+
- The only exception when you may need to make changes to the project folder is when the user explicitly wants to modify the existing project. Otherwise if the purpose is to simply write a launcher, the app logic folder should never be touched.
|
| 375 |
+
- When running shell commands, take full advantage of the Pinokio `shell.run` API, which provides features like `env`, `venv`, `input`, `path`, `sudo`, `on`, etc. which can greatly reduce the amount of script code.
|
| 376 |
+
- Python apps: Always use virtual environments via `venv` attribute. This attribute automatically creates a venv or uses if it already exists.
|
| 377 |
+
### 4. Try to support Cross-platform as much as possible
|
| 378 |
+
- Use cross-platform shell commands only.
|
| 379 |
+
- This means, prefer to use commands that work on all platforms instead of the current platform.
|
| 380 |
+
- If there are no cross platform commands, use Pinokio's template expressions to conditionally use commands depending on `platform`, `arch`, etc.
|
| 381 |
+
- Also try to utilize Pinokio Pterm APIs for various cross-platform system features.
|
| 382 |
+
- If it is impossible to implement a cross platform solution (due to the nature of the project itself), set the `platform`, `arch`, and/or `gpu` attributes of the `pinokio.json` file to declare the limitation.
|
| 383 |
+
- Pinokio provides various APIs for cross-platform way of calling commonly used system functions, or lets you selectively run commands depending on `platform`, `arch`, etc.
|
| 384 |
+
### 5. Do not make assumptions about Pinokio API
|
| 385 |
+
- Do NOT make assumptions about which Pinokio APIs exist. Check the documentation.
|
| 386 |
+
- Do NOT make assumptions about the Pinokio API syntax. Follow the documentation.
|
| 387 |
+
### 6. Scripts must be able to replicate install and launch steps 100%
|
| 388 |
+
- The whole point of the scripts is for others to easily download and invoke them via Pinokio interface with one click. Therefore, do not assume the end user's system state, and make everything self-contained.
|
| 389 |
+
- When a 3rd party package needs to be installed, or a 3rd party repository needs to be downloaded, include them in the scripts.
|
| 390 |
+
### 7 Dynamic UI rendering
|
| 391 |
+
- The `pinokio.js` launcher script can change dynamically depending on the current state of the script execution. Which means, depending on what the file returns, it can determine what the sidebar looks like at any given moment of the script cycle.
|
| 392 |
+
- `info.exists(relative_path)`: The `info.exists` can be used to check whether a relative path (relative to the script root path) exists. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 393 |
+
- `info.running(relative_path)`: The `info.running` can be used to check whether a script at a relative path is currently running (relative to the script root path) exists. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 394 |
+
- `info.local(relative_path)`: The `info.local` can be used to return all the local variables tied to a script that's currently running. The `pinokio.js` file can determine which menu items to return based on this value at any given moment.
|
| 395 |
+
- `default`: set the `default` attribute on any menu item for whichever menu needs to be selected by default at a given step. Some example scenarios:
|
| 396 |
+
- during the install process, the `install.js` menu item needs to be set as the `default`, so it automatically executes the script
|
| 397 |
+
- when launching the `start.js` menu item needs to be set as the `default`, so it automatically executes the script
|
| 398 |
+
- after the app has launched, the `default` needs to be set on the web UI URL, so the user is sent to the actual app automatically.
|
| 399 |
+
- Check the examples in the C:\pinokio\prototype\system\examples folder to see how these are being used.
|
| 400 |
+
### 8. No need for stop scripts
|
| 401 |
+
- `pinokio.js` does NOT need a separate `stop` script. Every script that can be started can also be natively stopped through the Pinokio UI, therefore you do not need a separate stop script for start script
|
| 402 |
+
### 9. Writing launchers for existing projects
|
| 403 |
+
- When writing or modifying pinokio launcher scripts, figure out the install/launch steps by reading the project folder `app`.
|
| 404 |
+
- In most cases, the `README.md` file in the `C:\pinokio\api\Z-Image-Pinokio.git` folder contains the instructions needed to install and run the app, but if not, figure out by scanning the rest of the project files.
|
| 405 |
+
- Install scripts should work for each specific operating system, so ignore Docker related instructions. Instead use install/launch instructions for each platform.
|
| 406 |
+
### 10. Don't use Docker unless really necessary
|
| 407 |
+
- Some projects suggest docker as installation options. But even in these cases, try to find "development" options to launch the app without relying on Docker, as much as possible. We do not need Docker since we can automatically install and launch apps specifically for the user's platform, since we can write scripts that run cross platform.
|
| 408 |
+
### 11. pinokio.json
|
| 409 |
+
- Do not touch the `version` field since the version is the script schema version and the one pre-set in `pinokio.js` must be used.
|
| 410 |
+
- `icon`: It's best if we have a user friendly icon to represent the app, so try to get an image and link it from `pinokio.json`.
|
| 411 |
+
- If the git repository for the `C:\pinokio\api\Z-Image-Pinokio.git` folder points to GitHub (for example https://github.com/<USERNAME>/<REPO_NAME>`, ask the user if they want to download the icon from GitHub, and if approved, get the `avatar_url` by fetching `https://api.github.com/users/<USERNAME>`, and then download the image to the root folder as `icon.png`, and set `icon.png` as the `icon` field of the `pinokio.json`.
|
| 412 |
+
### 12. Gitignore
|
| 413 |
+
- When a launcher involves cloning 3rd party repositories, downloading files dynamically, or some files to be generated, these need to be included in the .gitignore file. This may include things like:
|
| 414 |
+
- Cloning git repositories
|
| 415 |
+
- Downloading files
|
| 416 |
+
- Dynamically creating files during installation or running, such as Sqlite Databases, or environment variables, or anything specific to the user.
|
| 417 |
+
- Make sure these file paths are included in the .gitignore file, and if not, include them in .gitignore.
|
| 418 |
+
|
| 419 |
+
## AI Libraries (Pytorch, Xformers, Triton, Sageattention, etc.)
|
| 420 |
+
If the launcher has a dedicated built-in script named `torch.js`, it can be used as follows:
|
| 421 |
+
|
| 422 |
+
```
|
| 423 |
+
// install.js
|
| 424 |
+
module.exports = {
|
| 425 |
+
run: [
|
| 426 |
+
// Delete this step if your project does not use torch
|
| 427 |
+
{
|
| 428 |
+
method: "script.start",
|
| 429 |
+
params: {
|
| 430 |
+
uri: "torch.js",
|
| 431 |
+
params: {
|
| 432 |
+
path: "app",
|
| 433 |
+
venv: "venv", // Edit this to customize the venv folder path
|
| 434 |
+
// xformers: true // uncomment this line if your project requires xformers
|
| 435 |
+
// triton: true // uncomment this line if your project requires triton
|
| 436 |
+
// sageattention: true // uncomment this line if your project requires sageattention
|
| 437 |
+
}
|
| 438 |
+
}
|
| 439 |
+
},
|
| 440 |
+
// Edit this step with your custom install commands
|
| 441 |
+
{
|
| 442 |
+
method: "shell.run",
|
| 443 |
+
params: {
|
| 444 |
+
venv: "venv", // Edit this to customize the venv folder path
|
| 445 |
+
path: "app",
|
| 446 |
+
message: [
|
| 447 |
+
"uv pip install -r requirements.txt"
|
| 448 |
+
],
|
| 449 |
+
}
|
| 450 |
+
},
|
| 451 |
+
]
|
| 452 |
+
}
|
| 453 |
+
```
|
| 454 |
+
|
| 455 |
+
The `torch.js` script also includes ways to install pytorch dependent libraries such as xformers, triton, sagetattention. If any of these libraries need to be installed, use the torch.js to install in order to install them cross platform.
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
## Quick Reference
|
| 459 |
+
### Essential Documentation
|
| 460 |
+
- **Pinokio Programming:** See `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md → "Programming Pinokio" section
|
| 461 |
+
- **Dynamic Menus:** See `PINOKIO.md` at C:\pinokio\prototype\PINOKIO.md → "Dynamic menu rendering" section
|
| 462 |
+
- **CLI Commands:** See `PTERM.md` at C:\pinokio\prototype\PTERM.md
|
| 463 |
+
### Common Patterns
|
| 464 |
+
- **Python Virtual Env:** `shell.run` with `venv` attribute
|
| 465 |
+
- **Cross-platform Commands:** Always test on multiple platforms
|
| 466 |
+
- **Error Handling:** Check logs/api for launcher issues
|
| 467 |
+
- **GitHub Operations:** Use `gh` CLI for advanced GitHub features
|
| 468 |
+
## Development Principles
|
| 469 |
+
1. **Minimize Shell Usage:** Leverage API parameters instead of raw commands
|
| 470 |
+
2. **Maintain Separation:** Keep app logic and launchers separate
|
| 471 |
+
3. **Follow Conventions:** Match existing project patterns
|
| 472 |
+
4. **Test Thoroughly:** Use CLI to verify launcher functionality
|
| 473 |
+
5. **Document Changes:** Update relevant metadata and documentation
|
README.md
CHANGED
|
@@ -1,3 +1,98 @@
|
|
| 1 |
-
--
|
| 2 |
-
|
| 3 |
-
--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Z-Image-Turbo Special Edition for Pinokio
|
| 2 |
+
|
| 3 |
+
A Gradio web interface for the Z-Image-Turbo model, designed for easy installation via [Pinokio](https://pinokio.computer/).
|
| 4 |
+
This Version is based on https://github.com/PierrunoYT/Z-Image-Pinokio.git
|
| 5 |
+
|
| 6 |
+
## Features
|
| 7 |
+
|
| 8 |
+
- **One-Click Install** - Pinokio handles all dependencies automatically
|
| 9 |
+
- **Modern Gradio UI** - Clean interface with all generation options
|
| 10 |
+
- **High-Quality Images** - Uses the Z-Image-Turbo model from Tongyi-MAI
|
| 11 |
+
- **Fast Generation** - Turbo model generates images in just 8 steps
|
| 12 |
+
- **Full Control** - Adjustable dimensions, steps, guidance, and seed
|
| 13 |
+
- **Support Lora** - supports loading LoRA. You can download the LoRA of Z-Image Turbo from C Station for use, or use the LoRA you trained yourself.
|
| 14 |
+
- **Support Select transformer** - supports the use of custom transformers, and you can download the Z-Image model from C Station for use.
|
| 15 |
+
|
| 16 |
+
## Installation
|
| 17 |
+
|
| 18 |
+
### Via Pinokio (Recommended)
|
| 19 |
+
|
| 20 |
+
1. Install [Pinokio](https://pinokio.computer/)
|
| 21 |
+
2. Search for "Z-Image-Turbo" or add this repository URL
|
| 22 |
+
3. Click **Install** - Pinokio will:
|
| 23 |
+
- Create a Python virtual environment
|
| 24 |
+
- Install PyTorch with CUDA support
|
| 25 |
+
- Install diffusers from source (required for ZImagePipeline)
|
| 26 |
+
- Download the Z-Image-Turbo model (~12GB)
|
| 27 |
+
4. Click **Start** to launch the web UI
|
| 28 |
+
<img width="3192" height="1846" alt="image" src="https://github.com/user-attachments/assets/9eb880f8-7c1e-4e68-b3cf-7d6d6bd7c392" />
|
| 29 |
+
<img width="3200" height="1836" alt="image" src="https://github.com/user-attachments/assets/3b782a31-17eb-4642-b75c-a91b7d8e8858" />
|
| 30 |
+
|
| 31 |
+
### Manual Installation
|
| 32 |
+
|
| 33 |
+
```bash
|
| 34 |
+
# Clone the repository
|
| 35 |
+
git clone https://github.com/leewheel/Z-Image-Pinokio-Special-Edition.git
|
| 36 |
+
cd Z-Image-Pinokio-Special-Edition
|
| 37 |
+
|
| 38 |
+
# Create virtual environment
|
| 39 |
+
python -m venv env
|
| 40 |
+
source env/bin/activate # Linux/Mac
|
| 41 |
+
# or: env\Scripts\activate # Windows
|
| 42 |
+
|
| 43 |
+
# Install dependencies
|
| 44 |
+
pip install -r requirements.txt
|
| 45 |
+
pip install git+https://github.com/huggingface/diffusers
|
| 46 |
+
|
| 47 |
+
# Run the app
|
| 48 |
+
python app.py
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
## Usage
|
| 52 |
+
|
| 53 |
+
1. **Start** the app via Pinokio or `python app.py`
|
| 54 |
+
2. Open `http://localhost:7860` in your browser
|
| 55 |
+
3. Enter a prompt describing your image
|
| 56 |
+
4. Adjust settings:
|
| 57 |
+
- **Width/Height**: 512-2048px (default 1024x1024)
|
| 58 |
+
- **Inference Steps**: 1-20 (default 9, recommended for Turbo)
|
| 59 |
+
- **Guidance Scale**: 0.0-10.0 (default 0.0 for Turbo model)
|
| 60 |
+
- **Seed**: Set specific seed or randomize
|
| 61 |
+
5. Click **Generate**
|
| 62 |
+
|
| 63 |
+
## Settings Guide
|
| 64 |
+
|
| 65 |
+
| Setting | Default | Description |
|
| 66 |
+
|---------|---------|-------------|
|
| 67 |
+
| Width/Height | 1024 | Image dimensions (512-2048px) |
|
| 68 |
+
| Inference Steps | 9 | Number of denoising steps (8 actual DiT forwards) |
|
| 69 |
+
| Guidance Scale | 0.0 | CFG scale (0.0 recommended for Turbo) |
|
| 70 |
+
| Seed | Random | Reproducibility control |
|
| 71 |
+
|
| 72 |
+
## System Requirements
|
| 73 |
+
|
| 74 |
+
### Minimum
|
| 75 |
+
- **GPU**: 4GB VRAM (NVIDIA with CUDA)
|
| 76 |
+
- **RAM**: 16GB
|
| 77 |
+
- **Storage**: ~15GB for model
|
| 78 |
+
|
| 79 |
+
### Recommended
|
| 80 |
+
- **GPU**: 16GB+ VRAM (RTX 3090, 4090, etc.)
|
| 81 |
+
- **RAM**: 32GB
|
| 82 |
+
- **Storage**: SSD for faster loading
|
| 83 |
+
|
| 84 |
+
## Model Info
|
| 85 |
+
|
| 86 |
+
- **Model**: [Tongyi-MAI/Z-Image-Turbo](https://huggingface.co/Tongyi-MAI/Z-Image-Turbo)
|
| 87 |
+
- **License**: Apache 2.0
|
| 88 |
+
- **Size**: ~12GB
|
| 89 |
+
|
| 90 |
+
## Credits
|
| 91 |
+
|
| 92 |
+
- **Z-Image-Turbo**: [Tongyi-MAI (Alibaba)](https://huggingface.co/Tongyi-MAI)
|
| 93 |
+
- **Diffusers**: [Hugging Face](https://github.com/huggingface/diffusers)
|
| 94 |
+
- **Pinokio**: [Pinokio](https://pinokio.computer/)
|
| 95 |
+
|
| 96 |
+
## License
|
| 97 |
+
|
| 98 |
+
MIT
|
Sampleapp.py
ADDED
|
@@ -0,0 +1,646 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
os.environ["DIFFUSERS_USE_PEFT_BACKEND"] = "1" # Enable The PEFT LoRA backend of diffusers
|
| 3 |
+
|
| 4 |
+
import uuid
|
| 5 |
+
import random
|
| 6 |
+
from datetime import datetime
|
| 7 |
+
import re
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
import gradio as gr
|
| 11 |
+
from diffusers import ZImagePipeline, AutoencoderKL, ZImageTransformer2DModel
|
| 12 |
+
from transformers import AutoModelForCausalLM
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
# =========================
|
| 16 |
+
# Path configuration (all based on the directory where app.py is located)
|
| 17 |
+
# =========================
|
| 18 |
+
|
| 19 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 20 |
+
|
| 21 |
+
# Official model: Z-Image-Turbo snapshot in the HF cache
|
| 22 |
+
BASE_SNAPSHOT_DIR = os.path.join(
|
| 23 |
+
BASE_DIR,
|
| 24 |
+
"cache",
|
| 25 |
+
"HF_HOME",
|
| 26 |
+
"hub",
|
| 27 |
+
"models--Tongyi-MAI--Z-Image-Turbo",
|
| 28 |
+
"snapshots",
|
| 29 |
+
"5f4b9cbb80cc95ba44fe6667dfd75710f7db2947",
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
TRANSFORMER_ROOT = os.path.join(BASE_SNAPSHOT_DIR, "transformer")
|
| 33 |
+
TEXT_ENCODER_ROOT = os.path.join(BASE_SNAPSHOT_DIR, "text_encoder")
|
| 34 |
+
VAE_ROOT = os.path.join(BASE_SNAPSHOT_DIR, "vae")
|
| 35 |
+
|
| 36 |
+
# Custom model directory (All the models you added later will be placed here)
|
| 37 |
+
MOD_DIR = os.path.join(BASE_DIR, "MOD")
|
| 38 |
+
MOD_TRANSFORMER = os.path.join(MOD_DIR, "transformer")
|
| 39 |
+
MOD_TEXT_ENCODER = os.path.join(MOD_DIR, "text_encoder")
|
| 40 |
+
MOD_VAE = os.path.join(MOD_DIR, "vae")
|
| 41 |
+
|
| 42 |
+
# LoRA and output
|
| 43 |
+
LORA_ROOT = os.path.join(BASE_DIR, "lora")
|
| 44 |
+
OUTPUT_DIR = os.path.join(BASE_DIR, "outputs")
|
| 45 |
+
|
| 46 |
+
for p in [MOD_TRANSFORMER, MOD_TEXT_ENCODER, MOD_VAE, LORA_ROOT, OUTPUT_DIR]:
|
| 47 |
+
os.makedirs(p, exist_ok=True)
|
| 48 |
+
|
| 49 |
+
print("=== BASE_DIR ===", BASE_DIR)
|
| 50 |
+
print("=== OFFICIAL SNAPSHOT DIR ===", BASE_SNAPSHOT_DIR)
|
| 51 |
+
print("=== MOD DIR ===", MOD_DIR)
|
| 52 |
+
|
| 53 |
+
# Global pipeline cache
|
| 54 |
+
pipe = None
|
| 55 |
+
current_model_config = {
|
| 56 |
+
"transformer": "default",
|
| 57 |
+
"text_encoder": "default",
|
| 58 |
+
"vae": "default",
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
# =========================
|
| 63 |
+
# LoRA Tools
|
| 64 |
+
# =========================
|
| 65 |
+
|
| 66 |
+
def scan_lora_items():
|
| 67 |
+
"""Scan all .safetensors files under ./lora as optional LoRAs."""
|
| 68 |
+
if not os.path.isdir(LORA_ROOT):
|
| 69 |
+
return []
|
| 70 |
+
items = []
|
| 71 |
+
for name in sorted(os.listdir(LORA_ROOT)):
|
| 72 |
+
full = os.path.join(LORA_ROOT, name)
|
| 73 |
+
if os.path.isfile(full) and name.lower().endswith(".safetensors"):
|
| 74 |
+
items.append(name)
|
| 75 |
+
return items
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def build_lora_tags(selected_loras, lora_alpha):
|
| 79 |
+
"""Generate <lora:name:alpha> tags and unify alpha."""
|
| 80 |
+
tags = []
|
| 81 |
+
try:
|
| 82 |
+
alpha = float(lora_alpha)
|
| 83 |
+
except Exception:
|
| 84 |
+
alpha = 1.0
|
| 85 |
+
|
| 86 |
+
alpha_str = f"{alpha:.2f}".rstrip("0").rstrip(".")
|
| 87 |
+
|
| 88 |
+
for fname in selected_loras or []:
|
| 89 |
+
base = os.path.splitext(os.path.basename(fname))[0]
|
| 90 |
+
if not base:
|
| 91 |
+
continue
|
| 92 |
+
tags.append(f"<lora:{base}:{alpha_str}>")
|
| 93 |
+
return tags
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def update_prompt_with_lora(prompt, selected_loras, lora_alpha):
|
| 97 |
+
"""Embed/Update LoRA tags in the prompt"""
|
| 98 |
+
prompt = prompt or ""
|
| 99 |
+
# 先清理掉旧的 <lora:...> 标签
|
| 100 |
+
prompt_clean = re.sub(r"<lora:[^>]+>", "", prompt).strip()
|
| 101 |
+
tags = build_lora_tags(selected_loras, lora_alpha)
|
| 102 |
+
if tags:
|
| 103 |
+
if prompt_clean:
|
| 104 |
+
prompt_clean = prompt_clean + " " + " ".join(tags)
|
| 105 |
+
else:
|
| 106 |
+
prompt_clean = " ".join(tags)
|
| 107 |
+
return prompt_clean
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def apply_lora_to_pipeline(pipe_local, selected_loras, lora_alpha):
|
| 111 |
+
"""Inject LoRA into the pipeline (diffusers PEFT backend, multiple LoRAs + alpha)"""
|
| 112 |
+
if pipe_local is None:
|
| 113 |
+
return None
|
| 114 |
+
if not selected_loras:
|
| 115 |
+
return pipe_local
|
| 116 |
+
|
| 117 |
+
try:
|
| 118 |
+
alpha = float(lora_alpha)
|
| 119 |
+
except Exception:
|
| 120 |
+
alpha = 1.0
|
| 121 |
+
|
| 122 |
+
adapter_names = []
|
| 123 |
+
|
| 124 |
+
for lora_file in selected_loras:
|
| 125 |
+
lora_path = os.path.join(LORA_ROOT, lora_file)
|
| 126 |
+
if not os.path.isfile(lora_path):
|
| 127 |
+
print(f"[LoRA] The file does not exist, skipping.: {lora_path}")
|
| 128 |
+
continue
|
| 129 |
+
|
| 130 |
+
base_name = os.path.splitext(os.path.basename(lora_file))[0]
|
| 131 |
+
safe_adapter_name = re.sub(r"[^a-zA-Z0-9_]", "_", base_name)
|
| 132 |
+
|
| 133 |
+
try:
|
| 134 |
+
print(f"[LoRA] Loading: {lora_path} as adapter '{safe_adapter_name}'")
|
| 135 |
+
pipe_local.load_lora_weights(
|
| 136 |
+
lora_path,
|
| 137 |
+
adapter_name=safe_adapter_name,
|
| 138 |
+
)
|
| 139 |
+
adapter_names.append(safe_adapter_name)
|
| 140 |
+
except Exception as e:
|
| 141 |
+
print(f"❌ [LoRA] load failed {lora_file}: {e}")
|
| 142 |
+
|
| 143 |
+
if adapter_names:
|
| 144 |
+
pipe_local.set_adapters(
|
| 145 |
+
adapter_names=adapter_names,
|
| 146 |
+
adapter_weights=[alpha] * len(adapter_names),
|
| 147 |
+
)
|
| 148 |
+
print(f"✅ [LoRA] activated {len(adapter_names)} LoRAs, alpha={alpha}")
|
| 149 |
+
else:
|
| 150 |
+
print("[LoRA] No LoRA adapters were successfully loaded.")
|
| 151 |
+
|
| 152 |
+
return pipe_local
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
# =========================
|
| 156 |
+
# Model scanning and loading
|
| 157 |
+
# =========================
|
| 158 |
+
|
| 159 |
+
def scan_model_variants(root_dir, label="Model"):
|
| 160 |
+
"""
|
| 161 |
+
Scan the "available model subdirectories" under root_dir.
|
| 162 |
+
|
| 163 |
+
Rules: Only consider a directory as an optional model if:
|
| 164 |
+
- It is a subdirectory
|
| 165 |
+
- The subdirectory contains config.json
|
| 166 |
+
- And it contains at least one .safetensors or .safetensors.index.json
|
| 167 |
+
|
| 168 |
+
This allows compatibility with:
|
| 169 |
+
- Diffusers style: config.json + diffusion_pytorch_model.safetensors
|
| 170 |
+
- Z-Image AE: config.json + ae.safetensors
|
| 171 |
+
"""
|
| 172 |
+
if not os.path.isdir(root_dir):
|
| 173 |
+
return []
|
| 174 |
+
|
| 175 |
+
variants = []
|
| 176 |
+
print(f"🔍 [Scan] {label}: {root_dir}")
|
| 177 |
+
|
| 178 |
+
for name in sorted(os.listdir(root_dir)):
|
| 179 |
+
subdir = os.path.join(root_dir, name)
|
| 180 |
+
if not os.path.isdir(subdir):
|
| 181 |
+
continue
|
| 182 |
+
|
| 183 |
+
has_config = os.path.isfile(os.path.join(subdir, "config.json"))
|
| 184 |
+
|
| 185 |
+
has_safetensors = False
|
| 186 |
+
for f in os.listdir(subdir):
|
| 187 |
+
if f.endswith(".safetensors") or f.endswith(".safetensors.index.json"):
|
| 188 |
+
has_safetensors = True
|
| 189 |
+
break
|
| 190 |
+
|
| 191 |
+
if has_config and has_safetensors:
|
| 192 |
+
variants.append(name)
|
| 193 |
+
|
| 194 |
+
return variants
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def get_choices(mod_root, label):
|
| 198 |
+
"""Scan custom models only from the MOD folder, default = official snapshot"""
|
| 199 |
+
variants = scan_model_variants(mod_root, label=f"Custom-{label}")
|
| 200 |
+
return ["default"] + sorted(list(set(variants)))
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def resolve_model_dir(choice, mod_root):
|
| 204 |
+
"""Parse to the corresponding directory based on the drop-down selection result; return None by default."""
|
| 205 |
+
if choice == "default":
|
| 206 |
+
return None
|
| 207 |
+
subdir = os.path.join(mod_root, choice)
|
| 208 |
+
if os.path.isdir(subdir):
|
| 209 |
+
return subdir
|
| 210 |
+
print(f"❌ [Model] 未找到模型目录: {subdir}")
|
| 211 |
+
return None
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
def pick_vae_weight_name(vae_dir):
|
| 215 |
+
"""
|
| 216 |
+
为 VAE 选择合适的 safetensors 文件名:
|
| 217 |
+
- 优先 ae.safetensors
|
| 218 |
+
- 其次 diffusion_pytorch_model.safetensors
|
| 219 |
+
- 否则 None(交给 diffusers 自动判断)
|
| 220 |
+
"""
|
| 221 |
+
candidates = [
|
| 222 |
+
"ae.safetensors",
|
| 223 |
+
"diffusion_pytorch_model.safetensors",
|
| 224 |
+
"model.safetensors",
|
| 225 |
+
]
|
| 226 |
+
for name in candidates:
|
| 227 |
+
if os.path.isfile(os.path.join(vae_dir, name)):
|
| 228 |
+
return name
|
| 229 |
+
return None
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def load_pipeline(
|
| 233 |
+
transformer_choice: str = "default",
|
| 234 |
+
text_encoder_choice: str = "default",
|
| 235 |
+
vae_choice: str = "default",
|
| 236 |
+
):
|
| 237 |
+
"""按选择(T / TE / VAE)组装或复用 Z-Image pipeline"""
|
| 238 |
+
global pipe, current_model_config
|
| 239 |
+
|
| 240 |
+
config_tuple = {
|
| 241 |
+
"transformer": transformer_choice,
|
| 242 |
+
"text_encoder": text_encoder_choice,
|
| 243 |
+
"vae": vae_choice,
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
# 如果配置没变,直接复用
|
| 247 |
+
if pipe is not None and config_tuple == current_model_config:
|
| 248 |
+
return pipe
|
| 249 |
+
|
| 250 |
+
pipe = None
|
| 251 |
+
try:
|
| 252 |
+
torch.cuda.empty_cache()
|
| 253 |
+
except Exception:
|
| 254 |
+
pass
|
| 255 |
+
|
| 256 |
+
use_default = (
|
| 257 |
+
transformer_choice == "default"
|
| 258 |
+
and text_encoder_choice == "default"
|
| 259 |
+
and vae_choice == "default"
|
| 260 |
+
)
|
| 261 |
+
|
| 262 |
+
if use_default:
|
| 263 |
+
print("🛠 正在加载默认 Z-Image-Turbo Pipeline(全官方组件)...")
|
| 264 |
+
pipe_local = ZImagePipeline.from_pretrained(
|
| 265 |
+
"Tongyi-MAI/Z-Image-Turbo",
|
| 266 |
+
torch_dtype=torch.bfloat16,
|
| 267 |
+
low_cpu_mem_usage=False,
|
| 268 |
+
)
|
| 269 |
+
else:
|
| 270 |
+
print(
|
| 271 |
+
f"🛠 正在加载自定义 Pipeline: "
|
| 272 |
+
f"T={transformer_choice}, TE={text_encoder_choice}, VAE={vae_choice}"
|
| 273 |
+
)
|
| 274 |
+
base_repo = "Tongyi-MAI/Z-Image-Turbo"
|
| 275 |
+
|
| 276 |
+
# ==== Transformer ====
|
| 277 |
+
transformer_dir = resolve_model_dir(transformer_choice, MOD_TRANSFORMER)
|
| 278 |
+
if transformer_dir is not None:
|
| 279 |
+
print(f" - 自定义 Transformer: {transformer_dir}")
|
| 280 |
+
transformer = ZImageTransformer2DModel.from_pretrained(
|
| 281 |
+
transformer_dir,
|
| 282 |
+
torch_dtype=torch.bfloat16,
|
| 283 |
+
)
|
| 284 |
+
else:
|
| 285 |
+
transformer = None
|
| 286 |
+
|
| 287 |
+
# ==== Text Encoder ====
|
| 288 |
+
text_encoder_dir = resolve_model_dir(text_encoder_choice, MOD_TEXT_ENCODER)
|
| 289 |
+
if text_encoder_dir is not None:
|
| 290 |
+
print(f" - 自定义 Text Encoder: {text_encoder_dir}")
|
| 291 |
+
text_encoder = AutoModelForCausalLM.from_pretrained(
|
| 292 |
+
text_encoder_dir,
|
| 293 |
+
torch_dtype=torch.bfloat16,
|
| 294 |
+
)
|
| 295 |
+
else:
|
| 296 |
+
text_encoder = None
|
| 297 |
+
|
| 298 |
+
# ==== VAE ====
|
| 299 |
+
vae_dir = resolve_model_dir(vae_choice, MOD_VAE)
|
| 300 |
+
if vae_dir is not None:
|
| 301 |
+
print(f" - 自定义 VAE: {vae_dir}")
|
| 302 |
+
weight_name = pick_vae_weight_name(vae_dir)
|
| 303 |
+
if weight_name:
|
| 304 |
+
print(f" - 使用权重文件: {weight_name}")
|
| 305 |
+
vae = AutoencoderKL.from_pretrained(
|
| 306 |
+
vae_dir,
|
| 307 |
+
torch_dtype=torch.bfloat16,
|
| 308 |
+
use_safetensors=True,
|
| 309 |
+
weight_name=weight_name,
|
| 310 |
+
)
|
| 311 |
+
else:
|
| 312 |
+
print(" - 未显式找到 safetensors,尝试默认加载")
|
| 313 |
+
vae = AutoencoderKL.from_pretrained(
|
| 314 |
+
vae_dir,
|
| 315 |
+
torch_dtype=torch.bfloat16,
|
| 316 |
+
)
|
| 317 |
+
else:
|
| 318 |
+
vae = None
|
| 319 |
+
|
| 320 |
+
pipe_local = ZImagePipeline.from_pretrained(
|
| 321 |
+
base_repo,
|
| 322 |
+
torch_dtype=torch.bfloat16,
|
| 323 |
+
low_cpu_mem_usage=False,
|
| 324 |
+
transformer=transformer,
|
| 325 |
+
text_encoder=text_encoder,
|
| 326 |
+
vae=vae,
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
pipe = pipe_local
|
| 330 |
+
current_model_config = config_tuple
|
| 331 |
+
print("✅ Pipeline 已加载:", current_model_config)
|
| 332 |
+
return pipe
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
def normalize_format(fmt: str):
|
| 336 |
+
fmt = (fmt or "png").lower()
|
| 337 |
+
if fmt == "jpeg":
|
| 338 |
+
return "JPEG", "jpeg"
|
| 339 |
+
if fmt == "webp":
|
| 340 |
+
return "WEBP", "webp"
|
| 341 |
+
return "PNG", "png"
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
# =========================
|
| 345 |
+
# 核心生成函数
|
| 346 |
+
# =========================
|
| 347 |
+
|
| 348 |
+
def generate_image(
|
| 349 |
+
prompt,
|
| 350 |
+
selected_loras,
|
| 351 |
+
lora_alpha,
|
| 352 |
+
device,
|
| 353 |
+
num_images,
|
| 354 |
+
image_format,
|
| 355 |
+
width,
|
| 356 |
+
height,
|
| 357 |
+
num_inference_steps,
|
| 358 |
+
guidance_scale,
|
| 359 |
+
seed,
|
| 360 |
+
randomize_seed,
|
| 361 |
+
transformer_choice,
|
| 362 |
+
text_encoder_choice,
|
| 363 |
+
vae_choice,
|
| 364 |
+
):
|
| 365 |
+
# 1. 加载 / 切换 pipeline
|
| 366 |
+
pipe_local = load_pipeline(
|
| 367 |
+
transformer_choice=transformer_choice,
|
| 368 |
+
text_encoder_choice=text_encoder_choice,
|
| 369 |
+
vae_choice=vae_choice,
|
| 370 |
+
)
|
| 371 |
+
|
| 372 |
+
if pipe_local is None:
|
| 373 |
+
raise gr.Error("Pipeline 加载失败,请查看控制台日志。")
|
| 374 |
+
|
| 375 |
+
# 2. 设备
|
| 376 |
+
if device == "cuda" and not torch.cuda.is_available():
|
| 377 |
+
print("⚠ 选择了 cuda 但当前环境没有可用 GPU,自动切换到 cpu。")
|
| 378 |
+
device = "cpu"
|
| 379 |
+
pipe_local.to(device)
|
| 380 |
+
|
| 381 |
+
# 3. 注入 LoRA
|
| 382 |
+
pipe_local = apply_lora_to_pipeline(
|
| 383 |
+
pipe_local,
|
| 384 |
+
selected_loras,
|
| 385 |
+
lora_alpha,
|
| 386 |
+
)
|
| 387 |
+
|
| 388 |
+
# 4. 种子
|
| 389 |
+
if randomize_seed:
|
| 390 |
+
seed = random.randint(0, 2**32 - 1)
|
| 391 |
+
seed = int(seed)
|
| 392 |
+
generator_device = "cuda" if device == "cuda" else "cpu"
|
| 393 |
+
generator = torch.Generator(generator_device).manual_seed(seed)
|
| 394 |
+
|
| 395 |
+
# 5. 输出目录
|
| 396 |
+
date_str = datetime.now().strftime("%Y-%m-%d")
|
| 397 |
+
day_dir = os.path.join(OUTPUT_DIR, date_str)
|
| 398 |
+
os.makedirs(day_dir, exist_ok=True)
|
| 399 |
+
|
| 400 |
+
pil_format, ext = normalize_format(image_format)
|
| 401 |
+
effective_prompt = (prompt or "").strip()
|
| 402 |
+
|
| 403 |
+
print(
|
| 404 |
+
f"🚀 生成中: {width}x{height}, steps={num_inference_steps}, "
|
| 405 |
+
f"guidance={guidance_scale}, seed={seed}, device={device}"
|
| 406 |
+
)
|
| 407 |
+
|
| 408 |
+
filepaths = []
|
| 409 |
+
try:
|
| 410 |
+
for _ in range(int(num_images)):
|
| 411 |
+
result = pipe_local(
|
| 412 |
+
prompt=effective_prompt,
|
| 413 |
+
height=height,
|
| 414 |
+
width=width,
|
| 415 |
+
num_inference_steps=num_inference_steps,
|
| 416 |
+
guidance_scale=guidance_scale,
|
| 417 |
+
generator=generator,
|
| 418 |
+
)
|
| 419 |
+
image = result.images[0]
|
| 420 |
+
|
| 421 |
+
timestamp = datetime.now().strftime("%H%M%S")
|
| 422 |
+
unique = str(uuid.uuid4())[:8]
|
| 423 |
+
filename = os.path.join(day_dir, f"image_{timestamp}_{unique}.{ext}")
|
| 424 |
+
image.save(filename, format=pil_format)
|
| 425 |
+
filepaths.append(filename)
|
| 426 |
+
|
| 427 |
+
return filepaths, seed
|
| 428 |
+
except Exception as e:
|
| 429 |
+
print(f"💥 生成出错: {e}")
|
| 430 |
+
raise gr.Error(f"生成出错: {e}")
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
# =========================
|
| 434 |
+
# 预扫描 / 默认值
|
| 435 |
+
# =========================
|
| 436 |
+
|
| 437 |
+
default_device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 438 |
+
initial_lora_items = scan_lora_items()
|
| 439 |
+
|
| 440 |
+
transformer_choices = get_choices(MOD_TRANSFORMER, "Transformer")
|
| 441 |
+
text_encoder_choices = get_choices(MOD_TEXT_ENCODER, "TextEncoder")
|
| 442 |
+
vae_choices = get_choices(MOD_VAE, "VAE")
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
# =========================
|
| 446 |
+
# Gradio 界面
|
| 447 |
+
# =========================
|
| 448 |
+
|
| 449 |
+
with gr.Blocks(title="Z-Image-Turbo Pro") as demo:
|
| 450 |
+
gr.Markdown(
|
| 451 |
+
"""
|
| 452 |
+
# 🎨 Z-Image-Turbo Pro(MOD 专业版 LeeWheel)
|
| 453 |
+
|
| 454 |
+
- 官方底模:HF cache 中的 `Tongyi-MAI/Z-Image-Turbo`
|
| 455 |
+
- 自定义模型:
|
| 456 |
+
- `MOD/transformer/<Name>/` → Transformer
|
| 457 |
+
- `MOD/text_encoder/<Name>/` → Text Encoder
|
| 458 |
+
- `MOD/vae/<Name>/` → VAE(支持 AE:`config.json + ae.safetensors`)
|
| 459 |
+
- LoRA:`lora/*.safetensors`
|
| 460 |
+
"""
|
| 461 |
+
)
|
| 462 |
+
|
| 463 |
+
with gr.Row():
|
| 464 |
+
with gr.Column(scale=1):
|
| 465 |
+
prompt = gr.Textbox(
|
| 466 |
+
label="Prompt / 提示词",
|
| 467 |
+
placeholder="描述你想生成的图像...",
|
| 468 |
+
lines=3,
|
| 469 |
+
)
|
| 470 |
+
|
| 471 |
+
# LoRA 区域
|
| 472 |
+
gr.Markdown("### LoRA 设置(C 站 Z-Image LoRA 放在 ./lora)")
|
| 473 |
+
with gr.Row():
|
| 474 |
+
refresh_lora_btn = gr.Button("🔄 刷新 LoRA 列表", size="sm")
|
| 475 |
+
lora_multiselect = gr.CheckboxGroup(
|
| 476 |
+
label="选择 LoRA(可多选)",
|
| 477 |
+
choices=initial_lora_items,
|
| 478 |
+
value=[],
|
| 479 |
+
)
|
| 480 |
+
lora_alpha = gr.Slider(
|
| 481 |
+
label="LoRA 全局强度 alpha",
|
| 482 |
+
minimum=0.0,
|
| 483 |
+
maximum=2.0,
|
| 484 |
+
step=0.05,
|
| 485 |
+
value=1.0,
|
| 486 |
+
)
|
| 487 |
+
|
| 488 |
+
# 模型选择
|
| 489 |
+
gr.Markdown("### 底模组件选择(官方 + MOD)")
|
| 490 |
+
|
| 491 |
+
transformer_choice = gr.Dropdown(
|
| 492 |
+
label="Transformer(底模)",
|
| 493 |
+
choices=transformer_choices,
|
| 494 |
+
value="default",
|
| 495 |
+
)
|
| 496 |
+
text_encoder_choice = gr.Dropdown(
|
| 497 |
+
label="Text Encoder(文本编码器)",
|
| 498 |
+
choices=text_encoder_choices,
|
| 499 |
+
value="default",
|
| 500 |
+
)
|
| 501 |
+
vae_choice = gr.Dropdown(
|
| 502 |
+
label="VAE(图像解码器)",
|
| 503 |
+
choices=vae_choices,
|
| 504 |
+
value="default",
|
| 505 |
+
)
|
| 506 |
+
|
| 507 |
+
# 设备与参数
|
| 508 |
+
device = gr.Radio(
|
| 509 |
+
label="推理设备 / Device",
|
| 510 |
+
choices=["cuda", "cpu"],
|
| 511 |
+
value=default_device,
|
| 512 |
+
)
|
| 513 |
+
|
| 514 |
+
num_images = gr.Slider(
|
| 515 |
+
label="生成张数 / Number of Images",
|
| 516 |
+
minimum=1,
|
| 517 |
+
maximum=8,
|
| 518 |
+
step=1,
|
| 519 |
+
value=1,
|
| 520 |
+
)
|
| 521 |
+
|
| 522 |
+
image_format = gr.Dropdown(
|
| 523 |
+
label="输出格式 / Output Format",
|
| 524 |
+
choices=["png", "jpeg", "webp"],
|
| 525 |
+
value="png",
|
| 526 |
+
)
|
| 527 |
+
|
| 528 |
+
gr.Markdown("**分辨率预设 / Resolution Presets**")
|
| 529 |
+
with gr.Row():
|
| 530 |
+
preset_512 = gr.Button("512×512", size="sm")
|
| 531 |
+
preset_768 = gr.Button("768×768", size="sm")
|
| 532 |
+
preset_1024 = gr.Button("1024×1024", size="sm")
|
| 533 |
+
preset_landscape = gr.Button("1024×768", size="sm")
|
| 534 |
+
preset_portrait = gr.Button("768×1024", size="sm")
|
| 535 |
+
|
| 536 |
+
with gr.Row():
|
| 537 |
+
width = gr.Slider(
|
| 538 |
+
label="宽度 Width",
|
| 539 |
+
minimum=512,
|
| 540 |
+
maximum=2048,
|
| 541 |
+
step=64,
|
| 542 |
+
value=1024,
|
| 543 |
+
)
|
| 544 |
+
height = gr.Slider(
|
| 545 |
+
label="高度 Height",
|
| 546 |
+
minimum=512,
|
| 547 |
+
maximum=2048,
|
| 548 |
+
step=64,
|
| 549 |
+
value=1024,
|
| 550 |
+
)
|
| 551 |
+
|
| 552 |
+
with gr.Row():
|
| 553 |
+
num_inference_steps = gr.Slider(
|
| 554 |
+
label="采样步数 / Inference Steps",
|
| 555 |
+
minimum=1,
|
| 556 |
+
maximum=50,
|
| 557 |
+
step=1,
|
| 558 |
+
value=10,
|
| 559 |
+
)
|
| 560 |
+
guidance_scale = gr.Slider(
|
| 561 |
+
label="Guidance Scale (CFG)",
|
| 562 |
+
minimum=0.0,
|
| 563 |
+
maximum=10.0,
|
| 564 |
+
step=0.1,
|
| 565 |
+
value=0.0,
|
| 566 |
+
)
|
| 567 |
+
|
| 568 |
+
with gr.Row():
|
| 569 |
+
seed = gr.Number(
|
| 570 |
+
label="Seed",
|
| 571 |
+
value=42,
|
| 572 |
+
precision=0,
|
| 573 |
+
)
|
| 574 |
+
randomize_seed = gr.Checkbox(
|
| 575 |
+
label="Randomize Seed / 随机种子",
|
| 576 |
+
value=True,
|
| 577 |
+
)
|
| 578 |
+
|
| 579 |
+
generate_btn = gr.Button("🚀 生成 / Generate", variant="primary", size="lg")
|
| 580 |
+
|
| 581 |
+
with gr.Column(scale=1):
|
| 582 |
+
output_gallery = gr.Gallery(
|
| 583 |
+
label="Generated Images",
|
| 584 |
+
show_label=True,
|
| 585 |
+
columns=2,
|
| 586 |
+
rows=2,
|
| 587 |
+
type="filepath",
|
| 588 |
+
)
|
| 589 |
+
used_seed = gr.Number(label="Seed Used", interactive=False)
|
| 590 |
+
|
| 591 |
+
# LoRA & prompt 绑定
|
| 592 |
+
refresh_lora_btn.click(
|
| 593 |
+
fn=lambda: gr.update(choices=scan_lora_items(), value=[]),
|
| 594 |
+
inputs=[],
|
| 595 |
+
outputs=lora_multiselect,
|
| 596 |
+
)
|
| 597 |
+
|
| 598 |
+
lora_multiselect.change(
|
| 599 |
+
fn=update_prompt_with_lora,
|
| 600 |
+
inputs=[prompt, lora_multiselect, lora_alpha],
|
| 601 |
+
outputs=prompt,
|
| 602 |
+
)
|
| 603 |
+
lora_alpha.change(
|
| 604 |
+
fn=update_prompt_with_lora,
|
| 605 |
+
inputs=[prompt, lora_multiselect, lora_alpha],
|
| 606 |
+
outputs=prompt,
|
| 607 |
+
)
|
| 608 |
+
|
| 609 |
+
# 分辨率预设
|
| 610 |
+
preset_512.click(fn=lambda: (512, 512), outputs=[width, height])
|
| 611 |
+
preset_768.click(fn=lambda: (768, 768), outputs=[width, height])
|
| 612 |
+
preset_1024.click(fn=lambda: (1024, 1024), outputs=[width, height])
|
| 613 |
+
preset_landscape.click(fn=lambda: (1024, 768), outputs=[width, height])
|
| 614 |
+
preset_portrait.click(fn=lambda: (768, 1024), outputs=[width, height])
|
| 615 |
+
|
| 616 |
+
# 生成按钮
|
| 617 |
+
generate_btn.click(
|
| 618 |
+
fn=generate_image,
|
| 619 |
+
inputs=[
|
| 620 |
+
prompt,
|
| 621 |
+
lora_multiselect,
|
| 622 |
+
lora_alpha,
|
| 623 |
+
device,
|
| 624 |
+
num_images,
|
| 625 |
+
image_format,
|
| 626 |
+
width,
|
| 627 |
+
height,
|
| 628 |
+
num_inference_steps,
|
| 629 |
+
guidance_scale,
|
| 630 |
+
seed,
|
| 631 |
+
randomize_seed,
|
| 632 |
+
transformer_choice,
|
| 633 |
+
text_encoder_choice,
|
| 634 |
+
vae_choice,
|
| 635 |
+
],
|
| 636 |
+
outputs=[output_gallery, used_seed],
|
| 637 |
+
)
|
| 638 |
+
|
| 639 |
+
if __name__ == "__main__":
|
| 640 |
+
demo.queue()
|
| 641 |
+
demo.launch(
|
| 642 |
+
server_name="127.0.0.1",
|
| 643 |
+
server_port=7860,
|
| 644 |
+
inbrowser=False,
|
| 645 |
+
show_error=True,
|
| 646 |
+
)
|
app.py
ADDED
|
@@ -0,0 +1,1092 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import gc
|
| 4 |
+
import uuid
|
| 5 |
+
import random
|
| 6 |
+
import re
|
| 7 |
+
import datetime
|
| 8 |
+
import json
|
| 9 |
+
import tempfile
|
| 10 |
+
import locale
|
| 11 |
+
|
| 12 |
+
# =========================
|
| 13 |
+
# 语言检测
|
| 14 |
+
# =========================
|
| 15 |
+
try:
|
| 16 |
+
system_lang = locale.getdefaultlocale()[0]
|
| 17 |
+
is_chinese = system_lang and system_lang.startswith('zh')
|
| 18 |
+
except:
|
| 19 |
+
is_chinese = False
|
| 20 |
+
|
| 21 |
+
def get_message(key, *args):
|
| 22 |
+
messages = {
|
| 23 |
+
"peft_loaded": ("✅ PEFT 库已加载,LoRA 功能可用。", "✅ PEFT library loaded, LoRA functionality available."),
|
| 24 |
+
"peft_not_detected": ("⚠️ 警告: 未检测到 PEFT 库。LoRA 功能将禁用。", "⚠️ Warning: PEFT library not detected. LoRA functionality will be disabled."),
|
| 25 |
+
"lora_skipped": ("⚠️ [LoRA] 已跳过加载:PEFT 库未安装。", "⚠️ [LoRA] Skipped loading: PEFT library not installed."),
|
| 26 |
+
"transformer_not_loaded": ("⚠️ Transformer 未加载,无法应用 LoRA", "⚠️ Transformer not loaded, cannot apply LoRA"),
|
| 27 |
+
"lora_file_not_exist": ("⚠️ LoRA 文件不存在: {}", "⚠️ LoRA file does not exist: {}"),
|
| 28 |
+
"lora_loading": (" [LoRA] 正在加载: {} (权重: {} * {} = {:.2f})", " [LoRA] Loading: {} (weight: {} * {} = {:.2f})"),
|
| 29 |
+
"lora_loaded": ("✅ LoRA 加载成功: {}", "✅ LoRA loaded successfully: {}"),
|
| 30 |
+
"lora_failed": ("❌ LoRA 加载严重失败: {}", "❌ LoRA loading failed critically: {}"),
|
| 31 |
+
"applying_vae": ("正在应用自定义 VAE: {}", "Applying custom VAE: {}"),
|
| 32 |
+
"vae_loaded": ("✅ 自定义 VAE 加载成功", "✅ Custom VAE loaded successfully"),
|
| 33 |
+
"vae_failed": ("⚠️ 自定义 VAE 加载失败: {}", "⚠️ Custom VAE loading failed: {}"),
|
| 34 |
+
"forcing_to_ram": (" [System] 正在强制将模型搬运至 RAM (请稍候)...", " [System] Forcing model to RAM (please wait)..."),
|
| 35 |
+
"model_to_ram": (" [System] 模型已加载至 RAM。", " [System] Model loaded to RAM."),
|
| 36 |
+
"t2i_low_vram": (" [T2I] 已启用低显存优化模式", " [T2I] Low VRAM optimization mode enabled"),
|
| 37 |
+
"t2i_high_end": (" [T2I] 已启用高端机模式", " [T2I] High-end GPU mode enabled"),
|
| 38 |
+
"t2i_pipeline_loaded": ("✅ 文生图 Pipeline 加载完成", "✅ Text-to-Image Pipeline loaded"),
|
| 39 |
+
"i2i_pipeline_failed": ("加载图生图 Pipeline 失败:{}", "Failed to load Image-to-Image Pipeline: {}"),
|
| 40 |
+
"i2i_pipeline_loaded": ("✅ 图生图 Pipeline 加载完成", "✅ Image-to-Image Pipeline loaded"),
|
| 41 |
+
"i2i_low_vram": (" [I2I] 已启用低显存优化模式", " [I2I] Low VRAM optimization mode enabled"),
|
| 42 |
+
"i2i_high_end": (" [I2I] 已启用高端机模式", " [I2I] High-end GPU mode enabled"),
|
| 43 |
+
"generation_stopped": ("🛑 生成已被用户手动停止", "🛑 Generation stopped by user"),
|
| 44 |
+
"upload_image_first": ("⚠️ 请先上传图片!", "⚠️ Please upload an image first!"),
|
| 45 |
+
"i2i_model_failed": ("加载图生图模型失败: {}", "Failed to load Image-to-Image model: {}"),
|
| 46 |
+
"native_inpaint_failed": ("⚠️ 原生 Inpaint 失败 ({}),使用手动混合模式...", "⚠️ Native Inpaint failed ({}), using manual blending mode..."),
|
| 47 |
+
"paint_area": ("⚠️ 请使用画笔在图片上涂抹要修改的区域。", "⚠️ Please use the brush to paint the area to modify on the image."),
|
| 48 |
+
"mask_invalid": ("⚠️ Mask 无效,请确保涂抹了区域。", "⚠️ Mask invalid, please ensure an area is painted."),
|
| 49 |
+
"model_load_failed": ("模型加载失败: {}", "Model loading failed: {}"),
|
| 50 |
+
"inpainting_failed": ("局部重绘失败: {}", "Inpainting failed: {}"),
|
| 51 |
+
"generating": ("生成中", "Generating"),
|
| 52 |
+
"img2img_processing": ("图生图中", "Img2Img processing"),
|
| 53 |
+
}
|
| 54 |
+
zh, en = messages[key]
|
| 55 |
+
return (zh if is_chinese else en).format(*args)
|
| 56 |
+
|
| 57 |
+
# 环境配置
|
| 58 |
+
os.environ.pop("PYTHONHOME", None)
|
| 59 |
+
os.environ.pop("PYTHONPATH", None)
|
| 60 |
+
os.environ["DIFFUSERS_USE_PEFT_BACKEND"] = "true"
|
| 61 |
+
os.environ["PEFT_DEBUG"] = "false"
|
| 62 |
+
|
| 63 |
+
import torch
|
| 64 |
+
import numpy as np
|
| 65 |
+
from PIL import Image, ImageFilter, ImageOps, ImageEnhance, ImageDraw
|
| 66 |
+
|
| 67 |
+
import gradio as gr
|
| 68 |
+
from diffusers import (
|
| 69 |
+
ZImagePipeline,
|
| 70 |
+
ZImageImg2ImgPipeline,
|
| 71 |
+
AutoencoderKL,
|
| 72 |
+
ZImageTransformer2DModel,
|
| 73 |
+
FlowMatchEulerDiscreteScheduler
|
| 74 |
+
)
|
| 75 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 76 |
+
from safetensors.torch import load_file
|
| 77 |
+
|
| 78 |
+
# =========================
|
| 79 |
+
# 检测 PEFT 环境
|
| 80 |
+
# =========================
|
| 81 |
+
PEFT_AVAILABLE = False
|
| 82 |
+
try:
|
| 83 |
+
import peft
|
| 84 |
+
from diffusers.utils import is_peft_available
|
| 85 |
+
if is_peft_available():
|
| 86 |
+
PEFT_AVAILABLE = True
|
| 87 |
+
print(get_message("peft_loaded"))
|
| 88 |
+
else:
|
| 89 |
+
raise ImportError
|
| 90 |
+
except ImportError:
|
| 91 |
+
print(get_message("peft_not_detected"))
|
| 92 |
+
|
| 93 |
+
# =========================
|
| 94 |
+
# 双语文本字典
|
| 95 |
+
# =========================
|
| 96 |
+
TEXT = {
|
| 97 |
+
"zh": {
|
| 98 |
+
"title": "# 🎨 Z-Image-Turbo Low Vram Edition",
|
| 99 |
+
"lang_btn": "EN",
|
| 100 |
+
"tab_generate": "图像生成", "tab_edit": "图片编辑", "tab_img2img": "图生图 (增强版)", "tab_inpaint": "局部重绘",
|
| 101 |
+
"prompt": "Prompt", "prompt_placeholder": "输入你的描述...", "negative_prompt": "负面提示词", "negative_placeholder": "low quality, blurry, bad anatomy",
|
| 102 |
+
"refresh_lora": "🔄 刷新 LoRA", "refresh_model": "🔄 刷新模型", "lora_label": "LoRA", "lora_strength": "LoRA 强度", "lora_weight": "权重",
|
| 103 |
+
"model_section": "### 模型选择/Model Selection", "transformer": "Transformer", "vae": "VAE", "vram_type": "显存类型",
|
| 104 |
+
"vram_low": "24GB以下 (优化模式)", "vram_high": "高端机模式 (>=24GB)", "device": "设备", "num_images": "生成张数",
|
| 105 |
+
"output_format": "输出格式", "width": "宽度", "height": "高度", "steps": "步数", "cfg": "CFG", "seed": "种子", "random_seed": "随机种子",
|
| 106 |
+
"generate": "🚀 生成", "stop": "🛑 停止生成", "gallery": "生成结果", "used_seed": "使用种子",
|
| 107 |
+
"edit_upload": "上传图片", "rotate": "旋转角度 (度)", "crop_x": "裁剪 X (%)", "crop_y": "裁剪 Y (%)", "crop_w": "裁剪宽度 (%)", "crop_h": "裁剪高度 (%)",
|
| 108 |
+
"hflip": "水平翻转", "vflip": "垂直翻转", "edit_btn": "开始编辑", "edited_image": "编辑后的图片",
|
| 109 |
+
"filter": "应用滤镜", "brightness": "亮度调整 (%)", "contrast": "对比度调整 (%)", "saturation": "饱和度调整 (%)",
|
| 110 |
+
"i2i_ref": "上传参考图", "i2i_prompt": "修改提示词", "i2i_ph": "描述你希望图中发生的变化...", "i2i_mode": "Img2Img 模式",
|
| 111 |
+
"i2i_mode_a": "A. 严格保结构(微调风格)", "i2i_mode_b": "B. 强烈听 prompt(允许大改)", "i2i_out_w": "输出宽 (0=自动)", "i2i_out_h": "输出高 (0=自动)",
|
| 112 |
+
"i2i_tip": "**提示:** 宽高都为0时自动保持上传图比例并接近1024。", "i2i_strength": "重绘强度", "i2i_btn": "🎨 开始修改", "i2i_note": "注:使用官方 Z-Image Img2Img 引擎。",
|
| 113 |
+
"inpaint_editor": "绘制 Mask (白色为修改区,黑色为保留区)", "inpaint_tip": "提示:先上传图片,然后用画笔涂抹要修改的区域。", "inpaint_upload": "上传原图并绘制", "inpaint_desc": "📖 使用指南:涂抹区域(白色/彩色)将被重新生成,未涂抹区域保持原样。",
|
| 114 |
+
},
|
| 115 |
+
"en": {
|
| 116 |
+
"title": "# 🎨 Z-Image-Turbo Low Vram Edition", "lang_btn": "中文",
|
| 117 |
+
"tab_generate": "Image Generation", "tab_edit": "Image Editing", "tab_img2img": "Img2Img (Enhanced)", "tab_inpaint": "Inpainting",
|
| 118 |
+
"prompt": "Prompt", "prompt_placeholder": "Enter your description...", "negative_prompt": "Negative Prompt", "negative_placeholder": "low quality, blurry",
|
| 119 |
+
"refresh_lora": "🔄 Refresh LoRA", "refresh_model": "🔄 Refresh Models", "lora_label": "LoRA", "lora_strength": "LoRA Strength", "lora_weight": "Weight",
|
| 120 |
+
"model_section": "### Model Selection", "transformer": "Transformer", "vae": "VAE", "vram_type": "VRAM Type",
|
| 121 |
+
"vram_low": "Under 24GB (Optimized)", "vram_high": "High-End GPU Mode (>=24GB)", "device": "Device", "num_images": "Number of Images",
|
| 122 |
+
"output_format": "Output Format", "width": "Width", "height": "Height", "steps": "Steps", "cfg": "CFG", "seed": "Seed", "random_seed": "Random Seed",
|
| 123 |
+
"generate": "🚀 Generate", "stop": "🛑 Stop Generation", "gallery": "Generated Images", "used_seed": "Used Seed",
|
| 124 |
+
"edit_upload": "Upload Image", "rotate": "Rotation (degrees)", "crop_x": "Crop X (%)", "crop_y": "Crop Y (%)", "crop_w": "Crop Width (%)", "crop_h": "Crop Height (%)",
|
| 125 |
+
"hflip": "Horizontal Flip", "vflip": "Vertical Flip", "edit_btn": "Apply Edit", "edited_image": "Edited Image",
|
| 126 |
+
"filter": "Apply Filter", "brightness": "Brightness (%)", "contrast": "Contrast (%)", "saturation": "Saturation (%)",
|
| 127 |
+
"i2i_ref": "Upload Reference", "i2i_prompt": "Modification Prompt", "i2i_ph": "Describe changes...", "i2i_mode": "Img2Img Mode",
|
| 128 |
+
"i2i_mode_a": "A. Strict Structure (Style tweak)", "i2i_mode_b": "B. Strong Prompt (Allow changes)", "i2i_out_w": "Output Width (0=Auto)", "i2i_out_h": "Output Height (0=Auto)",
|
| 129 |
+
"i2i_tip": "**Tip:** Auto ratio if both 0.", "i2i_strength": "Denoising Strength", "i2i_btn": "🎨 Start Modification", "i2i_note": "Using official Z-Image Img2Img engine.",
|
| 130 |
+
"inpaint_editor": "Draw Mask (White=Modify, Black=Keep)", "inpaint_tip": "Tip: Upload image, then paint area to modify.", "inpaint_upload": "Upload & Paint", "inpaint_desc": "📖 Guide: Painted areas (white/color) will be regenerated. Unpainted areas stay original.",
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
# =========================
|
| 135 |
+
# 路径配置
|
| 136 |
+
# =========================
|
| 137 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 138 |
+
BASE_SNAPSHOT_DIR = os.path.join(BASE_DIR, "cache", "HF_HOME", "hub", "models--Tongyi-MAI--Z-Image-Turbo", "snapshots", "5f4b9cbb80cc95ba44fe6667dfd75710f7db2947")
|
| 139 |
+
if not os.path.exists(BASE_SNAPSHOT_DIR):
|
| 140 |
+
BASE_SNAPSHOT_DIR = os.path.join(BASE_DIR, "ckpts", "Z-Image-Turbo")
|
| 141 |
+
if not os.path.exists(BASE_SNAPSHOT_DIR):
|
| 142 |
+
BASE_SNAPSHOT_DIR = "."
|
| 143 |
+
|
| 144 |
+
TRANSFORMER_ROOT = os.path.join(BASE_SNAPSHOT_DIR, "transformer")
|
| 145 |
+
TEXT_ENCODER_ROOT = os.path.join(BASE_SNAPSHOT_DIR, "text_encoder")
|
| 146 |
+
VAE_ROOT = os.path.join(BASE_SNAPSHOT_DIR, "vae")
|
| 147 |
+
|
| 148 |
+
MOD_DIR = os.path.join(BASE_DIR, "MOD")
|
| 149 |
+
MOD_TRANSFORMER = os.path.join(MOD_DIR, "transformer")
|
| 150 |
+
MOD_VAE = os.path.join(MOD_DIR, "vae")
|
| 151 |
+
LORA_ROOT = os.path.join(BASE_DIR, "lora")
|
| 152 |
+
OUTPUT_DIR = os.path.join(BASE_DIR, "outputs")
|
| 153 |
+
|
| 154 |
+
for p in [MOD_TRANSFORMER, MOD_VAE, LORA_ROOT, OUTPUT_DIR]:
|
| 155 |
+
os.makedirs(p, exist_ok=True)
|
| 156 |
+
|
| 157 |
+
pipe_t2i = None
|
| 158 |
+
pipe_i2i = None
|
| 159 |
+
current_model_config = {"transformer": "default", "vae": "default", "is_low_vram": True}
|
| 160 |
+
is_generating_interrupted = False
|
| 161 |
+
|
| 162 |
+
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 163 |
+
DTYPE = torch.bfloat16 if DEVICE == "cuda" else torch.float32
|
| 164 |
+
|
| 165 |
+
def auto_flush_vram():
|
| 166 |
+
gc.collect()
|
| 167 |
+
if DEVICE == "cuda":
|
| 168 |
+
torch.cuda.empty_cache()
|
| 169 |
+
|
| 170 |
+
# =========================
|
| 171 |
+
# 核心优化:LoRA 加载逻辑
|
| 172 |
+
# =========================
|
| 173 |
+
def apply_lora_to_pipeline(pipe_local, lora_choice, lora_alpha, lora_scale=1.0):
|
| 174 |
+
if not PEFT_AVAILABLE:
|
| 175 |
+
print(get_message("lora_skipped"))
|
| 176 |
+
return pipe_local
|
| 177 |
+
|
| 178 |
+
if pipe_local is None:
|
| 179 |
+
return pipe_local
|
| 180 |
+
if pipe_local.transformer is None:
|
| 181 |
+
print(get_message("transformer_not_loaded"))
|
| 182 |
+
return pipe_local
|
| 183 |
+
|
| 184 |
+
if hasattr(pipe_local, "unload_lora_weights"):
|
| 185 |
+
try:
|
| 186 |
+
pipe_local.unload_lora_weights()
|
| 187 |
+
except Exception:
|
| 188 |
+
pass
|
| 189 |
+
|
| 190 |
+
if not lora_choice or lora_choice.lower() == "none":
|
| 191 |
+
return pipe_local
|
| 192 |
+
|
| 193 |
+
lora_path = os.path.join(LORA_ROOT, lora_choice)
|
| 194 |
+
if not os.path.exists(lora_path):
|
| 195 |
+
print(get_message("lora_file_not_exist", lora_path))
|
| 196 |
+
return pipe_local
|
| 197 |
+
|
| 198 |
+
try:
|
| 199 |
+
raw_alpha = float(lora_alpha)
|
| 200 |
+
effective_alpha = raw_alpha * lora_scale
|
| 201 |
+
|
| 202 |
+
if effective_alpha <= 0:
|
| 203 |
+
return pipe_local
|
| 204 |
+
|
| 205 |
+
adapter_name = re.sub(r"[^a-zA-Z0-9_]", "_", os.path.splitext(lora_choice)[0])
|
| 206 |
+
|
| 207 |
+
print(get_message("lora_loading", lora_choice, raw_alpha, lora_scale, effective_alpha))
|
| 208 |
+
pipe_local.load_lora_weights(
|
| 209 |
+
LORA_ROOT,
|
| 210 |
+
weight_name=lora_choice,
|
| 211 |
+
adapter_name=adapter_name
|
| 212 |
+
)
|
| 213 |
+
pipe_local.set_adapters([adapter_name], adapter_weights=[effective_alpha])
|
| 214 |
+
print(get_message("lora_loaded", adapter_name))
|
| 215 |
+
|
| 216 |
+
except Exception as e:
|
| 217 |
+
import traceback
|
| 218 |
+
print(get_message("lora_failed", e))
|
| 219 |
+
|
| 220 |
+
return pipe_local
|
| 221 |
+
|
| 222 |
+
def scan_lora_items():
|
| 223 |
+
if not os.path.isdir(LORA_ROOT):
|
| 224 |
+
return []
|
| 225 |
+
return sorted([f for f in os.listdir(LORA_ROOT) if f.lower().endswith((".safetensors", ".pt", ".pth"))])
|
| 226 |
+
|
| 227 |
+
def update_prompt_with_lora(prompt, lora_choice, lora_alpha):
|
| 228 |
+
prompt = (prompt or "").strip()
|
| 229 |
+
prompt_clean = re.sub(r"<lora:[^>]+>", "", prompt).strip()
|
| 230 |
+
if lora_choice and lora_choice.lower() != "none":
|
| 231 |
+
try:
|
| 232 |
+
alpha = float(lora_alpha)
|
| 233 |
+
except: alpha = 1.0
|
| 234 |
+
if alpha > 0:
|
| 235 |
+
name = os.path.splitext(lora_choice)[0]
|
| 236 |
+
alpha_str = f"{alpha:.2f}".rstrip("0").rstrip(".")
|
| 237 |
+
return f"{prompt_clean} <lora:{name}:{alpha_str}>"
|
| 238 |
+
return prompt_clean
|
| 239 |
+
|
| 240 |
+
# =========================
|
| 241 |
+
# 模型加载逻辑
|
| 242 |
+
# =========================
|
| 243 |
+
def load_t2i_pipeline(transformer_choice, vae_choice, is_low_vram):
|
| 244 |
+
global pipe_t2i, current_model_config
|
| 245 |
+
config_key = ("t2i", transformer_choice, vae_choice, is_low_vram)
|
| 246 |
+
if pipe_t2i is not None and current_model_config.get("t2i") == config_key:
|
| 247 |
+
return pipe_t2i
|
| 248 |
+
|
| 249 |
+
auto_flush_vram()
|
| 250 |
+
pipe_t2i = None
|
| 251 |
+
|
| 252 |
+
transformer = ZImageTransformer2DModel.from_pretrained(TRANSFORMER_ROOT, torch_dtype=DTYPE, local_files_only=True)
|
| 253 |
+
if transformer_choice != "default":
|
| 254 |
+
t_path = resolve_model_path(transformer_choice, MOD_TRANSFORMER)
|
| 255 |
+
if t_path:
|
| 256 |
+
if os.path.isdir(t_path):
|
| 257 |
+
custom_t = ZImageTransformer2DModel.from_pretrained(t_path, torch_dtype=DTYPE, local_files_only=True)
|
| 258 |
+
transformer = custom_t
|
| 259 |
+
else:
|
| 260 |
+
state = load_file(t_path, device="cpu")
|
| 261 |
+
processed = {}
|
| 262 |
+
prefix = "model.diffusion_model."
|
| 263 |
+
for k, v in state.items():
|
| 264 |
+
new_k = k[len(prefix):] if k.startswith(prefix) else k
|
| 265 |
+
processed[new_k] = v.to(DTYPE)
|
| 266 |
+
transformer.load_state_dict(processed, strict=False)
|
| 267 |
+
del state, processed
|
| 268 |
+
|
| 269 |
+
text_encoder = AutoModelForCausalLM.from_pretrained(TEXT_ENCODER_ROOT, torch_dtype=DTYPE, local_files_only=True)
|
| 270 |
+
|
| 271 |
+
pipe_t2i = ZImagePipeline.from_pretrained(
|
| 272 |
+
BASE_SNAPSHOT_DIR,
|
| 273 |
+
local_files_only=True,
|
| 274 |
+
transformer=transformer,
|
| 275 |
+
text_encoder=text_encoder,
|
| 276 |
+
)
|
| 277 |
+
pipe_t2i.to(dtype=DTYPE)
|
| 278 |
+
|
| 279 |
+
if vae_choice != "default":
|
| 280 |
+
v_path = resolve_model_path(vae_choice, MOD_VAE)
|
| 281 |
+
if v_path:
|
| 282 |
+
print(get_message("applying_vae", vae_choice))
|
| 283 |
+
vae_device_map = {"": "cpu"} if is_low_vram else None
|
| 284 |
+
try:
|
| 285 |
+
if os.path.isfile(v_path):
|
| 286 |
+
with tempfile.TemporaryDirectory() as tmpdir:
|
| 287 |
+
config_file_path = os.path.join(tmpdir, "config.json")
|
| 288 |
+
vae_config_dict = dict(pipe_t2i.vae.config)
|
| 289 |
+
with open(config_file_path, "w", encoding="utf-8") as f:
|
| 290 |
+
json.dump(vae_config_dict, f, indent=2)
|
| 291 |
+
try:
|
| 292 |
+
pipe_t2i.vae = AutoencoderKL.from_single_file(v_path, dtype=DTYPE, config=tmpdir, device_map=vae_device_map)
|
| 293 |
+
except TypeError:
|
| 294 |
+
pipe_t2i.vae = AutoencoderKL.from_single_file(v_path, torch_dtype=DTYPE, config=tmpdir, device_map=vae_device_map)
|
| 295 |
+
print(get_message("vae_loaded"))
|
| 296 |
+
else:
|
| 297 |
+
pipe_t2i.vae = AutoencoderKL.from_pretrained(v_path, torch_dtype=DTYPE, device_map=vae_device_map)
|
| 298 |
+
except Exception as e:
|
| 299 |
+
print(get_message("vae_failed", e))
|
| 300 |
+
|
| 301 |
+
if DEVICE == "cuda":
|
| 302 |
+
if is_low_vram:
|
| 303 |
+
print(get_message("forcing_to_ram"))
|
| 304 |
+
pipe_t2i.to("cpu")
|
| 305 |
+
print(get_message("model_to_ram"))
|
| 306 |
+
pipe_t2i.enable_sequential_cpu_offload()
|
| 307 |
+
print(get_message("t2i_low_vram"))
|
| 308 |
+
else:
|
| 309 |
+
pipe_t2i.to("cuda")
|
| 310 |
+
print(get_message("t2i_high_end"))
|
| 311 |
+
|
| 312 |
+
current_model_config["t2i"] = config_key
|
| 313 |
+
print("✅ 文生图 Pipeline 加载完成")
|
| 314 |
+
return pipe_t2i
|
| 315 |
+
|
| 316 |
+
def load_i2i_pipeline(transformer_choice, vae_choice, is_low_vram):
|
| 317 |
+
global pipe_i2i, current_model_config
|
| 318 |
+
config_key = ("i2i", transformer_choice, vae_choice, is_low_vram)
|
| 319 |
+
if pipe_i2i is not None and current_model_config.get("i2i") == config_key:
|
| 320 |
+
return pipe_i2i
|
| 321 |
+
|
| 322 |
+
auto_flush_vram()
|
| 323 |
+
pipe_i2i = None
|
| 324 |
+
|
| 325 |
+
transformer = ZImageTransformer2DModel.from_pretrained(TRANSFORMER_ROOT, torch_dtype=DTYPE, local_files_only=True)
|
| 326 |
+
if transformer_choice != "default":
|
| 327 |
+
t_path = resolve_model_path(transformer_choice, MOD_TRANSFORMER)
|
| 328 |
+
if t_path:
|
| 329 |
+
if os.path.isdir(t_path):
|
| 330 |
+
custom_t = ZImageTransformer2DModel.from_pretrained(t_path, torch_dtype=DTYPE, local_files_only=True)
|
| 331 |
+
transformer = custom_t
|
| 332 |
+
else:
|
| 333 |
+
state = load_file(t_path, device="cpu")
|
| 334 |
+
processed = {}
|
| 335 |
+
prefix = "model.diffusion_model."
|
| 336 |
+
for k, v in state.items():
|
| 337 |
+
new_k = k[len(prefix):] if k.startswith(prefix) else k
|
| 338 |
+
processed[new_k] = v.to(DTYPE)
|
| 339 |
+
transformer.load_state_dict(processed, strict=False)
|
| 340 |
+
del state, processed
|
| 341 |
+
|
| 342 |
+
try:
|
| 343 |
+
pipe_i2i = ZImageImg2ImgPipeline.from_pretrained(
|
| 344 |
+
BASE_SNAPSHOT_DIR,
|
| 345 |
+
local_files_only=True,
|
| 346 |
+
transformer=transformer,
|
| 347 |
+
)
|
| 348 |
+
except Exception as e:
|
| 349 |
+
raise gr.Error(f"加载图生图 Pipeline 失败:{str(e)}")
|
| 350 |
+
|
| 351 |
+
pipe_i2i.to(dtype=DTYPE)
|
| 352 |
+
|
| 353 |
+
if vae_choice != "default":
|
| 354 |
+
v_path = resolve_model_path(vae_choice, MOD_VAE)
|
| 355 |
+
if v_path:
|
| 356 |
+
print(get_message("applying_vae", vae_choice))
|
| 357 |
+
vae_device_map = {"": "cpu"} if is_low_vram else None
|
| 358 |
+
try:
|
| 359 |
+
if os.path.isfile(v_path):
|
| 360 |
+
with tempfile.TemporaryDirectory() as tmpdir:
|
| 361 |
+
config_file_path = os.path.join(tmpdir, "config.json")
|
| 362 |
+
vae_config_dict = dict(pipe_i2i.vae.config)
|
| 363 |
+
with open(config_file_path, "w", encoding="utf-8") as f:
|
| 364 |
+
json.dump(vae_config_dict, f, indent=2)
|
| 365 |
+
try:
|
| 366 |
+
pipe_i2i.vae = AutoencoderKL.from_single_file(v_path, dtype=DTYPE, config=tmpdir, device_map=vae_device_map)
|
| 367 |
+
except TypeError:
|
| 368 |
+
pipe_i2i.vae = AutoencoderKL.from_single_file(v_path, torch_dtype=DTYPE, config=tmpdir, device_map=vae_device_map)
|
| 369 |
+
print(get_message("vae_loaded"))
|
| 370 |
+
else:
|
| 371 |
+
pipe_i2i.vae = AutoencoderKL.from_pretrained(v_path, torch_dtype=DTYPE, device_map=vae_device_map)
|
| 372 |
+
except Exception as e:
|
| 373 |
+
print(get_message("vae_failed", e))
|
| 374 |
+
|
| 375 |
+
if DEVICE == "cuda":
|
| 376 |
+
if is_low_vram:
|
| 377 |
+
print(get_message("forcing_to_ram"))
|
| 378 |
+
pipe_i2i.to("cpu")
|
| 379 |
+
print(get_message("model_to_ram"))
|
| 380 |
+
pipe_i2i.enable_sequential_cpu_offload()
|
| 381 |
+
print(get_message("i2i_low_vram"))
|
| 382 |
+
else:
|
| 383 |
+
pipe_i2i.to("cuda")
|
| 384 |
+
print(get_message("i2i_high_end"))
|
| 385 |
+
|
| 386 |
+
current_model_config["i2i"] = config_key
|
| 387 |
+
print("✅ 图生图 Pipeline 加载完成")
|
| 388 |
+
return pipe_i2i
|
| 389 |
+
|
| 390 |
+
def interrupt_callback(pipe, step, timestep, callback_kwargs):
|
| 391 |
+
global is_generating_interrupted
|
| 392 |
+
if is_generating_interrupted:
|
| 393 |
+
raise gr.Error("🛑 生成已被用户手动停止")
|
| 394 |
+
return callback_kwargs
|
| 395 |
+
|
| 396 |
+
def scan_model_variants(root_dir):
|
| 397 |
+
if not os.path.isdir(root_dir):
|
| 398 |
+
return []
|
| 399 |
+
items = []
|
| 400 |
+
for name in os.listdir(root_dir):
|
| 401 |
+
path = os.path.join(root_dir, name)
|
| 402 |
+
if os.path.isdir(path):
|
| 403 |
+
if os.path.isfile(os.path.join(path, "config.json")):
|
| 404 |
+
items.append(name)
|
| 405 |
+
elif name.lower().endswith((".safetensors", ".bin")):
|
| 406 |
+
items.append(name)
|
| 407 |
+
return sorted(items)
|
| 408 |
+
|
| 409 |
+
def get_choices(mod_root):
|
| 410 |
+
return ["default"] + scan_model_variants(mod_root)
|
| 411 |
+
|
| 412 |
+
def resolve_model_path(choice, mod_root):
|
| 413 |
+
if choice == "default":
|
| 414 |
+
return None
|
| 415 |
+
path = os.path.join(mod_root, choice)
|
| 416 |
+
if os.path.exists(path):
|
| 417 |
+
return path
|
| 418 |
+
return None
|
| 419 |
+
|
| 420 |
+
def process_mask_for_inpaint(mask_image):
|
| 421 |
+
if mask_image is None:
|
| 422 |
+
return None
|
| 423 |
+
if mask_image.mode == 'RGBA':
|
| 424 |
+
import numpy as np
|
| 425 |
+
mask_array = np.array(mask_image)
|
| 426 |
+
alpha = mask_array[:, :, 3] if mask_array.shape[2] > 3 else None
|
| 427 |
+
rgb = mask_array[:, :, :3]
|
| 428 |
+
rgb_gray = np.dot(rgb, [0.299, 0.587, 0.114])
|
| 429 |
+
if alpha is not None:
|
| 430 |
+
mask_gray = np.where(alpha > 10, 255, 0).astype(np.uint8)
|
| 431 |
+
else:
|
| 432 |
+
mask_gray = np.where(rgb_gray > 10, 255, 0).astype(np.uint8)
|
| 433 |
+
mask = Image.fromarray(mask_gray, mode='L')
|
| 434 |
+
else:
|
| 435 |
+
if mask_image.mode != 'L':
|
| 436 |
+
mask_image = mask_image.convert('L')
|
| 437 |
+
mask = mask_image.point(lambda p: 255 if p > 10 else 0)
|
| 438 |
+
|
| 439 |
+
if mask.getextrema()[1] == 0:
|
| 440 |
+
return None
|
| 441 |
+
return mask
|
| 442 |
+
|
| 443 |
+
# =========================
|
| 444 |
+
# 生成与编辑函数
|
| 445 |
+
# =========================
|
| 446 |
+
|
| 447 |
+
def generate_image(prompt, lora_choice, lora_alpha, num_images, image_format,
|
| 448 |
+
width, height, num_inference_steps, guidance_scale, seed, randomize_seed,
|
| 449 |
+
transformer_choice, vae_choice, vram_type_str, progress=gr.Progress()):
|
| 450 |
+
global is_generating_interrupted
|
| 451 |
+
is_generating_interrupted = False
|
| 452 |
+
|
| 453 |
+
is_low_vram = "24GB" in vram_type_str or "Under 24GB" in vram_type_str or "24G以下" in vram_type_str or "24GB以下" in vram_type_str
|
| 454 |
+
|
| 455 |
+
pipe_local = load_t2i_pipeline(transformer_choice, vae_choice, is_low_vram)
|
| 456 |
+
pipe_local = apply_lora_to_pipeline(pipe_local, lora_choice, lora_alpha)
|
| 457 |
+
|
| 458 |
+
if randomize_seed:
|
| 459 |
+
seed = random.randint(0, 2**32 - 1)
|
| 460 |
+
generator = torch.Generator(DEVICE).manual_seed(int(seed))
|
| 461 |
+
|
| 462 |
+
date_str = datetime.datetime.now().strftime("%Y-%m-%d")
|
| 463 |
+
day_dir = os.path.join(OUTPUT_DIR, date_str)
|
| 464 |
+
os.makedirs(day_dir, exist_ok=True)
|
| 465 |
+
|
| 466 |
+
fmt_map = {"png": ("PNG", "png"), "jpeg": ("JPEG", "jpeg"), "webp": ("WEBP", "webp")}
|
| 467 |
+
pil_fmt, ext = fmt_map[image_format.lower()]
|
| 468 |
+
|
| 469 |
+
results = []
|
| 470 |
+
try:
|
| 471 |
+
for _ in progress.tqdm(range(int(num_images)), desc="生成中"):
|
| 472 |
+
if is_generating_interrupted:
|
| 473 |
+
break
|
| 474 |
+
img = pipe_local(
|
| 475 |
+
prompt=prompt.strip(),
|
| 476 |
+
width=width,
|
| 477 |
+
height=height,
|
| 478 |
+
num_inference_steps=num_inference_steps,
|
| 479 |
+
guidance_scale=guidance_scale,
|
| 480 |
+
generator=generator,
|
| 481 |
+
callback_on_step_end=interrupt_callback,
|
| 482 |
+
).images[0]
|
| 483 |
+
filename = os.path.join(day_dir, f"{datetime.datetime.now():%H%M%S}_{uuid.uuid4().hex[:4]}.{ext}")
|
| 484 |
+
img.save(filename, format=pil_fmt)
|
| 485 |
+
results.append(filename)
|
| 486 |
+
finally:
|
| 487 |
+
auto_flush_vram()
|
| 488 |
+
|
| 489 |
+
return results, seed
|
| 490 |
+
|
| 491 |
+
def run_img2img_enhanced(input_image, prompt, negative_prompt, lora_choice, lora_alpha,
|
| 492 |
+
num_images, image_format,
|
| 493 |
+
out_w, out_h, i2i_mode, strength_ui, steps_ui, cfg_ui,
|
| 494 |
+
seed, randomize_seed,
|
| 495 |
+
transformer_choice, vae_choice, vram_type_str, progress=gr.Progress()):
|
| 496 |
+
global is_generating_interrupted
|
| 497 |
+
is_generating_interrupted = False
|
| 498 |
+
|
| 499 |
+
is_low_vram = "24GB" in vram_type_str or "Under 24GB" in vram_type_str or "24G以下" in vram_type_str or "24GB以下" in vram_type_str
|
| 500 |
+
|
| 501 |
+
if input_image is None:
|
| 502 |
+
raise gr.Error("⚠️ 请先上传图片!")
|
| 503 |
+
|
| 504 |
+
try:
|
| 505 |
+
pipe_local = load_i2i_pipeline(transformer_choice, vae_choice, is_low_vram)
|
| 506 |
+
except Exception as e:
|
| 507 |
+
if isinstance(e, gr.Error): raise e
|
| 508 |
+
raise gr.Error(f"加载图生图模型失败: {str(e)}")
|
| 509 |
+
|
| 510 |
+
if i2i_mode.startswith("A"):
|
| 511 |
+
lora_scale = 0.35
|
| 512 |
+
strength = 0.30
|
| 513 |
+
steps = 8
|
| 514 |
+
cfg = 1.0
|
| 515 |
+
else:
|
| 516 |
+
lora_scale = 0.65
|
| 517 |
+
strength = 0.45
|
| 518 |
+
steps = 6
|
| 519 |
+
cfg = 1.5
|
| 520 |
+
|
| 521 |
+
pipe_local = apply_lora_to_pipeline(pipe_local, lora_choice, lora_alpha, lora_scale)
|
| 522 |
+
|
| 523 |
+
final_strength = strength_ui
|
| 524 |
+
final_steps = int(steps_ui)
|
| 525 |
+
final_cfg = cfg_ui
|
| 526 |
+
|
| 527 |
+
if randomize_seed:
|
| 528 |
+
seed = random.randint(0, 2**32 - 1)
|
| 529 |
+
generator = torch.Generator(DEVICE).manual_seed(int(seed))
|
| 530 |
+
|
| 531 |
+
orig_w, orig_h = input_image.size
|
| 532 |
+
if out_w == 0 or out_h == 0:
|
| 533 |
+
target_size = 1024
|
| 534 |
+
ratio = orig_w / orig_h
|
| 535 |
+
if ratio > 1:
|
| 536 |
+
w, h = target_size, int(target_size / ratio)
|
| 537 |
+
else:
|
| 538 |
+
w, h = int(target_size * ratio), target_size
|
| 539 |
+
else:
|
| 540 |
+
w, h = out_w, out_h
|
| 541 |
+
|
| 542 |
+
w = (w // 16) * 16
|
| 543 |
+
h = (h // 16) * 16
|
| 544 |
+
input_image = input_image.resize((w, h), Image.LANCZOS)
|
| 545 |
+
|
| 546 |
+
date_str = datetime.datetime.now().strftime("%Y-%m-%d")
|
| 547 |
+
day_dir = os.path.join(OUTPUT_DIR, date_str)
|
| 548 |
+
os.makedirs(day_dir, exist_ok=True)
|
| 549 |
+
|
| 550 |
+
fmt_map = {"png": ("PNG", "png"), "jpeg": ("JPEG", "jpeg"), "webp": ("WEBP", "webp")}
|
| 551 |
+
pil_fmt, ext = fmt_map[image_format.lower()]
|
| 552 |
+
|
| 553 |
+
results = []
|
| 554 |
+
|
| 555 |
+
try:
|
| 556 |
+
for _ in progress.tqdm(range(int(num_images)), desc="图生图中"):
|
| 557 |
+
if is_generating_interrupted:
|
| 558 |
+
break
|
| 559 |
+
|
| 560 |
+
img = pipe_local(
|
| 561 |
+
prompt=prompt.strip(),
|
| 562 |
+
negative_prompt=negative_prompt.strip(),
|
| 563 |
+
image=input_image,
|
| 564 |
+
strength=final_strength,
|
| 565 |
+
num_inference_steps=final_steps,
|
| 566 |
+
guidance_scale=final_cfg,
|
| 567 |
+
generator=generator,
|
| 568 |
+
callback_on_step_end=interrupt_callback,
|
| 569 |
+
).images[0]
|
| 570 |
+
|
| 571 |
+
filename = os.path.join(day_dir, f"i2i_{datetime.datetime.now():%H%M%S}_{uuid.uuid4().hex[:4]}.{ext}")
|
| 572 |
+
img.save(filename, format=pil_fmt)
|
| 573 |
+
results.append(filename)
|
| 574 |
+
finally:
|
| 575 |
+
auto_flush_vram()
|
| 576 |
+
|
| 577 |
+
return results, seed
|
| 578 |
+
|
| 579 |
+
def run_inpainting(image_editor_data, prompt, negative_prompt, lora_choice, lora_alpha,
|
| 580 |
+
strength, steps, cfg, seed, randomize_seed,
|
| 581 |
+
transformer_choice, vae_choice, vram_type_str, progress=gr.Progress()):
|
| 582 |
+
global is_generating_interrupted
|
| 583 |
+
is_generating_interrupted = False
|
| 584 |
+
|
| 585 |
+
is_low_vram = "24GB" in vram_type_str or "Under 24GB" in vram_type_str or "24G以下" in vram_type_str or "24GB以下" in vram_type_str
|
| 586 |
+
|
| 587 |
+
input_image = None
|
| 588 |
+
mask_layer = None
|
| 589 |
+
|
| 590 |
+
if isinstance(image_editor_data, dict):
|
| 591 |
+
if 'background' in image_editor_data:
|
| 592 |
+
input_image = image_editor_data['background']
|
| 593 |
+
if image_editor_data.get('layers'):
|
| 594 |
+
mask_layer = image_editor_data['layers'][0]
|
| 595 |
+
elif isinstance(image_editor_data, (tuple, list)):
|
| 596 |
+
input_image = image_editor_data[0]
|
| 597 |
+
mask_layer = image_editor_data[1]
|
| 598 |
+
elif isinstance(image_editor_data, Image.Image):
|
| 599 |
+
input_image = image_editor_data
|
| 600 |
+
|
| 601 |
+
if input_image is None:
|
| 602 |
+
raise gr.Error("⚠️ 请先上传图片!")
|
| 603 |
+
|
| 604 |
+
if input_image.mode == 'RGBA':
|
| 605 |
+
background = Image.new('RGB', input_image.size, (255,255,255))
|
| 606 |
+
background.paste(input_image, (0, 0), input_image)
|
| 607 |
+
input_image = background
|
| 608 |
+
else:
|
| 609 |
+
input_image = input_image.convert("RGB")
|
| 610 |
+
|
| 611 |
+
if mask_layer is None:
|
| 612 |
+
raise gr.Error("⚠️ 请使用画笔在图片上涂抹要修改的区域。")
|
| 613 |
+
|
| 614 |
+
mask = process_mask_for_inpaint(mask_layer)
|
| 615 |
+
if mask is None:
|
| 616 |
+
raise gr.Error("⚠️ Mask 无效,请确保涂抹了区域。")
|
| 617 |
+
|
| 618 |
+
try:
|
| 619 |
+
pipe_local = load_i2i_pipeline(transformer_choice, vae_choice, is_low_vram)
|
| 620 |
+
except Exception as e:
|
| 621 |
+
raise gr.Error(f"模型加载失败: {str(e)}")
|
| 622 |
+
|
| 623 |
+
pipe_local = apply_lora_to_pipeline(pipe_local, lora_choice, lora_alpha, lora_scale=0.6)
|
| 624 |
+
|
| 625 |
+
if randomize_seed:
|
| 626 |
+
seed = random.randint(0, 2**32 - 1)
|
| 627 |
+
generator = torch.Generator(DEVICE).manual_seed(int(seed))
|
| 628 |
+
|
| 629 |
+
orig_w, orig_h = input_image.size
|
| 630 |
+
if mask.size != (orig_w, orig_h):
|
| 631 |
+
mask = mask.resize((orig_w, orig_h), Image.LANCZOS)
|
| 632 |
+
|
| 633 |
+
date_str = datetime.datetime.now().strftime("%Y-%m-%d")
|
| 634 |
+
day_dir = os.path.join(OUTPUT_DIR, date_str)
|
| 635 |
+
os.makedirs(day_dir, exist_ok=True)
|
| 636 |
+
|
| 637 |
+
result_img = None
|
| 638 |
+
|
| 639 |
+
try:
|
| 640 |
+
try:
|
| 641 |
+
result_img = pipe_local(
|
| 642 |
+
prompt=prompt.strip(),
|
| 643 |
+
negative_prompt=negative_prompt.strip(),
|
| 644 |
+
image=input_image,
|
| 645 |
+
mask_image=mask,
|
| 646 |
+
strength=float(strength),
|
| 647 |
+
num_inference_steps=int(steps),
|
| 648 |
+
guidance_scale=float(cfg),
|
| 649 |
+
generator=generator,
|
| 650 |
+
callback_on_step_end=interrupt_callback
|
| 651 |
+
).images[0]
|
| 652 |
+
except (TypeError, AttributeError) as e:
|
| 653 |
+
print(f"⚠️ 原生 Inpaint 失败 ({e}),使用手动混合模式...")
|
| 654 |
+
|
| 655 |
+
img_array = np.array(input_image).astype(np.float32) /255.0
|
| 656 |
+
mask_array = np.array(mask.convert('L')).astype(np.float32) / 255.0
|
| 657 |
+
mask_3d = np.expand_dims(mask_array, axis=2)
|
| 658 |
+
mask_3d = np.repeat(mask_3d,3, axis=2)
|
| 659 |
+
|
| 660 |
+
noise = np.random.randn(*img_array.shape).astype(np.float32) * 0.1
|
| 661 |
+
inpaint_input_array = img_array * (1 - mask_3d) + (img_array + noise) * mask_3d
|
| 662 |
+
inpaint_input_array = np.clip(inpaint_input_array, 0, 1)
|
| 663 |
+
inpaint_input = Image.fromarray((inpaint_input_array * 255).astype(np.uint8))
|
| 664 |
+
|
| 665 |
+
generated = pipe_local(
|
| 666 |
+
prompt=prompt.strip(),
|
| 667 |
+
negative_prompt=negative_prompt.strip(),
|
| 668 |
+
image=inpaint_input,
|
| 669 |
+
strength=float(strength),
|
| 670 |
+
num_inference_steps=int(steps),
|
| 671 |
+
guidance_scale=float(cfg),
|
| 672 |
+
generator=generator,
|
| 673 |
+
callback_on_step_end=interrupt_callback
|
| 674 |
+
).images[0]
|
| 675 |
+
|
| 676 |
+
if generated.size != (orig_w, orig_h):
|
| 677 |
+
generated = generated.resize((orig_w, orig_h), Image.LANCZOS)
|
| 678 |
+
|
| 679 |
+
gen_array = np.array(generated).astype(np.float32) / 255.0
|
| 680 |
+
orig_array = np.array(input_image).astype(np.float32) / 255.0
|
| 681 |
+
|
| 682 |
+
final_array = orig_array * (1 - mask_3d) + gen_array * mask_3d
|
| 683 |
+
final_array = np.clip(final_array, 0, 1)
|
| 684 |
+
result_img = Image.fromarray((final_array * 255).astype(np.uint8))
|
| 685 |
+
|
| 686 |
+
filename = os.path.join(day_dir, f"inpaint_{datetime.datetime.now():%H%M%S}_{uuid.uuid4().hex[:4]}.png")
|
| 687 |
+
result_img.save(filename)
|
| 688 |
+
|
| 689 |
+
except Exception as e:
|
| 690 |
+
if "任务已手动停止" in str(e): raise
|
| 691 |
+
import traceback
|
| 692 |
+
traceback.print_exc()
|
| 693 |
+
raise gr.Error(f"局部重绘失败: {str(e)}")
|
| 694 |
+
finally:
|
| 695 |
+
auto_flush_vram()
|
| 696 |
+
|
| 697 |
+
return [result_img], seed
|
| 698 |
+
|
| 699 |
+
def edit_image(image, angle, x, y, w, h, hflip, vflip, filter_name, brightness, contrast, saturation):
|
| 700 |
+
if image is None:
|
| 701 |
+
return None
|
| 702 |
+
img = image.copy()
|
| 703 |
+
if angle != 0:
|
| 704 |
+
img = img.rotate(angle, expand=True)
|
| 705 |
+
if x or y or w < 100 or h < 100:
|
| 706 |
+
ow, oh = img.size
|
| 707 |
+
left = int(ow * x / 100)
|
| 708 |
+
top = int(oh * y / 100)
|
| 709 |
+
right = int(ow * (x + w) / 100)
|
| 710 |
+
bottom = int(oh * (y + h) / 100)
|
| 711 |
+
img = img.crop((left, top, right, bottom))
|
| 712 |
+
if hflip:
|
| 713 |
+
img = ImageOps.mirror(img)
|
| 714 |
+
if vflip:
|
| 715 |
+
img = ImageOps.flip(img)
|
| 716 |
+
if filter_name:
|
| 717 |
+
filter_map = {
|
| 718 |
+
"模糊": ImageFilter.BLUR, "轮廓": ImageFilter.CONTOUR, "细节": ImageFilter.DETAIL,
|
| 719 |
+
"边缘增强": ImageFilter.EDGE_ENHANCE, "更多边缘增强": ImageFilter.EDGE_ENHANCE_MORE,
|
| 720 |
+
"浮雕": ImageFilter.EMBOSS, "查找边缘": ImageFilter.FIND_EDGES,
|
| 721 |
+
"锐化": ImageFilter.SHARPEN, "平滑": ImageFilter.SMOOTH, "更多平滑": ImageFilter.SMOOTH_MORE,
|
| 722 |
+
}
|
| 723 |
+
f = filter_map.get(filter_name)
|
| 724 |
+
if f:
|
| 725 |
+
img = img.filter(f)
|
| 726 |
+
if brightness != 0:
|
| 727 |
+
img = ImageEnhance.Brightness(img).enhance(1 + brightness / 100)
|
| 728 |
+
if contrast != 0:
|
| 729 |
+
img = ImageEnhance.Contrast(img).enhance(1 + contrast / 100)
|
| 730 |
+
if saturation != 0:
|
| 731 |
+
img = ImageEnhance.Color(img).enhance(1 + saturation / 100)
|
| 732 |
+
return img
|
| 733 |
+
|
| 734 |
+
# =========================
|
| 735 |
+
# Gradio 界面构建
|
| 736 |
+
# =========================
|
| 737 |
+
TOTAL_VRAM = torch.cuda.get_device_properties(0).total_memory if DEVICE == "cuda" else 0
|
| 738 |
+
DEFAULT_PERF_MODE = "高端机模式 (>=24GB)" if TOTAL_VRAM >= 24 * 1024**3 else "24GB以下 (优化模式)"
|
| 739 |
+
|
| 740 |
+
with gr.Blocks() as demo:
|
| 741 |
+
lang_state = gr.State("zh")
|
| 742 |
+
|
| 743 |
+
with gr.Row():
|
| 744 |
+
title_md = gr.Markdown(TEXT["zh"]["title"])
|
| 745 |
+
lang_btn = gr.Button(TEXT["zh"]["lang_btn"], size="sm")
|
| 746 |
+
|
| 747 |
+
with gr.Tabs() as tabs:
|
| 748 |
+
with gr.Tab(TEXT["zh"]["tab_generate"]) as tab_gen:
|
| 749 |
+
with gr.Row():
|
| 750 |
+
with gr.Column(scale=4):
|
| 751 |
+
prompt = gr.Textbox(label=TEXT["zh"]["prompt"], lines=4, placeholder=TEXT["zh"]["prompt_placeholder"])
|
| 752 |
+
with gr.Row():
|
| 753 |
+
refresh_lora = gr.Button(TEXT["zh"]["refresh_lora"], size="sm")
|
| 754 |
+
refresh_model_t2i = gr.Button(TEXT["zh"]["refresh_model"], size="sm")
|
| 755 |
+
|
| 756 |
+
lora_choices = ["None"] + scan_lora_items()
|
| 757 |
+
lora_drop = gr.Dropdown(label=TEXT["zh"]["lora_label"], choices=lora_choices, value="None")
|
| 758 |
+
lora_alpha = gr.Slider(0, 2, 1, step=0.05, label=TEXT["zh"]["lora_strength"])
|
| 759 |
+
|
| 760 |
+
model_section_md = gr.Markdown(TEXT["zh"]["model_section"])
|
| 761 |
+
|
| 762 |
+
with gr.Row():
|
| 763 |
+
transformer_choice = gr.Dropdown(label=TEXT["zh"]["transformer"], choices=get_choices(MOD_TRANSFORMER), value="default")
|
| 764 |
+
vae_choice = gr.Dropdown(label=TEXT["zh"]["vae"], choices=get_choices(MOD_VAE), value="default")
|
| 765 |
+
|
| 766 |
+
vram_type = gr.Radio(
|
| 767 |
+
[TEXT["zh"]["vram_low"], TEXT["zh"]["vram_high"]],
|
| 768 |
+
label=TEXT["zh"]["vram_type"],
|
| 769 |
+
value=DEFAULT_PERF_MODE
|
| 770 |
+
)
|
| 771 |
+
device_ui = gr.Radio(["cuda", "cpu"], label=TEXT["zh"]["device"], value="cuda" if torch.cuda.is_available() else "cpu", visible=False)
|
| 772 |
+
|
| 773 |
+
num_images = gr.Slider(1, 8, 1, step=1, label=TEXT["zh"]["num_images"])
|
| 774 |
+
image_format = gr.Dropdown(["png", "jpeg", "webp"], value="png", label=TEXT["zh"]["output_format"])
|
| 775 |
+
|
| 776 |
+
with gr.Row():
|
| 777 |
+
width = gr.Slider(512, 2048, 1024, step=64, label=TEXT["zh"]["width"])
|
| 778 |
+
height = gr.Slider(512, 2048, 1024, step=64, label=TEXT["zh"]["height"])
|
| 779 |
+
num_inference_steps = gr.Slider(1, 50, 10, step=1, label=TEXT["zh"]["steps"])
|
| 780 |
+
guidance_scale = gr.Slider(0, 10, 0, step=0.1, label=TEXT["zh"]["cfg"])
|
| 781 |
+
seed = gr.Number(label=TEXT["zh"]["seed"], value=42, precision=0)
|
| 782 |
+
randomize_seed = gr.Checkbox(label=TEXT["zh"]["random_seed"], value=True)
|
| 783 |
+
|
| 784 |
+
with gr.Row():
|
| 785 |
+
generate_btn = gr.Button(TEXT["zh"]["generate"], variant="primary", size="lg")
|
| 786 |
+
stop_btn = gr.Button(TEXT["zh"]["stop"], variant="stop", size="lg", interactive=False)
|
| 787 |
+
|
| 788 |
+
with gr.Column(scale=6):
|
| 789 |
+
gallery = gr.Gallery(label=TEXT["zh"]["gallery"], columns=2, height="80vh")
|
| 790 |
+
used_seed = gr.Number(label=TEXT["zh"]["used_seed"], interactive=False)
|
| 791 |
+
|
| 792 |
+
with gr.Tab(TEXT["zh"]["tab_edit"]) as tab_edit:
|
| 793 |
+
with gr.Row():
|
| 794 |
+
with gr.Column():
|
| 795 |
+
image_input = gr.Image(label=TEXT["zh"]["edit_upload"], type="pil")
|
| 796 |
+
with gr.Group():
|
| 797 |
+
rotate_angle = gr.Slider(-360, 360, 0, step=1, label=TEXT["zh"]["rotate"])
|
| 798 |
+
crop_x = gr.Slider(0, 100, 0, step=1, label=TEXT["zh"]["crop_x"])
|
| 799 |
+
crop_y = gr.Slider(0, 100, 0, step=1, label=TEXT["zh"]["crop_y"])
|
| 800 |
+
crop_width = gr.Slider(0, 100, 100, step=1, label=TEXT["zh"]["crop_w"])
|
| 801 |
+
crop_height = gr.Slider(0, 100, 100, step=1, label=TEXT["zh"]["crop_h"])
|
| 802 |
+
flip_horizontal = gr.Checkbox(label=TEXT["zh"]["hflip"])
|
| 803 |
+
flip_vertical = gr.Checkbox(label=TEXT["zh"]["vflip"])
|
| 804 |
+
edit_btn = gr.Button(TEXT["zh"]["edit_btn"], variant="primary")
|
| 805 |
+
|
| 806 |
+
with gr.Column():
|
| 807 |
+
edited_image_output = gr.Image(label=TEXT["zh"]["edited_image"], type="pil")
|
| 808 |
+
with gr.Group():
|
| 809 |
+
apply_filter = gr.Dropdown(
|
| 810 |
+
["模糊", "轮廓", "细节", "边缘增强", "更多边缘增强", "浮雕", "查找边缘", "锐化", "平滑", "更多平滑"],
|
| 811 |
+
label=TEXT["zh"]["filter"]
|
| 812 |
+
)
|
| 813 |
+
brightness = gr.Slider(-100, 100, 0, step=1, label=TEXT["zh"]["brightness"])
|
| 814 |
+
contrast = gr.Slider(-100, 100, 0, step=1, label=TEXT["zh"]["contrast"])
|
| 815 |
+
saturation = gr.Slider(-100, 100, 0, step=1, label=TEXT["zh"]["saturation"])
|
| 816 |
+
|
| 817 |
+
with gr.Tab(TEXT["zh"]["tab_img2img"]) as tab_img2img:
|
| 818 |
+
i2i_status_md = gr.Markdown(TEXT["zh"]["i2i_note"])
|
| 819 |
+
with gr.Row():
|
| 820 |
+
with gr.Column(scale=4):
|
| 821 |
+
i2i_image_input = gr.Image(label=TEXT["zh"]["i2i_ref"], type="pil")
|
| 822 |
+
|
| 823 |
+
i2i_prompt = gr.Textbox(label=TEXT["zh"]["i2i_prompt"], lines=3, placeholder=TEXT["zh"]["i2i_ph"])
|
| 824 |
+
i2i_negative_prompt = gr.Textbox(label=TEXT["zh"]["negative_prompt"], lines=2, placeholder=TEXT["zh"]["negative_placeholder"])
|
| 825 |
+
|
| 826 |
+
with gr.Row():
|
| 827 |
+
i2i_refresh_lora = gr.Button(TEXT["zh"]["refresh_lora"], size="sm")
|
| 828 |
+
i2i_refresh_model = gr.Button(TEXT["zh"]["refresh_model"], size="sm")
|
| 829 |
+
|
| 830 |
+
i2i_lora_choices = ["None"] + scan_lora_items()
|
| 831 |
+
i2i_lora_drop = gr.Dropdown(label=TEXT["zh"]["lora_label"], choices=i2i_lora_choices, value="None")
|
| 832 |
+
i2i_lora_alpha = gr.Slider(0, 2, 1, step=0.05, label=TEXT["zh"]["lora_strength"])
|
| 833 |
+
|
| 834 |
+
with gr.Accordion(TEXT["zh"]["model_section"], open=False):
|
| 835 |
+
i2i_transformer_choice = gr.Dropdown(label=TEXT["zh"]["transformer"], choices=get_choices(MOD_TRANSFORMER), value="default")
|
| 836 |
+
i2i_vae_choice = gr.Dropdown(label=TEXT["zh"]["vae"], choices=get_choices(MOD_VAE), value="default")
|
| 837 |
+
i2i_vram_type = gr.Radio(
|
| 838 |
+
[TEXT["zh"]["vram_low"], TEXT["zh"]["vram_high"]],
|
| 839 |
+
label=TEXT["zh"]["vram_type"],
|
| 840 |
+
value=DEFAULT_PERF_MODE
|
| 841 |
+
)
|
| 842 |
+
|
| 843 |
+
i2i_mode = gr.Radio(
|
| 844 |
+
[TEXT["zh"]["i2i_mode_a"], TEXT["zh"]["i2i_mode_b"]],
|
| 845 |
+
label=TEXT["zh"]["i2i_mode"],
|
| 846 |
+
value=TEXT["zh"]["i2i_mode_a"]
|
| 847 |
+
)
|
| 848 |
+
|
| 849 |
+
with gr.Row():
|
| 850 |
+
i2i_out_w = gr.Slider(0, 2048, 0, step=16, label=TEXT["zh"]["i2i_out_w"])
|
| 851 |
+
i2i_out_h = gr.Slider(0, 2048, 0, step=16, label=TEXT["zh"]["i2i_out_h"])
|
| 852 |
+
i2i_tip_md = gr.Markdown(TEXT["zh"]["i2i_tip"])
|
| 853 |
+
|
| 854 |
+
i2i_strength = gr.Slider(0.1, 1.0, 0.4, step=0.05, label=TEXT["zh"]["i2i_strength"])
|
| 855 |
+
i2i_steps = gr.Slider(1, 50, 6, step=1, label=TEXT["zh"]["steps"])
|
| 856 |
+
i2i_cfg = gr.Slider(0.0, 5.0, 1.0, step=0.1, label=TEXT["zh"]["cfg"])
|
| 857 |
+
|
| 858 |
+
i2i_num_images = gr.Slider(1, 4, 1, step=1, label=TEXT["zh"]["num_images"])
|
| 859 |
+
i2i_image_format = gr.Dropdown(["png", "jpeg", "webp"], value="png", label=TEXT["zh"]["output_format"])
|
| 860 |
+
i2i_seed = gr.Number(label=TEXT["zh"]["seed"], value=42, precision=0)
|
| 861 |
+
i2i_randomize_seed = gr.Checkbox(label=TEXT["zh"]["random_seed"], value=True)
|
| 862 |
+
|
| 863 |
+
with gr.Row():
|
| 864 |
+
i2i_generate_btn = gr.Button(TEXT["zh"]["i2i_btn"], variant="primary", size="lg")
|
| 865 |
+
i2i_stop_btn = gr.Button(TEXT["zh"]["stop"], variant="stop", size="lg", interactive=False)
|
| 866 |
+
|
| 867 |
+
with gr.Column(scale=6):
|
| 868 |
+
i2i_gallery = gr.Gallery(label=TEXT["zh"]["gallery"], columns=2, height="80vh")
|
| 869 |
+
i2i_used_seed = gr.Number(label=TEXT["zh"]["used_seed"], interactive=False)
|
| 870 |
+
|
| 871 |
+
with gr.Tab(TEXT["zh"]["tab_inpaint"]) as tab_inpaint:
|
| 872 |
+
with gr.Row():
|
| 873 |
+
with gr.Column(scale=4):
|
| 874 |
+
inpaint_editor = gr.ImageEditor(
|
| 875 |
+
label=TEXT["zh"]["inpaint_upload"],
|
| 876 |
+
type="pil",
|
| 877 |
+
layers=True,
|
| 878 |
+
eraser=True,
|
| 879 |
+
brush=gr.Brush(colors=["#FFFFFF", "#000000", "#FF0000"], color_mode="fixed")
|
| 880 |
+
)
|
| 881 |
+
inpaint_tip_md = gr.Markdown(TEXT["zh"]["inpaint_desc"])
|
| 882 |
+
|
| 883 |
+
inpaint_prompt = gr.Textbox(label=TEXT["zh"]["i2i_prompt"], lines=3, placeholder=TEXT["zh"]["i2i_ph"])
|
| 884 |
+
inpaint_negative_prompt = gr.Textbox(label=TEXT["zh"]["negative_prompt"], lines=2, placeholder=TEXT["zh"]["negative_placeholder"])
|
| 885 |
+
|
| 886 |
+
with gr.Row():
|
| 887 |
+
inpaint_refresh_lora = gr.Button(TEXT["zh"]["refresh_lora"], size="sm")
|
| 888 |
+
inpaint_refresh_model = gr.Button(TEXT["zh"]["refresh_model"], size="sm")
|
| 889 |
+
|
| 890 |
+
inpaint_lora_choices = ["None"] + scan_lora_items()
|
| 891 |
+
inpaint_lora_drop = gr.Dropdown(label=TEXT["zh"]["lora_label"], choices=inpaint_lora_choices, value="None")
|
| 892 |
+
inpaint_lora_alpha = gr.Slider(0, 2, 1, step=0.05, label=TEXT["zh"]["lora_strength"])
|
| 893 |
+
|
| 894 |
+
with gr.Accordion(TEXT["zh"]["model_section"], open=False):
|
| 895 |
+
inpaint_transformer_choice = gr.Dropdown(label=TEXT["zh"]["transformer"], choices=get_choices(MOD_TRANSFORMER), value="default")
|
| 896 |
+
inpaint_vae_choice = gr.Dropdown(label=TEXT["zh"]["vae"], choices=get_choices(MOD_VAE), value="default")
|
| 897 |
+
inpaint_vram_type = gr.Radio(
|
| 898 |
+
[TEXT["zh"]["vram_low"], TEXT["zh"]["vram_high"]],
|
| 899 |
+
label=TEXT["zh"]["vram_type"],
|
| 900 |
+
value=DEFAULT_PERF_MODE
|
| 901 |
+
)
|
| 902 |
+
|
| 903 |
+
inpaint_strength = gr.Slider(0.1, 1.0, 0.7, step=0.05, label=TEXT["zh"]["i2i_strength"])
|
| 904 |
+
inpaint_steps = gr.Slider(1, 50, 8, step=1, label=TEXT["zh"]["steps"])
|
| 905 |
+
inpaint_cfg = gr.Slider(0.0, 5.0, 1.0, step=0.1, label=TEXT["zh"]["cfg"])
|
| 906 |
+
|
| 907 |
+
inpaint_seed = gr.Number(label=TEXT["zh"]["seed"], value=42, precision=0)
|
| 908 |
+
inpaint_randomize_seed = gr.Checkbox(label=TEXT["zh"]["random_seed"], value=True)
|
| 909 |
+
|
| 910 |
+
with gr.Row():
|
| 911 |
+
inpaint_generate_btn = gr.Button(TEXT["zh"]["i2i_btn"], variant="primary", size="lg")
|
| 912 |
+
inpaint_stop_btn = gr.Button(TEXT["zh"]["stop"], variant="stop", size="lg", interactive=False)
|
| 913 |
+
|
| 914 |
+
with gr.Column(scale=6):
|
| 915 |
+
inpaint_gallery = gr.Gallery(label=TEXT["zh"]["gallery"], columns=2, height="80vh")
|
| 916 |
+
inpaint_used_seed = gr.Number(label=TEXT["zh"]["used_seed"], interactive=False)
|
| 917 |
+
|
| 918 |
+
def switch_language_full(lang):
|
| 919 |
+
new_lang = "en" if lang == "zh" else "zh"
|
| 920 |
+
t = TEXT[new_lang]
|
| 921 |
+
|
| 922 |
+
# 修复:根据硬件显存大小,决定当前应该选哪个语言版本的选项
|
| 923 |
+
is_low_vram_hardware = TOTAL_VRAM < 24 * 1024**3
|
| 924 |
+
current_vram_val = t['vram_low'] if is_low_vram_hardware else t['vram_high']
|
| 925 |
+
|
| 926 |
+
# 修复:更新显存选项的值,不仅仅是选项列表
|
| 927 |
+
return (
|
| 928 |
+
new_lang, t['title'], t['lang_btn'],
|
| 929 |
+
gr.update(label=t['tab_generate']), gr.update(label=t['tab_edit']),
|
| 930 |
+
gr.update(label=t['tab_img2img']), gr.update(label=t['tab_inpaint']),
|
| 931 |
+
gr.update(label=t['prompt'], placeholder=t['prompt_placeholder']),
|
| 932 |
+
gr.update(value=t['refresh_lora']), gr.update(value=t['refresh_model']),
|
| 933 |
+
gr.update(label=t['lora_label']), gr.update(label=t['lora_strength']),
|
| 934 |
+
t['model_section'], gr.update(label=t['transformer']), gr.update(label=t['vae']),
|
| 935 |
+
# T2I VRAM: 更新选项和值
|
| 936 |
+
gr.update(label=t['vram_type'], choices=[t['vram_low'], t['vram_high']], value=current_vram_val),
|
| 937 |
+
gr.update(label=t['device']),
|
| 938 |
+
gr.update(label=t['num_images']), gr.update(label=t['output_format']),
|
| 939 |
+
gr.update(label=t['width']), gr.update(label=t['height']),
|
| 940 |
+
gr.update(label=t['steps']), gr.update(label=t['cfg']),
|
| 941 |
+
gr.update(label=t['seed']), gr.update(label=t['random_seed']),
|
| 942 |
+
gr.update(value=t['generate']), gr.update(value=t['stop']),
|
| 943 |
+
gr.update(label=t['gallery']), gr.update(label=t['used_seed']),
|
| 944 |
+
|
| 945 |
+
gr.update(label=t['edit_upload']),
|
| 946 |
+
gr.update(label=t['rotate']), gr.update(label=t['crop_x']), gr.update(label=t['crop_y']),
|
| 947 |
+
gr.update(label=t['crop_w']), gr.update(label=t['crop_h']),
|
| 948 |
+
gr.update(label=t['hflip']), gr.update(label=t['vflip']),
|
| 949 |
+
gr.update(value=t['edit_btn']), gr.update(label=t['edited_image']),
|
| 950 |
+
gr.update(label=t['filter']), gr.update(label=t['brightness']), gr.update(label=t['contrast']), gr.update(label=t['saturation']),
|
| 951 |
+
|
| 952 |
+
gr.update(value=t['i2i_note']),
|
| 953 |
+
gr.update(label=t['i2i_ref']),
|
| 954 |
+
gr.update(label=t['i2i_prompt'], placeholder=t['i2i_ph']),
|
| 955 |
+
gr.update(label=t['negative_prompt'], placeholder=t['negative_placeholder']),
|
| 956 |
+
gr.update(value=t['refresh_lora']), gr.update(value=t['refresh_model']),
|
| 957 |
+
gr.update(label=t['lora_label']), gr.update(label=t['lora_strength']),
|
| 958 |
+
gr.update(label=t['transformer']), gr.update(label=t['vae']),
|
| 959 |
+
# Img2Img VRAM: 更新选项和值
|
| 960 |
+
gr.update(label=t['vram_type'], choices=[t['vram_low'], t['vram_high']], value=current_vram_val),
|
| 961 |
+
gr.update(label=t['i2i_mode'], choices=[t['i2i_mode_a'], t['i2i_mode_b']]),
|
| 962 |
+
gr.update(label=t['i2i_out_w']), gr.update(label=t['i2i_out_h']),
|
| 963 |
+
gr.update(value=t['i2i_tip']),
|
| 964 |
+
gr.update(label=t['i2i_strength']),
|
| 965 |
+
gr.update(label=t['steps']), gr.update(label=t['cfg']),
|
| 966 |
+
gr.update(label=t['num_images']), gr.update(label=t['output_format']),
|
| 967 |
+
gr.update(label=t['seed']), gr.update(label=t['random_seed']),
|
| 968 |
+
gr.update(value=t['i2i_btn']), gr.update(value=t['stop']),
|
| 969 |
+
gr.update(label=t['gallery']), gr.update(label=t['used_seed']),
|
| 970 |
+
|
| 971 |
+
gr.update(label=t['inpaint_upload']),
|
| 972 |
+
gr.update(value=t['inpaint_desc']),
|
| 973 |
+
gr.update(label=t['i2i_prompt'], placeholder=t['i2i_ph']),
|
| 974 |
+
gr.update(label=t['negative_prompt'], placeholder=t['negative_placeholder']),
|
| 975 |
+
gr.update(value=t['refresh_lora']), gr.update(value=t['refresh_model']),
|
| 976 |
+
gr.update(label=t['lora_label']), gr.update(label=t['lora_strength']),
|
| 977 |
+
gr.update(label=t['transformer']), gr.update(label=t['vae']),
|
| 978 |
+
# Inpaint VRAM: 更新选项和值
|
| 979 |
+
gr.update(label=t['vram_type'], choices=[t['vram_low'], t['vram_high']], value=current_vram_val),
|
| 980 |
+
gr.update(label=t['i2i_strength']),
|
| 981 |
+
gr.update(label=t['steps']), gr.update(label=t['cfg']),
|
| 982 |
+
gr.update(label=t['seed']), gr.update(label=t['random_seed']),
|
| 983 |
+
gr.update(value=t['i2i_btn']), gr.update(value=t['stop']),
|
| 984 |
+
gr.update(label=t['gallery']), gr.update(label=t['used_seed']),
|
| 985 |
+
)
|
| 986 |
+
|
| 987 |
+
lang_btn.click(
|
| 988 |
+
fn=switch_language_full,
|
| 989 |
+
inputs=lang_state,
|
| 990 |
+
outputs=[
|
| 991 |
+
lang_state, title_md, lang_btn,
|
| 992 |
+
tab_gen, tab_edit, tab_img2img, tab_inpaint,
|
| 993 |
+
prompt, refresh_lora, refresh_model_t2i, lora_drop, lora_alpha, model_section_md,
|
| 994 |
+
transformer_choice, vae_choice, vram_type, device_ui, num_images, image_format,
|
| 995 |
+
width, height, num_inference_steps, guidance_scale, seed, randomize_seed,
|
| 996 |
+
generate_btn, stop_btn, gallery, used_seed,
|
| 997 |
+
image_input, rotate_angle, crop_x, crop_y, crop_width, crop_height,
|
| 998 |
+
flip_horizontal, flip_vertical, edit_btn, edited_image_output,
|
| 999 |
+
apply_filter, brightness, contrast, saturation,
|
| 1000 |
+
i2i_status_md, i2i_image_input, i2i_prompt, i2i_negative_prompt,
|
| 1001 |
+
i2i_refresh_lora, i2i_refresh_model, i2i_lora_drop, i2i_lora_alpha,
|
| 1002 |
+
i2i_transformer_choice, i2i_vae_choice, i2i_vram_type, i2i_mode,
|
| 1003 |
+
i2i_out_w, i2i_out_h, i2i_tip_md,
|
| 1004 |
+
i2i_strength, i2i_steps, i2i_cfg,
|
| 1005 |
+
i2i_num_images, i2i_image_format, i2i_seed, i2i_randomize_seed,
|
| 1006 |
+
i2i_generate_btn, i2i_stop_btn, i2i_gallery, i2i_used_seed,
|
| 1007 |
+
inpaint_editor, inpaint_tip_md,
|
| 1008 |
+
inpaint_prompt, inpaint_negative_prompt,
|
| 1009 |
+
inpaint_refresh_lora, inpaint_refresh_model, inpaint_lora_drop, inpaint_lora_alpha,
|
| 1010 |
+
inpaint_transformer_choice, inpaint_vae_choice, inpaint_vram_type,
|
| 1011 |
+
inpaint_strength, inpaint_steps, inpaint_cfg,
|
| 1012 |
+
inpaint_seed, inpaint_randomize_seed,
|
| 1013 |
+
inpaint_generate_btn, inpaint_stop_btn, inpaint_gallery, inpaint_used_seed
|
| 1014 |
+
]
|
| 1015 |
+
)
|
| 1016 |
+
|
| 1017 |
+
refresh_lora.click(fn=scan_lora_items, outputs=[lora_drop, i2i_lora_drop, inpaint_lora_drop])
|
| 1018 |
+
lora_drop.change(update_prompt_with_lora, [prompt, lora_drop, lora_alpha], prompt)
|
| 1019 |
+
|
| 1020 |
+
def refresh_models_t2i():
|
| 1021 |
+
return gr.update(choices=get_choices(MOD_TRANSFORMER)), gr.update(choices=get_choices(MOD_VAE))
|
| 1022 |
+
refresh_model_t2i.click(fn=refresh_models_t2i, outputs=[transformer_choice, vae_choice])
|
| 1023 |
+
|
| 1024 |
+
def start_gen(): return gr.update(interactive=False), gr.update(interactive=True)
|
| 1025 |
+
def end_gen(): return gr.update(interactive=True), gr.update(interactive=False)
|
| 1026 |
+
def trigger_stop():
|
| 1027 |
+
global is_generating_interrupted
|
| 1028 |
+
is_generating_interrupted = True
|
| 1029 |
+
|
| 1030 |
+
generate_event = generate_btn.click(fn=start_gen, outputs=[generate_btn, stop_btn]).then(
|
| 1031 |
+
fn=generate_image,
|
| 1032 |
+
inputs=[prompt, lora_drop, lora_alpha, num_images, image_format,
|
| 1033 |
+
width, height, num_inference_steps, guidance_scale, seed, randomize_seed,
|
| 1034 |
+
transformer_choice, vae_choice, vram_type],
|
| 1035 |
+
outputs=[gallery, used_seed]
|
| 1036 |
+
).then(fn=end_gen, outputs=[generate_btn, stop_btn])
|
| 1037 |
+
|
| 1038 |
+
stop_btn.click(fn=trigger_stop).then(fn=end_gen, outputs=[generate_btn, stop_btn], cancels=[generate_event])
|
| 1039 |
+
|
| 1040 |
+
i2i_refresh_lora.click(fn=scan_lora_items, outputs=[lora_drop, i2i_lora_drop, inpaint_lora_drop])
|
| 1041 |
+
i2i_lora_drop.change(update_prompt_with_lora, [i2i_prompt, i2i_lora_drop, i2i_lora_alpha], i2i_prompt)
|
| 1042 |
+
|
| 1043 |
+
def refresh_models_i2i():
|
| 1044 |
+
return gr.update(choices=get_choices(MOD_TRANSFORMER)), gr.update(choices=get_choices(MOD_VAE))
|
| 1045 |
+
i2i_refresh_model.click(fn=refresh_models_i2i, outputs=[i2i_transformer_choice, i2i_vae_choice])
|
| 1046 |
+
|
| 1047 |
+
def start_i2i(): return gr.update(interactive=False), gr.update(interactive=True)
|
| 1048 |
+
def end_i2i(): return gr.update(interactive=True), gr.update(interactive=False)
|
| 1049 |
+
|
| 1050 |
+
i2i_generate_event = i2i_generate_btn.click(fn=start_i2i, outputs=[i2i_generate_btn, i2i_stop_btn]).then(
|
| 1051 |
+
fn=run_img2img_enhanced,
|
| 1052 |
+
inputs=[i2i_image_input, i2i_prompt, i2i_negative_prompt, i2i_lora_drop, i2i_lora_alpha,
|
| 1053 |
+
i2i_num_images, i2i_image_format,
|
| 1054 |
+
i2i_out_w, i2i_out_h, i2i_mode, i2i_strength, i2i_steps, i2i_cfg,
|
| 1055 |
+
i2i_seed, i2i_randomize_seed,
|
| 1056 |
+
i2i_transformer_choice, i2i_vae_choice, i2i_vram_type],
|
| 1057 |
+
outputs=[i2i_gallery, i2i_used_seed]
|
| 1058 |
+
).then(fn=end_i2i, outputs=[i2i_generate_btn, i2i_stop_btn])
|
| 1059 |
+
|
| 1060 |
+
i2i_stop_btn.click(fn=trigger_stop).then(fn=end_i2i, outputs=[i2i_generate_btn, i2i_stop_btn], cancels=[i2i_generate_event])
|
| 1061 |
+
|
| 1062 |
+
inpaint_refresh_lora.click(fn=scan_lora_items, outputs=[lora_drop, i2i_lora_drop, inpaint_lora_drop])
|
| 1063 |
+
inpaint_lora_drop.change(update_prompt_with_lora, [inpaint_prompt, inpaint_lora_drop, inpaint_lora_alpha], inpaint_prompt)
|
| 1064 |
+
|
| 1065 |
+
def refresh_models_inpaint():
|
| 1066 |
+
return gr.update(choices=get_choices(MOD_TRANSFORMER)), gr.update(choices=get_choices(MOD_VAE))
|
| 1067 |
+
inpaint_refresh_model.click(fn=refresh_models_inpaint, outputs=[inpaint_transformer_choice, inpaint_vae_choice])
|
| 1068 |
+
|
| 1069 |
+
def start_inpaint(): return gr.update(interactive=False), gr.update(interactive=True)
|
| 1070 |
+
def end_inpaint(): return gr.update(interactive=True), gr.update(interactive=False)
|
| 1071 |
+
|
| 1072 |
+
inpaint_generate_event = inpaint_generate_btn.click(fn=start_inpaint, outputs=[inpaint_generate_btn, inpaint_stop_btn]).then(
|
| 1073 |
+
fn=run_inpainting,
|
| 1074 |
+
inputs=[inpaint_editor, inpaint_prompt, inpaint_negative_prompt, inpaint_lora_drop, inpaint_lora_alpha,
|
| 1075 |
+
inpaint_strength, inpaint_steps, inpaint_cfg,
|
| 1076 |
+
inpaint_seed, inpaint_randomize_seed,
|
| 1077 |
+
inpaint_transformer_choice, inpaint_vae_choice, inpaint_vram_type],
|
| 1078 |
+
outputs=[inpaint_gallery, inpaint_used_seed]
|
| 1079 |
+
).then(fn=end_inpaint, outputs=[inpaint_generate_btn, inpaint_stop_btn])
|
| 1080 |
+
|
| 1081 |
+
inpaint_stop_btn.click(fn=trigger_stop).then(fn=end_inpaint, outputs=[inpaint_generate_btn, inpaint_stop_btn], cancels=[inpaint_generate_event])
|
| 1082 |
+
|
| 1083 |
+
edit_btn.click(
|
| 1084 |
+
fn=edit_image,
|
| 1085 |
+
inputs=[image_input, rotate_angle, crop_x, crop_y, crop_width, crop_height,
|
| 1086 |
+
flip_horizontal, flip_vertical, apply_filter, brightness, contrast, saturation],
|
| 1087 |
+
outputs=edited_image_output
|
| 1088 |
+
)
|
| 1089 |
+
|
| 1090 |
+
if __name__ == "__main__":
|
| 1091 |
+
demo.queue(max_size=20)
|
| 1092 |
+
demo.launch(show_error=True)
|
cache/GRADIO_TEMP_DIR/0104dcbf4ecd060d0f9908e75116ea3b1283defbe8d1daa5b2aebfdd27a50720/184610_17e9.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/0115b4830ff0665ad782cfb50ea81e83d576c6fe5787c6c33a77c9ea179743ca/174332_8794.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/01ad82d69b6d2baad6c60480ceb366ed05003c5c92b9c21c0cf4c37c411b3ff8/200033_b851.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/03a7badba46e5b655ff716e02c5996baee5f2e7e4ac5f44d0ad24a61489fadc3/image_195204_7aec6c64.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/048523a346ad5312fb0116b07c6cc9d4d7a7fea69ce7027d7fd7beb01268fc18/092559_b8fc.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/096db37f01b8c62d97c41220a5ca2b84aa4a1ae15b04d046f947d3d6892735fb/image.webp
ADDED
|
cache/GRADIO_TEMP_DIR/09b443d9c4850dd13d4a39a8edff6b7e68c9f5a428a73fc494ece1fa5066ee0c/201057_0a1c.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/0b504490c285e741239ac4793322e48efc91691c04e98a6c7a3af21d304a5aa4/image.webp
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/0c8c8364e4d7ddeb83cc7c5777e36b5f7687da1bb308d2406fd842ea9f46fa95/i2i_133141_5337.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/0fd950fa33ea4eafe5c16b1cc0eed01eb5ebf2f23b1e6f414533b25180379b05/image_205528_7ee5527a.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/104f4b9668733006f0a761e9778c747031df2334a0dd6f389ae5d49d5cc02b9e/200437_0006.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/10c5d8f6cacea6cc5f16e475aa9c4fdff517728969b2629a26c679c5648135d5/193107_4fa5.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/127b98c0ad0f191eff7d10eba1f1bad79fa49c9889aa3a2dfe12c05d28348aba/095514_01ac.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/12d002a8885b4d41fbb41d98e32ce71f728aa5bde8a939300ed6af71179b599d/103637_26f5.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/168afac92d4203ba0e6fad2bf01e3d1a10295a59cad570e0d32316117755b7e3/composite.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/19b76ad6a7d98ae4eb1d1626871e171b636bb0103bd68fea8ff92685d2207695/160727_8256.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/1a0791be9abd585a9187d0f87b23e9ceafb0d09b978af6f11e5b45999d7ff792/image_203420_c47cecb7.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/1aaa830b48dfb49d28389f438e732a2a73cc948878b7f969802687356e75a63c/173952_0241.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/1cc5d28158556bf5a8eb7992502a482d4b898f463262649559b77a1aaa10fd84/161034_21ec.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/213a256e430446bb846446a77053d3c3e2d5a7e3d40adf060bb1358d2929bed1/193806_c514.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/21db64ee5ade0d05ff3961c7df2ee7016e8a13e954396707b37d42de6b2b32d5/image_233514_3851872b.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/235047bf74248ec689408227f1fb364afb770712ddd3da625a3c660c6a29cc35/background.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/25d5add9dafa195a945d962f6064049cdf902c723358c1eb978615f82c47cd54/123145_46ab.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/2ddd025a39e016cbc97dfd9130f9b1302492be2ba4e33b0acefb761adaa1b14d/image.webp
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/30a053ec6f1d5ed7e7c8397450be98cb173037115d5e03394d99102741d0faf7/195841_e909.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/334d0bc165496fef845d69069319d2e574ef489656422f14f8b4553dff7583f8/184528_885b.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/364d8c3a6f8a226ff370cb2e4f59d8f37f6a23520f322adf77ae3d7d996d6176/191110_1134.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/36e56881752c197dfd65a693b7d7134852117eaa97fbff30739a01390082a69e/i2i_132542_eeff.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/3948f38c2fecc017128b1b7eb41f3f327fc06aba806d9bdc886368ef3228319d/193253_2da5.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/3ba5ac95cfc5de2c15635232fb57a2b719388c6891302cbc32127defbdbde758/image_123428_296a1507.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/3ece35a85d9ffae8b8270058ab850d5d419785b01ec68819a3c6aace56673bc4/161239_fdad.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/41fbb34e88e6b1e8e459504ada125267c8bf7acc97ac8d364df233623828879d/i2i_132029_852c.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/42a832bbae0d16a84c93cd3c85948cbd891e5ae55ec6e05ab905fb1dde3af489/190833_457f.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/430a73612de086da34654223fecad865c06011c87597bcc3d22cc10074e5ea2b/image.webp
ADDED
|
cache/GRADIO_TEMP_DIR/48c7aa110490f236dc8353a8a4b7a1b9610cd489d0723e9acc4671ffbc1fd9e5/211433_2542.png
ADDED
|
Git LFS Details
|
cache/GRADIO_TEMP_DIR/4ca1ba410ee0e412d101e38b900f26235b1ac4ba2bd20e3a9cb4695a31770b8b/152321_78e9.png
ADDED
|
Git LFS Details
|