specimba commited on
Commit
cac0037
·
verified ·
1 Parent(s): 794644c

Deploy NEXUS command center v2.2

Browse files
.coderabbit.yaml ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ language: en-US
2
+ early_access: false
3
+ reviews:
4
+ profile: assertive
5
+ request_changes_workflow: true
6
+ high_level_summary: true
7
+ high_level_summary_instructions: |
8
+ Summarize this PR as work on NEXUS Visual Weaver, a Hugging Face Build Small
9
+ Hackathon Gradio command-center for governed visual creation. Lead with
10
+ user-facing dashboard/workflow impact, then model-governance impact, then
11
+ security/export-gate impact, then verification evidence.
12
+
13
+ Preserve these project anchors in summaries when relevant:
14
+ - FLUX.2 is the pinned image-generation lane.
15
+ - NVIDIA LocateAnything-3B is the pinned grounding lane.
16
+ - ST3GG is the always-on security/export gate.
17
+ - Adult Mode must remain opt-in and must not disable provenance, consent,
18
+ age, export, dataset-partition, or ST3GG gates.
19
+ - ModelRelay/GMR helper rotation is quota-aware and only applies to helper
20
+ lanes, not pinned core lanes.
21
+
22
+ Avoid marketing filler, poems, exaggerated claims, or claims that provider
23
+ calls actually ran unless tests or logs prove it. Distinguish implemented
24
+ runtime behavior from dry-run preview surfaces and planning scaffolds.
25
+ poem: false
26
+ review_status: true
27
+ collapse_walkthrough: false
28
+ path_filters:
29
+ - "!outputs/**"
30
+ - "!**/__pycache__/**"
31
+ - "!pytest-cache-files-*/**"
32
+ path_instructions:
33
+ - path: "src/nexus_visual_weaver/**"
34
+ instructions: "Focus on Gradio runtime correctness, defensive security gates, model-lane governance, parameter-budget logic, and test coverage for fallback behavior."
35
+ - path: "app.py"
36
+ instructions: "Check that UI callbacks update the intended regions, never leak secrets, and keep Adult Mode security gates active."
37
+ - path: "tests/**"
38
+ instructions: "Prefer focused regression tests for workflow routing, ST3GG evidence, catalog filtering, and ModelRelay quota behavior."
39
+ chat:
40
+ auto_reply: true
.env.example ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Copy to .env locally or configure these as Hugging Face Space secrets.
2
+ # Never commit real values.
3
+
4
+ HF_TOKEN=
5
+ MODAL_TOKEN_ID=
6
+ MODAL_TOKEN_SECRET=
7
+ OPENAI_API_KEY=
8
+ NEXUS_PORT=7860
9
+
.gitattributes CHANGED
@@ -1,35 +1,11 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz 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
 
1
+ * text=auto eol=lf
2
+ *.py text eol=lf
3
+ *.md text eol=lf
4
+ *.yml text eol=lf
5
+ *.yaml text eol=lf
6
+ *.json text eol=lf
7
+ *.png binary
8
+ *.jpg binary
9
+ *.jpeg binary
10
+ *.webp binary
11
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/CODEOWNERS ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ * @specimba
2
+
.github/pull_request_template.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## What Changed
2
+ -
3
+
4
+ ## Why
5
+ -
6
+
7
+ ## Safety Gates
8
+ - [ ] No secrets, tokens, generated auth folders, or provider credentials are committed.
9
+ - [ ] Adult catalog behavior remains opt-in and does not disable ST3GG, consent, provenance, export, or dataset gates.
10
+ - [ ] Pinned lanes remain pinned: FLUX.2 image generation, LocateAnything grounding, ST3GG security.
11
+ - [ ] Generated outputs, moodboards, logs, caches, and local previews stay untracked.
12
+
13
+ ## Verification
14
+ - [ ] `python -m compileall app.py src tests`
15
+ - [ ] `python -m pytest -q tests -p no:cacheprovider --basetemp=C:\tmp\pytest-nvw-full`
16
+
17
+ ## Screenshots / Notes
18
+ -
19
+
.github/workflows/ci.yml ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ test:
14
+ name: Python tests
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - name: Checkout
18
+ uses: actions/checkout@v4
19
+
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.11"
24
+ cache: pip
25
+
26
+ - name: Install dependencies
27
+ run: |
28
+ python -m pip install --upgrade pip
29
+ python -m pip install -r requirements.txt
30
+
31
+ - name: Compile
32
+ run: python -m compileall app.py src tests
33
+
34
+ - name: Import app
35
+ run: python -c "import app; print('app import ok')"
36
+
37
+ - name: Test
38
+ env:
39
+ PYTEST_DISABLE_PLUGIN_AUTOLOAD: "1"
40
+ run: python -m pytest -q tests -p no:cacheprovider
.gitignore ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ .pytest_cache/
6
+ pytest-cache-files-*/
7
+ .mypy_cache/
8
+ .ruff_cache/
9
+ .coverage
10
+ .coverage.*
11
+ htmlcov/
12
+ .tox/
13
+ .nox/
14
+
15
+ # Packaging/build artifacts
16
+ build/
17
+ dist/
18
+ *.egg-info/
19
+ .eggs/
20
+ *.egg
21
+ MANIFEST
22
+
23
+ # Virtual environments
24
+ .venv/
25
+ venv/
26
+ env/
27
+ ENV/
28
+ env.bak/
29
+ venv.bak/
30
+
31
+ # Local secrets and credentials
32
+ .env
33
+ .env.*
34
+ !.env.example
35
+ .envrc
36
+ *.pem
37
+ *.key
38
+ *.p12
39
+ *.pfx
40
+ .huggingface/
41
+ .cache/huggingface/
42
+ .modal.toml
43
+ .netrc
44
+ .pypirc
45
+
46
+ # App/runtime outputs
47
+ .gradio/
48
+ .playwright-mcp/
49
+ server*.log
50
+ *.log
51
+ outputs/
52
+ nexus-visual-weaver-command-center*.png
53
+
54
+ # Codex/local tool state
55
+ .codex-home/
56
+ outputs/moodboards/*/.codex-home/
57
+
58
+ # Notebooks/docs build
59
+ .ipynb_checkpoints/
60
+ docs/_build/
61
+ site/
62
+
63
+ # OS/editor noise
64
+ .DS_Store
65
+ Thumbs.db
66
+ .idea/
67
+ .vscode/
68
+ tempCodeRunnerFile.py
69
+
AGENTS.md ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AGENTS.md
2
+
3
+ ## Operating Rules
4
+
5
+ - Keep changes scoped and verifiable.
6
+ - Prefer focused tests over broad, slow runs.
7
+ - Do not launch long-running local servers unless the user asks for visual validation.
8
+ - Do not commit generated outputs, local logs, caches, preview artifacts, or credentials.
9
+ - Use Hugging Face Space secrets and local `.env` files for provider credentials.
10
+ - Preserve the pinned lanes unless the user explicitly approves a model-governance change:
11
+ - FLUX.2 for image generation
12
+ - LocateAnything-3B for grounding
13
+ - ST3GG for security/export review
14
+
15
+ ## Verification
16
+
17
+ Use these gates before claiming completion:
18
+
19
+ ```powershell
20
+ python -m compileall app.py src tests
21
+ $env:PYTEST_DISABLE_PLUGIN_AUTOLOAD='1'
22
+ python -m pytest -q tests -p no:cacheprovider --basetemp=C:\tmp\pytest-nvw-full
23
+ ```
24
+
25
+ ## Review Focus
26
+
27
+ - Gradio callback wiring and region updates.
28
+ - Adult Mode starts off and never disables safety gates.
29
+ - ModelRelay respects parameter, license, quota, cooldown, and pinned-lane rules.
30
+ - ST3GG scan results do not expose payload bytes or raw hidden content.
31
+
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
README.md CHANGED
@@ -1,14 +1,108 @@
1
  ---
2
  title: NEXUS Visual Weaver
3
- emoji: 🖼
4
- colorFrom: purple
5
- colorTo: red
6
  sdk: gradio
7
- sdk_version: 6.5.1
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
- short_description: create cutting edge images, scenarios, videos and lores
 
 
 
 
 
 
 
 
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: NEXUS Visual Weaver
3
+ emoji: 🧵
4
+ colorFrom: red
5
+ colorTo: gray
6
  sdk: gradio
7
+ sdk_version: 6.12.0
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
+ short_description: Governed gothic couture visual creation command center
12
+ models:
13
+ - black-forest-labs/FLUX.2-klein-9B
14
+ - nvidia/LocateAnything-3B
15
+ tags:
16
+ - gradio
17
+ - mcp-server
18
+ - visual-creation
19
+ - hackathon
20
  ---
21
 
22
+ # NEXUS Visual Weaver
23
+
24
+ Dark creative-operations command center for the Hugging Face Build Small Hackathon.
25
+
26
+ NEXUS Visual Weaver is a Gradio Space prototype for governed image and video creation. It combines a couture-oriented workflow dashboard, outfit and lore planning, model-lane governance, and an always-on defensive export gate.
27
+
28
+ ## Direction
29
+
30
+ The interface is built around a command-center surface:
31
+
32
+ - workflow graph for `Seed Prompt -> Refine -> Judge -> Locate -> Generate -> Video Path -> Human Checkpoint`
33
+ - contextual inspector with taste rings, material checks, model stack, relay status, and ST3GG evidence
34
+ - wardrobe drawer for garments, materials, footwear, accessories, locks, and reference-region intent
35
+ - lore-to-video timeline for compact cinematic beats
36
+ - provider handoff cards for dry-run visibility before any paid, gated, or quota-limited call
37
+
38
+ ## Model Governance
39
+
40
+ Pinned lanes do not rotate:
41
+
42
+ - `image_generation`: FLUX.2 primary image lane
43
+ - `grounding`: NVIDIA LocateAnything-3B grounding anchor
44
+ - `security`: ST3GG defensive scanner/export gate
45
+
46
+ Helper lanes may rotate with quota, license, health, and parameter-budget checks:
47
+
48
+ - prompt routing
49
+ - taste judging
50
+ - audio lore TTS
51
+ - video repair
52
+ - HF catalog research
53
+ - Modal job runner
54
+
55
+ Public demo mode excludes private, commercial-uncleared, and research-only helper models. Private research mode can expose more candidates, but it never disables consent, provenance, ST3GG, export, or dataset-partition gates.
56
+
57
+ ## Current Features
58
+
59
+ - Gradio Blocks dashboard with split update regions.
60
+ - Active workflow graph and checkpointed run record.
61
+ - Taste profile scoring from `assets/taste_profile.json`.
62
+ - Wardrobe slot planning for couture, gothic, fantasy, footwear, accessories, and material control.
63
+ - HF model and LoRA catalog with Adult Mode hidden by default.
64
+ - GMR/ModelRelay-inspired helper model selection.
65
+ - ST3GG-inspired scan adapter with magic detection, mismatch review, purification actions, and export-gate state.
66
+ - Focused regression tests for catalog scope, workflow planning, ModelRelay behavior, and scanner evidence.
67
+
68
+ ## Local Setup
69
+
70
+ ```powershell
71
+ python -m pip install -r requirements.txt
72
+ python app.py
73
+ ```
74
+
75
+ The app reads `NEXUS_PORT` or `PORT` when present, otherwise it launches on `7860`.
76
+
77
+ ## Verification
78
+
79
+ ```powershell
80
+ python -m compileall app.py src tests
81
+ $env:PYTEST_DISABLE_PLUGIN_AUTOLOAD='1'
82
+ python -m pytest -q tests -p no:cacheprovider --basetemp=C:\tmp\pytest-nvw-full
83
+ ```
84
+
85
+ ## Secret Policy
86
+
87
+ Do not commit provider credentials. Use Hugging Face Space secrets or local `.env` files for:
88
+
89
+ - `HF_TOKEN`
90
+ - `MODAL_TOKEN_ID`
91
+ - `MODAL_TOKEN_SECRET`
92
+ - `OPENAI_API_KEY`
93
+ - provider-specific API keys or bearer tokens
94
+
95
+ Generated outputs, local moodboards, logs, caches, auth folders, and preview artifacts are intentionally ignored.
96
+
97
+ ## Review Workflow
98
+
99
+ - Bootstrap commit establishes the public GitHub repository baseline.
100
+ - Future substantial changes should use `codex/specimba/<scope>` branches and draft pull requests.
101
+ - GitHub Actions runs compile and pytest.
102
+ - CodeRabbit is configured to focus review on Gradio runtime correctness, model governance, security gates, Adult Mode behavior, and regression coverage.
103
+
104
+ See [docs/RELEASE_WORKFLOW.md](docs/RELEASE_WORKFLOW.md) for the push and review gate.
105
+
106
+ ## License
107
+
108
+ Apache-2.0. See [LICENSE](LICENSE).
SECURITY.md ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Security Policy
2
+
3
+ ## Supported Scope
4
+
5
+ This repository is an active hackathon prototype. Security-sensitive changes include:
6
+
7
+ - provider authentication
8
+ - file upload handling
9
+ - ST3GG scan/export behavior
10
+ - Adult Mode catalog gating
11
+ - model relay and provider routing
12
+ - generated artifact handling
13
+
14
+ ## Secret Handling
15
+
16
+ Never commit real tokens, API keys, bearer tokens, private keys, OAuth material, or provider credentials.
17
+
18
+ Use:
19
+
20
+ - Hugging Face Space secrets for deployment
21
+ - local `.env` files for development
22
+ - `.env.example` for placeholder names only
23
+
24
+ Ignored local paths include `.env*`, `.huggingface/`, `.modal.toml`, `.codex-home/`, logs, caches, and generated `outputs/`.
25
+
26
+ ## Required Review Gates
27
+
28
+ Before merging or deploying:
29
+
30
+ 1. Run compile and pytest.
31
+ 2. Run a secret-pattern scan over tracked files.
32
+ 3. Confirm Adult Mode remains opt-in.
33
+ 4. Confirm ST3GG, consent, provenance, export, and dataset-partition gates remain active in every mode.
34
+ 5. Confirm generated outputs and local auth folders are not committed.
35
+
36
+ ## Reporting
37
+
38
+ Open a private issue or contact the repository owner if you find a credential leak, unsafe export path, or bypass of Adult Mode/ST3GG behavior.
39
+
app.py CHANGED
@@ -1,154 +1,295 @@
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
- import numpy as np
3
- import random
4
-
5
- # import spaces #[uncomment to use ZeroGPU]
6
- from diffusers import DiffusionPipeline
7
- import torch
8
-
9
- device = "cuda" if torch.cuda.is_available() else "cpu"
10
- model_repo_id = "stabilityai/sdxl-turbo" # Replace to the model you would like to use
11
-
12
- if torch.cuda.is_available():
13
- torch_dtype = torch.float16
14
- else:
15
- torch_dtype = torch.float32
16
-
17
- pipe = DiffusionPipeline.from_pretrained(model_repo_id, torch_dtype=torch_dtype)
18
- pipe = pipe.to(device)
19
-
20
- MAX_SEED = np.iinfo(np.int32).max
21
- MAX_IMAGE_SIZE = 1024
22
-
23
-
24
- # @spaces.GPU #[uncomment to use ZeroGPU]
25
- def infer(
26
- prompt,
27
- negative_prompt,
28
- seed,
29
- randomize_seed,
30
- width,
31
- height,
32
- guidance_scale,
33
- num_inference_steps,
34
- progress=gr.Progress(track_tqdm=True),
35
- ):
36
- if randomize_seed:
37
- seed = random.randint(0, MAX_SEED)
38
-
39
- generator = torch.Generator().manual_seed(seed)
40
-
41
- image = pipe(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  prompt=prompt,
43
- negative_prompt=negative_prompt,
44
- guidance_scale=guidance_scale,
45
- num_inference_steps=num_inference_steps,
46
- width=width,
47
- height=height,
48
- generator=generator,
49
- ).images[0]
50
-
51
- return image, seed
52
-
53
-
54
- examples = [
55
- "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k",
56
- "An astronaut riding a green horse",
57
- "A delicious ceviche cheesecake slice",
58
- ]
59
-
60
- css = """
61
- #col-container {
62
- margin: 0 auto;
63
- max-width: 640px;
64
- }
65
- """
66
-
67
- with gr.Blocks(css=css) as demo:
68
- with gr.Column(elem_id="col-container"):
69
- gr.Markdown(" # Text-to-Image Gradio Template")
70
 
71
- with gr.Row():
72
- prompt = gr.Text(
73
- label="Prompt",
74
- show_label=False,
75
- max_lines=1,
76
- placeholder="Enter your prompt",
77
- container=False,
78
- )
79
 
80
- run_button = gr.Button("Run", scale=0, variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
- result = gr.Image(label="Result", show_label=False)
83
 
84
- with gr.Accordion("Advanced Settings", open=False):
85
- negative_prompt = gr.Text(
86
- label="Negative prompt",
87
- max_lines=1,
88
- placeholder="Enter a negative prompt",
89
- visible=False,
90
- )
 
91
 
92
- seed = gr.Slider(
93
- label="Seed",
94
- minimum=0,
95
- maximum=MAX_SEED,
96
- step=1,
97
- value=0,
98
- )
99
 
100
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
101
 
102
- with gr.Row():
103
- width = gr.Slider(
104
- label="Width",
105
- minimum=256,
106
- maximum=MAX_IMAGE_SIZE,
107
- step=32,
108
- value=1024, # Replace with defaults that work for your model
109
- )
110
 
111
- height = gr.Slider(
112
- label="Height",
113
- minimum=256,
114
- maximum=MAX_IMAGE_SIZE,
115
- step=32,
116
- value=1024, # Replace with defaults that work for your model
 
 
 
 
 
 
 
 
 
117
  )
118
-
119
- with gr.Row():
120
- guidance_scale = gr.Slider(
121
- label="Guidance scale",
122
- minimum=0.0,
123
- maximum=10.0,
124
- step=0.1,
125
- value=0.0, # Replace with defaults that work for your model
126
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
- num_inference_steps = gr.Slider(
129
- label="Number of inference steps",
130
- minimum=1,
131
- maximum=50,
132
- step=1,
133
- value=2, # Replace with defaults that work for your model
134
- )
 
 
 
 
 
 
 
 
 
 
135
 
136
- gr.Examples(examples=examples, inputs=[prompt])
137
- gr.on(
138
- triggers=[run_button.click, prompt.submit],
139
- fn=infer,
140
- inputs=[
141
- prompt,
142
- negative_prompt,
143
- seed,
144
- randomize_seed,
145
- width,
146
- height,
147
- guidance_scale,
148
- num_inference_steps,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  ],
150
- outputs=[result, seed],
 
 
 
 
 
 
 
 
 
 
 
151
  )
152
 
 
153
  if __name__ == "__main__":
154
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """NEXUS Visual Weaver - Build Small Hackathon command center."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
  import gradio as gr
11
+
12
+ ROOT = Path(__file__).resolve().parent
13
+ SRC = ROOT / "src"
14
+ if str(SRC) not in sys.path:
15
+ sys.path.insert(0, str(SRC))
16
+
17
+ try:
18
+ import spaces # type: ignore # noqa: F401
19
+ except Exception: # pragma: no cover - local development does not require Spaces.
20
+ spaces = None
21
+
22
+ from nexus_visual_weaver.catalog import catalog_summary
23
+ from nexus_visual_weaver.model_relay import WeaverModelRelay
24
+ from nexus_visual_weaver.planner import build_command_center_run
25
+ from nexus_visual_weaver.render import render_catalog_table, render_command_header, render_dashboard_regions
26
+ from nexus_visual_weaver.security import scan_file
27
+ from nexus_visual_weaver.styles import APP_CSS
28
+
29
+ APP_THEME = gr.themes.Base(
30
+ primary_hue="rose",
31
+ secondary_hue="cyan",
32
+ neutral_hue="slate",
33
+ radius_size="sm",
34
+ font=["Inter", "ui-sans-serif", "system-ui"],
35
+ )
36
+
37
+
38
+ DEFAULT_PROMPT = (
39
+ "A Slavic archivist in a rain-slick neon city, wearing a structured black patent "
40
+ "leather long coat with faux fur collar, Chantilly lace neckline, glowing crimson "
41
+ "hardware, platform boots, NEXUS sigils and floating code streams behind her."
42
+ )
43
+
44
+ MODEL_RELAY = WeaverModelRelay()
45
+
46
+
47
+ def _relay_snapshot(adult_mode: bool = False) -> dict[str, Any]:
48
+ return MODEL_RELAY.dashboard_snapshot(public_demo=not adult_mode)
49
+
50
+
51
+ def _file_path(uploaded: Any) -> str | None:
52
+ if uploaded is None:
53
+ return None
54
+ if isinstance(uploaded, str):
55
+ return uploaded
56
+ path = getattr(uploaded, "name", None)
57
+ return str(path) if path else None
58
+
59
+
60
+ SECTIONS = ["Forge", "Wardrobe", "Lore", "Models", "Security", "Runs"]
61
+
62
+
63
+ def _dashboard_regions(
64
+ run: Any | None = None,
65
+ adult_mode: bool = False,
66
+ scan: dict[str, Any] | None = None,
67
+ active_section: str = "Forge",
68
+ ) -> dict[str, str]:
69
+ return render_dashboard_regions(
70
+ run=run,
71
+ adult_mode=adult_mode,
72
+ scan=scan,
73
+ relay_status=_relay_snapshot(adult_mode),
74
+ active_section=active_section,
75
+ )
76
+
77
+
78
+ def run_weave(
79
+ prompt: str,
80
+ reasoning_mode: str,
81
+ video_preset: str,
82
+ adult_mode: bool,
83
+ upload: Any,
84
+ active_section: str,
85
+ ) -> tuple[str, str, str, str, str, str, str, str, str, str, dict[str, Any], dict[str, Any], dict[str, Any]]:
86
+ prompt = prompt.strip() or DEFAULT_PROMPT
87
+ run = build_command_center_run(
88
  prompt=prompt,
89
+ mode=reasoning_mode,
90
+ video_preset=video_preset,
91
+ adult_mode=adult_mode,
92
+ )
93
+ scan = scan_file(_file_path(upload))
94
+ regions = _dashboard_regions(run=run, adult_mode=adult_mode, scan=scan, active_section=active_section)
95
+ catalog = render_catalog_table(adult_mode=adult_mode)
96
+ return (
97
+ regions["topbar"],
98
+ regions["command_rail"],
99
+ regions["workflow"],
100
+ regions["operations"],
101
+ regions["inspector"],
102
+ regions["drawer"],
103
+ regions["status"],
104
+ regions["artifacts"],
105
+ regions["providers"],
106
+ catalog,
107
+ run.to_dict(),
108
+ catalog_summary(adult_mode),
109
+ scan,
110
+ )
 
 
 
 
 
111
 
 
 
 
 
 
 
 
 
112
 
113
+ def toggle_adult_visibility(
114
+ adult_mode: bool,
115
+ active_section: str,
116
+ upload: Any,
117
+ ) -> tuple[str, str, str, str, str, str, str, dict[str, Any], dict[str, Any]]:
118
+ scan = scan_file(_file_path(upload))
119
+ regions = _dashboard_regions(adult_mode=adult_mode, scan=scan, active_section=active_section)
120
+ return (
121
+ regions["topbar"],
122
+ regions["command_rail"],
123
+ regions["operations"],
124
+ regions["inspector"],
125
+ regions["artifacts"],
126
+ regions["providers"],
127
+ render_catalog_table(adult_mode=adult_mode),
128
+ catalog_summary(adult_mode),
129
+ scan,
130
+ )
131
 
 
132
 
133
+ def refresh_section(
134
+ active_section: str,
135
+ adult_mode: bool,
136
+ upload: Any,
137
+ ) -> tuple[str, str, str, str, str, dict[str, Any]]:
138
+ scan = scan_file(_file_path(upload))
139
+ regions = _dashboard_regions(adult_mode=adult_mode, scan=scan, active_section=active_section)
140
+ return regions["command_rail"], regions["operations"], regions["inspector"], regions["artifacts"], regions["providers"], scan
141
 
 
 
 
 
 
 
 
142
 
143
+ initial_regions = _dashboard_regions(scan=scan_file(None))
144
 
145
+ with gr.Blocks(title="NEXUS Visual Weaver") as demo:
146
+ topbar_html = gr.HTML(initial_regions["topbar"], container=False)
 
 
 
 
 
 
147
 
148
+ with gr.Group(elem_id="nw-inputs", elem_classes=["nw-control-panel"]):
149
+ gr.HTML(render_command_header(), container=False)
150
+ with gr.Row():
151
+ prompt = gr.Textbox(
152
+ value=DEFAULT_PROMPT,
153
+ label="Creative Brief",
154
+ lines=3,
155
+ max_lines=6,
156
+ scale=5,
157
+ )
158
+ with gr.Column(scale=2):
159
+ reasoning_mode = gr.Radio(
160
+ ["Strict", "Frontier"],
161
+ value="Strict",
162
+ label="Reasoning Mode",
163
  )
164
+ video_preset = gr.Dropdown(
165
+ ["Wan2.2 I2V", "LTX-2.3"],
166
+ value="Wan2.2 I2V",
167
+ label="Video Path Preset",
 
 
 
 
168
  )
169
+ with gr.Row():
170
+ upload = gr.File(
171
+ label="Reference / Output For ST3GG Scan",
172
+ file_count="single",
173
+ type="filepath",
174
+ scale=3,
175
+ )
176
+ adult_mode = gr.Checkbox(
177
+ value=False,
178
+ label="Adult Mode 18+ catalog scope",
179
+ info="Off by default. Enables adult-tagged catalog entries but does not disable security, consent, or export gates.",
180
+ scale=2,
181
+ )
182
+ run_btn = gr.Button("Run Active Weave", variant="primary", scale=1)
183
+ stop_btn = gr.Button("Stop Provider Job", variant="stop", interactive=False, scale=1)
184
 
185
+ with gr.Row(elem_id="nw-workspace", elem_classes=["nw-workspace"]):
186
+ with gr.Column(scale=1, min_width=150, elem_id="nw-native-rail"):
187
+ section_nav = gr.Radio(
188
+ SECTIONS,
189
+ value="Forge",
190
+ label="Command Rail",
191
+ elem_id="nw-section-nav",
192
+ )
193
+ command_rail_html = gr.HTML(initial_regions["command_rail"], container=False)
194
+ with gr.Column(scale=5, min_width=620, elem_id="nw-main-column"):
195
+ workflow_html = gr.HTML(initial_regions["workflow"], container=False)
196
+ operations_html = gr.HTML(initial_regions["operations"], container=False)
197
+ artifact_html = gr.HTML(initial_regions["artifacts"], container=False)
198
+ drawer_html = gr.HTML(initial_regions["drawer"], container=False)
199
+ with gr.Column(scale=2, min_width=340, elem_id="nw-side-column"):
200
+ inspector_html = gr.HTML(initial_regions["inspector"], container=False)
201
+ provider_html = gr.HTML(initial_regions["providers"], container=False)
202
 
203
+ status_html = gr.HTML(initial_regions["status"], container=False)
204
+
205
+ with gr.Accordion("Catalog, run record, and security evidence", open=False):
206
+ catalog_html = gr.HTML(render_catalog_table(False), container=False)
207
+ with gr.Row():
208
+ run_json = gr.JSON(label="GenerationRun")
209
+ catalog_json = gr.JSON(label="Catalog Summary")
210
+ scan_json = gr.JSON(label="ST3GG Scan")
211
+
212
+ run_btn.click(
213
+ fn=run_weave,
214
+ inputs=[prompt, reasoning_mode, video_preset, adult_mode, upload, section_nav],
215
+ outputs=[
216
+ topbar_html,
217
+ command_rail_html,
218
+ workflow_html,
219
+ operations_html,
220
+ inspector_html,
221
+ drawer_html,
222
+ status_html,
223
+ artifact_html,
224
+ provider_html,
225
+ catalog_html,
226
+ run_json,
227
+ catalog_json,
228
+ scan_json,
229
+ ],
230
+ api_name="run_active_weave",
231
+ )
232
+ prompt.submit(
233
+ fn=run_weave,
234
+ inputs=[prompt, reasoning_mode, video_preset, adult_mode, upload, section_nav],
235
+ outputs=[
236
+ topbar_html,
237
+ command_rail_html,
238
+ workflow_html,
239
+ operations_html,
240
+ inspector_html,
241
+ drawer_html,
242
+ status_html,
243
+ artifact_html,
244
+ provider_html,
245
+ catalog_html,
246
+ run_json,
247
+ catalog_json,
248
+ scan_json,
249
+ ],
250
+ api_name=False,
251
+ )
252
+ adult_mode.change(
253
+ fn=toggle_adult_visibility,
254
+ inputs=[adult_mode, section_nav, upload],
255
+ outputs=[
256
+ topbar_html,
257
+ command_rail_html,
258
+ operations_html,
259
+ inspector_html,
260
+ artifact_html,
261
+ provider_html,
262
+ catalog_html,
263
+ catalog_json,
264
+ scan_json,
265
  ],
266
+ api_name="toggle_adult_catalog",
267
+ )
268
+ section_nav.change(
269
+ fn=refresh_section,
270
+ inputs=[section_nav, adult_mode, upload],
271
+ outputs=[command_rail_html, operations_html, inspector_html, artifact_html, provider_html, scan_json],
272
+ api_name=False,
273
+ )
274
+ demo.load(
275
+ fn=lambda: (render_catalog_table(False), catalog_summary(False), scan_file(None)),
276
+ outputs=[catalog_html, catalog_json, scan_json],
277
+ api_name=False,
278
  )
279
 
280
+
281
  if __name__ == "__main__":
282
+ if hasattr(sys.stdout, "reconfigure"):
283
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
284
+ if hasattr(sys.stderr, "reconfigure"):
285
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
286
+
287
+ demo.launch(
288
+ server_name="0.0.0.0",
289
+ server_port=int(os.environ.get("NEXUS_PORT", os.environ.get("PORT", "7860"))),
290
+ quiet=True,
291
+ mcp_server=True,
292
+ ssr_mode=False,
293
+ css=APP_CSS,
294
+ theme=APP_THEME,
295
+ )
assets/taste_profile.json ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "1.0.0",
3
+ "last_updated": "2026-06-04T04:30:00+03:00",
4
+ "source": "GROK_IMAGINE taste_profile.json, normalized for NEXUS Visual Weaver",
5
+ "description": "Locked taste profile for governed visual creation. Every prompt, outfit, image edit, and video plan is scored against this profile.",
6
+ "locked_features": {
7
+ "materials": {
8
+ "primary": ["patent_leather"],
9
+ "accents": ["faux_fur", "chantilly_lace"],
10
+ "hardware": ["crimson_hardware"]
11
+ },
12
+ "footwear": ["platform_boots"],
13
+ "model_aesthetic": {
14
+ "ethnicity": "slavic_model",
15
+ "features": ["high_cheekbones", "intense_focused_eyes", "pale_matte_skin", "full_lips"]
16
+ },
17
+ "lighting_and_atmosphere": [
18
+ "cinematic_dramatic_lighting",
19
+ "deep_obsidian_shadows",
20
+ "electric_neon_highlights",
21
+ "rain_slicked_surfaces",
22
+ "floating_code_data_streams"
23
+ ],
24
+ "thematic_elements": [
25
+ "gothic_haute_couture",
26
+ "cyberpunk_decadence",
27
+ "nexus_sigils",
28
+ "orchestrator_node_glyphs",
29
+ "holographic_butterfly_wings",
30
+ "iridescent_data_streams"
31
+ ],
32
+ "rendering": {
33
+ "style": "ultra_photorealistic_flux2",
34
+ "detail_level": "extreme_micro_detail",
35
+ "materials_detail": [
36
+ "leather_grain",
37
+ "fur_texture",
38
+ "lace_threads",
39
+ "metallic_reflections",
40
+ "skin_pores"
41
+ ]
42
+ }
43
+ },
44
+ "enforcement_rules": {
45
+ "must_include": [
46
+ "At least one primary material (patent_leather)",
47
+ "Slavic model features",
48
+ "Crimson hardware accents",
49
+ "Platform boots or equivalent strong footwear detail"
50
+ ],
51
+ "should_include": [
52
+ "Faux fur or Chantilly lace accents",
53
+ "NEXUS thematic elements",
54
+ "High-contrast material textures"
55
+ ],
56
+ "forbidden": [
57
+ "Soft pastel colors unless used as deliberate neon contrast",
58
+ "Realistic modern fashion without cyberpunk/gothic elevation",
59
+ "Low-detail or blurry material rendering"
60
+ ],
61
+ "trust_scoring_guidelines": {
62
+ "0.90-1.00": "Perfect alignment - promote to Wisdom",
63
+ "0.80-0.89": "Strong alignment - approve with minor notes",
64
+ "0.70-0.79": "Acceptable but needs refinement",
65
+ "< 0.70": "Reject or force major re-refinement"
66
+ }
67
+ }
68
+ }
69
+
docs/HACKATHON_EVALUATION.md ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hackathon Evaluation Snapshot
2
+
3
+ ## Judge-Facing Product Signal
4
+
5
+ NEXUS Visual Weaver should open as a working command center, not a landing page. The current target is a Gradio dashboard where judges can immediately see:
6
+
7
+ - a central workflow graph from prompt to human checkpoint
8
+ - a contextual operations panel for Forge, Wardrobe, Lore, Models, Security, and Runs
9
+ - a right-side inspector with taste, material, ModelRelay, and ST3GG state
10
+ - an artifact preview lane that is honest about dry-run/provider handoff status
11
+ - wardrobe and lore drawers that make gothic couture, footwear, accessories, and video continuity concrete
12
+
13
+ ## Current Strengths
14
+
15
+ - Gradio-compatible app shape with `mcp_server=True`.
16
+ - Pinned model governance is visible: FLUX.2, LocateAnything-3B, and ST3GG.
17
+ - Adult Mode starts off and is framed as catalog scope, not a safety bypass.
18
+ - ModelRelay/GMR helper rotation is represented without replacing pinned lanes.
19
+ - Tests cover catalog scope, workflow planning, ModelRelay behavior, scanner evidence, and dashboard fallback rendering.
20
+
21
+ ## Remaining Gaps
22
+
23
+ - Provider calls are still represented as dry-run handoff surfaces.
24
+ - The dashboard needs at least one judge-safe generation or mocked provider success path with clear provenance.
25
+ - Visual validation screenshots should be captured after the next UI pass.
26
+ - Docstring coverage is repo-wide 0/76 and should be handled separately if we decide to enforce that review gate.
27
+ - GitHub Actions cannot run until the account billing lock is resolved.
28
+
29
+ ## Next Implementation Priority
30
+
31
+ 1. Add a judge-safe demo run path that produces deterministic visible output without secrets.
32
+ 2. Add provider-status badges for configured, dry-run, blocked, and failed states in the top bar and artifact lane.
33
+ 3. Add Playwright/browser visual checks for desktop and mobile overflow once CI is unblocked.
34
+ 4. Prepare the Hugging Face Space README and app card with model-governance, safety, and hackathon reward framing.
docs/RELEASE_WORKFLOW.md ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Release Workflow
2
+
3
+ NEXUS Visual Weaver uses GitHub for rollback-safe development and Hugging Face Spaces for the hackathon demo.
4
+
5
+ ## Branching
6
+
7
+ - Bootstrap only: first commit can land on `main` because the public repository starts empty.
8
+ - Normal work: create `codex/specimba/<short-scope>` branches.
9
+ - Big changes: open draft pull requests and let CI plus review bots comment before merge.
10
+
11
+ ## Push Gate
12
+
13
+ Before pushing:
14
+
15
+ 1. Run `python -m compileall app.py src tests`.
16
+ 2. Run `python -m pytest -q tests -p no:cacheprovider --basetemp=C:\tmp\pytest-nvw-full`.
17
+ 3. Run a secret scan over tracked files.
18
+ 4. Confirm generated outputs, local logs, caches, Space auth folders, and provider tokens are ignored.
19
+
20
+ ## Secrets
21
+
22
+ Use Hugging Face Space secrets or local `.env` files. Do not commit real values for:
23
+
24
+ - `HF_TOKEN`
25
+ - `MODAL_TOKEN_ID`
26
+ - `MODAL_TOKEN_SECRET`
27
+ - `OPENAI_API_KEY`
28
+ - Provider-specific API keys or bearer tokens
29
+
30
+ ## Review Automation
31
+
32
+ - GitHub Actions runs compile and pytest on `main` and pull requests.
33
+ - CodeRabbit can review pull requests using `.coderabbit.yaml`.
34
+ - Human review should focus on model governance, Adult Mode gates, ST3GG export behavior, and hackathon demo clarity.
35
+
pytest.ini ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ [pytest]
2
+ pythonpath = src
3
+ testpaths = tests
4
+
requirements.txt CHANGED
@@ -1,6 +1,3 @@
1
- accelerate
2
- diffusers
3
- invisible_watermark
4
- torch
5
- transformers
6
- xformers
 
1
+ gradio==6.12.0
2
+ huggingface_hub==1.18.0
3
+ pytest==9.0.3
 
 
 
src/nexus_visual_weaver/__init__.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """NEXUS Visual Weaver command-center package."""
2
+
3
+ from .catalog import DEFAULT_ACTIVE_STACK, catalog_summary, filter_catalog, parameter_budget
4
+ from .model_relay import ContextPacket, LaneDecision, ModelRecord, WeaverModelRelay
5
+ from .planner import build_command_center_run
6
+ from .schema import (
7
+ AdapterRecipe,
8
+ CreativeRequest,
9
+ GenerationRun,
10
+ GroundingTarget,
11
+ HumanCheckpoint,
12
+ InspectionReport,
13
+ LoreBeatSet,
14
+ ModelCandidate,
15
+ OutfitGraph,
16
+ TasteProfile,
17
+ TasteRefinedPrompt,
18
+ VideoPlan,
19
+ WardrobeSlot,
20
+ WisdomRecord,
21
+ )
22
+
23
+ __all__ = [
24
+ "AdapterRecipe",
25
+ "CreativeRequest",
26
+ "GenerationRun",
27
+ "GroundingTarget",
28
+ "HumanCheckpoint",
29
+ "InspectionReport",
30
+ "LoreBeatSet",
31
+ "ModelCandidate",
32
+ "OutfitGraph",
33
+ "TasteProfile",
34
+ "TasteRefinedPrompt",
35
+ "VideoPlan",
36
+ "WardrobeSlot",
37
+ "WisdomRecord",
38
+ "ContextPacket",
39
+ "DEFAULT_ACTIVE_STACK",
40
+ "LaneDecision",
41
+ "ModelRecord",
42
+ "WeaverModelRelay",
43
+ "build_command_center_run",
44
+ "catalog_summary",
45
+ "filter_catalog",
46
+ "parameter_budget",
47
+ ]
src/nexus_visual_weaver/catalog.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Seeded HF model and adapter catalog for the command center."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .schema import AdapterRecipe, ModelCandidate
6
+
7
+ MODEL_CATALOG: list[ModelCandidate] = [
8
+ ModelCandidate(
9
+ repo_id="black-forest-labs/FLUX.2-klein-9B",
10
+ role="image_generator",
11
+ task="image-to-image",
12
+ params_b=9.0,
13
+ runtime="diffusers / provider",
14
+ license="other",
15
+ gated=True,
16
+ source_url="https://hf.co/black-forest-labs/FLUX.2-klein-9B",
17
+ ),
18
+ ModelCandidate(
19
+ repo_id="Brunobkr/OFFELLIA_Q4_0_gemma-4-12B-it.gguf",
20
+ role="multimodal_judge",
21
+ task="image-text-to-text",
22
+ params_b=12.0,
23
+ runtime="llama.cpp GGUF",
24
+ license="apache-2.0",
25
+ source_url="https://hf.co/Brunobkr/OFFELLIA_Q4_0_gemma-4-12B-it.gguf",
26
+ ),
27
+ ModelCandidate(
28
+ repo_id="nvidia/LocateAnything-3B",
29
+ role="visual_grounding",
30
+ task="image-text-to-text",
31
+ params_b=3.83,
32
+ runtime="transformers",
33
+ license="other",
34
+ source_url="https://hf.co/nvidia/LocateAnything-3B",
35
+ ),
36
+ ModelCandidate(
37
+ repo_id="openbmb/MiniCPM5-1B",
38
+ role="router",
39
+ task="text-generation / tool-calling",
40
+ params_b=1.08,
41
+ runtime="transformers",
42
+ license="apache-2.0",
43
+ source_url="https://hf.co/openbmb/MiniCPM5-1B",
44
+ ),
45
+ ModelCandidate(
46
+ repo_id="onnx-community/functiongemma-270m-it-ONNX",
47
+ role="fallback_router",
48
+ task="text-generation",
49
+ params_b=0.27,
50
+ runtime="transformers.js / ONNX",
51
+ license="gemma",
52
+ source_url="https://hf.co/onnx-community/functiongemma-270m-it-ONNX",
53
+ ),
54
+ ModelCandidate(
55
+ repo_id="Brunobkr/OFFELLIA_IQ4_XS_gemma-4-12B-it-heretic",
56
+ role="adult_mode_text_judge",
57
+ task="text-generation",
58
+ params_b=12.0,
59
+ runtime="llama.cpp GGUF",
60
+ license="other",
61
+ adult_only=True,
62
+ source_url="https://hf.co/Brunobkr/OFFELLIA_IQ4_XS_gemma-4-12B-it-heretic",
63
+ ),
64
+ ModelCandidate(
65
+ repo_id="Wan-AI/Wan2.2-I2V-A14B-Diffusers",
66
+ role="video_swap_preset",
67
+ task="image-to-video",
68
+ params_b=14.0,
69
+ runtime="diffusers / provider",
70
+ license="apache-2.0",
71
+ source_url="https://hf.co/Wan-AI/Wan2.2-I2V-A14B-Diffusers",
72
+ ),
73
+ ModelCandidate(
74
+ repo_id="Lightricks/LTX-2.3",
75
+ role="video_swap_preset",
76
+ task="image-to-video",
77
+ params_b=22.0,
78
+ runtime="diffusers",
79
+ license="other",
80
+ source_url="https://hf.co/Lightricks/LTX-2.3",
81
+ ),
82
+ ]
83
+
84
+ ADAPTER_CATALOG: list[AdapterRecipe] = [
85
+ AdapterRecipe(
86
+ repo_id="DeverStyle/Flux.2-Klein-Loras",
87
+ adapter_for="black-forest-labs/FLUX.2-klein-9B",
88
+ task="text-to-image style stack",
89
+ license="apache-2.0",
90
+ ),
91
+ AdapterRecipe(
92
+ repo_id="nomadoor/flux-2-klein-9B-schematic-lora",
93
+ adapter_for="black-forest-labs/FLUX.2-klein-base-9B",
94
+ task="pose/depth/segmentation schematic control",
95
+ license="other",
96
+ ),
97
+ AdapterRecipe(
98
+ repo_id="fal/Qwen-Image-Edit-2511-Multiple-Angles-LoRA",
99
+ adapter_for="Qwen/Qwen-Image-Edit-2511",
100
+ task="camera-angle edit",
101
+ license="apache-2.0",
102
+ ),
103
+ AdapterRecipe(
104
+ repo_id="joyfox/LTX-2.3-Transition-LORA",
105
+ adapter_for="Lightricks/LTX-2.3",
106
+ task="image-to-video transition",
107
+ license="apache-2.0",
108
+ ),
109
+ AdapterRecipe(
110
+ repo_id="LiconStudio/Ltx2.3-VBVR-lora-I2V",
111
+ adapter_for="Lightricks/LTX-2.3",
112
+ task="video reasoning / I2V",
113
+ license="other",
114
+ ),
115
+ AdapterRecipe(
116
+ repo_id="ScottzillaSystems/qwen-image-edit-plus-nsfw-lora",
117
+ adapter_for="Qwen/Qwen-Image-Edit-2511",
118
+ task="adult image edit catalog entry",
119
+ license="openrail++",
120
+ adult_only=True,
121
+ ),
122
+ AdapterRecipe(
123
+ repo_id="lopi999/Wan2.2-I2V_General-NSFW-LoRA",
124
+ adapter_for="Wan-AI/Wan2.2-I2V-A14B",
125
+ task="adult video adapter catalog entry",
126
+ license="unknown",
127
+ adult_only=True,
128
+ ),
129
+ ]
130
+
131
+ DEFAULT_ACTIVE_STACK = [
132
+ "black-forest-labs/FLUX.2-klein-9B",
133
+ "Brunobkr/OFFELLIA_Q4_0_gemma-4-12B-it.gguf",
134
+ "nvidia/LocateAnything-3B",
135
+ "openbmb/MiniCPM5-1B",
136
+ ]
137
+
138
+
139
+ def filter_catalog(adult_mode: bool = False) -> tuple[list[ModelCandidate], list[AdapterRecipe]]:
140
+ models = [model for model in MODEL_CATALOG if adult_mode or not model.adult_only]
141
+ adapters = [adapter for adapter in ADAPTER_CATALOG if adult_mode or not adapter.adult_only]
142
+ return models, adapters
143
+
144
+
145
+ def active_stack(adult_mode: bool = False) -> list[ModelCandidate]:
146
+ allowed, _ = filter_catalog(adult_mode)
147
+ by_id = {model.repo_id: model for model in allowed}
148
+ return [by_id[repo_id] for repo_id in DEFAULT_ACTIVE_STACK if repo_id in by_id]
149
+
150
+
151
+ def parameter_budget(stack: list[ModelCandidate] | None = None) -> dict[str, float | str]:
152
+ chosen = stack or active_stack(False)
153
+ total = round(sum(model.params_b for model in chosen), 2)
154
+ return {
155
+ "active_b": total,
156
+ "limit_b": 32.0,
157
+ "remaining_b": round(32.0 - total, 2),
158
+ "status": "pass" if total <= 32.0 else "over_budget",
159
+ }
160
+
161
+
162
+ def catalog_summary(adult_mode: bool = False) -> dict[str, int | float | str]:
163
+ models, adapters = filter_catalog(adult_mode)
164
+ budget = parameter_budget(active_stack(adult_mode))
165
+ return {
166
+ "models_visible": len(models),
167
+ "adapters_visible": len(adapters),
168
+ "adult_catalog": "enabled" if adult_mode else "hidden",
169
+ **budget,
170
+ }
171
+
src/nexus_visual_weaver/grounding.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """LocateAnything-style grounding simulation for planning and UI state."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .schema import GroundingTarget, InspectionReport, OutfitGraph
6
+
7
+
8
+ def inspect_outfit(outfit: OutfitGraph) -> InspectionReport:
9
+ targets: list[GroundingTarget] = []
10
+ for slot in outfit.slots:
11
+ if slot.name in {"outerwear", "upper_body", "footwear", "jewelry", "background_context"}:
12
+ confidence = 0.92 if slot.locked else 0.78
13
+ targets.append(
14
+ GroundingTarget(
15
+ slot_name=slot.name,
16
+ query=f"locate {slot.description}",
17
+ expected_region=slot.locate_region,
18
+ confidence=confidence,
19
+ )
20
+ )
21
+
22
+ drift_flags: list[str] = []
23
+ if not any(slot.name == "footwear" and slot.locked for slot in outfit.slots):
24
+ drift_flags.append("footwear requires stronger prompt lock")
25
+ if outfit.score < 0.78:
26
+ drift_flags.append("material contrast needs refinement before render")
27
+
28
+ return InspectionReport(
29
+ status="pass" if not drift_flags else "review",
30
+ targets=targets,
31
+ drift_flags=drift_flags,
32
+ )
33
+
src/nexus_visual_weaver/lore.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Lore beat and checkpointed video planning."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .schema import LoreBeatSet, VideoPlan
6
+
7
+
8
+ def build_lore_beats(prompt: str) -> LoreBeatSet:
9
+ seed = prompt.strip()[:90] or "The Archivist enters the neon archive."
10
+ beats = [
11
+ {"id": "01", "title": "The Archivist", "cue": seed},
12
+ {"id": "02", "title": "Neon Gates", "cue": "rain-lit threshold, NEXUS glyphs waking in the glass"},
13
+ {"id": "03", "title": "The Confrontation", "cue": "wardrobe details and crimson hardware become identity anchors"},
14
+ {"id": "04", "title": "Shattered Truth", "cue": "LocateAnything confirms outfit continuity under motion"},
15
+ {"id": "05", "title": "Raven's Choice", "cue": "human checkpoint decides whether to promote the run"},
16
+ {"id": "06", "title": "Into the Rain", "cue": "video path locks the final camera move and frame pacing"},
17
+ ]
18
+ return LoreBeatSet(beats=beats, tone="gothic couture cyberpunk / controlled cinematic tension")
19
+
20
+
21
+ def build_video_plan(preset: str = "Wan2.2 I2V") -> VideoPlan:
22
+ if "LTX" in preset:
23
+ return VideoPlan(
24
+ preset="LTX-2.3",
25
+ source="approved image candidate",
26
+ camera_move="slow parallax push with fabric and rain continuity locks",
27
+ duration_seconds=5.3,
28
+ fps=24,
29
+ continuity_locks=["wardrobe slots", "face/pose", "crimson hardware", "rain direction"],
30
+ )
31
+ return VideoPlan(
32
+ preset="Wan2.2 I2V",
33
+ source="approved image candidate",
34
+ camera_move="controlled dolly-in with coat, lace, boots, and code-stream stabilization",
35
+ duration_seconds=4.8,
36
+ fps=24,
37
+ continuity_locks=["outerwear", "footwear", "jewelry", "NEXUS sigils"],
38
+ )
39
+
src/nexus_visual_weaver/model_relay.py ADDED
@@ -0,0 +1,823 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Quota-aware helper model rotation for NEXUS Visual Weaver.
2
+
3
+ The relay mirrors the useful GMR/ModelRelay ideas from NEXUS without copying
4
+ the source: pinned creative anchors stay fixed, helper lanes can rotate, and
5
+ all decisions carry a compact context packet for fallback continuity.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Callable
11
+ from dataclasses import asdict, dataclass, field
12
+ from datetime import datetime, timedelta, timezone
13
+ from typing import Any
14
+
15
+
16
+ PINNED_LANES = {"image_generation", "grounding", "security"}
17
+ ROTATABLE_LANES = {
18
+ "prompt_router",
19
+ "taste_judge",
20
+ "audio_lore_tts",
21
+ "video_repair",
22
+ "hf_catalog_research",
23
+ "modal_job_runner",
24
+ }
25
+ PUBLIC_SAFE_LICENSES = {"apache-2.0", "mit", "bsd-3-clause", "gemma", "public_safe", "openrail"}
26
+ PRIVATE_LICENSES = {"private_research", "research_noncommercial", "commercial_required", "other", "unknown", "review_required"}
27
+ STRATEGY_ALIASES = {
28
+ "speed": "latency_first",
29
+ "fast": "latency_first",
30
+ "safe_public": "license_safe_public",
31
+ "public": "license_safe_public",
32
+ "private": "private_research",
33
+ }
34
+ DEFAULT_ROTATION_BUDGET_B = 5.0
35
+
36
+
37
+ def utc_now() -> datetime:
38
+ return datetime.now(timezone.utc)
39
+
40
+
41
+ def _iso(value: datetime | None) -> str | None:
42
+ return value.isoformat(timespec="seconds") if value else None
43
+
44
+
45
+ @dataclass
46
+ class ContextPacket:
47
+ lane: str
48
+ task: str
49
+ public_demo: bool
50
+ budget_b: float
51
+ metadata: dict[str, Any] = field(default_factory=dict)
52
+
53
+ def to_dict(self) -> dict[str, Any]:
54
+ return asdict(self)
55
+
56
+
57
+ @dataclass
58
+ class ModelRecord:
59
+ model_id: str
60
+ lane: str
61
+ provider: str
62
+ repo_id: str
63
+ license_gate: str
64
+ params_b: float
65
+ cost_hint: str
66
+ rpm_limit: int
67
+ rpd_limit: int
68
+ cooldown_until: datetime | None = None
69
+ health: str = "healthy"
70
+ latency_ms: int = 500
71
+ quality_score: float = 0.75
72
+ fallback_chain: tuple[str, ...] = ()
73
+ pinned: bool = False
74
+ adult_capable: bool = False
75
+ last_failure: str | None = None
76
+ success_count: int = 0
77
+ failure_count: int = 0
78
+ minute_calls: list[datetime] = field(default_factory=list)
79
+ day_calls: list[datetime] = field(default_factory=list)
80
+
81
+ @property
82
+ def public_safe(self) -> bool:
83
+ return self.license_gate in PUBLIC_SAFE_LICENSES and not self.adult_capable
84
+
85
+ def in_cooldown(self, now: datetime | None = None) -> bool:
86
+ now = now or utc_now()
87
+ return bool(self.cooldown_until and self.cooldown_until > now)
88
+
89
+ def to_dict(self, now: datetime | None = None) -> dict[str, Any]:
90
+ now = now or utc_now()
91
+ return {
92
+ "model_id": self.model_id,
93
+ "lane": self.lane,
94
+ "provider": self.provider,
95
+ "repo_id": self.repo_id,
96
+ "license_gate": self.license_gate,
97
+ "params_b": self.params_b,
98
+ "cost_hint": self.cost_hint,
99
+ "rpm_limit": self.rpm_limit,
100
+ "rpd_limit": self.rpd_limit,
101
+ "cooldown_until": _iso(self.cooldown_until),
102
+ "in_cooldown": self.in_cooldown(now),
103
+ "health": self.health,
104
+ "latency_ms": self.latency_ms,
105
+ "quality_score": self.quality_score,
106
+ "fallback_chain": list(self.fallback_chain),
107
+ "pinned": self.pinned,
108
+ "adult_capable": self.adult_capable,
109
+ "last_failure": self.last_failure,
110
+ "success_count": self.success_count,
111
+ "failure_count": self.failure_count,
112
+ "rpm_used": len(self.minute_calls),
113
+ "rpd_used": len(self.day_calls),
114
+ }
115
+
116
+
117
+ @dataclass
118
+ class LaneDecision:
119
+ lane: str
120
+ strategy: str
121
+ primary: ModelRecord | None
122
+ fallbacks: list[ModelRecord]
123
+ reason: str
124
+ expected_cost_hint: str
125
+ quota_impact: dict[str, Any]
126
+ context_packet: ContextPacket
127
+ skipped: list[str] = field(default_factory=list)
128
+
129
+ @property
130
+ def pinned(self) -> bool:
131
+ return bool(self.primary and self.primary.pinned)
132
+
133
+ @property
134
+ def rotatable(self) -> bool:
135
+ return self.lane in ROTATABLE_LANES
136
+
137
+ def to_dict(self, now: datetime | None = None) -> dict[str, Any]:
138
+ now = now or utc_now()
139
+ return {
140
+ "lane": self.lane,
141
+ "strategy": self.strategy,
142
+ "primary": self.primary.to_dict(now) if self.primary else None,
143
+ "fallbacks": [record.to_dict(now) for record in self.fallbacks],
144
+ "reason": self.reason,
145
+ "expected_cost_hint": self.expected_cost_hint,
146
+ "quota_impact": self.quota_impact,
147
+ "context_packet": self.context_packet.to_dict(),
148
+ "skipped": self.skipped,
149
+ "pinned": self.pinned,
150
+ "rotatable": self.rotatable,
151
+ }
152
+
153
+
154
+ @dataclass
155
+ class _DedupEntry:
156
+ value: Any
157
+ expires_at: datetime
158
+
159
+
160
+ class WeaverModelRelay:
161
+ """Selects helper models while preserving pinned creative anchors."""
162
+
163
+ def __init__(
164
+ self,
165
+ records: list[ModelRecord] | None = None,
166
+ now_fn: Callable[[], datetime] = utc_now,
167
+ ) -> None:
168
+ self._now_fn = now_fn
169
+ self.records: dict[str, ModelRecord] = {record.model_id: record for record in (records or default_model_records())}
170
+ self._dedup: dict[str, _DedupEntry] = {}
171
+ self._dedup_hits = 0
172
+
173
+ def select_lane(
174
+ self,
175
+ lane: str,
176
+ task: str = "",
177
+ budget: float | None = None,
178
+ public_demo: bool = True,
179
+ strategy: str = "quality_first",
180
+ ) -> LaneDecision:
181
+ lane = self.normalize_lane(lane)
182
+ strategy = self.normalize_strategy(strategy)
183
+ budget_b = float(budget if budget is not None else (32.0 if lane in PINNED_LANES else DEFAULT_ROTATION_BUDGET_B))
184
+ now = self._now()
185
+ context = ContextPacket(lane=lane, task=task or lane, public_demo=public_demo, budget_b=budget_b)
186
+
187
+ lane_records = [record for record in self.records.values() if record.lane == lane]
188
+ if lane in PINNED_LANES:
189
+ primary = next((record for record in lane_records if record.pinned), None)
190
+ return LaneDecision(
191
+ lane=lane,
192
+ strategy="pinned",
193
+ primary=primary,
194
+ fallbacks=[],
195
+ reason="Pinned core lane; rotation disabled for creative identity, grounding, or security.",
196
+ expected_cost_hint=primary.cost_hint if primary else "unavailable",
197
+ quota_impact=self._quota_impact(primary, now) if primary else {},
198
+ context_packet=context,
199
+ skipped=[] if primary else [f"{lane}: no pinned model registered"],
200
+ )
201
+
202
+ candidates, skipped = self._eligible_records(lane_records, budget_b, public_demo, strategy, now)
203
+ ordered = sorted(candidates, key=lambda record: self._score(record, strategy, now), reverse=True)
204
+ primary = ordered[0] if ordered else None
205
+ fallbacks = ordered[1:4]
206
+ if primary:
207
+ reason = self._decision_reason(primary, strategy, public_demo)
208
+ cost_hint = primary.cost_hint
209
+ quota_impact = self._quota_impact(primary, now)
210
+ else:
211
+ reason = "No eligible helper model for lane after budget, license, health, and quota filters."
212
+ cost_hint = "blocked"
213
+ quota_impact = {"status": "blocked"}
214
+
215
+ return LaneDecision(
216
+ lane=lane,
217
+ strategy=strategy,
218
+ primary=primary,
219
+ fallbacks=fallbacks,
220
+ reason=reason,
221
+ expected_cost_hint=cost_hint,
222
+ quota_impact=quota_impact,
223
+ context_packet=context,
224
+ skipped=skipped,
225
+ )
226
+
227
+ def record_success(self, model_id: str, latency_ms: int | None = None) -> None:
228
+ record = self._require_model(model_id)
229
+ now = self._now()
230
+ self._prune_calls(record, now)
231
+ record.minute_calls.append(now)
232
+ record.day_calls.append(now)
233
+ record.success_count += 1
234
+ record.last_failure = None
235
+ record.health = "healthy"
236
+ if latency_ms is not None:
237
+ record.latency_ms = int((record.latency_ms * 0.7) + (latency_ms * 0.3))
238
+
239
+ def record_failure(self, model_id: str, error: str = "execution failed") -> None:
240
+ record = self._require_model(model_id)
241
+ record.failure_count += 1
242
+ record.last_failure = error
243
+ record.health = "degraded" if record.failure_count < 3 else "unhealthy"
244
+ if record.failure_count >= 3:
245
+ self.enter_cooldown(model_id, retry_after_seconds=300)
246
+
247
+ def enter_cooldown(self, model_id: str, retry_after_seconds: int = 60) -> None:
248
+ record = self._require_model(model_id)
249
+ record.cooldown_until = self._now() + timedelta(seconds=retry_after_seconds)
250
+
251
+ def metadata_lookup(self, key: str, resolver: Callable[[], Any], ttl_seconds: int = 300) -> Any:
252
+ now = self._now()
253
+ cached = self._dedup.get(key)
254
+ if cached and cached.expires_at > now:
255
+ self._dedup_hits += 1
256
+ return cached.value
257
+ value = resolver()
258
+ self._dedup[key] = _DedupEntry(value=value, expires_at=now + timedelta(seconds=ttl_seconds))
259
+ return value
260
+
261
+ def get_rotation_status(self) -> dict[str, Any]:
262
+ now = self._now()
263
+ for record in self.records.values():
264
+ self._prune_calls(record, now)
265
+ pinned = {record.lane: record.to_dict(now) for record in self.records.values() if record.pinned}
266
+ lanes = {}
267
+ for lane in sorted(PINNED_LANES | ROTATABLE_LANES):
268
+ lane_records = [record for record in self.records.values() if record.lane == lane]
269
+ if not lane_records:
270
+ continue
271
+ blocked = [record for record in lane_records if self._quota_blocked(record, now) or record.in_cooldown(now)]
272
+ lanes[lane] = {
273
+ "pinned": lane in PINNED_LANES,
274
+ "models": len(lane_records),
275
+ "blocked": len(blocked),
276
+ "healthy": sum(1 for record in lane_records if record.health == "healthy"),
277
+ }
278
+ rotation_safe = all(record.health != "unhealthy" and not record.in_cooldown(now) for record in self.records.values() if record.pinned)
279
+ return {
280
+ "rotation_safe": rotation_safe,
281
+ "pinned": pinned,
282
+ "lanes": lanes,
283
+ "dedup_cache_size": len(self._dedup),
284
+ "dedup_hits": self._dedup_hits,
285
+ "updated_at": _iso(now),
286
+ }
287
+
288
+ def dashboard_snapshot(self, public_demo: bool = True) -> dict[str, Any]:
289
+ status = self.get_rotation_status()
290
+ preview_lanes = [
291
+ ("prompt_router", "strict tool JSON and prompt routing", "latency_first"),
292
+ ("taste_judge", "taste/profile checkpoint", "quality_first"),
293
+ ("audio_lore_tts", "optional lore narration, off by default", "license_safe_public" if public_demo else "quality_first"),
294
+ ("hf_catalog_research", "HF metadata search/cache", "quota_saver"),
295
+ ("modal_job_runner", "Modal credit jobs and LoRA evaluation", "private_research" if not public_demo else "license_safe_public"),
296
+ ]
297
+ status["decisions"] = [
298
+ self.select_lane(lane, task=task, public_demo=public_demo, strategy=strategy).to_dict(self._now())
299
+ for lane, task, strategy in preview_lanes
300
+ ]
301
+ return status
302
+
303
+ @staticmethod
304
+ def normalize_strategy(strategy: str) -> str:
305
+ lowered = strategy.strip().lower()
306
+ normalized = STRATEGY_ALIASES.get(lowered, lowered)
307
+ allowed = {"quality_first", "quota_saver", "latency_first", "license_safe_public", "private_research"}
308
+ return normalized if normalized in allowed else "quality_first"
309
+
310
+ @staticmethod
311
+ def normalize_lane(lane: str) -> str:
312
+ normalized = lane.strip().lower().replace("-", "_").replace(" ", "_")
313
+ aliases = {
314
+ "image": "image_generation",
315
+ "locate": "grounding",
316
+ "st3gg": "security",
317
+ "router": "prompt_router",
318
+ "judge": "taste_judge",
319
+ "tts": "audio_lore_tts",
320
+ "catalog": "hf_catalog_research",
321
+ "modal": "modal_job_runner",
322
+ }
323
+ return aliases.get(normalized, normalized)
324
+
325
+ def _eligible_records(
326
+ self,
327
+ records: list[ModelRecord],
328
+ budget_b: float,
329
+ public_demo: bool,
330
+ strategy: str,
331
+ now: datetime,
332
+ ) -> tuple[list[ModelRecord], list[str]]:
333
+ eligible: list[ModelRecord] = []
334
+ skipped: list[str] = []
335
+ require_public_safe = public_demo or strategy == "license_safe_public"
336
+ for record in records:
337
+ self._prune_calls(record, now)
338
+ if record.pinned:
339
+ skipped.append(f"{record.model_id}: pinned lane cannot rotate")
340
+ continue
341
+ if record.health in {"excluded", "unhealthy"}:
342
+ skipped.append(f"{record.model_id}: health={record.health}")
343
+ continue
344
+ if record.params_b > budget_b:
345
+ skipped.append(f"{record.model_id}: {record.params_b:.2f}B exceeds {budget_b:.2f}B helper budget")
346
+ continue
347
+ if require_public_safe and not record.public_safe:
348
+ skipped.append(f"{record.model_id}: license gate {record.license_gate} excluded for public demo")
349
+ continue
350
+ if record.in_cooldown(now):
351
+ skipped.append(f"{record.model_id}: cooldown active until {_iso(record.cooldown_until)}")
352
+ continue
353
+ if self._quota_blocked(record, now):
354
+ record.cooldown_until = now + timedelta(seconds=60)
355
+ skipped.append(f"{record.model_id}: quota exhausted, cooldown entered")
356
+ continue
357
+ eligible.append(record)
358
+ return eligible, skipped
359
+
360
+ def _score(self, record: ModelRecord, strategy: str, now: datetime) -> float:
361
+ rpm_headroom = 1.0 - (len(record.minute_calls) / max(record.rpm_limit, 1))
362
+ rpd_headroom = 1.0 - (len(record.day_calls) / max(record.rpd_limit, 1))
363
+ quota_headroom = max(0.0, (rpm_headroom + rpd_headroom) / 2)
364
+ latency_score = 1.0 / max(record.latency_ms, 1)
365
+ provider_bonus = 0.06 if record.provider in {"local", "hf_cli", "hf_api"} else 0.0
366
+ public_bonus = 0.08 if record.public_safe else 0.0
367
+ health_penalty = 0.12 if record.health == "degraded" else 0.0
368
+
369
+ if strategy == "quota_saver":
370
+ return (quota_headroom * 0.46) + (provider_bonus * 2.0) + (latency_score * 40.0) + (record.quality_score * 0.18) - health_penalty
371
+ if strategy == "latency_first":
372
+ return (latency_score * 250.0) + (record.quality_score * 0.28) + (quota_headroom * 0.20) + provider_bonus - health_penalty
373
+ if strategy == "license_safe_public":
374
+ return (public_bonus * 2.0) + (record.quality_score * 0.55) + (quota_headroom * 0.25) + (latency_score * 40.0) - health_penalty
375
+ if strategy == "private_research":
376
+ private_bonus = 0.06 if record.license_gate in PRIVATE_LICENSES else 0.0
377
+ return (record.quality_score * 0.88) + (quota_headroom * 0.10) + (latency_score * 5.0) + private_bonus - health_penalty
378
+ return (record.quality_score * 0.64) + (quota_headroom * 0.20) + (latency_score * 30.0) + public_bonus - health_penalty
379
+
380
+ def _decision_reason(self, primary: ModelRecord, strategy: str, public_demo: bool) -> str:
381
+ if strategy == "license_safe_public":
382
+ return f"{primary.model_id} selected because it is public-demo safe and within helper budget."
383
+ if strategy == "quota_saver":
384
+ return f"{primary.model_id} selected to preserve provider quota and reuse cheaper metadata paths."
385
+ if strategy == "latency_first":
386
+ return f"{primary.model_id} selected for fast dashboard feedback."
387
+ if strategy == "private_research" and not public_demo:
388
+ return f"{primary.model_id} selected for private research quality; public export gates still apply."
389
+ return f"{primary.model_id} selected by quality-first helper rotation."
390
+
391
+ def _quota_impact(self, record: ModelRecord | None, now: datetime) -> dict[str, Any]:
392
+ if record is None:
393
+ return {"status": "blocked"}
394
+ self._prune_calls(record, now)
395
+ return {
396
+ "status": "ready" if not self._quota_blocked(record, now) and not record.in_cooldown(now) else "limited",
397
+ "provider": record.provider,
398
+ "rpm_used": len(record.minute_calls),
399
+ "rpm_limit": record.rpm_limit,
400
+ "rpd_used": len(record.day_calls),
401
+ "rpd_limit": record.rpd_limit,
402
+ "cooldown_until": _iso(record.cooldown_until),
403
+ }
404
+
405
+ def _quota_blocked(self, record: ModelRecord, now: datetime) -> bool:
406
+ self._prune_calls(record, now)
407
+ return len(record.minute_calls) >= record.rpm_limit or len(record.day_calls) >= record.rpd_limit
408
+
409
+ def _prune_calls(self, record: ModelRecord, now: datetime) -> None:
410
+ minute_cutoff = now - timedelta(minutes=1)
411
+ day_cutoff = now - timedelta(days=1)
412
+ record.minute_calls = [stamp for stamp in record.minute_calls if stamp > minute_cutoff]
413
+ record.day_calls = [stamp for stamp in record.day_calls if stamp > day_cutoff]
414
+ if record.cooldown_until and record.cooldown_until <= now:
415
+ record.cooldown_until = None
416
+
417
+ def _require_model(self, model_id: str) -> ModelRecord:
418
+ try:
419
+ return self.records[model_id]
420
+ except KeyError as exc:
421
+ raise KeyError(f"Unknown model_id: {model_id}") from exc
422
+
423
+ def _now(self) -> datetime:
424
+ value = self._now_fn()
425
+ if value.tzinfo is None:
426
+ return value.replace(tzinfo=timezone.utc)
427
+ return value
428
+
429
+
430
+ def default_model_records() -> list[ModelRecord]:
431
+ return [
432
+ ModelRecord(
433
+ model_id="flux2-klein-primary",
434
+ lane="image_generation",
435
+ provider="hf",
436
+ repo_id="black-forest-labs/FLUX.2-klein-9B",
437
+ license_gate="review_required",
438
+ params_b=9.0,
439
+ cost_hint="provider_or_local",
440
+ rpm_limit=8,
441
+ rpd_limit=60,
442
+ quality_score=0.96,
443
+ latency_ms=26000,
444
+ pinned=True,
445
+ ),
446
+ ModelRecord(
447
+ model_id="locateanything-3b-anchor",
448
+ lane="grounding",
449
+ provider="hf_nvidia",
450
+ repo_id="nvidia/LocateAnything-3B",
451
+ license_gate="review_required",
452
+ params_b=3.83,
453
+ cost_hint="provider_or_local",
454
+ rpm_limit=30,
455
+ rpd_limit=300,
456
+ quality_score=0.92,
457
+ latency_ms=1800,
458
+ pinned=True,
459
+ ),
460
+ ModelRecord(
461
+ model_id="st3gg-local-scan",
462
+ lane="security",
463
+ provider="local",
464
+ repo_id="ST3GG/local-defensive-scan",
465
+ license_gate="internal",
466
+ params_b=0.0,
467
+ cost_hint="local",
468
+ rpm_limit=10000,
469
+ rpd_limit=100000,
470
+ quality_score=0.9,
471
+ latency_ms=20,
472
+ pinned=True,
473
+ ),
474
+ ModelRecord(
475
+ model_id="functiongemma-270m-router",
476
+ lane="prompt_router",
477
+ provider="local",
478
+ repo_id="onnx-community/functiongemma-270m-it-ONNX",
479
+ license_gate="gemma",
480
+ params_b=0.27,
481
+ cost_hint="local_free",
482
+ rpm_limit=240,
483
+ rpd_limit=10000,
484
+ quality_score=0.74,
485
+ latency_ms=80,
486
+ fallback_chain=("minicpm5-1b-router", "qwen3-0.6b-router"),
487
+ ),
488
+ ModelRecord(
489
+ model_id="minicpm5-1b-router",
490
+ lane="prompt_router",
491
+ provider="hf",
492
+ repo_id="openbmb/MiniCPM5-1B",
493
+ license_gate="apache-2.0",
494
+ params_b=1.08,
495
+ cost_hint="free_tier",
496
+ rpm_limit=60,
497
+ rpd_limit=1000,
498
+ quality_score=0.79,
499
+ latency_ms=160,
500
+ fallback_chain=("functiongemma-270m-router", "qwen3-0.6b-router"),
501
+ ),
502
+ ModelRecord(
503
+ model_id="qwen3-0.6b-router",
504
+ lane="prompt_router",
505
+ provider="hf",
506
+ repo_id="Qwen/Qwen3-0.6B",
507
+ license_gate="apache-2.0",
508
+ params_b=0.60,
509
+ cost_hint="free_tier",
510
+ rpm_limit=60,
511
+ rpd_limit=1000,
512
+ quality_score=0.72,
513
+ latency_ms=130,
514
+ ),
515
+ ModelRecord(
516
+ model_id="netlify-ai-gateway-helper",
517
+ lane="prompt_router",
518
+ provider="netlify",
519
+ repo_id="netlify/ai-gateway",
520
+ license_gate="public_safe",
521
+ params_b=0.0,
522
+ cost_hint="optional_gateway_secret_required",
523
+ rpm_limit=0,
524
+ rpd_limit=0,
525
+ quality_score=0.76,
526
+ latency_ms=320,
527
+ health="excluded",
528
+ ),
529
+ ModelRecord(
530
+ model_id="cloudflare-agent-helper",
531
+ lane="prompt_router",
532
+ provider="cloudflare",
533
+ repo_id="cloudflare/agents-sdk",
534
+ license_gate="public_safe",
535
+ params_b=0.0,
536
+ cost_hint="optional_post_mvp_agent",
537
+ rpm_limit=0,
538
+ rpd_limit=0,
539
+ quality_score=0.72,
540
+ latency_ms=280,
541
+ health="excluded",
542
+ ),
543
+ ModelRecord(
544
+ model_id="offellia-gemma4-12b-private",
545
+ lane="taste_judge",
546
+ provider="local",
547
+ repo_id="Brunobkr/OFFELLIA_Q4_0_gemma-4-12B-it.gguf",
548
+ license_gate="private_research",
549
+ params_b=12.0,
550
+ cost_hint="local_gpu",
551
+ rpm_limit=20,
552
+ rpd_limit=200,
553
+ quality_score=0.95,
554
+ latency_ms=4200,
555
+ fallback_chain=("nemotron-mini-4b-judge", "smolvlm2-2.2b-judge"),
556
+ ),
557
+ ModelRecord(
558
+ model_id="nemotron-mini-4b-judge",
559
+ lane="taste_judge",
560
+ provider="hf_nvidia",
561
+ repo_id="nvidia/Nemotron-Mini-4B-Instruct",
562
+ license_gate="public_safe",
563
+ params_b=4.0,
564
+ cost_hint="free_tier_or_provider",
565
+ rpm_limit=35,
566
+ rpd_limit=600,
567
+ quality_score=0.86,
568
+ latency_ms=900,
569
+ fallback_chain=("smolvlm2-2.2b-judge", "functiongemma-270m-judge-lite"),
570
+ ),
571
+ ModelRecord(
572
+ model_id="smolvlm2-2.2b-judge",
573
+ lane="taste_judge",
574
+ provider="hf",
575
+ repo_id="HuggingFaceTB/SmolVLM2-2.2B-Instruct",
576
+ license_gate="apache-2.0",
577
+ params_b=2.2,
578
+ cost_hint="free_tier",
579
+ rpm_limit=45,
580
+ rpd_limit=800,
581
+ quality_score=0.81,
582
+ latency_ms=760,
583
+ ),
584
+ ModelRecord(
585
+ model_id="functiongemma-270m-judge-lite",
586
+ lane="taste_judge",
587
+ provider="local",
588
+ repo_id="onnx-community/functiongemma-270m-it-ONNX",
589
+ license_gate="gemma",
590
+ params_b=0.27,
591
+ cost_hint="local_free",
592
+ rpm_limit=240,
593
+ rpd_limit=10000,
594
+ quality_score=0.64,
595
+ latency_ms=90,
596
+ ),
597
+ ModelRecord(
598
+ model_id="dia-1.6b-tts",
599
+ lane="audio_lore_tts",
600
+ provider="hf",
601
+ repo_id="nari-labs/Dia-1.6B",
602
+ license_gate="review_required",
603
+ params_b=1.6,
604
+ cost_hint="free_tier_or_modal",
605
+ rpm_limit=25,
606
+ rpd_limit=300,
607
+ quality_score=0.88,
608
+ latency_ms=2100,
609
+ ),
610
+ ModelRecord(
611
+ model_id="vibevoice-1.5b-tts",
612
+ lane="audio_lore_tts",
613
+ provider="hf",
614
+ repo_id="microsoft/VibeVoice-1.5B",
615
+ license_gate="review_required",
616
+ params_b=1.5,
617
+ cost_hint="free_tier_or_modal",
618
+ rpm_limit=25,
619
+ rpd_limit=300,
620
+ quality_score=0.85,
621
+ latency_ms=1900,
622
+ ),
623
+ ModelRecord(
624
+ model_id="qwen3-tts-1.7b",
625
+ lane="audio_lore_tts",
626
+ provider="hf",
627
+ repo_id="Qwen/Qwen3-TTS-1.7B",
628
+ license_gate="review_required",
629
+ params_b=1.7,
630
+ cost_hint="free_tier_or_modal",
631
+ rpm_limit=25,
632
+ rpd_limit=300,
633
+ quality_score=0.84,
634
+ latency_ms=1800,
635
+ ),
636
+ ModelRecord(
637
+ model_id="voxcpm2-tts",
638
+ lane="audio_lore_tts",
639
+ provider="hf",
640
+ repo_id="openbmb/VoxCPM2",
641
+ license_gate="apache-2.0",
642
+ params_b=2.4,
643
+ cost_hint="free_tier_or_modal",
644
+ rpm_limit=25,
645
+ rpd_limit=300,
646
+ quality_score=0.82,
647
+ latency_ms=1700,
648
+ ),
649
+ ModelRecord(
650
+ model_id="zonos-tts",
651
+ lane="audio_lore_tts",
652
+ provider="hf",
653
+ repo_id="Zyphra/Zonos-v0.1-transformer",
654
+ license_gate="apache-2.0",
655
+ params_b=1.6,
656
+ cost_hint="free_tier_or_modal",
657
+ rpm_limit=25,
658
+ rpd_limit=300,
659
+ quality_score=0.81,
660
+ latency_ms=1600,
661
+ ),
662
+ ModelRecord(
663
+ model_id="kokoro-82m-tts",
664
+ lane="audio_lore_tts",
665
+ provider="local",
666
+ repo_id="hexgrad/Kokoro-82M",
667
+ license_gate="apache-2.0",
668
+ params_b=0.082,
669
+ cost_hint="local_free",
670
+ rpm_limit=240,
671
+ rpd_limit=10000,
672
+ quality_score=0.73,
673
+ latency_ms=120,
674
+ ),
675
+ ModelRecord(
676
+ model_id="chatterbox-tts",
677
+ lane="audio_lore_tts",
678
+ provider="hf",
679
+ repo_id="ResembleAI/chatterbox",
680
+ license_gate="mit",
681
+ params_b=0.5,
682
+ cost_hint="free_tier",
683
+ rpm_limit=40,
684
+ rpd_limit=800,
685
+ quality_score=0.76,
686
+ latency_ms=420,
687
+ ),
688
+ ModelRecord(
689
+ model_id="higgs-audio-v3-excluded",
690
+ lane="audio_lore_tts",
691
+ provider="hf",
692
+ repo_id="bosonai/higgs-audio-v3",
693
+ license_gate="commercial_required",
694
+ params_b=4.0,
695
+ cost_hint="paid_or_uncleared",
696
+ rpm_limit=0,
697
+ rpd_limit=0,
698
+ quality_score=0.89,
699
+ latency_ms=1800,
700
+ health="excluded",
701
+ ),
702
+ ModelRecord(
703
+ model_id="netflix-void-modal",
704
+ lane="video_repair",
705
+ provider="modal",
706
+ repo_id="Netflix/VOID",
707
+ license_gate="private_research",
708
+ params_b=1.3,
709
+ cost_hint="modal_credits",
710
+ rpm_limit=10,
711
+ rpd_limit=120,
712
+ quality_score=0.84,
713
+ latency_ms=12000,
714
+ fallback_chain=("void-q5-offline",),
715
+ ),
716
+ ModelRecord(
717
+ model_id="void-q5-offline",
718
+ lane="video_repair",
719
+ provider="local",
720
+ repo_id="local/VOID-Q5-video-repair",
721
+ license_gate="private_research",
722
+ params_b=1.3,
723
+ cost_hint="offline",
724
+ rpm_limit=20,
725
+ rpd_limit=200,
726
+ quality_score=0.78,
727
+ latency_ms=16000,
728
+ ),
729
+ ModelRecord(
730
+ model_id="fal-media-adapter",
731
+ lane="video_repair",
732
+ provider="fal",
733
+ repo_id="fal-ai/optional-media-generation",
734
+ license_gate="commercial_required",
735
+ params_b=0.0,
736
+ cost_hint="optional_external_provider",
737
+ rpm_limit=0,
738
+ rpd_limit=0,
739
+ quality_score=0.8,
740
+ latency_ms=6000,
741
+ health="excluded",
742
+ ),
743
+ ModelRecord(
744
+ model_id="hf-api-metadata-cache",
745
+ lane="hf_catalog_research",
746
+ provider="hf_api",
747
+ repo_id="huggingface/hub-api",
748
+ license_gate="public_safe",
749
+ params_b=0.0,
750
+ cost_hint="free_tier_cached",
751
+ rpm_limit=180,
752
+ rpd_limit=3000,
753
+ quality_score=0.86,
754
+ latency_ms=240,
755
+ fallback_chain=("hf-cli-model-search", "local-catalog-cache"),
756
+ ),
757
+ ModelRecord(
758
+ model_id="hf-cli-model-search",
759
+ lane="hf_catalog_research",
760
+ provider="hf_cli",
761
+ repo_id="huggingface/hub-cli",
762
+ license_gate="public_safe",
763
+ params_b=0.0,
764
+ cost_hint="free_tier_cli",
765
+ rpm_limit=60,
766
+ rpd_limit=1000,
767
+ quality_score=0.84,
768
+ latency_ms=600,
769
+ ),
770
+ ModelRecord(
771
+ model_id="local-catalog-cache",
772
+ lane="hf_catalog_research",
773
+ provider="local",
774
+ repo_id="local/nexus-weaver-catalog-cache",
775
+ license_gate="public_safe",
776
+ params_b=0.0,
777
+ cost_hint="local_cache",
778
+ rpm_limit=10000,
779
+ rpd_limit=100000,
780
+ quality_score=0.65,
781
+ latency_ms=6,
782
+ ),
783
+ ModelRecord(
784
+ model_id="modal-lora-eval-runner",
785
+ lane="modal_job_runner",
786
+ provider="modal",
787
+ repo_id="modal/nexus-lora-eval",
788
+ license_gate="private_research",
789
+ params_b=0.0,
790
+ cost_hint="modal_251_credit",
791
+ rpm_limit=20,
792
+ rpd_limit=180,
793
+ quality_score=0.87,
794
+ latency_ms=30000,
795
+ fallback_chain=("modal-dry-run-planner",),
796
+ ),
797
+ ModelRecord(
798
+ model_id="modal-video-repair-batch",
799
+ lane="modal_job_runner",
800
+ provider="modal",
801
+ repo_id="modal/nexus-video-repair-batch",
802
+ license_gate="private_research",
803
+ params_b=0.0,
804
+ cost_hint="modal_251_credit",
805
+ rpm_limit=10,
806
+ rpd_limit=80,
807
+ quality_score=0.82,
808
+ latency_ms=45000,
809
+ ),
810
+ ModelRecord(
811
+ model_id="modal-dry-run-planner",
812
+ lane="modal_job_runner",
813
+ provider="local",
814
+ repo_id="local/modal-job-dry-run",
815
+ license_gate="public_safe",
816
+ params_b=0.0,
817
+ cost_hint="local_free",
818
+ rpm_limit=10000,
819
+ rpd_limit=100000,
820
+ quality_score=0.58,
821
+ latency_ms=12,
822
+ ),
823
+ ]
src/nexus_visual_weaver/planner.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """High-level command-center orchestration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import uuid4
6
+
7
+ from .catalog import ADAPTER_CATALOG, active_stack
8
+ from .grounding import inspect_outfit
9
+ from .lore import build_lore_beats, build_video_plan
10
+ from .schema import CreativeRequest, GenerationRun, HumanCheckpoint
11
+ from .taste import refine_prompt
12
+ from .wardrobe import build_outfit_graph
13
+
14
+
15
+ def build_command_center_run(
16
+ prompt: str,
17
+ mode: str = "Strict",
18
+ video_preset: str = "Wan2.2 I2V",
19
+ adult_mode: bool = False,
20
+ ) -> GenerationRun:
21
+ request = CreativeRequest(prompt=prompt, adult_mode=adult_mode)
22
+ refined = refine_prompt(prompt, adult_mode=adult_mode)
23
+ outfit = build_outfit_graph(refined.refined, adult_mode=adult_mode)
24
+ inspection = inspect_outfit(outfit)
25
+ lore = build_lore_beats(refined.refined)
26
+ video = build_video_plan(video_preset)
27
+ stack = active_stack(adult_mode)
28
+ adapters = [adapter for adapter in ADAPTER_CATALOG if adult_mode or not adapter.adult_only][:4]
29
+ trust_score = round((refined.score + outfit.score + (0.86 if inspection.status == "pass" else 0.72)) / 3, 2)
30
+ required_actions = []
31
+ if adult_mode:
32
+ required_actions.append("Confirm 18+ session scope and keep exports partitioned")
33
+ if inspection.drift_flags:
34
+ required_actions.extend(inspection.drift_flags)
35
+ if mode.lower().startswith("frontier"):
36
+ required_actions.append("Frontier mode requires human checkpoint before video render")
37
+
38
+ checkpoint = HumanCheckpoint(
39
+ checkpoint_id=f"nw-{uuid4().hex[:8]}",
40
+ recommendation="approve_candidate" if trust_score >= 0.78 else "revise_before_generation",
41
+ trust_score=trust_score,
42
+ required_actions=required_actions or ["Review candidate thumbnails before promotion"],
43
+ )
44
+ return GenerationRun(
45
+ request=request,
46
+ refined_prompt=refined,
47
+ outfit=outfit,
48
+ model_stack=stack,
49
+ adapters=adapters,
50
+ inspection=inspection,
51
+ lore=lore,
52
+ video=video,
53
+ checkpoint=checkpoint,
54
+ )
55
+
src/nexus_visual_weaver/render.py ADDED
@@ -0,0 +1,681 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HTML rendering helpers for the Gradio command center."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from html import escape
7
+
8
+ from .catalog import catalog_summary, parameter_budget
9
+ from .schema import GenerationRun
10
+ from .workflow import WorkflowState
11
+
12
+
13
+ def badge(label: str, tone: str = "neutral") -> str:
14
+ return f'<span class="nw-badge nw-{tone}">{escape(label)}</span>'
15
+
16
+
17
+ def icon(name: str) -> str:
18
+ paths = {
19
+ "forge": '<path d="M12 2l2.2 5.1L19 9.1l-4.8 2L12 16l-2.2-4.9L5 9.1l4.8-2L12 2z"/><path d="M4 14l1.3 3L8 18.2l-2.7 1.1L4 22l-1.3-2.7L0 18.2 2.7 17 4 14z" transform="translate(2 -1)"/>',
20
+ "wardrobe": '<path d="M9 3h6l1.5 3 3 1.5-2 4.5-2.5-1.1V21H9V10.9L6.5 12l-2-4.5 3-1.5L9 3z"/><path d="M9 3c.5 1.5 1.5 2.3 3 2.3S14.5 4.5 15 3"/>',
21
+ "lore": '<path d="M5 4h10a4 4 0 014 4v12H8a3 3 0 00-3-3V4z"/><path d="M5 17a3 3 0 013-3h11"/><path d="M9 8h6M9 11h5"/>',
22
+ "models": '<path d="M12 2l8 4.6v9.2L12 20.5l-8-4.7V6.6L12 2z"/><path d="M4 6.6l8 4.6 8-4.6M12 11.2v9.3"/>',
23
+ "security": '<path d="M12 2l8 3v6c0 5-3.4 9.2-8 11-4.6-1.8-8-6-8-11V5l8-3z"/><path d="M8.5 12l2.2 2.2 4.8-5"/>',
24
+ "runs": '<path d="M6 4h12v16H6z"/><path d="M9 8h6M9 12h6M9 16h3"/>',
25
+ "zoom": '<circle cx="10" cy="10" r="5"/><path d="M14 14l5 5M10 7v6M7 10h6"/>',
26
+ "frame": '<path d="M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5"/>',
27
+ "lock": '<rect x="5" y="10" width="14" height="10" rx="2"/><path d="M8 10V7a4 4 0 018 0v3"/>',
28
+ "dot": '<circle cx="12" cy="12" r="5"/>',
29
+ }
30
+ return f'<svg class="nw-icon" viewBox="0 0 24 24" aria-hidden="true">{paths.get(name, paths["dot"])}</svg>'
31
+
32
+
33
+ def _metric(label: str, value: str, tone: str = "neutral") -> str:
34
+ return f'<div class="nw-metric nw-metric-{tone}"><small>{escape(label)}</small><strong>{escape(value)}</strong></div>'
35
+
36
+
37
+ def _env_configured(*names: str) -> bool:
38
+ return any(bool(os.environ.get(name)) for name in names)
39
+
40
+
41
+ def _space_runtime_status() -> dict[str, str]:
42
+ space_id = os.environ.get("SPACE_ID") or os.environ.get("HF_SPACE_ID") or "local-preview"
43
+ hardware = os.environ.get("SPACE_HARDWARE") or os.environ.get("NEXUS_SPACE_HARDWARE") or "ZeroGPU"
44
+ bucket = "/data mounted" if os.path.isdir("/data") else "bucket optional"
45
+ secrets = "providers configured" if _env_configured("FAL_KEY", "NETLIFY_AUTH_TOKEN", "OPENAI_API_KEY", "MODAL_TOKEN_ID") else "no provider secrets"
46
+ return {
47
+ "space_id": space_id,
48
+ "hardware": hardware,
49
+ "bucket": bucket,
50
+ "secrets": secrets,
51
+ }
52
+
53
+
54
+ def render_command_header() -> str:
55
+ return f"""
56
+ <section class="nw-command-header">
57
+ <div>
58
+ <small>COMMAND INPUT</small>
59
+ <strong>Raven Chronicle Active Weave</strong>
60
+ <span>Prompt, reference scan, model route, and checkpoint controls stay in one sticky operator strip.</span>
61
+ </div>
62
+ <div class="nw-command-pills">
63
+ {badge("SFW DEFAULT", "pass")}
64
+ {badge("ST3GG ALWAYS ON", "cyan")}
65
+ {badge("FLUX.2 PINNED", "accent")}
66
+ {badge("HUMAN CHECKPOINT", "warn")}
67
+ </div>
68
+ </section>
69
+ """
70
+
71
+
72
+ def render_topbar(adult_mode: bool = False, relay_status: dict | None = None) -> str:
73
+ summary = catalog_summary(adult_mode)
74
+ active = float(summary["active_b"])
75
+ pct = max(0, min(100, int((active / 32.0) * 100)))
76
+ adult_label = "ON - research partition" if adult_mode else "OFF"
77
+ relay_status = relay_status or {}
78
+ rotation_safe = bool(relay_status.get("rotation_safe", True))
79
+ relay_label = "Rotation Safe" if rotation_safe else "Rotation Limited"
80
+ relay_tone = "pass" if rotation_safe else "warn"
81
+ space = _space_runtime_status()
82
+ return f"""
83
+ <div class="nw-topbar">
84
+ <div class="nw-brand"><span>NEXUS</span><strong>Visual Weaver</strong></div>
85
+ <div class="nw-topitem"><small>Project</small><strong>Raven Chronicle</strong><i></i></div>
86
+ <div class="nw-topitem"><small>Active Preset</small><strong>Dark Couture v2.4</strong><i></i></div>
87
+ <div class="nw-budget">
88
+ <div><strong>32B Parameter Budget</strong><small>{active:.2f}B / 32B ({pct}%)</small></div>
89
+ <div class="nw-meter"><i style="width:{pct}%"></i></div>
90
+ </div>
91
+ <div class="nw-status"><span class="nw-live-dot"></span><strong>HF Connected</strong><small>Hugging Face</small></div>
92
+ <div class="nw-status nw-gmr"><small>HF / Modal / GMR</small>{badge(relay_label, relay_tone)}<small>Helper rotation only</small></div>
93
+ <div class="nw-status nw-space"><small>{escape(space["space_id"])}</small><strong>{escape(space["hardware"])}</strong><small>{escape(space["bucket"])} / {escape(space["secrets"])}</small></div>
94
+ <div class="nw-adult">
95
+ <strong>Adult Mode {icon("lock")}</strong>
96
+ <span class="nw-toggle {'is-on' if adult_mode else ''}"><i></i>{escape(adult_label)}</span>
97
+ </div>
98
+ <div class="nw-locked"><b>18+</b><span>Locked. Enable in Security with explicit justification.</span></div>
99
+ </div>
100
+ """
101
+
102
+
103
+ def render_left_rail(active_section: str = "Forge") -> str:
104
+ items = [("Forge", "forge"), ("Wardrobe", "wardrobe"), ("Lore", "lore"), ("Models", "models"), ("Security", "security"), ("Runs", "runs")]
105
+ rows = "".join(
106
+ f'<div class="nw-rail-item {"active" if label == active_section else ""}">{icon(icon_name)}<span>{escape(label)}</span></div>'
107
+ for label, icon_name in items
108
+ )
109
+ return f"""
110
+ <nav class="nw-rail">
111
+ <div class="nw-rail-main">{rows}</div>
112
+ <div class="nw-rail-foot">
113
+ <div>{icon("security")}<strong>Security Guardian</strong><span>Active</span></div>
114
+ <div>{icon("lock")}<strong>ST3GG v2.3.1</strong><span>Always-on</span></div>
115
+ </div>
116
+ </nav>
117
+ """
118
+
119
+
120
+ def render_command_rail(active_section: str = "Forge") -> str:
121
+ section = escape(active_section)
122
+ hints = {
123
+ "Forge": ("Active Weave", "Prompt, judge, locate, generate, checkpoint."),
124
+ "Wardrobe": ("Outfit Slots", "Materials, footwear, locks, reference regions."),
125
+ "Lore": ("Video Continuity", "Identity, garment meaning, scene motion."),
126
+ "Models": ("Relay Stack", "Pinned core plus quota-aware helper rotation."),
127
+ "Security": ("ST3GG Gate", "Scan, purify, provenance, export decision."),
128
+ "Runs": ("Run Ledger", "Checkpointed dry-runs and handoff packets."),
129
+ }
130
+ title, body = hints.get(active_section, hints["Forge"])
131
+ return f"""
132
+ <div class="nw-native-rail">
133
+ <strong>{escape(title)}</strong>
134
+ <span>{escape(body)}</span>
135
+ {badge(f"Selected: {section}", "muted")}
136
+ </div>
137
+ """
138
+
139
+
140
+ def render_workflow(run: GenerationRun | None = None) -> str:
141
+ workflow = WorkflowState.default()
142
+ score = run.checkpoint.trust_score if run else 0.82
143
+ checkpoint_id = run.checkpoint.checkpoint_id if run else "nw-dry-run"
144
+ recommendation = run.checkpoint.recommendation.replace("_", " ").title() if run else "Awaiting Run"
145
+ required_actions = run.checkpoint.required_actions if run else ["Review candidate thumbnails before promotion"]
146
+ action_label = required_actions[0] if required_actions else "No action pending"
147
+ model_label = _short_repo(run.model_stack[0].repo_id) if run and run.model_stack else "FLUX.2"
148
+ locate_label = run.inspection.locate_model.split("/")[-1] if run else "LocateAnything-3B"
149
+ nodes = {
150
+ "seed": (35, 52, 190, 210, "Seed Prompt", ["Rogue archivist moving", "through rain-slick neon", "city, couture layers."], "Text-to-Image (FLUX.2)", "complete", "red"),
151
+ "refine": (275, 52, 185, 160, "Refine", ["Prompt Refiner", "Style Harmonizer", "Negative Purge"], "Qwen2.5-7B", "complete", "violet"),
152
+ "judge": (540, 52, 185, 160, "Judge", ["Aesthetic Scorer", "ST3GG Policy Filter", f"Score {score:.2f}"], "OFFELLIA / Gemma", "complete", "blue"),
153
+ "locate": (785, 52, 185, 160, "Locate", ["Reference Locator", "Pose & Composition", "IP-Adapter"], "Refs 3/5", "complete", "cyan"),
154
+ "generate": (275, 280, 235, 210, "Generate", ["Image / Video Generation", "FLUX.2 + adapter stack", "High-detail couture"], "Steps 30 CFG 7.5", "ready", "green"),
155
+ "video": (590, 280, 235, 210, "Video Path", ["Image to Video", "Frame interpolation", run.video.preset if run else "Wan2.2 / LTX swap"], "Duration 5.6s 24fps", "ready", "blue"),
156
+ "checkpoint": (880, 285, 185, 185, "Human Checkpoint", ["Human review required", "Verify intent, vibe,", "and output before final."], "Review Now", "paused", "amber"),
157
+ }
158
+ edges = [
159
+ ("seed", "refine"), ("refine", "judge"), ("judge", "locate"), ("locate", "video"),
160
+ ("refine", "generate"), ("generate", "video"), ("video", "checkpoint"), ("judge", "checkpoint"),
161
+ ]
162
+ lines = []
163
+ for source, target in edges:
164
+ x1, y1, w1, h1, *_ = nodes[source]
165
+ x2, y2, w2, h2, *_ = nodes[target]
166
+ y_offset = 76 if source in {"seed", "refine", "judge", "locate"} else 96
167
+ lines.append(
168
+ f'<path d="M{x1 + w1} {y1 + y_offset} C{x1 + w1 + 55} {y1 + y_offset}, {x2 - 55} {y2 + 76}, {x2} {y2 + 76}" />'
169
+ )
170
+ cards = []
171
+ for node_id, (x, y, width, height, title, body, footer, status, tone) in nodes.items():
172
+ body_lines = "".join(f'<text x="{x + 16}" y="{y + 74 + idx * 22}" class="nw-node-line">{escape(line)}</text>' for idx, line in enumerate(body))
173
+ thumb_strip = ""
174
+ if node_id in {"generate", "video"}:
175
+ thumbs = []
176
+ for idx in range(4):
177
+ tx = x + 16 + idx * 47
178
+ thumbs.append(
179
+ f'<rect class="nw-thumb nw-thumb-{idx}" x="{tx}" y="{y + 112}" rx="4" width="39" height="52"></rect>'
180
+ )
181
+ thumb_strip = "".join(thumbs)
182
+ cards.append(
183
+ f"""
184
+ <g class="nw-node nw-node-{status} nw-node-{tone}">
185
+ <rect x="{x}" y="{y}" rx="9" width="{width}" height="{height}"></rect>
186
+ <text x="{x + 16}" y="{y + 31}" class="nw-node-title">{escape(title)}</text>
187
+ {body_lines}
188
+ {thumb_strip}
189
+ <line x1="{x + 16}" y1="{y + height - 48}" x2="{x + width - 16}" y2="{y + height - 48}" class="nw-node-sep" />
190
+ <text x="{x + 16}" y="{y + height - 20}" class="nw-node-footer">{escape(footer)}</text>
191
+ <circle cx="{x + width - 20}" cy="{y + height - 20}" r="5" class="nw-node-ok"></circle>
192
+ </g>
193
+ """
194
+ )
195
+ return f"""
196
+ <section class="nw-panel nw-canvas">
197
+ <div class="nw-panel-head nw-canvas-head">
198
+ <div><strong>Active Weave</strong><small><span class="nw-live-dot"></span> Live / Weave ID: 4f7c9e2b</small></div>
199
+ <div class="nw-tools nw-static-tools"><span>Layout Auto</span><span>{icon("zoom")}</span><span>{icon("frame")}</span></div>
200
+ </div>
201
+ <svg class="nw-graph" viewBox="0 0 1110 530" role="img" aria-label="NEXUS workflow graph">
202
+ <defs>
203
+ <pattern id="nw-grid" width="12" height="12" patternUnits="userSpaceOnUse">
204
+ <circle cx="1" cy="1" r="0.8" fill="#2d3944" />
205
+ </pattern>
206
+ <linearGradient id="nw-node-shine" x1="0" x2="1"><stop offset="0" stop-color="rgba(255,255,255,.07)" /><stop offset="1" stop-color="rgba(255,255,255,0)" /></linearGradient>
207
+ </defs>
208
+ <rect width="1110" height="530" fill="url(#nw-grid)"></rect>
209
+ <g class="nw-edges">{"".join(lines)}</g>
210
+ {"".join(cards)}
211
+ </svg>
212
+ <div class="nw-legend">
213
+ {badge("Text Flow", "accent")} {badge("Refine Loop", "violet")} {badge("Policy Gate", "blue")} {badge("Media Flow", "cyan")} {badge("Human Gate", "warn")}
214
+ </div>
215
+ <div class="nw-weave-console">
216
+ <div class="nw-console-card nw-console-primary">
217
+ <small>Selected Node</small>
218
+ <strong>Human Checkpoint</strong>
219
+ <span>{escape(recommendation)} / {escape(checkpoint_id)}</span>
220
+ </div>
221
+ <div class="nw-console-card">
222
+ <small>Next Operator Action</small>
223
+ <strong>{escape(action_label)}</strong>
224
+ <span>Checkpoint blocks video promotion until reviewed.</span>
225
+ </div>
226
+ <div class="nw-console-card">
227
+ <small>Pinned Model Lanes</small>
228
+ <strong>{escape(model_label)} + {escape(locate_label)} + ST3GG</strong>
229
+ <span>Core lanes stay fixed; helper lanes may rotate.</span>
230
+ </div>
231
+ <div class="nw-console-card">
232
+ <small>Hackathon Signal</small>
233
+ <strong>Workflow, governance, visual creation</strong>
234
+ <span>Judge view keeps product purpose visible without a landing page.</span>
235
+ </div>
236
+ </div>
237
+ </section>
238
+ """
239
+
240
+
241
+ def render_artifact_lane(run: GenerationRun | None = None, scan: dict | None = None) -> str:
242
+ scan = scan or {"status": "idle", "export_gate": "pending"}
243
+ prompt_label = "Prompt proof"
244
+ outfit_label = "Outfit map"
245
+ locate_label = "Grounding overlay"
246
+ video_label = run.video.preset if run else "Video path"
247
+ active_prompt = run.refined_prompt.refined[:150] if run else "Awaiting first weave. The preview stage shows dry-run handoff packets until provider output exists."
248
+ checkpoint = getattr(run.checkpoint, "recommendation", "pending") if run else "pending"
249
+ demo_seed = (run.checkpoint.checkpoint_id[-4:] if run else "0000").upper()
250
+ artifacts = [
251
+ (prompt_label, "Taste-refined brief", "dry-run", "material-0", "01"),
252
+ (outfit_label, "Wardrobe slots and locks", "checkpointed", "material-1", "02"),
253
+ (locate_label, "LocateAnything region plan", "configured", "material-4", "03"),
254
+ (video_label, "Checkpointed storyboard", "blocked" if scan.get("export_gate") == "blocked" else "ready", "story-2", "04"),
255
+ ]
256
+ cards = "".join(
257
+ f"""
258
+ <div class="nw-artifact-card">
259
+ <small>{escape(index)}</small>
260
+ <i class="nw-{texture}"></i>
261
+ <strong>{escape(title)}</strong>
262
+ <span>{escape(body)}</span>
263
+ {badge(status.upper(), "warn" if status == "blocked" else "muted")}
264
+ </div>
265
+ """
266
+ for title, body, status, texture, index in artifacts
267
+ )
268
+ export_gate = str(scan.get("export_gate", "pending")).upper()
269
+ continuity = ", ".join(run.video.continuity_locks[:4]) if run else "outerwear, footwear, jewelry, NEXUS sigils"
270
+ return f"""
271
+ <section class="nw-panel nw-artifacts">
272
+ <div class="nw-panel-head">
273
+ <div><strong>Artifact Preview Lane</strong><small>Honest handoff packets until a provider call succeeds</small></div>
274
+ {badge(f"Export {export_gate}", "warn" if export_gate == "BLOCKED" else "pass" if export_gate == "CLEAR" else "muted")}
275
+ </div>
276
+ <div class="nw-preview-stage">
277
+ <div class="nw-preview-frame">
278
+ <i class="nw-preview-image"></i>
279
+ <div class="nw-preview-caption">
280
+ <small>PRIMARY OUTPUT STAGE / JUDGE-SAFE DEMO OUTPUT / SEED {escape(demo_seed)}</small>
281
+ <strong>Deterministic Raven Chronicle proof frame</strong>
282
+ <span>{escape(active_prompt)}</span>
283
+ </div>
284
+ </div>
285
+ <div class="nw-preview-meta">
286
+ <div><small>checkpoint</small><strong>{escape(str(checkpoint).replace("_", " ").title())}</strong></div>
287
+ <div><small>export gate</small><strong>{escape(export_gate)}</strong></div>
288
+ <div><small>preview mode</small><strong>Dry Run</strong></div>
289
+ </div>
290
+ </div>
291
+ <div class="nw-preview-ribbon">
292
+ <span>{icon("security")} ST3GG before export</span>
293
+ <span>{icon("wardrobe")} continuity: {escape(continuity)}</span>
294
+ <span>{icon("models")} provider call remains checkpointed; state: dry-run / configured / blocked / failed</span>
295
+ </div>
296
+ <div class="nw-artifact-grid">{cards}</div>
297
+ </section>
298
+ """
299
+
300
+
301
+ def render_operations_panel(
302
+ active_section: str = "Forge",
303
+ run: GenerationRun | None = None,
304
+ scan: dict | None = None,
305
+ relay_status: dict | None = None,
306
+ *,
307
+ adult_mode: bool = False,
308
+ ) -> str:
309
+ scan = scan or {"status": "idle", "export_gate": "pending", "findings": []}
310
+ relay_status = relay_status or {}
311
+ section = active_section if active_section in {"Forge", "Wardrobe", "Lore", "Models", "Security", "Runs"} else "Forge"
312
+ checkpoint = getattr(run, "checkpoint", None) if run else None
313
+ outfit = getattr(run, "outfit", None) if run else None
314
+ lore = getattr(run, "lore", None) if run else None
315
+ run_id = getattr(checkpoint, "checkpoint_id", "not-started")
316
+ outfit_count = len(getattr(outfit, "slots", []) or [])
317
+ lore_count = len(getattr(lore, "beats", []) or [])
318
+ scan_status = str(scan.get("status", "idle")).upper()
319
+ export_gate = str(scan.get("export_gate", "pending")).upper()
320
+ decisions = relay_status.get("decisions", [])
321
+ first_decision = decisions[0] if decisions else {}
322
+ first_primary = (first_decision.get("primary") or {}) if first_decision else {}
323
+ adult_scope = "Private research scope" if adult_mode else "Public demo scope"
324
+ panels = {
325
+ "Forge": [
326
+ ("Prompt contract", "Taste-refined prompt, material locks, negative purge, and checkpoint requirements."),
327
+ ("Active run", f"{run_id} / checkpoint remains human-reviewed before video promotion."),
328
+ ("Provider posture", "Dry-run packets are visible before paid or gated calls."),
329
+ ],
330
+ "Wardrobe": [
331
+ ("Slot coverage", f"{outfit_count or 9} garment/accessory regions tracked with locks and edit priority."),
332
+ ("Footwear focus", "Platform boots, stilettos, high-heel boots, hardware, and silhouette constraints stay first-class."),
333
+ ("Locate map", "Reference regions feed preflight and post-generation outfit verification."),
334
+ ],
335
+ "Lore": [
336
+ ("Beat budget", f"{lore_count or 6} compact beats: identity, garment meaning, world context, emotion, motion."),
337
+ ("Video checkpoint", "Video presets remain handoff plans until human approval."),
338
+ ("Continuity locks", "Lore-to-video keeps garment meaning and motion cue visible without tab sprawl."),
339
+ ],
340
+ "Models": [
341
+ ("Primary helper", _short_repo(str(first_primary.get("repo_id", "pending")))),
342
+ ("Rotation mode", "Pinned core stays fixed; helper lanes rotate by license, budget, quota, and health."),
343
+ ("Scope", adult_scope),
344
+ ],
345
+ "Security": [
346
+ ("ST3GG state", f"{scan_status} / export {export_gate}"),
347
+ (
348
+ "Findings",
349
+ "; ".join(str(item) for item in (scan.get("findings") or [])[:2])
350
+ or ("No findings." if scan_status != "IDLE" else "No upload selected."),
351
+ ),
352
+ ("Public export", "Consent, provenance, metadata, age, dataset, and payload gates stay active."),
353
+ ],
354
+ "Runs": [
355
+ ("Current checkpoint", run_id),
356
+ ("Ledger mode", "Run JSON, catalog summary, and ST3GG evidence remain in the evidence accordion."),
357
+ ("Rollback path", "Feature branches and draft PRs carry implementation checkpoints."),
358
+ ],
359
+ }
360
+ rows = "".join(
361
+ f"""
362
+ <div class="nw-operation-card">
363
+ <small>{escape(title)}</small>
364
+ <strong>{escape(body)}</strong>
365
+ <i></i>
366
+ </div>
367
+ """
368
+ for title, body in panels[section]
369
+ )
370
+ return f"""
371
+ <section class="nw-panel nw-operations">
372
+ <div class="nw-panel-head">
373
+ <div><strong>{escape(section)} Operations</strong><small>Section-aware control surface for the selected command rail lane</small></div>
374
+ {badge(f"{escape(section).upper()} ACTIVE", "cyan")}
375
+ </div>
376
+ <div class="nw-operation-grid">{rows}</div>
377
+ </section>
378
+ """
379
+
380
+
381
+ def _short_repo(repo_id: str) -> str:
382
+ return repo_id.split("/")[-1]
383
+
384
+
385
+ def _render_relay_panel(relay_status: dict | None = None) -> str:
386
+ relay_status = relay_status or {}
387
+ pinned = relay_status.get("pinned", {})
388
+ decisions = relay_status.get("decisions", [])
389
+ pinned_labels = []
390
+ for lane in ["image_generation", "grounding", "security"]:
391
+ record = pinned.get(lane)
392
+ if record:
393
+ pinned_labels.append(_short_repo(record["repo_id"]))
394
+ pinned_rows = [
395
+ f"""
396
+ <li>
397
+ <span>pinned core</span>
398
+ <strong>{escape(" / ".join(pinned_labels[:3]) or "pending")}</strong>
399
+ <em>image, grounding, and security never rotate</em>
400
+ </li>
401
+ """
402
+ ]
403
+ decision_rows = []
404
+ for decision in decisions[:2]:
405
+ primary = decision.get("primary") or {}
406
+ fallbacks = decision.get("fallbacks") or []
407
+ fallback_label = ", ".join(_short_repo(item["repo_id"]) for item in fallbacks[:2]) or "none"
408
+ decision_rows.append(
409
+ f"""
410
+ <li>
411
+ <span>{escape(decision["lane"].replace("_", " "))}</span>
412
+ <strong>{escape(_short_repo(primary.get("repo_id", "blocked")))}</strong>
413
+ <em>{escape(decision["strategy"])} / fallback: {escape(fallback_label)}</em>
414
+ </li>
415
+ """
416
+ )
417
+ rows = "".join(pinned_rows + decision_rows)
418
+ if not rows:
419
+ rows = "<li><span>GMR</span><strong>snapshot pending</strong><em>relay idle</em></li>"
420
+ dedup_hits = relay_status.get("dedup_hits", 0)
421
+ return f"""
422
+ <h3>GMR ModelRelay</h3>
423
+ <ul class="nw-relay">{rows}</ul>
424
+ <div class="nw-relay-foot">
425
+ {badge("FLUX.2 pinned", "pass")} {badge("LocateAnything pinned", "pass")} {badge(f"dedup hits {dedup_hits}", "muted")}
426
+ </div>
427
+ """
428
+
429
+
430
+ def render_provider_cards(relay_status: dict | None = None, adult_mode: bool = False) -> str:
431
+ relay_status = relay_status or {}
432
+ decisions = relay_status.get("decisions", [])
433
+ optional_statuses = {
434
+ "fal": "configured" if _env_configured("FAL_KEY") else "blocked",
435
+ "netlify": "configured" if _env_configured("NETLIFY_AUTH_TOKEN", "NETLIFY_SITE_ID", "OPENAI_BASE_URL") else "blocked",
436
+ "cloudflare": "configured" if _env_configured("CLOUDFLARE_API_TOKEN", "CF_ACCOUNT_ID") else "blocked",
437
+ }
438
+ cards = []
439
+ for decision in decisions[:5]:
440
+ primary = decision.get("primary") or {}
441
+ quota = decision.get("quota_impact") or {}
442
+ provider = primary.get("provider", "blocked")
443
+ repo = _short_repo(primary.get("repo_id", "blocked"))
444
+ lane = decision.get("lane", "helper").replace("_", " ")
445
+ status = quota.get("status", "blocked")
446
+ provider_state = "dry-run" if status == "ready" else "blocked" if status == "blocked" else "limited"
447
+ if provider in optional_statuses:
448
+ provider_state = optional_statuses[provider]
449
+ tone = "pass" if provider_state == "configured" else "warn" if provider_state in {"limited", "blocked", "failed"} else "muted"
450
+ gate = primary.get("license_gate", "unknown")
451
+ cards.append(
452
+ f"""
453
+ <div class="nw-provider-card">
454
+ <small>{escape(lane)}</small>
455
+ <strong>{escape(repo)}</strong>
456
+ <span>{escape(str(provider))} / {escape(str(gate))}</span>
457
+ <i class="nw-provider-meter" style="--health:{'86' if provider_state in {'configured', 'dry-run'} else '52' if provider_state == 'limited' else '22'}"></i>
458
+ <div>{badge(provider_state.upper(), tone)}{badge("CHECKPOINTED", "muted")}</div>
459
+ </div>
460
+ """
461
+ )
462
+ if not cards:
463
+ cards.append('<div class="nw-provider-card"><small>providers</small><strong>snapshot pending</strong><span>relay idle</span><div>{}</div></div>'.format(badge("DRY-RUN", "muted")))
464
+ for provider, state in optional_statuses.items():
465
+ cards.append(
466
+ f"""
467
+ <div class="nw-provider-card nw-provider-optional">
468
+ <small>optional gateway</small>
469
+ <strong>{escape(provider.title())}</strong>
470
+ <span>off by default / secrets required</span>
471
+ <i class="nw-provider-meter" style="--health:{'74' if state == 'configured' else '18'}"></i>
472
+ <div>{badge(state.upper(), "pass" if state == "configured" else "warn")}{badge("NOT MVP DEFAULT", "muted")}</div>
473
+ </div>
474
+ """
475
+ )
476
+ mode_label = "private research" if adult_mode else "public demo safe"
477
+ return f"""
478
+ <section class="nw-panel nw-providers">
479
+ <div class="nw-panel-head">
480
+ <div><strong>Provider Handoff Cards</strong><small>Configured as visible packets before any paid or gated call</small></div>
481
+ {badge(mode_label.upper(), "warn" if adult_mode else "pass")}
482
+ </div>
483
+ <div class="nw-provider-grid">{"".join(cards)}</div>
484
+ </section>
485
+ """
486
+
487
+
488
+ def _scan_status_tone(scan_status: str) -> str:
489
+ if scan_status == "pass":
490
+ return "pass"
491
+ if scan_status in {"review", "error"}:
492
+ return "warn"
493
+ return "muted"
494
+
495
+
496
+ def render_inspector(run: GenerationRun | None = None, scan: dict | None = None, relay_status: dict | None = None) -> str:
497
+ if run:
498
+ checks = [
499
+ ("Patent Leather", True),
500
+ ("Faux Fur", any(slot.material == "faux_fur" for slot in run.outfit.slots)),
501
+ ("Lace / Mesh", any("lace" in slot.material for slot in run.outfit.slots)),
502
+ ("Crimson Hardware", any(slot.material == "crimson_hardware" for slot in run.outfit.slots)),
503
+ ("Platform Boots", any(slot.name == "footwear" for slot in run.outfit.slots)),
504
+ ("Layered Garments", True),
505
+ ]
506
+ stack_label = " / ".join(_short_repo(model.repo_id) for model in run.model_stack[:3])
507
+ model_rows = f"<li><span>active stack</span><strong>{escape(stack_label)}</strong></li>"
508
+ score = int(run.checkpoint.trust_score * 100)
509
+ scan_status = (scan or {}).get("status", "pass")
510
+ else:
511
+ checks = [(label, True) for label in ["Patent Leather", "Faux Fur", "Lace / Mesh", "Crimson Hardware", "Platform Boots", "Layered Garments"]]
512
+ model_rows = "<li><span>active stack</span><strong>FLUX.2 / OFFELLIA / LocateAnything</strong></li>"
513
+ score = 86
514
+ scan_status = (scan or {}).get("status", "pass")
515
+ checks_html = "".join(f'<li><span>{"✓" if ok else "!"}</span>{escape(label)}</li>' for label, ok in checks)
516
+ relay = _render_relay_panel(relay_status)
517
+ scan = scan or {"status": scan_status, "findings": [], "purification_actions": [], "export_gate": "pending"}
518
+ findings = scan.get("findings") or []
519
+ actions = scan.get("purification_actions") or ["metadata strip ready", "IEND truncation ready", "LSB review ready"]
520
+ finding_rows = "".join(f"<li>{escape(str(item))}</li>" for item in findings[:4]) or "<li>No upload selected. Scanner ready.</li>"
521
+ action_rows = "".join(f"<li>{escape(str(item))}</li>" for item in actions[:4])
522
+ export_gate = str(scan.get("export_gate", "pending")).upper()
523
+ return f"""
524
+ <aside class="nw-panel nw-inspector">
525
+ <div class="nw-panel-head"><strong>Inspector</strong>{badge("Selected: Judge", "muted")}</div>
526
+ <h3>Taste Profile</h3>
527
+ <div class="nw-rings">
528
+ <div style="--v:{score};--ring:#f59e42"><b>{score}</b><small>Composition</small></div>
529
+ <div style="--v:82;--ring:#fb6b5f"><b>82</b><small>Color</small></div>
530
+ <div style="--v:79;--ring:#e86158"><b>79</b><small>Mood</small></div>
531
+ <div style="--v:91;--ring:#ec4899"><b>91</b><small>Cohesion</small></div>
532
+ </div>
533
+ <h3>Material Checklist</h3>
534
+ <ul class="nw-checks">{checks_html}</ul>
535
+ <h3>Model Stack</h3>
536
+ <ul class="nw-models">{model_rows}</ul>
537
+ <h3>ST3GG Scan</h3>
538
+ <div class="nw-scan">
539
+ <div>{badge(str(scan_status).upper(), _scan_status_tone(str(scan_status)))}<span>Always-on defensive review</span></div>
540
+ <i style="width:78%"></i>
541
+ <dl><dt>Policy</dt><dd>NEXUS Safe Policy v2.1</dd><dt>Purify</dt><dd>{escape(", ".join(str(item) for item in actions[:2]))}</dd><dt>Export</dt><dd>{escape(export_gate)}</dd></dl>
542
+ <ul class="nw-scan-list">{finding_rows}</ul>
543
+ <ul class="nw-scan-list nw-scan-actions">{action_rows}</ul>
544
+ </div>
545
+ {relay}
546
+ </aside>
547
+ """
548
+
549
+
550
+ def render_drawer(run: GenerationRun | None = None) -> str:
551
+ if run:
552
+ slots = run.outfit.slots
553
+ beats = run.lore.beats
554
+ else:
555
+ slots = []
556
+ beats = []
557
+ slot_cards = "".join(
558
+ f"""
559
+ <div class="nw-swatch {'is-locked' if slot.locked else ''}">
560
+ <i class="nw-material-{idx % 6}"></i><strong>{escape(slot.name.replace("_", " ").title())}</strong>
561
+ <small>{escape(slot.material.replace("_", " "))}</small>
562
+ <span>{'locked' if slot.locked else 'editable'} / p{slot.edit_priority}</span>
563
+ </div>
564
+ """
565
+ for idx, slot in enumerate(slots[:8])
566
+ )
567
+ if not slot_cards:
568
+ slot_cards = "".join(
569
+ f'<div class="nw-swatch"><i class="nw-material-{idx % 6}"></i><strong>{label}</strong><small>{mat}</small><span>editable / p{5 - idx // 2}</span></div>'
570
+ for idx, (label, mat) in enumerate([
571
+ ("Patent Leather", "jet black"), ("Faux Fur", "ash gray"), ("Lace Mesh", "noir"),
572
+ ("Crimson Hardware", "polished"), ("Platform Boots", "matte black"), ("Long Coat", "wool blend"),
573
+ ])
574
+ )
575
+ beat_cards = "".join(
576
+ f'<div class="nw-beat"><i class="nw-story-{idx % 6}"></i><strong>{escape(beat["id"])} {escape(beat["title"])}</strong><small>{escape(beat["cue"][:80])}</small><span class="nw-mini-chip">checkpointed</span></div>'
577
+ for idx, beat in enumerate(beats[:6])
578
+ )
579
+ if not beat_cards:
580
+ beat_cards = "".join(f'<div class="nw-beat"><i class="nw-story-{i % 6}"></i><strong>0{i} Beat</strong><small>Scene continuity cue</small><span class="nw-mini-chip">checkpointed</span></div>' for i in range(1, 7))
581
+ return f"""
582
+ <section class="nw-bottom">
583
+ <div class="nw-panel nw-wardrobe">
584
+ <div class="nw-panel-head"><div><strong>Outfit Wardrobe</strong><small>Couture slots, locks, LocateAnything regions, and edit priority</small></div><span class="nw-chip">All Categories</span></div>
585
+ <div class="nw-filter-row"><span>All</span><span>Patent</span><span>Lace</span><span>Hardware</span><span>Boots / heels</span><span>Outerwear</span><span>Props</span></div>
586
+ <div class="nw-swatches">{slot_cards}</div>
587
+ </div>
588
+ <div class="nw-panel nw-lore">
589
+ <div class="nw-panel-head"><div><strong>Lore-to-Video Timeline</strong><small>Identity, garment meaning, motion cue, and video checkpoint path</small></div><div class="nw-tools nw-static-tools"><span>6 Beats</span><span>24 FPS</span></div></div>
590
+ <div class="nw-beats">{beat_cards}</div>
591
+ </div>
592
+ </section>
593
+ """
594
+
595
+
596
+ def render_status_bar() -> str:
597
+ space = _space_runtime_status()
598
+ return f"""
599
+ <footer class="nw-statusbar">
600
+ {_metric("Runs", "112")}
601
+ {_metric("Queue", "2")}
602
+ {_metric("GPU", "46%", "bar")}
603
+ {_metric("VRAM", "18.2 / 40 GB", "bar")}
604
+ {_metric("Temp", "62 C")}
605
+ {_metric("HF Space", space["hardware"])}
606
+ <div class="nw-autosave"><span class="nw-live-dot"></span><strong>Auto-save</strong><small>On</small></div>
607
+ <div class="nw-stop nw-stop-idle">No live provider job</div>
608
+ </footer>
609
+ """
610
+
611
+
612
+ def render_catalog_table(adult_mode: bool = False) -> str:
613
+ from .catalog import filter_catalog
614
+
615
+ models, adapters = filter_catalog(adult_mode)
616
+ model_rows = "".join(
617
+ f"<tr><td>{escape(model.repo_id)}</td><td>{escape(model.role)}</td><td>{model.params_b:.2f}B</td><td>{escape(model.license)}</td><td>{'18+' if model.adult_only else 'General'}</td></tr>"
618
+ for model in models
619
+ )
620
+ adapter_rows = "".join(
621
+ f"<tr><td>{escape(adapter.repo_id)}</td><td>{escape(adapter.adapter_for)}</td><td>{escape(adapter.task)}</td><td>{'18+' if adapter.adult_only else 'General'}</td></tr>"
622
+ for adapter in adapters
623
+ )
624
+ return f"""
625
+ <div class="nw-catalog">
626
+ <h3>HF Model Catalog</h3>
627
+ <table><thead><tr><th>Repo</th><th>Role</th><th>Params</th><th>License</th><th>Scope</th></tr></thead><tbody>{model_rows}</tbody></table>
628
+ <h3>LoRA / Adapter Shelf</h3>
629
+ <table><thead><tr><th>Repo</th><th>Adapter For</th><th>Task</th><th>Scope</th></tr></thead><tbody>{adapter_rows}</tbody></table>
630
+ </div>
631
+ """
632
+
633
+
634
+ def render_dashboard(
635
+ run: GenerationRun | None = None,
636
+ adult_mode: bool = False,
637
+ scan: dict | None = None,
638
+ relay_status: dict | None = None,
639
+ active_section: str = "Forge",
640
+ ) -> str:
641
+ regions = render_dashboard_regions(run, adult_mode, scan, relay_status, active_section)
642
+ return f"""
643
+ <div class="nw-app">
644
+ {regions["topbar"]}
645
+ <div class="nw-shell">
646
+ {regions["rail"]}
647
+ <div class="nw-main-stack">
648
+ {regions["workflow"]}
649
+ {regions["operations"]}
650
+ {regions["artifacts"]}
651
+ </div>
652
+ <div class="nw-side-stack">
653
+ {regions["inspector"]}
654
+ {regions["providers"]}
655
+ </div>
656
+ {regions["drawer"]}
657
+ </div>
658
+ {regions["status"]}
659
+ </div>
660
+ """
661
+
662
+
663
+ def render_dashboard_regions(
664
+ run: GenerationRun | None = None,
665
+ adult_mode: bool = False,
666
+ scan: dict | None = None,
667
+ relay_status: dict | None = None,
668
+ active_section: str = "Forge",
669
+ ) -> dict[str, str]:
670
+ return {
671
+ "topbar": render_topbar(adult_mode, relay_status),
672
+ "rail": render_left_rail(active_section),
673
+ "command_rail": render_command_rail(active_section),
674
+ "workflow": render_workflow(run),
675
+ "operations": render_operations_panel(active_section, run, scan, relay_status, adult_mode=adult_mode),
676
+ "inspector": render_inspector(run, scan, relay_status),
677
+ "drawer": render_drawer(run),
678
+ "status": render_status_bar(),
679
+ "artifacts": render_artifact_lane(run, scan),
680
+ "providers": render_provider_cards(relay_status, adult_mode),
681
+ }
src/nexus_visual_weaver/schema.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Typed data surfaces for the NEXUS Visual Weaver dashboard."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import asdict, dataclass, field
6
+ from datetime import datetime, timezone
7
+ from typing import Any
8
+
9
+
10
+ def utc_now() -> str:
11
+ return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class CreativeRequest:
16
+ prompt: str
17
+ output_goal: str = "image_to_video"
18
+ adult_mode: bool = False
19
+ references: list[str] = field(default_factory=list)
20
+ created_at: str = field(default_factory=utc_now)
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class TasteProfile:
25
+ version: str
26
+ locked_features: dict[str, Any]
27
+ must_include: list[str]
28
+ should_include: list[str]
29
+ forbidden: list[str]
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class TasteRefinedPrompt:
34
+ original: str
35
+ refined: str
36
+ additions: list[str]
37
+ score: float
38
+ missing_features: list[str]
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class WardrobeSlot:
43
+ name: str
44
+ description: str
45
+ material: str
46
+ palette: str
47
+ lora_hint: str
48
+ locked: bool = False
49
+ adult_only: bool = False
50
+ locate_region: str = "pending"
51
+ edit_priority: int = 3
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class OutfitGraph:
56
+ slots: list[WardrobeSlot]
57
+ score: float
58
+
59
+ @property
60
+ def locked_count(self) -> int:
61
+ return sum(1 for slot in self.slots if slot.locked)
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class ModelCandidate:
66
+ repo_id: str
67
+ role: str
68
+ task: str
69
+ params_b: float
70
+ runtime: str
71
+ license: str
72
+ gated: bool = False
73
+ adult_only: bool = False
74
+ source_url: str = ""
75
+
76
+
77
+ @dataclass(frozen=True)
78
+ class AdapterRecipe:
79
+ repo_id: str
80
+ adapter_for: str
81
+ task: str
82
+ weight: float = 0.75
83
+ license: str = "unknown"
84
+ adult_only: bool = False
85
+ compatibility: str = "compatible"
86
+
87
+
88
+ @dataclass(frozen=True)
89
+ class GroundingTarget:
90
+ slot_name: str
91
+ query: str
92
+ expected_region: str
93
+ confidence: float
94
+
95
+
96
+ @dataclass(frozen=True)
97
+ class InspectionReport:
98
+ status: str
99
+ targets: list[GroundingTarget]
100
+ drift_flags: list[str]
101
+ locate_model: str = "nvidia/LocateAnything-3B"
102
+
103
+
104
+ @dataclass(frozen=True)
105
+ class LoreBeatSet:
106
+ beats: list[dict[str, str]]
107
+ tone: str
108
+
109
+
110
+ @dataclass(frozen=True)
111
+ class VideoPlan:
112
+ preset: str
113
+ source: str
114
+ camera_move: str
115
+ duration_seconds: float
116
+ fps: int
117
+ continuity_locks: list[str]
118
+ checkpoint_required: bool = True
119
+
120
+
121
+ @dataclass(frozen=True)
122
+ class HumanCheckpoint:
123
+ checkpoint_id: str
124
+ recommendation: str
125
+ trust_score: float
126
+ required_actions: list[str]
127
+
128
+
129
+ @dataclass(frozen=True)
130
+ class GenerationRun:
131
+ request: CreativeRequest
132
+ refined_prompt: TasteRefinedPrompt
133
+ outfit: OutfitGraph
134
+ model_stack: list[ModelCandidate]
135
+ adapters: list[AdapterRecipe]
136
+ inspection: InspectionReport
137
+ lore: LoreBeatSet
138
+ video: VideoPlan
139
+ checkpoint: HumanCheckpoint
140
+ created_at: str = field(default_factory=utc_now)
141
+
142
+ def to_dict(self) -> dict[str, Any]:
143
+ return asdict(self)
144
+
145
+
146
+ @dataclass(frozen=True)
147
+ class WisdomRecord:
148
+ run_id: str
149
+ approved: bool
150
+ dataset_target: str
151
+ lessons: list[str]
152
+ created_at: str = field(default_factory=utc_now)
153
+
src/nexus_visual_weaver/security.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Always-on ST3GG-inspired defensive scanning adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp"}
10
+ ARCHIVE_EXTENSIONS = {".zip", ".7z", ".rar"}
11
+
12
+
13
+ def _entropy(data: bytes) -> float:
14
+ if not data:
15
+ return 0.0
16
+ counts = [0] * 256
17
+ for byte in data:
18
+ counts[byte] += 1
19
+ total = len(data)
20
+ return -sum((count / total) * math.log2(count / total) for count in counts if count)
21
+
22
+
23
+ def _magic_type(data: bytes) -> str:
24
+ if data.startswith(b"\x89PNG\r\n\x1a\n"):
25
+ return "png"
26
+ if data.startswith(b"\xff\xd8\xff"):
27
+ return "jpeg"
28
+ if data.startswith((b"GIF87a", b"GIF89a")):
29
+ return "gif"
30
+ if len(data) >= 12 and data[:4] == b"RIFF" and data[8:12] == b"WEBP":
31
+ return "webp"
32
+ if data.startswith(b"BM"):
33
+ return "bmp"
34
+ if data.startswith(b"PK\x03\x04"):
35
+ return "zip"
36
+ if data.startswith(b"7z\xbc\xaf\x27\x1c"):
37
+ return "7z"
38
+ if data.startswith(b"Rar!\x1a\x07"):
39
+ return "rar"
40
+ if data.startswith(b"%PDF-"):
41
+ return "pdf"
42
+ return "unknown"
43
+
44
+
45
+ def _expected_magic(extension: str) -> str | None:
46
+ return {
47
+ ".png": "png",
48
+ ".jpg": "jpeg",
49
+ ".jpeg": "jpeg",
50
+ ".webp": "webp",
51
+ ".gif": "gif",
52
+ ".bmp": "bmp",
53
+ ".zip": "zip",
54
+ ".7z": "7z",
55
+ ".rar": "rar",
56
+ }.get(extension)
57
+
58
+
59
+ def _png_trailing_data(target: Path, size_bytes: int) -> bool:
60
+ if size_bytes > 5_000_000:
61
+ return False
62
+ data = target.read_bytes()
63
+ marker = b"IEND\xaeB`\x82"
64
+ idx = data.rfind(marker)
65
+ return idx >= 0 and idx + len(marker) < len(data)
66
+
67
+
68
+ def scan_file(path: str | None) -> dict[str, Any]:
69
+ purification_actions = [
70
+ "strip metadata before export",
71
+ "truncate PNG after IEND when needed",
72
+ "run LSB statistical review",
73
+ "recompress JPEG/WebP derivative for public export",
74
+ ]
75
+ if not path:
76
+ return {
77
+ "status": "idle",
78
+ "scanner": "ST3GG defensive adapter",
79
+ "findings": ["No upload selected. Always-on scanner ready."],
80
+ "purification_actions": purification_actions,
81
+ "export_gate": "pending",
82
+ "payload_excerpt": None,
83
+ }
84
+
85
+ target = Path(path)
86
+ if not target.exists() or not target.is_file():
87
+ return {
88
+ "status": "error",
89
+ "scanner": "ST3GG defensive adapter",
90
+ "findings": ["File path is unavailable to scanner."],
91
+ "purification_actions": purification_actions,
92
+ "export_gate": "blocked",
93
+ "payload_excerpt": None,
94
+ }
95
+
96
+ size_bytes = target.stat().st_size
97
+ with target.open("rb") as handle:
98
+ sample = handle.read(65536)
99
+ entropy = round(_entropy(sample), 3)
100
+ extension = target.suffix.lower()
101
+ magic = _magic_type(sample)
102
+ expected_magic = _expected_magic(extension)
103
+ review_reasons: list[str] = []
104
+ findings = [
105
+ f"extension={extension or 'none'}",
106
+ f"magic={magic}",
107
+ f"size_bytes={size_bytes}",
108
+ f"entropy_sample={entropy}",
109
+ ]
110
+ if entropy > 7.7:
111
+ review_reasons.append("high entropy sample; review metadata/embedded payload risk")
112
+ if extension in IMAGE_EXTENSIONS:
113
+ findings.append("image file queued for metadata and LSB review")
114
+ if expected_magic and magic != expected_magic:
115
+ review_reasons.append("image extension does not match detected file signature")
116
+ if extension == ".png" and magic == "png" and _png_trailing_data(target, size_bytes):
117
+ review_reasons.append("PNG contains trailing data after IEND marker")
118
+ if extension in ARCHIVE_EXTENSIONS or magic in {"zip", "7z", "rar"}:
119
+ review_reasons.append("archive upload requires explicit review before export")
120
+ if expected_magic is None and magic in {"zip", "7z", "rar"}:
121
+ review_reasons.append("archive signature found without matching extension")
122
+
123
+ findings.extend(review_reasons)
124
+ status = "review" if review_reasons else "pass"
125
+ return {
126
+ "status": status,
127
+ "scanner": "ST3GG defensive adapter",
128
+ "findings": findings,
129
+ "purification_actions": purification_actions,
130
+ "export_gate": "clear" if status == "pass" else "blocked",
131
+ "size_bytes": size_bytes,
132
+ "extension": extension or "none",
133
+ "magic": magic,
134
+ "entropy_sample": entropy,
135
+ "payload_excerpt": None,
136
+ }
src/nexus_visual_weaver/styles.py ADDED
@@ -0,0 +1,877 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CSS for the command center dashboard."""
2
+
3
+ APP_CSS = """
4
+ :root {
5
+ --nw-bg: #05070a;
6
+ --nw-panel: #090d12;
7
+ --nw-panel-2: #10161d;
8
+ --nw-panel-3: #151c24;
9
+ --nw-line: #222a33;
10
+ --nw-line-strong: #33404c;
11
+ --nw-text: #f3f7fb;
12
+ --nw-muted: #97a4ae;
13
+ --nw-faint: #66737d;
14
+ --nw-red: #ff365f;
15
+ --nw-red-soft: #46121d;
16
+ --nw-cyan: #20d9e8;
17
+ --nw-blue: #6b7dff;
18
+ --nw-violet: #b45cff;
19
+ --nw-green: #26d782;
20
+ --nw-amber: #e8b44e;
21
+ }
22
+ body, .gradio-container {
23
+ background: var(--nw-bg) !important;
24
+ color: var(--nw-text) !important;
25
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
26
+ }
27
+ .gradio-container { max-width: none !important; padding: 0 !important; }
28
+ footer { display: none !important; }
29
+ .nw-app {
30
+ min-height: 100vh;
31
+ background:
32
+ linear-gradient(90deg, rgba(255,255,255,.025) 1px, transparent 1px),
33
+ linear-gradient(180deg, rgba(255,255,255,.018) 1px, transparent 1px),
34
+ linear-gradient(180deg, #070a0e 0%, #05070a 52%, #06090d 100%),
35
+ var(--nw-bg);
36
+ background-size: 48px 48px, 48px 48px, auto;
37
+ }
38
+ .nw-topbar {
39
+ min-height: 64px;
40
+ display: grid;
41
+ grid-template-columns: 220px 138px 164px minmax(200px, 1fr) 132px 150px 178px 138px 190px;
42
+ gap: 0;
43
+ align-items: stretch;
44
+ border-bottom: 1px solid #171c22;
45
+ background: rgba(5, 7, 10, 0.98);
46
+ box-shadow: 0 1px 0 rgba(255,255,255,.035), 0 12px 36px rgba(0,0,0,.36);
47
+ }
48
+ .nw-brand {
49
+ display: flex;
50
+ align-items: center;
51
+ padding: 0 22px;
52
+ font-size: 22px;
53
+ font-weight: 650;
54
+ letter-spacing: 0;
55
+ border-right: 1px solid var(--nw-line);
56
+ color: var(--nw-text);
57
+ white-space: nowrap;
58
+ }
59
+ .nw-brand span { color: var(--nw-red); margin-right: 10px; font-weight: 850; }
60
+ .nw-brand strong { font-size: 18px; font-weight: 650; white-space: nowrap; }
61
+ .nw-topbar strong,
62
+ .nw-panel strong,
63
+ .nw-inspector strong {
64
+ color: var(--nw-text);
65
+ }
66
+ .nw-topitem, .nw-budget, .nw-status, .nw-adult {
67
+ padding: 10px 18px;
68
+ border-right: 1px solid var(--nw-line);
69
+ display: flex;
70
+ flex-direction: column;
71
+ justify-content: center;
72
+ gap: 3px;
73
+ min-width: 0;
74
+ }
75
+ .nw-topitem small, .nw-budget small, .nw-adult small, .nw-status small { color: var(--nw-muted); font-size: 11px; line-height: 1.2; }
76
+ .nw-topitem strong, .nw-budget strong, .nw-status strong, .nw-adult strong { font-size: 13px; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
77
+ .nw-topitem i {
78
+ position: absolute;
79
+ width: 6px;
80
+ height: 6px;
81
+ border-right: 1px solid var(--nw-muted);
82
+ border-bottom: 1px solid var(--nw-muted);
83
+ transform: rotate(45deg);
84
+ right: 18px;
85
+ }
86
+ .nw-gmr { gap: 5px; }
87
+ .nw-space strong { color: var(--nw-cyan); }
88
+ .nw-meter { height: 8px; background: #20262d; border-radius: 8px; overflow: hidden; margin-top: 5px; box-shadow: inset 0 0 0 1px rgba(255,255,255,.05); }
89
+ .nw-meter i { display: block; height: 100%; background: linear-gradient(90deg, var(--nw-red), #ff6b7f); box-shadow: 0 0 16px rgba(255,54,95,.34); }
90
+ .nw-live-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; background: var(--nw-green); box-shadow: 0 0 12px rgba(38,215,130,.7); margin-right: 7px; vertical-align: -1px; }
91
+ .nw-status { position: relative; }
92
+ .nw-status .nw-live-dot { position: absolute; top: 21px; left: 18px; }
93
+ .nw-status > strong { padding-left: 18px; }
94
+ .nw-toggle {
95
+ display: inline-flex;
96
+ align-items: center;
97
+ width: fit-content;
98
+ gap: 7px;
99
+ min-height: 24px;
100
+ padding: 2px 8px 2px 3px;
101
+ border: 1px solid var(--nw-line-strong);
102
+ border-radius: 999px;
103
+ color: var(--nw-muted);
104
+ font-size: 11px;
105
+ }
106
+ .nw-toggle i { width: 20px; height: 20px; border-radius: 50%; background: #bac2c9; box-shadow: inset 0 -3px 8px rgba(0,0,0,.35); }
107
+ .nw-toggle.is-on { color: var(--nw-amber); border-color: rgba(232,180,78,.45); }
108
+ .nw-locked {
109
+ display: grid;
110
+ grid-template-columns: 38px 1fr;
111
+ align-items: center;
112
+ gap: 10px;
113
+ padding: 10px 14px;
114
+ border-left: 1px solid var(--nw-line);
115
+ color: #ff6b7d;
116
+ background: linear-gradient(90deg, rgba(255,54,95,.12), rgba(255,54,95,.04));
117
+ }
118
+ .nw-locked b { border: 1px solid rgba(255,54,95,.55); border-radius: 5px; padding: 7px 6px; text-align: center; font-size: 13px; }
119
+ .nw-locked span { font-size: 11px; line-height: 1.25; }
120
+ .nw-icon { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
121
+ .nw-shell {
122
+ display: grid;
123
+ grid-template-columns: 118px minmax(650px, 1fr) 360px;
124
+ grid-template-rows: 570px auto;
125
+ gap: 8px;
126
+ padding: 8px 8px 0;
127
+ }
128
+ .nw-rail {
129
+ grid-row: 1 / span 2;
130
+ background: linear-gradient(180deg, rgba(11, 15, 20, .98), rgba(6, 9, 12, .98));
131
+ border: 1px solid var(--nw-line);
132
+ border-radius: 7px;
133
+ padding: 8px 6px;
134
+ display: flex;
135
+ flex-direction: column;
136
+ gap: 10px;
137
+ }
138
+ .nw-rail-main { display: grid; gap: 6px; }
139
+ .nw-rail .nw-icon { width: 21px; height: 21px; }
140
+ .nw-rail-foot { margin-top: auto; display: grid; gap: 8px; }
141
+ .nw-rail-foot div {
142
+ border: 1px solid var(--nw-line);
143
+ border-radius: 7px;
144
+ background: rgba(255,255,255,.025);
145
+ color: var(--nw-muted);
146
+ padding: 9px 8px;
147
+ display: grid;
148
+ grid-template-columns: 22px 1fr;
149
+ gap: 2px 7px;
150
+ }
151
+ .nw-rail-foot strong { font-size: 11px; line-height: 1.25; }
152
+ .nw-rail-foot span { grid-column: 2; color: var(--nw-muted); font-size: 11px; }
153
+ .nw-rail-foot .nw-icon { color: var(--nw-green); grid-row: 1 / span 2; width: 19px; height: 19px; align-self: center; }
154
+ .nw-rail-foot div:last-child .nw-icon { color: var(--nw-muted); }
155
+ .nw-rail-foot div:last-child { margin-bottom: 70px; }
156
+ .nw-rail::after {
157
+ content: "Runs 112 Queue 2";
158
+ color: var(--nw-muted);
159
+ font-size: 11px;
160
+ padding: 11px 6px 3px;
161
+ border-top: 1px solid var(--nw-line);
162
+ }
163
+ .nw-rail-item {
164
+ background: transparent;
165
+ border: 1px solid transparent;
166
+ color: #c9d0d8;
167
+ border-radius: 7px;
168
+ padding: 13px 10px;
169
+ font-size: 12px;
170
+ display: flex;
171
+ gap: 10px;
172
+ justify-content: flex-start;
173
+ align-items: center;
174
+ width: 100%;
175
+ min-height: 54px;
176
+ }
177
+ .nw-rail-item span {
178
+ color: currentColor;
179
+ font-size: 12px;
180
+ font-weight: 560;
181
+ }
182
+ .nw-rail-item.active {
183
+ color: var(--nw-text);
184
+ border-color: rgba(255,54,95,.32);
185
+ background: linear-gradient(90deg, rgba(255,54,95,.20), rgba(255,54,95,.05));
186
+ box-shadow: inset 3px 0 0 var(--nw-red);
187
+ }
188
+ .nw-panel {
189
+ background: linear-gradient(180deg, rgba(14, 19, 25, .97), rgba(7, 11, 15, .98));
190
+ border: 1px solid var(--nw-line);
191
+ border-radius: 7px;
192
+ box-shadow: 0 18px 55px rgba(0, 0, 0, .28), inset 0 1px 0 rgba(255,255,255,.04);
193
+ }
194
+ .nw-panel-head {
195
+ min-height: 50px;
196
+ padding: 10px 16px;
197
+ border-bottom: 1px solid var(--nw-line);
198
+ display: flex;
199
+ justify-content: space-between;
200
+ align-items: center;
201
+ gap: 12px;
202
+ }
203
+ .nw-panel-head strong { font-size: 15px; font-weight: 620; }
204
+ .nw-panel-head small { display: block; color: var(--nw-muted); font-size: 12px; margin-top: 3px; }
205
+ .nw-panel-head button,
206
+ .nw-tools button,
207
+ .nw-tools select,
208
+ .nw-panel-head select,
209
+ .nw-beat select {
210
+ min-height: 28px;
211
+ border: 1px solid var(--nw-line-strong);
212
+ background: rgba(255,255,255,.025);
213
+ color: var(--nw-text);
214
+ border-radius: 5px;
215
+ font-size: 11px;
216
+ padding: 4px 9px;
217
+ }
218
+ .nw-tools { display: flex; align-items: center; gap: 8px; color: var(--nw-muted); font-size: 12px; }
219
+ .nw-tools .nw-icon { width: 15px; height: 15px; }
220
+ .nw-static-tools span,
221
+ .nw-chip,
222
+ .nw-mini-chip {
223
+ min-height: 26px;
224
+ display: inline-flex;
225
+ align-items: center;
226
+ gap: 6px;
227
+ border: 1px solid var(--nw-line-strong);
228
+ background: rgba(255,255,255,.025);
229
+ color: var(--nw-muted);
230
+ border-radius: 5px;
231
+ font-size: 11px;
232
+ padding: 4px 8px;
233
+ white-space: nowrap;
234
+ }
235
+ .nw-mini-chip { width: fit-content; min-height: 22px; padding: 2px 7px; }
236
+ .nw-canvas { overflow: hidden; height: 570px; }
237
+ .nw-graph { width: 100%; min-height: 548px; display: block; background: #070b0f; }
238
+ .nw-edges path {
239
+ fill: none;
240
+ stroke: var(--nw-cyan);
241
+ stroke-width: 2.2;
242
+ opacity: .82;
243
+ }
244
+ .nw-node rect {
245
+ fill: rgba(13, 18, 24, .96);
246
+ stroke: var(--nw-line-strong);
247
+ stroke-width: 1.25;
248
+ filter: drop-shadow(0 16px 18px rgba(0,0,0,.25));
249
+ }
250
+ .nw-node-red rect { stroke: rgba(255,54,95,.74); }
251
+ .nw-node-violet rect { stroke: rgba(180,92,255,.72); }
252
+ .nw-node-blue rect { stroke: rgba(107,125,255,.72); }
253
+ .nw-node-cyan rect { stroke: rgba(32,217,232,.74); }
254
+ .nw-node-green rect { stroke: rgba(38,215,130,.68); }
255
+ .nw-node-amber rect { stroke: rgba(232,180,78,.78); }
256
+ .nw-node-title { fill: var(--nw-text); font-size: 15px; font-weight: 650; }
257
+ .nw-node-line { fill: #cbd4dc; font-size: 12px; }
258
+ .nw-node-footer { fill: var(--nw-muted); font-size: 11px; }
259
+ .nw-node-sep { stroke: rgba(255,255,255,.08); stroke-width: 1; }
260
+ .nw-node-ok { fill: var(--nw-green); }
261
+ .nw-thumb { stroke: rgba(255,255,255,.16); stroke-width: 1; fill: #17202a; }
262
+ .nw-thumb-0 { fill: #101417; }
263
+ .nw-thumb-1 { fill: #1e2428; }
264
+ .nw-thumb-2 { fill: #33111b; }
265
+ .nw-thumb-3 { fill: #0f2630; }
266
+ .nw-legend { padding: 0 16px 13px; display: flex; gap: 8px; flex-wrap: wrap; }
267
+ .nw-weave-console {
268
+ display: grid;
269
+ grid-template-columns: 1.1fr 1.4fr 1.25fr 1.35fr;
270
+ gap: 8px;
271
+ padding: 0 12px 12px;
272
+ }
273
+ .nw-console-card {
274
+ min-height: 88px;
275
+ border: 1px solid var(--nw-line);
276
+ border-radius: 6px;
277
+ background:
278
+ linear-gradient(135deg, rgba(255,255,255,.04), transparent 40%),
279
+ rgba(255,255,255,.018);
280
+ padding: 10px;
281
+ display: grid;
282
+ align-content: space-between;
283
+ gap: 5px;
284
+ }
285
+ .nw-console-primary {
286
+ border-color: rgba(255,54,95,.36);
287
+ background:
288
+ linear-gradient(135deg, rgba(255,54,95,.14), transparent 48%),
289
+ rgba(255,255,255,.02);
290
+ }
291
+ .nw-console-card small {
292
+ color: var(--nw-faint);
293
+ font-size: 10px;
294
+ font-weight: 760;
295
+ letter-spacing: 0;
296
+ }
297
+ .nw-console-card strong {
298
+ color: var(--nw-text);
299
+ font-size: 12px;
300
+ line-height: 1.25;
301
+ }
302
+ .nw-console-card span {
303
+ color: var(--nw-muted);
304
+ font-size: 11px;
305
+ line-height: 1.35;
306
+ }
307
+ .nw-badge {
308
+ display: inline-flex;
309
+ align-items: center;
310
+ min-height: 24px;
311
+ padding: 3px 9px;
312
+ border-radius: 5px;
313
+ border: 1px solid var(--nw-line-strong);
314
+ color: var(--nw-muted);
315
+ font-size: 11px;
316
+ font-weight: 650;
317
+ width: fit-content;
318
+ }
319
+ .nw-pass { color: var(--nw-green); border-color: rgba(46, 229, 157, .35); background: rgba(46, 229, 157, .08); }
320
+ .nw-warn { color: var(--nw-amber); border-color: rgba(245, 184, 61, .35); background: rgba(245, 184, 61, .08); }
321
+ .nw-accent { color: var(--nw-red); border-color: rgba(244, 63, 94, .35); background: rgba(244, 63, 94, .08); }
322
+ .nw-cyan { color: var(--nw-cyan); border-color: rgba(34, 211, 238, .35); background: rgba(34, 211, 238, .08); }
323
+ .nw-violet { color: var(--nw-violet); border-color: rgba(180,92,255,.35); background: rgba(180,92,255,.08); }
324
+ .nw-blue { color: var(--nw-blue); border-color: rgba(107,125,255,.35); background: rgba(107,125,255,.08); }
325
+ .nw-muted { color: var(--nw-muted); }
326
+ .nw-inspector { grid-column: 3; grid-row: 1; height: 570px; padding-bottom: 12px; overflow-y: auto; }
327
+ .nw-inspector h3 { font-size: 12px; margin: 12px 16px 6px; color: var(--nw-text); font-weight: 620; }
328
+ .nw-rings { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; padding: 12px 16px; }
329
+ .nw-rings div {
330
+ aspect-ratio: 1;
331
+ border-radius: 999px;
332
+ display: grid;
333
+ place-content: center;
334
+ text-align: center;
335
+ background:
336
+ radial-gradient(circle at center, #10161d 0 55%, transparent 56%),
337
+ conic-gradient(var(--ring) calc(var(--v) * 1%), rgba(255,255,255,.08) 0);
338
+ box-shadow: inset 0 0 0 1px rgba(255,255,255,.06);
339
+ }
340
+ .nw-rings b { font-size: 18px; }
341
+ .nw-rings small { color: var(--nw-muted); font-size: 9px; max-width: 58px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
342
+ .nw-checks, .nw-models, .nw-relay { list-style: none; margin: 0 16px; padding: 0; display: grid; gap: 5px; }
343
+ .nw-checks { grid-template-columns: repeat(2, minmax(0, 1fr)); }
344
+ .nw-checks li, .nw-models li, .nw-relay li {
345
+ border: 1px solid var(--nw-line);
346
+ border-radius: 5px;
347
+ padding: 6px 8px;
348
+ background: linear-gradient(90deg, rgba(255,255,255,.035), rgba(255,255,255,.016));
349
+ display: flex;
350
+ justify-content: flex-start;
351
+ gap: 8px;
352
+ font-size: 10px;
353
+ color: #dce8ef;
354
+ }
355
+ .nw-checks li span { color: var(--nw-green); font-weight: 900; }
356
+ .nw-models span { color: var(--nw-muted); }
357
+ .nw-models li {
358
+ justify-content: space-between;
359
+ }
360
+ .nw-models strong {
361
+ color: #dce8ef;
362
+ font-size: 11px;
363
+ text-align: right;
364
+ }
365
+ .nw-relay li {
366
+ display: grid;
367
+ grid-template-columns: minmax(74px, .7fr) minmax(90px, 1fr);
368
+ gap: 4px 8px;
369
+ }
370
+ .nw-relay span { color: var(--nw-muted); font-size: 11px; }
371
+ .nw-relay strong { font-size: 11px; color: #dce8ef; text-align: right; }
372
+ .nw-relay em {
373
+ grid-column: 1 / span 2;
374
+ color: var(--nw-muted);
375
+ font-style: normal;
376
+ font-size: 10px;
377
+ line-height: 1.3;
378
+ }
379
+ .nw-relay-foot { margin: 9px 16px 0; display: flex; flex-wrap: wrap; gap: 6px; }
380
+ .nw-scan { margin: 0 16px; padding: 9px; border: 1px solid var(--nw-line); border-radius: 6px; display: grid; gap: 7px; background: rgba(255,255,255,.02); }
381
+ .nw-scan > div { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
382
+ .nw-scan span { color: var(--nw-muted); font-size: 11px; }
383
+ .nw-scan i { display: block; height: 7px; border-radius: 7px; background: linear-gradient(90deg, var(--nw-green), rgba(38,215,130,.25)); }
384
+ .nw-scan dl { display: grid; grid-template-columns: 90px 1fr; gap: 5px 8px; margin: 0; font-size: 10px; }
385
+ .nw-scan dt { color: var(--nw-muted); }
386
+ .nw-scan dd { color: #d8e1e8; margin: 0; text-align: right; }
387
+ .nw-scan-list {
388
+ margin: 0;
389
+ padding: 0;
390
+ list-style: none;
391
+ display: grid;
392
+ gap: 4px;
393
+ }
394
+ .nw-scan-list li {
395
+ border: 1px solid rgba(255,255,255,.06);
396
+ background: rgba(255,255,255,.022);
397
+ border-radius: 5px;
398
+ padding: 5px 7px;
399
+ color: #d8e1e8;
400
+ font-size: 10px;
401
+ line-height: 1.3;
402
+ }
403
+ .nw-scan-actions li { color: var(--nw-muted); }
404
+ .nw-bottom { grid-column: 2 / span 2; grid-row: 2; display: grid; grid-template-columns: .95fr 1.05fr; gap: 10px; }
405
+ .nw-filter-row { display: flex; gap: 7px; padding: 9px 12px 0; overflow-x: auto; }
406
+ .nw-filter-row span { border: 1px solid var(--nw-line); border-radius: 5px; padding: 6px 11px; color: var(--nw-muted); font-size: 11px; background: rgba(255,255,255,.018); white-space: nowrap; }
407
+ .nw-filter-row span:first-child { color: var(--nw-red); border-color: rgba(255,54,95,.45); }
408
+ .nw-swatches, .nw-beats { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(112px, 1fr); gap: 8px; overflow-x: auto; padding: 12px; }
409
+ .nw-beats { grid-auto-columns: minmax(128px, 1fr); }
410
+ .nw-swatch, .nw-beat { min-height: 134px; border: 1px solid var(--nw-line); border-radius: 6px; padding: 7px; background: rgba(255,255,255,.024); display: grid; align-content: start; gap: 6px; }
411
+ .nw-swatch.is-locked {
412
+ border-color: rgba(38,215,130,.34);
413
+ background:
414
+ linear-gradient(135deg, rgba(38,215,130,.08), transparent 42%),
415
+ rgba(255,255,255,.024);
416
+ }
417
+ .nw-swatch i, .nw-beat i {
418
+ display: block; height: 72px; border-radius: 5px;
419
+ border: 1px solid #303841;
420
+ box-shadow: inset 0 0 24px rgba(0,0,0,.4);
421
+ }
422
+ .nw-swatch .nw-material-0, .nw-material-0 { background: linear-gradient(135deg, #030405, #20252a 42%, #f5f5f1 44%, #0b0d10 46%, #11181e); }
423
+ .nw-swatch .nw-material-1, .nw-material-1 { background: linear-gradient(135deg, #c8c8bd, #545750 35%, #15181b 70%, #08090a); }
424
+ .nw-swatch .nw-material-2, .nw-material-2 { background: repeating-linear-gradient(135deg, #080a0d 0 6px, #262a2e 7px 9px, #0d1013 10px 16px); }
425
+ .nw-swatch .nw-material-3, .nw-material-3 { background: radial-gradient(circle at 55% 48%, #ff375f 0 7px, #6b1320 8px 19px, #0a0b0d 20px), linear-gradient(135deg, #0b0b0d, #2c0e16); }
426
+ .nw-swatch .nw-material-4, .nw-material-4 { background: linear-gradient(160deg, #060709, #111820 45%, #3b4148 46%, #090b0d 70%); }
427
+ .nw-swatch .nw-material-5, .nw-material-5 { background: linear-gradient(135deg, #151719, #292d30 40%, #0a0c0f 60%, #1d2226); }
428
+ .nw-story-0 { background: linear-gradient(135deg, #29211a, #0c1217 45%, #38444b); }
429
+ .nw-story-1 { background: linear-gradient(135deg, #101922, #1a5960 42%, #711c36 70%, #090b0e); }
430
+ .nw-story-2 { background: linear-gradient(135deg, #14191e, #363f4a 45%, #0b0d10); }
431
+ .nw-story-3 { background: linear-gradient(135deg, #121316, #c9d1d4 50%, #1a1015 55%, #07090c); }
432
+ .nw-story-4 { background: linear-gradient(135deg, #0f1115, #2d1a27, #050607 68%); }
433
+ .nw-story-5 { background: linear-gradient(135deg, #111820, #333b43 45%, #0a0d11); }
434
+ .nw-swatch strong, .nw-beat strong { font-size: 11px; color: var(--nw-text); line-height: 1.25; }
435
+ .nw-swatch small, .nw-beat small { font-size: 10px; color: var(--nw-muted); line-height: 1.3; }
436
+ .nw-swatch span {
437
+ width: fit-content;
438
+ border: 1px solid rgba(255,255,255,.08);
439
+ border-radius: 5px;
440
+ color: var(--nw-faint);
441
+ font-size: 10px;
442
+ padding: 2px 6px;
443
+ }
444
+ .nw-beat select { min-height: 24px; width: 100%; padding: 2px 5px; color: var(--nw-muted); }
445
+ .nw-catalog { padding: 16px; }
446
+ .nw-catalog table { width: 100%; border-collapse: collapse; font-size: 12px; }
447
+ .nw-catalog th, .nw-catalog td { border-bottom: 1px solid var(--nw-line); padding: 9px 8px; text-align: left; vertical-align: top; }
448
+ .nw-catalog th { color: var(--nw-muted); font-size: 11px; text-transform: uppercase; }
449
+ #nw-inputs .form, #nw-inputs .block { background: var(--nw-panel) !important; border-color: var(--nw-line) !important; }
450
+ #nw-inputs,
451
+ #nw-inputs .styler,
452
+ #nw-inputs .form,
453
+ #nw-inputs .block,
454
+ #nw-inputs .wrap,
455
+ #nw-inputs .container,
456
+ #nw-inputs .upload-container,
457
+ #nw-inputs .file-preview,
458
+ #nw-inputs .empty,
459
+ #nw-inputs .input-container,
460
+ #nw-inputs .prose,
461
+ #nw-inputs [data-testid],
462
+ #nw-inputs fieldset {
463
+ background: #080d12 !important;
464
+ border-color: var(--nw-line) !important;
465
+ color: var(--nw-text) !important;
466
+ }
467
+ #nw-inputs textarea, #nw-inputs input, #nw-inputs select {
468
+ background: #091018 !important;
469
+ color: var(--nw-text) !important;
470
+ border-color: var(--nw-line-strong) !important;
471
+ }
472
+ #nw-inputs label, #nw-inputs .label-wrap, #nw-inputs span, #nw-inputs p { color: var(--nw-text) !important; }
473
+ #nw-inputs button {
474
+ border-radius: 5px !important;
475
+ border-color: var(--nw-line-strong) !important;
476
+ background: #101820 !important;
477
+ color: var(--nw-text) !important;
478
+ font-size: 13px !important;
479
+ }
480
+ #nw-inputs button.primary,
481
+ #nw-inputs button[variant="primary"] {
482
+ background: linear-gradient(180deg, #ff3b62, #c51a3b) !important;
483
+ border-color: rgba(255,54,95,.58) !important;
484
+ }
485
+ .nw-control-panel {
486
+ margin: 8px 8px 10px 8px;
487
+ padding: 12px;
488
+ border: 1px solid var(--nw-line);
489
+ border-radius: 7px;
490
+ background:
491
+ linear-gradient(135deg, rgba(255,54,95,.08), transparent 28%),
492
+ linear-gradient(180deg, rgba(13,18,24,.98), rgba(7,11,15,.98));
493
+ position: sticky;
494
+ top: 0;
495
+ z-index: 20;
496
+ box-shadow: 0 14px 32px rgba(0,0,0,.28), inset 0 1px 0 rgba(255,255,255,.04);
497
+ }
498
+ .nw-command-header {
499
+ display: grid;
500
+ grid-template-columns: minmax(260px, 1fr) auto;
501
+ align-items: center;
502
+ gap: 14px;
503
+ padding: 2px 2px 12px;
504
+ border-bottom: 1px solid rgba(255,255,255,.06);
505
+ margin-bottom: 12px;
506
+ }
507
+ .nw-command-header small {
508
+ color: var(--nw-red);
509
+ font-size: 10px;
510
+ font-weight: 800;
511
+ letter-spacing: 0;
512
+ }
513
+ .nw-command-header strong {
514
+ display: block;
515
+ color: var(--nw-text);
516
+ font-size: 17px;
517
+ font-weight: 680;
518
+ line-height: 1.25;
519
+ }
520
+ .nw-command-header span {
521
+ display: block;
522
+ color: var(--nw-muted);
523
+ font-size: 12px;
524
+ line-height: 1.35;
525
+ margin-top: 3px;
526
+ }
527
+ .nw-command-pills {
528
+ display: flex;
529
+ flex-wrap: wrap;
530
+ justify-content: flex-end;
531
+ gap: 7px;
532
+ }
533
+ #nw-workspace {
534
+ padding: 0 8px;
535
+ gap: 8px !important;
536
+ align-items: stretch;
537
+ }
538
+ #nw-workspace > .form,
539
+ #nw-workspace .block,
540
+ #nw-workspace .panel,
541
+ #nw-native-rail,
542
+ #nw-main-column,
543
+ #nw-side-column {
544
+ background: transparent !important;
545
+ border: 0 !important;
546
+ box-shadow: none !important;
547
+ }
548
+ #nw-native-rail,
549
+ #nw-main-column,
550
+ #nw-side-column {
551
+ gap: 8px !important;
552
+ }
553
+ #nw-section-nav,
554
+ #nw-section-nav .form,
555
+ #nw-section-nav fieldset,
556
+ #nw-section-nav .wrap {
557
+ background: linear-gradient(180deg, rgba(11, 15, 20, .98), rgba(6, 9, 12, .98)) !important;
558
+ border-color: var(--nw-line) !important;
559
+ color: var(--nw-text) !important;
560
+ border-radius: 7px !important;
561
+ }
562
+ #nw-section-nav label,
563
+ #nw-section-nav span {
564
+ color: var(--nw-text) !important;
565
+ }
566
+ #nw-section-nav .wrap {
567
+ display: grid !important;
568
+ gap: 5px !important;
569
+ }
570
+ #nw-section-nav input[type="radio"] {
571
+ accent-color: var(--nw-red);
572
+ }
573
+ .nw-native-rail {
574
+ border: 1px solid var(--nw-line);
575
+ background: linear-gradient(180deg, rgba(12,17,23,.97), rgba(7,10,14,.98));
576
+ border-radius: 7px;
577
+ padding: 12px;
578
+ display: grid;
579
+ gap: 8px;
580
+ }
581
+ .nw-native-rail strong {
582
+ color: var(--nw-text);
583
+ font-size: 13px;
584
+ }
585
+ .nw-native-rail span {
586
+ color: var(--nw-muted);
587
+ font-size: 11px;
588
+ line-height: 1.4;
589
+ }
590
+ #nw-workspace .nw-canvas,
591
+ #nw-workspace .nw-inspector {
592
+ grid-column: auto;
593
+ grid-row: auto;
594
+ }
595
+ #nw-workspace .nw-canvas {
596
+ height: auto;
597
+ min-height: 570px;
598
+ }
599
+ #nw-workspace .nw-inspector {
600
+ height: auto;
601
+ min-height: 570px;
602
+ }
603
+ .nw-main-stack,
604
+ .nw-side-stack {
605
+ display: grid;
606
+ gap: 8px;
607
+ }
608
+ .nw-artifacts {
609
+ overflow: hidden;
610
+ }
611
+ .nw-preview-stage {
612
+ display: grid;
613
+ grid-template-columns: minmax(280px, 1fr) 190px;
614
+ gap: 10px;
615
+ padding: 12px 12px 0;
616
+ }
617
+ .nw-preview-frame {
618
+ min-height: 190px;
619
+ border: 1px solid rgba(255,255,255,.08);
620
+ border-radius: 7px;
621
+ overflow: hidden;
622
+ display: grid;
623
+ grid-template-columns: minmax(180px, .86fr) 1fr;
624
+ background:
625
+ radial-gradient(circle at 18% 18%, rgba(255,54,95,.18), transparent 32%),
626
+ linear-gradient(135deg, #0a0d11, #111820 52%, #080a0d);
627
+ }
628
+ .nw-preview-image {
629
+ min-height: 190px;
630
+ display: block;
631
+ background:
632
+ linear-gradient(130deg, rgba(255,255,255,.16) 0 2px, transparent 3px 52%),
633
+ radial-gradient(circle at 42% 34%, #f5f0e8 0 4px, transparent 5px),
634
+ radial-gradient(circle at 44% 50%, #242930 0 30px, transparent 31px),
635
+ linear-gradient(155deg, #050608, #171d23 44%, #4f1020 45%, #07090c 63%);
636
+ border-right: 1px solid rgba(255,255,255,.08);
637
+ }
638
+ .nw-preview-caption {
639
+ display: grid;
640
+ align-content: end;
641
+ gap: 6px;
642
+ padding: 16px;
643
+ }
644
+ .nw-preview-caption small,
645
+ .nw-preview-meta small,
646
+ .nw-artifact-card small {
647
+ color: var(--nw-faint);
648
+ font-size: 10px;
649
+ font-weight: 760;
650
+ letter-spacing: 0;
651
+ }
652
+ .nw-preview-caption strong {
653
+ font-size: 15px;
654
+ color: var(--nw-text);
655
+ line-height: 1.25;
656
+ }
657
+ .nw-preview-caption span {
658
+ color: #cbd5dc;
659
+ font-size: 12px;
660
+ line-height: 1.45;
661
+ }
662
+ .nw-preview-meta {
663
+ display: grid;
664
+ gap: 8px;
665
+ }
666
+ .nw-preview-meta div {
667
+ border: 1px solid var(--nw-line);
668
+ border-radius: 6px;
669
+ background: rgba(255,255,255,.024);
670
+ padding: 10px;
671
+ display: grid;
672
+ align-content: center;
673
+ gap: 4px;
674
+ }
675
+ .nw-preview-meta strong {
676
+ color: var(--nw-text);
677
+ font-size: 12px;
678
+ line-height: 1.25;
679
+ }
680
+ .nw-preview-ribbon {
681
+ margin: 10px 12px 0;
682
+ display: grid;
683
+ grid-template-columns: repeat(3, minmax(0, 1fr));
684
+ gap: 8px;
685
+ }
686
+ .nw-preview-ribbon span {
687
+ min-height: 34px;
688
+ border: 1px solid var(--nw-line);
689
+ border-radius: 6px;
690
+ background: rgba(255,255,255,.02);
691
+ color: #cfd8df;
692
+ font-size: 11px;
693
+ line-height: 1.3;
694
+ display: flex;
695
+ align-items: center;
696
+ gap: 8px;
697
+ padding: 7px 9px;
698
+ min-width: 0;
699
+ }
700
+ .nw-preview-ribbon .nw-icon {
701
+ width: 16px;
702
+ height: 16px;
703
+ flex: 0 0 auto;
704
+ color: var(--nw-cyan);
705
+ }
706
+ .nw-operations {
707
+ overflow: hidden;
708
+ }
709
+ .nw-operation-grid {
710
+ padding: 12px;
711
+ display: grid;
712
+ grid-template-columns: repeat(3, minmax(0, 1fr));
713
+ gap: 8px;
714
+ }
715
+ .nw-operation-card {
716
+ min-height: 116px;
717
+ border: 1px solid var(--nw-line);
718
+ border-radius: 6px;
719
+ background:
720
+ linear-gradient(135deg, rgba(32,217,232,.07), transparent 34%),
721
+ rgba(255,255,255,.022);
722
+ padding: 11px;
723
+ display: grid;
724
+ align-content: space-between;
725
+ gap: 10px;
726
+ }
727
+ .nw-operation-card small {
728
+ color: var(--nw-cyan);
729
+ font-size: 10px;
730
+ font-weight: 760;
731
+ letter-spacing: 0;
732
+ }
733
+ .nw-operation-card strong {
734
+ color: #dce8ef;
735
+ font-size: 12px;
736
+ line-height: 1.4;
737
+ font-weight: 560;
738
+ }
739
+ .nw-operation-card i {
740
+ display: block;
741
+ height: 5px;
742
+ border-radius: 6px;
743
+ background:
744
+ linear-gradient(90deg, var(--nw-cyan), rgba(32,217,232,.18) 68%, rgba(255,255,255,.06));
745
+ box-shadow: 0 0 14px rgba(32,217,232,.14);
746
+ }
747
+ .nw-artifact-grid {
748
+ padding: 12px;
749
+ display: grid;
750
+ grid-template-columns: repeat(4, minmax(120px, 1fr));
751
+ gap: 8px;
752
+ }
753
+ .nw-artifact-card,
754
+ .nw-provider-card {
755
+ border: 1px solid var(--nw-line);
756
+ border-radius: 6px;
757
+ background: linear-gradient(180deg, rgba(255,255,255,.035), rgba(255,255,255,.014));
758
+ padding: 8px;
759
+ display: grid;
760
+ gap: 6px;
761
+ min-width: 0;
762
+ }
763
+ .nw-artifact-card > small {
764
+ justify-self: end;
765
+ }
766
+ .nw-artifact-card i {
767
+ display: block;
768
+ height: 86px;
769
+ border-radius: 5px;
770
+ border: 1px solid #303841;
771
+ box-shadow: inset 0 0 24px rgba(0,0,0,.42);
772
+ }
773
+ .nw-artifact-card strong,
774
+ .nw-provider-card strong {
775
+ color: var(--nw-text);
776
+ font-size: 12px;
777
+ line-height: 1.25;
778
+ overflow-wrap: anywhere;
779
+ }
780
+ .nw-artifact-card span,
781
+ .nw-provider-card span,
782
+ .nw-provider-card small {
783
+ color: var(--nw-muted);
784
+ font-size: 11px;
785
+ line-height: 1.3;
786
+ }
787
+ .nw-providers {
788
+ overflow: hidden;
789
+ }
790
+ .nw-provider-grid {
791
+ display: grid;
792
+ grid-template-columns: repeat(2, minmax(0, 1fr));
793
+ gap: 8px;
794
+ padding: 12px;
795
+ }
796
+ .nw-provider-card {
797
+ min-height: 112px;
798
+ }
799
+ .nw-provider-meter {
800
+ height: 6px;
801
+ border-radius: 8px;
802
+ background:
803
+ linear-gradient(90deg, var(--nw-green) 0 calc(var(--health) * 1%), #252d35 calc(var(--health) * 1%) 100%);
804
+ box-shadow: inset 0 0 0 1px rgba(255,255,255,.05);
805
+ }
806
+ .nw-provider-card div {
807
+ display: flex;
808
+ gap: 6px;
809
+ flex-wrap: wrap;
810
+ }
811
+ .nw-statusbar {
812
+ height: 54px;
813
+ margin: 8px;
814
+ display: grid !important;
815
+ grid-template-columns: 110px 120px 160px 200px 140px 150px 1fr 160px;
816
+ align-items: stretch;
817
+ border: 1px solid var(--nw-line);
818
+ border-radius: 7px;
819
+ overflow: hidden;
820
+ background: rgba(7, 10, 14, .98);
821
+ }
822
+ .nw-metric, .nw-autosave {
823
+ border-right: 1px solid var(--nw-line);
824
+ padding: 8px 18px;
825
+ display: flex;
826
+ align-items: center;
827
+ gap: 12px;
828
+ }
829
+ .nw-metric small, .nw-autosave small { color: var(--nw-muted); font-size: 11px; }
830
+ .nw-metric strong, .nw-autosave strong { color: var(--nw-text); font-size: 13px; font-weight: 560; }
831
+ .nw-metric-bar::after {
832
+ content: "";
833
+ height: 6px;
834
+ flex: 1;
835
+ border-radius: 8px;
836
+ background: linear-gradient(90deg, var(--nw-green) 0 46%, #232a31 47% 100%);
837
+ }
838
+ .nw-autosave { justify-content: flex-end; }
839
+ .nw-stop {
840
+ margin: 8px;
841
+ border: 1px solid rgba(255,54,95,.55);
842
+ background: linear-gradient(180deg, #f32d56, #bd1739);
843
+ color: white;
844
+ border-radius: 6px;
845
+ font-weight: 650;
846
+ font-size: 13px;
847
+ }
848
+ .nw-stop-idle {
849
+ display: grid;
850
+ place-items: center;
851
+ border-color: var(--nw-line-strong);
852
+ background: rgba(255,255,255,.025);
853
+ color: var(--nw-muted);
854
+ }
855
+ @media (max-width: 1100px) {
856
+ .nw-topbar { grid-template-columns: 1fr; }
857
+ .nw-shell { grid-template-columns: 1fr; grid-template-rows: auto; }
858
+ .nw-rail, .nw-inspector, .nw-bottom { grid-column: 1; grid-row: auto; }
859
+ .nw-rail { flex-direction: row; overflow-x: auto; }
860
+ .nw-rail-main { display: flex; }
861
+ .nw-rail-foot, .nw-rail::after { display: none; }
862
+ .nw-bottom { grid-template-columns: 1fr; }
863
+ #nw-workspace { flex-direction: column; }
864
+ .nw-command-header, .nw-preview-stage, .nw-preview-frame { grid-template-columns: 1fr; }
865
+ .nw-command-pills { justify-content: flex-start; }
866
+ .nw-operation-grid { grid-template-columns: 1fr; }
867
+ .nw-weave-console, .nw-preview-ribbon { grid-template-columns: 1fr; }
868
+ .nw-artifact-grid, .nw-provider-grid { grid-template-columns: 1fr 1fr; }
869
+ .nw-graph { min-height: 430px; }
870
+ .nw-statusbar { grid-template-columns: 1fr 1fr; height: auto; }
871
+ }
872
+ @media (max-width: 720px) {
873
+ .nw-artifact-grid, .nw-provider-grid { grid-template-columns: 1fr; }
874
+ .nw-rings { grid-template-columns: repeat(2, 1fr); }
875
+ .nw-checks { grid-template-columns: 1fr; }
876
+ }
877
+ """
src/nexus_visual_weaver/taste.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Taste profile loading and deterministic scoring."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ from pathlib import Path
8
+
9
+ from .schema import TasteProfile, TasteRefinedPrompt
10
+
11
+ DEFAULT_TASTE_PATH = Path(__file__).resolve().parents[2] / "assets" / "taste_profile.json"
12
+
13
+ FEATURE_ALIASES: dict[str, tuple[str, ...]] = {
14
+ "patent_leather": ("patent leather", "gloss leather", "black leather", "latex-tech"),
15
+ "faux_fur": ("faux fur", "fur trim", "black fur", "fur collar"),
16
+ "chantilly_lace": ("chantilly lace", "lace", "lace mesh", "lace threads"),
17
+ "crimson_hardware": ("crimson hardware", "red metal", "red buckles", "crimson buckles", "red choker"),
18
+ "platform_boots": ("platform boots", "platform boot", "heavy boots", "tall boots"),
19
+ "slavic_model": ("slavic", "high cheekbones", "pale matte skin", "intense focused eyes"),
20
+ "nexus_sigils": ("nexus sigil", "nexus sigils", "orchestrator glyph", "node glyph"),
21
+ "rain_slicked_surfaces": ("rain", "rain-slicked", "wet pavement", "neon rain"),
22
+ "floating_code_data_streams": ("floating code", "data streams", "code streams"),
23
+ }
24
+
25
+
26
+ def load_taste_profile(path: Path | str = DEFAULT_TASTE_PATH) -> TasteProfile:
27
+ data = json.loads(Path(path).read_text(encoding="utf-8"))
28
+ rules = data.get("enforcement_rules", {})
29
+ return TasteProfile(
30
+ version=data.get("version", "unknown"),
31
+ locked_features=data.get("locked_features", {}),
32
+ must_include=rules.get("must_include", []),
33
+ should_include=rules.get("should_include", []),
34
+ forbidden=rules.get("forbidden", []),
35
+ )
36
+
37
+
38
+ def _has_alias(text: str, aliases: tuple[str, ...]) -> bool:
39
+ lowered = text.lower()
40
+ return any(re.search(rf"\b{re.escape(alias)}\b", lowered) for alias in aliases)
41
+
42
+
43
+ def score_prompt(prompt: str) -> tuple[float, list[str], list[str]]:
44
+ found: list[str] = []
45
+ missing: list[str] = []
46
+ for feature, aliases in FEATURE_ALIASES.items():
47
+ if _has_alias(prompt, aliases):
48
+ found.append(feature)
49
+ else:
50
+ missing.append(feature)
51
+
52
+ must = ["patent_leather", "crimson_hardware", "platform_boots", "slavic_model"]
53
+ must_hits = sum(1 for feature in must if feature in found)
54
+ optional_hits = len(found) - must_hits
55
+ score = min(0.98, 0.38 + must_hits * 0.11 + optional_hits * 0.045)
56
+ return round(score, 2), missing, found
57
+
58
+
59
+ def refine_prompt(prompt: str, adult_mode: bool = False) -> TasteRefinedPrompt:
60
+ additions: list[str] = []
61
+ score, missing, _ = score_prompt(prompt)
62
+ supplement_map = {
63
+ "patent_leather": "rich black patent leather with visible grain and wet reflections",
64
+ "faux_fur": "dense black faux fur trim at collar and cuffs",
65
+ "chantilly_lace": "delicate Chantilly lace mesh at neckline and sleeves",
66
+ "crimson_hardware": "glowing crimson hardware on buckles, choker, and closures",
67
+ "platform_boots": "structured platform boots with polished black soles",
68
+ "slavic_model": "Slavic model features with high cheekbones and intense focused eyes",
69
+ "nexus_sigils": "subtle NEXUS sigils and orchestrator node glyphs woven into holographic streams",
70
+ "rain_slicked_surfaces": "rain-slicked cinematic surfaces under cyan and magenta neon",
71
+ "floating_code_data_streams": "floating code and iridescent data streams in the background",
72
+ }
73
+ for feature in missing:
74
+ if feature in supplement_map and len(additions) < 6:
75
+ additions.append(supplement_map[feature])
76
+
77
+ mode_clause = "adult catalog remains opt-in and partitioned" if adult_mode else "public-safe presentation"
78
+ refined = " ".join(
79
+ part.strip()
80
+ for part in [prompt.strip(), ", ".join(additions), mode_clause, "ultra-photorealistic FLUX.2 texture detail"]
81
+ if part.strip()
82
+ )
83
+ final_score, final_missing, _ = score_prompt(refined)
84
+ return TasteRefinedPrompt(
85
+ original=prompt,
86
+ refined=refined,
87
+ additions=additions,
88
+ score=final_score,
89
+ missing_features=final_missing,
90
+ )
91
+
src/nexus_visual_weaver/wardrobe.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Wardrobe slot extraction and validation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .schema import OutfitGraph, WardrobeSlot
6
+
7
+ SLOT_BLUEPRINTS: list[tuple[str, str, str, str, str]] = [
8
+ ("hair_headwear", "sleek hair or sculptural headwear", "hair / metal", "obsidian with neon edge", "identity lock"),
9
+ ("upper_body", "structured bodice or fitted top", "chantilly_lace", "black lace and mesh", "lace / mesh adapter"),
10
+ ("outerwear", "long technical coat or jacket", "patent_leather", "jet black", "FLUX.2 material texture"),
11
+ ("hands", "gloves with interface details", "patent_leather", "black with crimson nodes", "detail LoRA"),
12
+ ("lower_body", "tailored lower silhouette", "layered_garments", "dark graphite", "garment consistency"),
13
+ ("footwear", "strong platform boots", "polished_leather", "matte black sole", "boot detail"),
14
+ ("jewelry", "choker, buckles, and hard points", "crimson_hardware", "crimson metal", "hardware glow"),
15
+ ("props", "holographic sigils and data tools", "holographic_glass", "cyan and magenta", "NEXUS glyphs"),
16
+ ("background_context", "rain, code, and location atmosphere", "neon_rain", "obsidian/cyan/crimson", "environmental continuity"),
17
+ ]
18
+
19
+
20
+ def build_outfit_graph(prompt: str, adult_mode: bool = False) -> OutfitGraph:
21
+ lowered = prompt.lower()
22
+ slots: list[WardrobeSlot] = []
23
+ for index, (name, description, material, palette, lora_hint) in enumerate(SLOT_BLUEPRINTS):
24
+ locked = any(token in lowered for token in name.split("_")) or material.replace("_", " ") in lowered
25
+ if name == "outerwear" and "coat" in lowered:
26
+ locked = True
27
+ if name == "footwear" and ("boot" in lowered or "platform" in lowered):
28
+ locked = True
29
+ if name == "jewelry" and ("crimson" in lowered or "hardware" in lowered):
30
+ locked = True
31
+ slots.append(
32
+ WardrobeSlot(
33
+ name=name,
34
+ description=description,
35
+ material=material,
36
+ palette=palette,
37
+ lora_hint=lora_hint,
38
+ locked=locked,
39
+ adult_only=False if not adult_mode else name in {"upper_body", "lower_body"},
40
+ locate_region="auto-map",
41
+ edit_priority=max(1, 5 - index // 2),
42
+ )
43
+ )
44
+ locked_count = sum(1 for slot in slots if slot.locked)
45
+ score = round(0.68 + min(0.24, locked_count * 0.035), 2)
46
+ return OutfitGraph(slots=slots, score=score)
47
+
src/nexus_visual_weaver/workflow.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Small deterministic workflow state model for the dashboard graph."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+
7
+
8
+ @dataclass
9
+ class WorkflowNode:
10
+ id: str
11
+ title: str
12
+ lane: str
13
+ status: str = "pending"
14
+ score: float | None = None
15
+
16
+
17
+ @dataclass
18
+ class WorkflowState:
19
+ nodes: list[WorkflowNode] = field(default_factory=list)
20
+ paused_at: str = "human_checkpoint"
21
+
22
+ @classmethod
23
+ def default(cls) -> "WorkflowState":
24
+ return cls(
25
+ nodes=[
26
+ WorkflowNode("seed", "Seed Prompt", "Creative", "complete"),
27
+ WorkflowNode("refine", "Refine", "Creative", "complete", 0.86),
28
+ WorkflowNode("judge", "Judge", "Model", "complete", 0.84),
29
+ WorkflowNode("locate", "Locate", "Grounding", "complete", 0.88),
30
+ WorkflowNode("generate", "Generate", "Model", "ready"),
31
+ WorkflowNode("video", "Video Path", "Output", "ready"),
32
+ WorkflowNode("checkpoint", "Human Checkpoint", "Guardrail", "paused"),
33
+ ]
34
+ )
35
+
tests/fixtures/sample.png ADDED
tests/test_command_center.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+
3
+ from nexus_visual_weaver.catalog import active_stack, catalog_summary, filter_catalog, parameter_budget
4
+ from nexus_visual_weaver.grounding import inspect_outfit
5
+ from nexus_visual_weaver.model_relay import WeaverModelRelay
6
+ from nexus_visual_weaver.planner import build_command_center_run
7
+ from nexus_visual_weaver.render import render_command_header, render_dashboard_regions, render_operations_panel
8
+ from nexus_visual_weaver.security import scan_file
9
+ from nexus_visual_weaver.taste import refine_prompt, score_prompt
10
+ from nexus_visual_weaver.wardrobe import build_outfit_graph
11
+
12
+
13
+ def test_taste_refinement_adds_locked_features() -> None:
14
+ refined = refine_prompt("Cyberpunk woman in neon rain")
15
+ assert refined.score >= 0.75
16
+ assert "patent leather" in refined.refined.lower()
17
+ assert "crimson hardware" in refined.refined.lower()
18
+
19
+
20
+ def test_wardrobe_slots_have_required_structure() -> None:
21
+ outfit = build_outfit_graph("black patent leather coat, crimson hardware, platform boots")
22
+ slot_names = {slot.name for slot in outfit.slots}
23
+ assert {"outerwear", "upper_body", "footwear", "jewelry", "background_context"} <= slot_names
24
+ assert all(slot.edit_priority >= 1 for slot in outfit.slots)
25
+
26
+
27
+ def test_locateanything_inspection_flags_drift_when_needed() -> None:
28
+ outfit = build_outfit_graph("minimal cyberpunk portrait")
29
+ report = inspect_outfit(outfit)
30
+ assert report.locate_model == "nvidia/LocateAnything-3B"
31
+ assert report.targets
32
+ assert "footwear requires stronger prompt lock" in report.drift_flags
33
+
34
+
35
+ def test_adult_catalog_hidden_by_default_and_visible_when_enabled() -> None:
36
+ models_default, adapters_default = filter_catalog(False)
37
+ models_adult, adapters_adult = filter_catalog(True)
38
+ assert not any(model.adult_only for model in models_default)
39
+ assert not any(adapter.adult_only for adapter in adapters_default)
40
+ assert any(model.adult_only for model in models_adult)
41
+ assert any(adapter.adult_only for adapter in adapters_adult)
42
+
43
+
44
+ def test_active_parameter_budget_passes_default_stack() -> None:
45
+ budget = parameter_budget(active_stack(False))
46
+ assert budget["status"] == "pass"
47
+ assert budget["active_b"] <= 32.0
48
+
49
+
50
+ def test_command_center_run_is_checkpointed() -> None:
51
+ run = build_command_center_run(
52
+ "Slavic model, patent leather, faux fur, Chantilly lace, crimson hardware, platform boots, NEXUS sigils"
53
+ )
54
+ assert run.checkpoint.checkpoint_id.startswith("nw-")
55
+ assert run.video.checkpoint_required is True
56
+ assert run.inspection.targets
57
+ assert run.model_stack[2].repo_id == "nvidia/LocateAnything-3B"
58
+
59
+
60
+ def test_security_scan_does_not_return_payload_excerpt() -> None:
61
+ sample = Path(__file__).parent / "fixtures" / "sample.png"
62
+ scan = scan_file(str(sample))
63
+ assert scan["payload_excerpt"] is None
64
+ assert scan["status"] in {"pass", "review"}
65
+ assert scan["purification_actions"]
66
+ assert scan["export_gate"] in {"clear", "blocked"}
67
+
68
+
69
+ def test_security_scan_flags_extension_magic_mismatch() -> None:
70
+ sample = Path(__file__).parent / "fixtures" / "sample.png"
71
+ scan = scan_file(str(sample))
72
+
73
+ assert scan["extension"] == ".png"
74
+ assert scan["magic"] == "unknown"
75
+ assert scan["status"] == "review"
76
+ assert scan["export_gate"] == "blocked"
77
+ assert any("extension does not match" in finding for finding in scan["findings"])
78
+
79
+
80
+ def test_dashboard_regions_expose_artifacts_and_provider_cards() -> None:
81
+ run = build_command_center_run("gothic couture archivist, patent leather, platform boots")
82
+ relay = WeaverModelRelay()
83
+ regions = render_dashboard_regions(
84
+ run=run,
85
+ adult_mode=False,
86
+ scan=scan_file(None),
87
+ relay_status=relay.dashboard_snapshot(public_demo=True),
88
+ )
89
+
90
+ assert "Artifact Preview Lane" in regions["artifacts"]
91
+ assert "nw-preview-stage" in regions["artifacts"]
92
+ assert "nw-preview-ribbon" in regions["artifacts"]
93
+ assert "PRIMARY OUTPUT STAGE" in regions["artifacts"]
94
+ assert "JUDGE-SAFE DEMO OUTPUT" in regions["artifacts"]
95
+ assert "state: dry-run / configured / blocked / failed" in regions["artifacts"]
96
+ assert "Forge Operations" in regions["operations"]
97
+ assert "Provider Handoff Cards" in regions["providers"]
98
+ assert "nw-provider-meter" in regions["providers"]
99
+ assert "optional gateway" in regions["providers"]
100
+ assert "CHECKPOINTED" in regions["providers"]
101
+ assert "Selected: Forge" in regions["command_rail"]
102
+ assert "ST3GG Scan" in regions["inspector"]
103
+ assert "nw-weave-console" in regions["workflow"]
104
+ assert "Hackathon Signal" in regions["workflow"]
105
+ assert "Boots / heels" in regions["drawer"]
106
+ assert "checkpointed" in regions["drawer"]
107
+
108
+
109
+ def test_dashboard_regions_render_with_empty_relay_and_default_scan() -> None:
110
+ regions = render_dashboard_regions(relay_status={}, scan=None, active_section="Forge")
111
+
112
+ assert "Forge Operations" in regions["operations"]
113
+ assert "not-started" in regions["operations"]
114
+ assert "snapshot pending" in regions["providers"]
115
+ assert "Selected: Forge" in regions["command_rail"]
116
+ assert "provider call remains checkpointed" in regions["artifacts"]
117
+
118
+
119
+ def test_dashboard_operations_follow_selected_section() -> None:
120
+ relay = WeaverModelRelay()
121
+ sections = {
122
+ "Wardrobe": "Footwear focus",
123
+ "Lore": "Beat budget",
124
+ "Models": "Rotation mode",
125
+ "Security": "ST3GG state",
126
+ "Runs": "Ledger mode",
127
+ }
128
+
129
+ for section, marker in sections.items():
130
+ regions = render_dashboard_regions(
131
+ adult_mode=(section == "Models"),
132
+ scan=scan_file(None),
133
+ relay_status=relay.dashboard_snapshot(public_demo=section != "Models"),
134
+ active_section=section,
135
+ )
136
+ assert f"{section} Operations" in regions["operations"]
137
+ assert marker in regions["operations"]
138
+ assert f"Selected: {section}" in regions["command_rail"]
139
+
140
+
141
+ def test_security_operations_distinguish_clean_scan_from_idle() -> None:
142
+ clean_scan = {"status": "pass", "export_gate": "clear", "findings": []}
143
+ html = render_operations_panel(active_section="Security", scan=clean_scan)
144
+
145
+ assert "No findings." in html
146
+ assert "No upload selected." not in html
147
+
148
+
149
+ def test_command_header_exposes_governed_run_controls() -> None:
150
+ header = render_command_header()
151
+
152
+ assert "Raven Chronicle Active Weave" in header
153
+ assert "ST3GG ALWAYS ON" in header
154
+ assert "FLUX.2 PINNED" in header
155
+ assert "HUMAN CHECKPOINT" in header
156
+
157
+
158
+ def test_dashboard_surfaces_hf_space_status_without_secrets(monkeypatch) -> None:
159
+ for name in ["FAL_KEY", "NETLIFY_AUTH_TOKEN", "NETLIFY_SITE_ID", "OPENAI_BASE_URL", "OPENAI_API_KEY", "MODAL_TOKEN_ID"]:
160
+ monkeypatch.delenv(name, raising=False)
161
+
162
+ regions = render_dashboard_regions(relay_status=WeaverModelRelay().dashboard_snapshot(public_demo=True))
163
+
164
+ assert "ZeroGPU" in regions["topbar"]
165
+ assert "no provider secrets" in regions["topbar"]
166
+ assert "HF Space" in regions["status"]
167
+
168
+
169
+ def test_catalog_summary_reflects_adult_scope() -> None:
170
+ default_summary = catalog_summary(False)
171
+ adult_summary = catalog_summary(True)
172
+ assert default_summary["adult_catalog"] == "hidden"
173
+ assert adult_summary["adult_catalog"] == "enabled"
174
+ assert adult_summary["models_visible"] > default_summary["models_visible"]
tests/test_model_relay.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from nexus_visual_weaver.model_relay import WeaverModelRelay
4
+ from nexus_visual_weaver.render import render_dashboard
5
+
6
+
7
+ def test_pinned_lanes_do_not_rotate() -> None:
8
+ relay = WeaverModelRelay()
9
+ decision = relay.select_lane("image_generation", strategy="private_research")
10
+
11
+ assert decision.pinned is True
12
+ assert decision.rotatable is False
13
+ assert decision.primary is not None
14
+ assert decision.primary.repo_id == "black-forest-labs/FLUX.2-klein-9B"
15
+ assert decision.fallbacks == []
16
+ assert "rotation disabled" in decision.reason
17
+
18
+
19
+ def test_public_private_taste_judge_respects_license_and_budget() -> None:
20
+ relay = WeaverModelRelay()
21
+
22
+ public = relay.select_lane("taste_judge", public_demo=True, strategy="quality_first")
23
+ private = relay.select_lane("taste_judge", budget=12.0, public_demo=False, strategy="private_research")
24
+
25
+ assert public.primary is not None
26
+ assert public.primary.params_b <= 5.0
27
+ assert public.primary.public_safe is True
28
+ assert "OFFELLIA" not in public.primary.repo_id
29
+ assert private.primary is not None
30
+ assert private.primary.model_id == "offellia-gemma4-12b-private"
31
+
32
+
33
+ def test_audio_tts_excludes_higgs_and_keeps_one_active_model() -> None:
34
+ relay = WeaverModelRelay()
35
+
36
+ decision = relay.select_lane("audio_lore_tts", public_demo=True, strategy="license_safe_public")
37
+ selected_ids = [decision.primary.model_id] + [record.model_id for record in decision.fallbacks]
38
+
39
+ assert decision.primary is not None
40
+ assert decision.primary.params_b <= 5.0
41
+ assert decision.primary.model_id != "higgs-audio-v3-excluded"
42
+ assert "higgs-audio-v3-excluded" not in selected_ids
43
+ assert decision.primary.lane == "audio_lore_tts"
44
+
45
+
46
+ def test_quota_exhaustion_enters_cooldown_and_uses_fallback() -> None:
47
+ relay = WeaverModelRelay()
48
+ exhausted = relay.records["hf-api-metadata-cache"]
49
+ exhausted.rpm_limit = 1
50
+ relay.record_success(exhausted.model_id)
51
+
52
+ decision = relay.select_lane("hf_catalog_research", public_demo=True, strategy="quota_saver")
53
+
54
+ assert exhausted.cooldown_until is not None
55
+ assert decision.primary is not None
56
+ assert decision.primary.model_id != exhausted.model_id
57
+ assert any("quota exhausted" in reason for reason in decision.skipped)
58
+
59
+
60
+ def test_metadata_dedup_prevents_repeated_hf_lookup() -> None:
61
+ relay = WeaverModelRelay()
62
+ calls = {"count": 0}
63
+
64
+ def resolver() -> dict[str, int]:
65
+ calls["count"] += 1
66
+ return {"calls": calls["count"]}
67
+
68
+ first = relay.metadata_lookup("hf:black-forest-labs/FLUX.2-klein-9B", resolver)
69
+ second = relay.metadata_lookup("hf:black-forest-labs/FLUX.2-klein-9B", resolver)
70
+
71
+ assert first == second == {"calls": 1}
72
+ assert calls["count"] == 1
73
+ assert relay.get_rotation_status()["dedup_hits"] == 1
74
+
75
+
76
+ def test_regressions_cost_speed_alias_and_failure_handlers() -> None:
77
+ relay = WeaverModelRelay()
78
+ record = relay.records["minicpm5-1b-router"]
79
+ original_cost_hint = record.cost_hint
80
+
81
+ speed_decision = relay.select_lane("router", strategy="speed")
82
+ fast_decision = relay.select_lane("router", strategy="fast")
83
+ relay.record_failure(record.model_id, "temporary provider error")
84
+ relay.record_success(record.model_id, latency_ms=120)
85
+
86
+ assert speed_decision.strategy == "latency_first"
87
+ assert fast_decision.strategy == "latency_first"
88
+ assert record.cost_hint == original_cost_hint
89
+ assert record.failure_count == 1
90
+ assert record.success_count == 1
91
+ assert record.health == "healthy"
92
+
93
+
94
+ def test_context_packet_survives_video_repair_fallback() -> None:
95
+ relay = WeaverModelRelay()
96
+ first = relay.select_lane("video_repair", public_demo=False, strategy="private_research")
97
+ assert first.primary is not None
98
+
99
+ for _ in range(3):
100
+ relay.record_failure(first.primary.model_id, "modal batch unavailable")
101
+
102
+ fallback = relay.select_lane("video_repair", public_demo=False, strategy="private_research")
103
+
104
+ assert fallback.primary is not None
105
+ assert fallback.primary.model_id != first.primary.model_id
106
+ assert fallback.context_packet.lane == "video_repair"
107
+ assert fallback.context_packet.task == "video_repair"
108
+ assert any("health=unhealthy" in reason for reason in fallback.skipped)
109
+
110
+
111
+ def test_dashboard_surfaces_gmr_pinned_models_and_fallbacks() -> None:
112
+ relay = WeaverModelRelay()
113
+ html = render_dashboard(relay_status=relay.dashboard_snapshot(public_demo=True))
114
+
115
+ assert "GMR ModelRelay" in html
116
+ assert "FLUX.2 pinned" in html
117
+ assert "LocateAnything pinned" in html
118
+ assert "fallback:" in html
119
+ assert "Rotation Safe" in html
120
+
121
+
122
+ def test_optional_external_gateways_are_registered_but_excluded_by_default() -> None:
123
+ relay = WeaverModelRelay()
124
+
125
+ assert relay.records["netlify-ai-gateway-helper"].provider == "netlify"
126
+ assert relay.records["cloudflare-agent-helper"].provider == "cloudflare"
127
+ assert relay.records["fal-media-adapter"].provider == "fal"
128
+ assert relay.records["netflix-void-modal"].health == "healthy"
129
+ assert relay.records["netlify-ai-gateway-helper"].health == "excluded"
130
+ assert relay.records["fal-media-adapter"].health == "excluded"