Spaces:
Runtime error
Runtime error
Deploy NEXUS command center v2.2
Browse files- .coderabbit.yaml +40 -0
- .env.example +9 -0
- .gitattributes +11 -35
- .github/CODEOWNERS +2 -0
- .github/pull_request_template.md +19 -0
- .github/workflows/ci.yml +40 -0
- .gitignore +69 -0
- AGENTS.md +31 -0
- LICENSE +201 -0
- README.md +100 -6
- SECURITY.md +39 -0
- app.py +277 -136
- assets/taste_profile.json +69 -0
- docs/HACKATHON_EVALUATION.md +34 -0
- docs/RELEASE_WORKFLOW.md +35 -0
- pytest.ini +4 -0
- requirements.txt +3 -6
- src/nexus_visual_weaver/__init__.py +47 -0
- src/nexus_visual_weaver/catalog.py +171 -0
- src/nexus_visual_weaver/grounding.py +33 -0
- src/nexus_visual_weaver/lore.py +39 -0
- src/nexus_visual_weaver/model_relay.py +823 -0
- src/nexus_visual_weaver/planner.py +55 -0
- src/nexus_visual_weaver/render.py +681 -0
- src/nexus_visual_weaver/schema.py +153 -0
- src/nexus_visual_weaver/security.py +136 -0
- src/nexus_visual_weaver/styles.py +877 -0
- src/nexus_visual_weaver/taste.py +91 -0
- src/nexus_visual_weaver/wardrobe.py +47 -0
- src/nexus_visual_weaver/workflow.py +35 -0
- tests/fixtures/sample.png +0 -0
- tests/test_command_center.py +174 -0
- tests/test_model_relay.py +130 -0
.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 |
-
*
|
| 2 |
-
*.
|
| 3 |
-
*.
|
| 4 |
-
*.
|
| 5 |
-
*.
|
| 6 |
-
*.
|
| 7 |
-
*.
|
| 8 |
-
*.
|
| 9 |
-
*.
|
| 10 |
-
*.
|
| 11 |
-
|
| 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:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 6.
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
license: apache-2.0
|
| 11 |
-
short_description:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
---
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
)
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
prompt=prompt,
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
)
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
]
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
-
result = gr.Image(label="Result", show_label=False)
|
| 83 |
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 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 |
-
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 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 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
)
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
label="
|
| 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 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
],
|
| 150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
)
|
| 152 |
|
|
|
|
| 153 |
if __name__ == "__main__":
|
| 154 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 2 |
-
|
| 3 |
-
|
| 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"
|