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 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