polats commited on
Commit
7da8fc4
·
verified ·
1 Parent(s): fa1c86e

Deploy Karate Wiener (kimodo kata maker)

Browse files
app.py CHANGED
@@ -127,6 +127,30 @@ CLOTHING_CATALOG = [
127
  DEFAULT_CLOTHING = {"head": "beanie_green", "face": "nerdy_glasses",
128
  "torso_over": "kimono", "legs": "kimono_trousers"}
129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  APP_CSS = """
131
  #main-preview {
132
  min-width: 0;
@@ -2252,6 +2276,8 @@ def _drawers_html() -> str:
2252
  'data-drawer="kimodo-compose-drawer" title="Storyboard composer: sequence stances into a kata">COMPOSE</button>',
2253
  '<button id="kimodo-clothing-toggle" class="kimodo-left-tab" type="button" '
2254
  'data-drawer="kimodo-clothing-drawer" title="Add clothing to the citizen">CLOTHING</button>',
 
 
2255
  '</div>',
2256
  # Actions library (clip cards populated in a later phase).
2257
  '<aside id="kimodo-actions-drawer" class="kimodo-drawer" aria-label="Actions library">',
@@ -2311,6 +2337,8 @@ def _drawers_html() -> str:
2311
  f'{html.escape(item["label"])}</button>'
2312
  )
2313
  parts.append("</aside>")
 
 
2314
  # Fixed bottom-of-page Build & Play — shown only while a sequence is storyboarded.
2315
  parts.append('<button id="kimodo-compose-launch" type="button">▶ Build &amp; Play</button>')
2316
  parts.append("</div>")
@@ -3248,6 +3276,9 @@ _COMPOSE_JS = r"""
3248
  """
3249
 
3250
 
 
 
 
3251
  DRAWER_JS = f"""
3252
  (() => {{
3253
  let attempts = 0;
@@ -3333,6 +3364,9 @@ DRAWER_JS = f"""
3333
  if (open && drawer.id === 'kimodo-stances-drawer') refreshStances();
3334
  // Build the storyboard composer (palette + strip) when its drawer opens.
3335
  if (open && drawer.id === 'kimodo-compose-drawer') composeOpen();
 
 
 
3336
  // Shift the viewer camera to keep the character centered beside the open drawer.
3337
  sendViewportInset();
3338
  }}
@@ -3417,6 +3451,8 @@ DRAWER_JS = f"""
3417
  {_KATA_JS}
3418
  {_STANCES_JS}
3419
  {_COMPOSE_JS}
 
 
3420
 
3421
  window.addEventListener('message', (event) => {{
3422
  const msg = event.data || {{}};
@@ -3451,7 +3487,8 @@ DRAWER_JS = f"""
3451
  """
3452
  APP_HEAD = (
3453
  '<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>'
3454
- f"<style>{APP_CSS}</style><script>{DRAWER_JS}</script>"
 
3455
  )
3456
 
3457
 
@@ -4085,6 +4122,13 @@ with gr.Blocks(title="Kimodo ZeroGPU", css=_BLOCKS_CSS) as demo:
4085
  fn=_generate_button_label, inputs=[anonymous_client_id], outputs=[btn], api_name=False,
4086
  )
4087
 
 
 
 
 
 
 
 
4088
  # Display model drives the persistent viewer live via postMessage (no reload).
4089
  # Clothing is handled by the fixed browser drawer so it can behave like kata's drawers.
4090
  model_dd.change(
 
127
  DEFAULT_CLOTHING = {"head": "beanie_green", "face": "nerdy_glasses",
128
  "torso_over": "kimono", "legs": "kimono_trousers"}
129
 
130
+ # --- Pluggable side tabs ---------------------------------------------------
131
+ # A NEW side tab is added by DROPPING FILES into ./tabs — NO app.py edit needed,
132
+ # so multiple people can build tabs in parallel without touching shared code:
133
+ # tabs/<name>.tab.html -> the rail button (.kimodo-left-tab[data-drawer=…])
134
+ # tabs/<name>.drawer.html -> the slide-in <aside id="kimodo-<name>-drawer"> panel
135
+ # tabs/<name>.tab.css -> styles for that tab
136
+ # tabs/<name>.tab.js -> behavior; runs INSIDE the drawer-JS scope, so it can
137
+ # call the shared helpers (previewFrame, fetchManifest,
138
+ # fetchRecord, loadIntoViewer, setOpen, sendViewportInset…).
139
+ # All matching files are globbed + concatenated in sorted filename order at import.
140
+ # The drawer manager auto-discovers the new button+aside from the DOM; the tab hooks
141
+ # its "drawer opened" behavior WITHOUT editing setOpen via:
142
+ # window.addEventListener('kimodo-drawer-open', (e) => {
143
+ # if (e.detail === 'kimodo-<name>-drawer') { /* refresh / build */ }
144
+ # });
145
+ # Tabs needing a Python/@GPU backend are the only case that still edits app.py — at
146
+ # the "NEW-TAB BACKEND EXTENSION POINT" marker near the compose wiring.
147
+ # See tabs/README.md + tabs/templates/ for a complete copy-paste example.
148
+ _TABS_DIR = Path(__file__).parent / "tabs"
149
+ def _read_tabs(pattern: str) -> str:
150
+ if not _TABS_DIR.is_dir():
151
+ return ""
152
+ return "".join(p.read_text(encoding="utf-8") for p in sorted(_TABS_DIR.glob(pattern)))
153
+
154
  APP_CSS = """
155
  #main-preview {
156
  min-width: 0;
 
2276
  'data-drawer="kimodo-compose-drawer" title="Storyboard composer: sequence stances into a kata">COMPOSE</button>',
2277
  '<button id="kimodo-clothing-toggle" class="kimodo-left-tab" type="button" '
2278
  'data-drawer="kimodo-clothing-drawer" title="Add clothing to the citizen">CLOTHING</button>',
2279
+ # Pluggable tabs drop a rail button here (tabs/<name>.tab.html).
2280
+ _read_tabs("*.tab.html"),
2281
  '</div>',
2282
  # Actions library (clip cards populated in a later phase).
2283
  '<aside id="kimodo-actions-drawer" class="kimodo-drawer" aria-label="Actions library">',
 
2337
  f'{html.escape(item["label"])}</button>'
2338
  )
2339
  parts.append("</aside>")
2340
+ # Pluggable tabs drop their slide-in <aside> panel here (tabs/<name>.drawer.html).
2341
+ parts.append(_read_tabs("*.drawer.html"))
2342
  # Fixed bottom-of-page Build & Play — shown only while a sequence is storyboarded.
2343
  parts.append('<button id="kimodo-compose-launch" type="button">▶ Build &amp; Play</button>')
2344
  parts.append("</div>")
 
3276
  """
3277
 
3278
 
3279
+ # Pluggable side-tab behavior, concatenated into the drawer-JS scope (see _read_tabs).
3280
+ _EXTRA_TABS_JS = _read_tabs("*.tab.js")
3281
+
3282
  DRAWER_JS = f"""
3283
  (() => {{
3284
  let attempts = 0;
 
3364
  if (open && drawer.id === 'kimodo-stances-drawer') refreshStances();
3365
  // Build the storyboard composer (palette + strip) when its drawer opens.
3366
  if (open && drawer.id === 'kimodo-compose-drawer') composeOpen();
3367
+ // Pluggable tabs (tabs/*.tab.js) hook their open behavior off this event,
3368
+ // so a new tab never has to edit setOpen. detail = the opened drawer's id.
3369
+ if (open) window.dispatchEvent(new CustomEvent('kimodo-drawer-open', {{ detail: drawer.id }}));
3370
  // Shift the viewer camera to keep the character centered beside the open drawer.
3371
  sendViewportInset();
3372
  }}
 
3451
  {_KATA_JS}
3452
  {_STANCES_JS}
3453
  {_COMPOSE_JS}
3454
+ // --- Pluggable tabs (tabs/*.tab.js) — run in this same scope, after the helpers. ---
3455
+ {_EXTRA_TABS_JS}
3456
 
3457
  window.addEventListener('message', (event) => {{
3458
  const msg = event.data || {{}};
 
3487
  """
3488
  APP_HEAD = (
3489
  '<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>'
3490
+ # APP_CSS + any pluggable-tab styles (tabs/<name>.tab.css), then the drawer JS.
3491
+ f"<style>{APP_CSS}{_read_tabs('*.tab.css')}</style><script>{DRAWER_JS}</script>"
3492
  )
3493
 
3494
 
 
4122
  fn=_generate_button_label, inputs=[anonymous_client_id], outputs=[btn], api_name=False,
4123
  )
4124
 
4125
+ # ===================== NEW-TAB BACKEND EXTENSION POINT =====================
4126
+ # Front-end-only tabs need NOTHING here (drop files in tabs/). A tab that needs
4127
+ # a Python/@GPU backend adds its hidden gr components + .click wiring BELOW this
4128
+ # line (mirror the compose_btn pattern: a hidden #<name>-btn the tab JS clicks,
4129
+ # outputs routed to [preview, meta, file_out]). Append only — don't edit above.
4130
+ # ==========================================================================
4131
+
4132
  # Display model drives the persistent viewer live via postMessage (no reload).
4133
  # Clothing is handled by the fixed browser drawer so it can behave like kata's drawers.
4134
  model_dd.change(
tabs/README.md ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Pluggable side tabs
2
+
3
+ A side tab is added by **dropping files into this folder** — you do **not** edit
4
+ `app.py` (unless your tab needs a Python/@GPU backend). This lets several tabs be
5
+ built in parallel with zero shared-code conflicts.
6
+
7
+ ## The 4 files (replace `<name>` with your tab's slug, e.g. `effects`)
8
+
9
+ | File | What it is | Globbed into |
10
+ |------|------------|--------------|
11
+ | `<name>.tab.html` | the rail button | the left tab rail |
12
+ | `<name>.drawer.html` | the slide-in `<aside>` panel | the drawer container |
13
+ | `<name>.tab.css` | styles for your tab | the page `<style>` |
14
+ | `<name>.tab.js` | behavior | the drawer-JS scope (shared helpers available) |
15
+
16
+ All four are optional, but a useful tab has at least `.drawer.html` + `.tab.html`.
17
+ Files load in **sorted filename order** at import.
18
+
19
+ ## ID conventions (the drawer manager auto-discovers these from the DOM)
20
+
21
+ - Rail button: `id="kimodo-<name>-toggle"`, `class="kimodo-left-tab"`,
22
+ `data-drawer="kimodo-<name>-drawer"`.
23
+ - Drawer panel: `<aside id="kimodo-<name>-drawer" class="kimodo-drawer">` (add
24
+ `kimodo-drawer--wide` for a wider panel). Opening it is handled for you
25
+ (one drawer open at a time, tab-rail shift, camera re-centers beside it).
26
+
27
+ ## Showing your tab
28
+
29
+ Minimal mode currently **hides every rail tab except COMPOSE**. Your `.tab.css`
30
+ must un-hide yours:
31
+
32
+ ```css
33
+ #kimodo-<name>-toggle { display: block !important; }
34
+ ```
35
+
36
+ ## Hooking "drawer opened" (no setOpen edit)
37
+
38
+ ```js
39
+ window.addEventListener('kimodo-drawer-open', (e) => {
40
+ if (e.detail !== 'kimodo-<name>-drawer') return;
41
+ // build/refresh your panel here
42
+ });
43
+ ```
44
+
45
+ ## Shared helpers your `.tab.js` can call (same scope)
46
+
47
+ - `fetchManifest()` → `[{id, prompt, ...}]` of all saved clips/stances.
48
+ - `fetchRecord(id)` → a clip record (incl. `posed_joints`, `global_quats_xyzw`).
49
+ - `loadIntoViewer(rec, id, label)` → play a clip in the 3D viewer (set
50
+ `rec.num_joints` + `rec.bone_names = SMPLX22_BONES` first if missing).
51
+ - `previewFrame()` → the viewer iframe (for custom `postMessage`).
52
+ - `setOpen(drawerEl, bool)`, `sendViewportInset()`, `stitchPath(ids)`,
53
+ `DATASET_BASE`, `KIMODO_BACKEND`.
54
+
55
+ ## Tabs that need a Python/@GPU backend
56
+
57
+ Front-end-only tabs need nothing in `app.py`. If you need server generation, add a
58
+ hidden `gr` component + `.click` wiring **below** the
59
+ `NEW-TAB BACKEND EXTENSION POINT` marker in `app.py` (mirror the `compose_btn`
60
+ pattern: a hidden `#<name>-btn` your JS clicks; outputs → `[preview, meta, file_out]`).
61
+ That marker is the **only** shared line a backend tab touches — append only.
62
+
63
+ ## Deploy
64
+
65
+ `tabs/` ships automatically with the Space (no ignore rule). `tabs/templates/` is a
66
+ subfolder, so its example files are **not** loaded (the globs are non-recursive).
67
+
68
+ See `tabs/templates/` for a complete, copy-paste example tab.
tabs/templates/example.drawer.html ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <!-- The slide-in panel. Copy to tabs/<name>.drawer.html. Use class
2
+ "kimodo-drawer" (add " kimodo-drawer--wide" for a wider panel). -->
3
+ <aside id="kimodo-example-drawer" class="kimodo-drawer" aria-label="Example tab">
4
+ <h2>Example tab</h2>
5
+ <div class="kimodo-drawer-sub">A starter tab — lists saved clips; click one to
6
+ play it in the viewer. Replace this with your own UI.</div>
7
+ <div id="kimodo-example-list"></div>
8
+ </aside>
tabs/templates/example.tab.css ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Styles for the example tab. Copy to tabs/<name>.tab.css.
2
+ The un-hide rule is REQUIRED: minimal mode hides every rail tab but COMPOSE. */
3
+ #kimodo-example-toggle { display: block !important; color: #9fe6c0; }
4
+ #kimodo-example-toggle.active { background: #1f3a2c !important; border-color: #3a6 !important; color: #9fe6c0 !important; }
5
+
6
+ .kimodo-ex-item {
7
+ display: block; width: 100%; text-align: left; margin-bottom: 6px;
8
+ padding: 8px 10px; font-size: 12px; cursor: pointer; border-radius: 7px;
9
+ background: #26262b; color: #e3e3e8; border: 1px solid #3a3a40;
10
+ }
11
+ .kimodo-ex-item:hover { background: #30303a; }
tabs/templates/example.tab.html ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ <!-- Rail button. Copy to tabs/<name>.tab.html and rename ids/label.
2
+ NOTE: files under tabs/templates/ are NOT loaded (globs are non-recursive);
3
+ move a copy up to tabs/ to activate it. -->
4
+ <button id="kimodo-example-toggle" class="kimodo-left-tab" type="button"
5
+ data-drawer="kimodo-example-drawer" title="Example pluggable tab">EXAMPLE</button>
tabs/templates/example.tab.js ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Behavior for the example tab. Copy to tabs/<name>.tab.js.
2
+ // This block is concatenated INTO the drawer-JS scope, AFTER the shared helpers,
3
+ // so it can call them directly (fetchManifest, fetchRecord, loadIntoViewer, …).
4
+ // Wrap in an IIFE to keep your locals from colliding with other tabs.
5
+ (function () {
6
+ let loaded = false;
7
+
8
+ async function refreshExample() {
9
+ const list = document.getElementById('kimodo-example-list');
10
+ if (!list) return;
11
+ list.textContent = 'loading…';
12
+ let items = [];
13
+ try { items = await fetchManifest(); } catch (e) { list.textContent = 'failed to load'; return; }
14
+ list.innerHTML = '';
15
+ for (const m of items.slice(0, 25)) {
16
+ const b = document.createElement('button');
17
+ b.type = 'button'; b.className = 'kimodo-ex-item';
18
+ b.textContent = (m.prompt || m.id).slice(0, 48);
19
+ b.onclick = async () => {
20
+ try {
21
+ const rec = await fetchRecord(m.id);
22
+ // The viewer skips clips whose num_joints != 22; derive them if missing.
23
+ if (!rec.num_joints) rec.num_joints = (rec.posed_joints && rec.posed_joints[0]) ? rec.posed_joints[0].length : 22;
24
+ if (!rec.bone_names || !rec.bone_names.length) rec.bone_names = SMPLX22_BONES;
25
+ loadIntoViewer(rec, m.id, m.prompt || m.id);
26
+ } catch (e) { console.error('example load failed', e); }
27
+ };
28
+ list.appendChild(b);
29
+ }
30
+ loaded = true;
31
+ }
32
+
33
+ // Build the panel when THIS drawer opens (no setOpen edit needed).
34
+ window.addEventListener('kimodo-drawer-open', (e) => {
35
+ if (e.detail === 'kimodo-example-drawer' && !loaded) refreshExample();
36
+ });
37
+ })();