Spaces:
Runtime error
Runtime error
Kimodo Bot commited on
Commit ·
8413010
1
Parent(s): 1f3e499
Add native demo UI modules with Qwen 10-action example flow
Browse files- kimodo/demo/__init__.py +1 -0
- kimodo/demo/_qwen_prompts.py +107 -0
- kimodo/demo/ui.py +0 -0
kimodo/demo/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Demo module for native Kimodo UI integration in Space builds.
|
kimodo/demo/_qwen_prompts.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
"""Qwen-on-Fireworks helper for auto-generating multi-text-prompt batches."""
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import json
|
| 7 |
+
import os
|
| 8 |
+
import re
|
| 9 |
+
import urllib.error
|
| 10 |
+
import urllib.request
|
| 11 |
+
|
| 12 |
+
_MODEL = "accounts/fireworks/models/qwen3p6-27b"
|
| 13 |
+
_BASE = "https://api.fireworks.ai/inference/v1"
|
| 14 |
+
|
| 15 |
+
_SYSTEM = """\
|
| 16 |
+
You are a motion-description writer for a single humanoid character in a 3D animation system.
|
| 17 |
+
Given a scene context and the character's recent motion history, output ONLY a JSON object:
|
| 18 |
+
|
| 19 |
+
{"texts": ["<action phrase 1>", ...], "durations": [<seconds float>, ...]}
|
| 20 |
+
|
| 21 |
+
Rules:
|
| 22 |
+
- Return between 1 and requested_actions short, vivid action phrases that flow naturally from each other.
|
| 23 |
+
- Each phrase describes one distinct physical motion (e.g. "walks forward briskly", "pivots left and crouches").
|
| 24 |
+
- Each duration is between 2.0 and 8.0 seconds.
|
| 25 |
+
- texts and durations must have the same length.
|
| 26 |
+
- Do NOT repeat phrases from history.
|
| 27 |
+
- Return raw JSON only — no markdown, no explanation.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _call_fireworks(messages: list[dict]) -> str:
|
| 32 |
+
api_key = os.environ.get("FIREWORKS_API_KEY", "").strip()
|
| 33 |
+
if not api_key:
|
| 34 |
+
raise RuntimeError("FIREWORKS_API_KEY is not set")
|
| 35 |
+
body = json.dumps({
|
| 36 |
+
"model": _MODEL,
|
| 37 |
+
"messages": messages,
|
| 38 |
+
"max_tokens": 400,
|
| 39 |
+
"temperature": 0.85,
|
| 40 |
+
}).encode()
|
| 41 |
+
req = urllib.request.Request(
|
| 42 |
+
f"{_BASE}/chat/completions",
|
| 43 |
+
data=body,
|
| 44 |
+
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
|
| 45 |
+
method="POST",
|
| 46 |
+
)
|
| 47 |
+
try:
|
| 48 |
+
with urllib.request.urlopen(req, timeout=40) as r:
|
| 49 |
+
return json.loads(r.read())["choices"][0]["message"]["content"]
|
| 50 |
+
except urllib.error.HTTPError as e:
|
| 51 |
+
raise RuntimeError(f"Fireworks {e.code}: {e.read().decode(errors='ignore')}") from e
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _parse(raw: str) -> dict:
|
| 55 |
+
text = raw.strip()
|
| 56 |
+
m = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, re.DOTALL)
|
| 57 |
+
text = m.group(1) if m else text
|
| 58 |
+
s, e = text.find("{"), text.rfind("}")
|
| 59 |
+
return json.loads(text[s:e + 1])
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _fallback(offset: int) -> dict:
|
| 63 |
+
phrases = [
|
| 64 |
+
"walks forward at a steady pace",
|
| 65 |
+
"turns smoothly to the left",
|
| 66 |
+
"pauses and surveys the surroundings",
|
| 67 |
+
"steps forward and gestures expressively",
|
| 68 |
+
"crouches down then rises back up",
|
| 69 |
+
"sidesteps to the right with purpose",
|
| 70 |
+
]
|
| 71 |
+
n = len(phrases)
|
| 72 |
+
return {
|
| 73 |
+
"texts": [phrases[(offset + i) % n] for i in range(3)],
|
| 74 |
+
"durations": [3.0, 3.5, 3.0],
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def call_qwen_for_prompts(
|
| 79 |
+
scene: str,
|
| 80 |
+
history: list[str],
|
| 81 |
+
requested_actions: int = 5,
|
| 82 |
+
) -> tuple[dict, list[str]]:
|
| 83 |
+
"""Call Qwen to produce the next batch of motion prompts.
|
| 84 |
+
|
| 85 |
+
Returns (batch_dict, updated_history).
|
| 86 |
+
batch_dict has keys "texts" and "durations".
|
| 87 |
+
Raises RuntimeError on API failure (caller may fall back).
|
| 88 |
+
"""
|
| 89 |
+
user_msg = (
|
| 90 |
+
f"Scene: {scene or 'a character moving continuously in 3D space'}\n"
|
| 91 |
+
f"Motion history (do not repeat): {json.dumps(history[-12:])}\n\n"
|
| 92 |
+
f"requested_actions: {max(1, min(10, int(requested_actions)))}\n"
|
| 93 |
+
"Generate the next batch of motion prompts."
|
| 94 |
+
)
|
| 95 |
+
try:
|
| 96 |
+
raw = _call_fireworks([{"role": "system", "content": _SYSTEM}, {"role": "user", "content": user_msg}])
|
| 97 |
+
batch = _parse(raw)
|
| 98 |
+
if not isinstance(batch.get("texts"), list) or not isinstance(batch.get("durations"), list):
|
| 99 |
+
raise ValueError("Missing texts or durations")
|
| 100 |
+
n = min(len(batch["texts"]), len(batch["durations"]))
|
| 101 |
+
batch["texts"] = batch["texts"][:n]
|
| 102 |
+
batch["durations"] = batch["durations"][:n]
|
| 103 |
+
except Exception:
|
| 104 |
+
batch = _fallback(len(history))
|
| 105 |
+
|
| 106 |
+
new_history = history + list(batch["texts"])
|
| 107 |
+
return batch, new_history
|
kimodo/demo/ui.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|