storyloom-engine 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. storyloom_engine-2.3.0/LICENSE +21 -0
  2. storyloom_engine-2.3.0/MANIFEST.in +4 -0
  3. storyloom_engine-2.3.0/PKG-INFO +20 -0
  4. storyloom_engine-2.3.0/README.md +194 -0
  5. storyloom_engine-2.3.0/pyproject.toml +32 -0
  6. storyloom_engine-2.3.0/setup.cfg +4 -0
  7. storyloom_engine-2.3.0/setup.py +139 -0
  8. storyloom_engine-2.3.0/src/storyloom/__init__.py +46 -0
  9. storyloom_engine-2.3.0/src/storyloom/assets/__init__.py +19 -0
  10. storyloom_engine-2.3.0/src/storyloom/assets/_library.py +507 -0
  11. storyloom_engine-2.3.0/src/storyloom/assets/_manifest.py +83 -0
  12. storyloom_engine-2.3.0/src/storyloom/assets/_roster.py +251 -0
  13. storyloom_engine-2.3.0/src/storyloom/assets/_types.py +153 -0
  14. storyloom_engine-2.3.0/src/storyloom/config.py +105 -0
  15. storyloom_engine-2.3.0/src/storyloom/content/en/guide.md +124 -0
  16. storyloom_engine-2.3.0/src/storyloom/content/zh-CN/guide.md +122 -0
  17. storyloom_engine-2.3.0/src/storyloom/content/zh-TW/guide.md +122 -0
  18. storyloom_engine-2.3.0/src/storyloom/core/__init__.py +42 -0
  19. storyloom_engine-2.3.0/src/storyloom/core/co_create.py +1346 -0
  20. storyloom_engine-2.3.0/src/storyloom/core/context_manager.py +162 -0
  21. storyloom_engine-2.3.0/src/storyloom/core/event_dispatcher.py +228 -0
  22. storyloom_engine-2.3.0/src/storyloom/core/game_loop.py +1593 -0
  23. storyloom_engine-2.3.0/src/storyloom/core/lang_meta/en.json +7 -0
  24. storyloom_engine-2.3.0/src/storyloom/core/lang_meta/zh-CN.json +7 -0
  25. storyloom_engine-2.3.0/src/storyloom/core/lang_meta/zh-TW.json +7 -0
  26. storyloom_engine-2.3.0/src/storyloom/core/prebuild.py +999 -0
  27. storyloom_engine-2.3.0/src/storyloom/core/prompt_builder.py +1129 -0
  28. storyloom_engine-2.3.0/src/storyloom/core/save_manager.py +436 -0
  29. storyloom_engine-2.3.0/src/storyloom/core/session.py +360 -0
  30. storyloom_engine-2.3.0/src/storyloom/core/state_manager.py +616 -0
  31. storyloom_engine-2.3.0/src/storyloom/core/update_manager.py +559 -0
  32. storyloom_engine-2.3.0/src/storyloom/dev_cli/__init__.py +24 -0
  33. storyloom_engine-2.3.0/src/storyloom/dev_cli/__main__.py +4 -0
  34. storyloom_engine-2.3.0/src/storyloom/dev_cli/_terminal.py +112 -0
  35. storyloom_engine-2.3.0/src/storyloom/dev_cli/game_driver.py +773 -0
  36. storyloom_engine-2.3.0/src/storyloom/dev_cli/observer.py +164 -0
  37. storyloom_engine-2.3.0/src/storyloom/i18n.py +110 -0
  38. storyloom_engine-2.3.0/src/storyloom/i18n_compile.py +39 -0
  39. storyloom_engine-2.3.0/src/storyloom/io/__init__.py +41 -0
  40. storyloom_engine-2.3.0/src/storyloom/io/_types.py +54 -0
  41. storyloom_engine-2.3.0/src/storyloom/io/api_client.py +403 -0
  42. storyloom_engine-2.3.0/src/storyloom/io/img_api_client.py +681 -0
  43. storyloom_engine-2.3.0/src/storyloom/io/img_prompts.py +70 -0
  44. storyloom_engine-2.3.0/src/storyloom/io/img_utils.py +436 -0
  45. storyloom_engine-2.3.0/src/storyloom/io/thinking.py +256 -0
  46. storyloom_engine-2.3.0/src/storyloom/launcher.py +116 -0
  47. storyloom_engine-2.3.0/src/storyloom/locale/README.md +30 -0
  48. storyloom_engine-2.3.0/src/storyloom/locale/zh_CN/LC_MESSAGES/storyloom.po +19 -0
  49. storyloom_engine-2.3.0/src/storyloom/locale/zh_TW/LC_MESSAGES/storyloom.po +19 -0
  50. storyloom_engine-2.3.0/src/storyloom/models/u2netp.onnx +0 -0
  51. storyloom_engine-2.3.0/src/storyloom/parser/__init__.py +24 -0
  52. storyloom_engine-2.3.0/src/storyloom/parser/stream_parser.py +688 -0
  53. storyloom_engine-2.3.0/src/storyloom/tasks/__init__.py +21 -0
  54. storyloom_engine-2.3.0/src/storyloom/tasks/_generator.py +136 -0
  55. storyloom_engine-2.3.0/src/storyloom/tasks/_llm_generate.py +619 -0
  56. storyloom_engine-2.3.0/src/storyloom/tasks/_llm_match.py +260 -0
  57. storyloom_engine-2.3.0/src/storyloom/tasks/_pool.py +72 -0
  58. storyloom_engine-2.3.0/src/storyloom/tasks/_types.py +94 -0
  59. storyloom_engine-2.3.0/src/storyloom/user_config.py +342 -0
  60. storyloom_engine-2.3.0/src/storyloom/web/__init__.py +13 -0
  61. storyloom_engine-2.3.0/src/storyloom/web/__main__.py +3 -0
  62. storyloom_engine-2.3.0/src/storyloom/web/server.py +1726 -0
  63. storyloom_engine-2.3.0/src/storyloom/web/sessions.py +291 -0
  64. storyloom_engine-2.3.0/src/storyloom/web/static/css/graph.css +336 -0
  65. storyloom_engine-2.3.0/src/storyloom/web/static/css/main.css +3515 -0
  66. storyloom_engine-2.3.0/src/storyloom/web/static/css/theme-dark.css +45 -0
  67. storyloom_engine-2.3.0/src/storyloom/web/static/css/theme-light.css +45 -0
  68. storyloom_engine-2.3.0/src/storyloom/web/static/css/variables.css +68 -0
  69. storyloom_engine-2.3.0/src/storyloom/web/static/index.html +34 -0
  70. storyloom_engine-2.3.0/src/storyloom/web/static/js/adventure-log.js +225 -0
  71. storyloom_engine-2.3.0/src/storyloom/web/static/js/api.js +55 -0
  72. storyloom_engine-2.3.0/src/storyloom/web/static/js/assets.js +325 -0
  73. storyloom_engine-2.3.0/src/storyloom/web/static/js/co-create.js +1100 -0
  74. storyloom_engine-2.3.0/src/storyloom/web/static/js/credits.js +29 -0
  75. storyloom_engine-2.3.0/src/storyloom/web/static/js/display.js +468 -0
  76. storyloom_engine-2.3.0/src/storyloom/web/static/js/game.js +824 -0
  77. storyloom_engine-2.3.0/src/storyloom/web/static/js/graph-renderer.js +574 -0
  78. storyloom_engine-2.3.0/src/storyloom/web/static/js/icons.js +304 -0
  79. storyloom_engine-2.3.0/src/storyloom/web/static/js/router.js +1708 -0
  80. storyloom_engine-2.3.0/src/storyloom/web/static/js/sse-client.js +196 -0
  81. storyloom_engine-2.3.0/src/storyloom/web/static/js/state.js +463 -0
  82. storyloom_engine-2.3.0/src/storyloom/web/static/js/vendor/i18next.min.js +1 -0
  83. storyloom_engine-2.3.0/src/storyloom/web/static/js/vendor/i18nextHttpBackend.min.js +1 -0
  84. storyloom_engine-2.3.0/src/storyloom/web/static/locales/en.json +1 -0
  85. storyloom_engine-2.3.0/src/storyloom/web/static/locales/zh-CN.json +201 -0
  86. storyloom_engine-2.3.0/src/storyloom/web/static/locales/zh-TW.json +201 -0
  87. storyloom_engine-2.3.0/src/storyloom_engine.egg-info/PKG-INFO +20 -0
  88. storyloom_engine-2.3.0/src/storyloom_engine.egg-info/SOURCES.txt +116 -0
  89. storyloom_engine-2.3.0/src/storyloom_engine.egg-info/dependency_links.txt +1 -0
  90. storyloom_engine-2.3.0/src/storyloom_engine.egg-info/entry_points.txt +2 -0
  91. storyloom_engine-2.3.0/src/storyloom_engine.egg-info/requires.txt +15 -0
  92. storyloom_engine-2.3.0/src/storyloom_engine.egg-info/top_level.txt +1 -0
  93. storyloom_engine-2.3.0/tests/test_api_client.py +215 -0
  94. storyloom_engine-2.3.0/tests/test_assets.py +1964 -0
  95. storyloom_engine-2.3.0/tests/test_co_create.py +1164 -0
  96. storyloom_engine-2.3.0/tests/test_context_manager.py +188 -0
  97. storyloom_engine-2.3.0/tests/test_game_loop.py +1614 -0
  98. storyloom_engine-2.3.0/tests/test_graph_mode_pipeline.py +475 -0
  99. storyloom_engine-2.3.0/tests/test_i18n.py +70 -0
  100. storyloom_engine-2.3.0/tests/test_i18n_compile.py +42 -0
  101. storyloom_engine-2.3.0/tests/test_img_api_client.py +454 -0
  102. storyloom_engine-2.3.0/tests/test_img_utils.py +447 -0
  103. storyloom_engine-2.3.0/tests/test_integration.py +263 -0
  104. storyloom_engine-2.3.0/tests/test_launcher.py +135 -0
  105. storyloom_engine-2.3.0/tests/test_llm_generate.py +1080 -0
  106. storyloom_engine-2.3.0/tests/test_llm_match.py +733 -0
  107. storyloom_engine-2.3.0/tests/test_manifest.py +204 -0
  108. storyloom_engine-2.3.0/tests/test_pipeline_integration.py +767 -0
  109. storyloom_engine-2.3.0/tests/test_prebuild.py +1207 -0
  110. storyloom_engine-2.3.0/tests/test_prompt_builder.py +339 -0
  111. storyloom_engine-2.3.0/tests/test_save_manager.py +365 -0
  112. storyloom_engine-2.3.0/tests/test_session.py +240 -0
  113. storyloom_engine-2.3.0/tests/test_state_manager.py +471 -0
  114. storyloom_engine-2.3.0/tests/test_stream_parser.py +661 -0
  115. storyloom_engine-2.3.0/tests/test_task_framework.py +1051 -0
  116. storyloom_engine-2.3.0/tests/test_update_manager.py +550 -0
  117. storyloom_engine-2.3.0/tests/test_user_config.py +360 -0
  118. storyloom_engine-2.3.0/tests/test_web_server.py +995 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Slev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ graft src/storyloom/web/static
2
+ graft src/storyloom/locale
3
+ graft src/storyloom/content
4
+ global-exclude *.mo
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: storyloom-engine
3
+ Version: 2.3.0
4
+ Summary: AI-powered real-time visual novel engine
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ License-File: LICENSE
8
+ Requires-Dist: httpx>=0.28.0
9
+ Requires-Dist: fastapi>=0.115.0
10
+ Requires-Dist: uvicorn[standard]>=0.34.0
11
+ Provides-Extra: bg
12
+ Requires-Dist: onnxruntime>=1.18; extra == "bg"
13
+ Requires-Dist: numpy>=1.24; extra == "bg"
14
+ Requires-Dist: pillow>=10.0; extra == "bg"
15
+ Provides-Extra: desktop
16
+ Requires-Dist: pywebview>=5.0; extra == "desktop"
17
+ Provides-Extra: test
18
+ Requires-Dist: babel>=2.10; extra == "test"
19
+ Requires-Dist: pytest>=7.0; extra == "test"
20
+ Dynamic: license-file
@@ -0,0 +1,194 @@
1
+ <p align="center">
2
+ <img src="assets/icons/logo-lockup.png" alt="Storyloom logo" width="300">
3
+ </p>
4
+
5
+ # Storyloom
6
+
7
+ > An AI-powered real-time visual novel engine.
8
+
9
+ [![Python](https://img.shields.io/badge/python-%3E%3D10-blue)](https://www.python.org/)
10
+ [![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
11
+ [![Tests](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/SiriLee/e809f4fcaea1700560591c9867659fc0/raw/badge.json)](https://github.com/SiriLee/Storyloom/actions)
12
+
13
+ <!-- TODO: screenshot or GIF demo -->
14
+
15
+ ---
16
+
17
+ ## Installation
18
+
19
+ ### Standalone binary
20
+
21
+ *No Python required.*
22
+
23
+ Download `storyloom-v{VERSION}-{platform}.zip` from
24
+ [Releases](https://github.com/SiriLee/Storyloom/releases/latest), extract,
25
+ and run `Storyloom`.
26
+
27
+ ### From PyPI
28
+
29
+ *Requires Python ≥ 3.10.*
30
+
31
+ ```bash
32
+ pip install storyloom-engine
33
+ ```
34
+
35
+ Optional extras:
36
+
37
+ ```bash
38
+ pip install "storyloom-engine[desktop,bg]" # native window + background removal
39
+ ```
40
+
41
+ | Extra | Adds |
42
+ |-------|------|
43
+ | `desktop` | Native desktop window via `pywebview` — falls back to browser if unavailable |
44
+ | `bg` | Background removal for generated images (`onnxruntime`; model already bundled) |
45
+
46
+ System media assets are not included in the wheel. Download them via
47
+ **Settings → Updates** on first launch.
48
+
49
+ ### From source
50
+
51
+ ```bash
52
+ git clone https://github.com/SiriLee/Storyloom.git
53
+ cd Storyloom
54
+ pip install -e ".[desktop,bg]"
55
+ ```
56
+
57
+ ### Data directory & uninstall
58
+
59
+ User data — `config.json`, `saves/`, `media/`, `system_media/` — lives
60
+ outside the installed package:
61
+
62
+ | Install | Data directory |
63
+ |---------|----------------|
64
+ | Standalone binary | Next to the `Storyloom` executable |
65
+ | Wheel (pip) | `~/.local/share/Storyloom` (Linux), `~/Library/Application Support/Storyloom` (macOS), `%APPDATA%\Storyloom` (Windows) |
66
+ | From source | The repository root |
67
+
68
+ Override with the `STORYLOOM_APP_DIR` environment variable.
69
+
70
+ `pip uninstall storyloom` removes only the package — **not** your data.
71
+ Saves and media are kept. To remove everything:
72
+
73
+ ```bash
74
+ pip uninstall storyloom
75
+ rm -rf ~/.local/share/Storyloom # adjust path per the table above
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Usage
81
+
82
+ ```bash
83
+ storyloom-web # native desktop window (or browser fallback)
84
+ storyloom-web --browser # always open in browser
85
+ storyloom-web --port 8080 # custom port (default: auto-assign)
86
+ storyloom-web --help # show all options
87
+
88
+ # or
89
+ python -m storyloom.web
90
+ ```
91
+
92
+ First launch opens the Settings page. Enter your API key, select a mode
93
+ (**Text** or **Graph**), and start a new game.
94
+
95
+ > **System media assets** (~267 MB of character portraits and background
96
+ > images) are included in the standalone release zip alongside the binary.
97
+ > Wheel and source users can download them via **Settings → Updates**
98
+ > inside the app.
99
+
100
+ ---
101
+
102
+ ## Features
103
+
104
+ | | |
105
+ |---|---|
106
+ | Streaming XML pipeline | `StreamParser → StateManager → EventDispatcher` — parsed line-by-line, no buffering |
107
+ | Bridge pre-fetch | Next API call fires mid-paragraph; latency hidden behind reading time |
108
+ | State validation | LLM *suggests* writes; engine type-checks before applying; rejected writes feed back |
109
+ | Two-layer branching | In-scene choices + outline-level route forks at checkpoints |
110
+ | Asset pipeline | O(1) catalog match → LLM fallback → AI generation; async, non-blocking |
111
+ | Context management | Sliding window + Round 1 anchor + checkpoint compression; ~50K tokens |
112
+ | Co-creation | AI interviews you about your idea before generating world, characters, and plot |
113
+ | Save / load | Atomic JSON saves; mode-agnostic — switch text/graph any time |
114
+ | i18n | English, 简体中文, 繁體中文 (gettext) |
115
+ | Web UI | FastAPI + SSE + vanilla JS SPA |
116
+ | CLI | Terminal client with debug observer |
117
+ | Packaging | Standalone binary (PyInstaller) + pip wheel + system asset pack |
118
+
119
+ ---
120
+
121
+ ## Architecture
122
+
123
+ ```mermaid
124
+ graph TD
125
+ LLM[Director LLM]
126
+ Parser[Stream Parser]
127
+ State[State Manager]
128
+ Tasks[Task Generator + Pool]
129
+ Dispatcher[Event Dispatcher]
130
+ UI[UI]
131
+
132
+ LLM -- "token stream" --> Parser
133
+ Parser -- "event" --> State
134
+ Parser -. "asset trigger" .-> Tasks
135
+
136
+ State -- "processed event" --> Dispatcher
137
+ Tasks -- "completed task" --> Dispatcher
138
+
139
+ Dispatcher -- "bound event" --> UI
140
+ UI -. "choice" .-> State
141
+
142
+ State -. "pre-fetch" .-> LLM
143
+ ```
144
+
145
+ Solid lines are streaming data flow. Dotted lines are one-shot triggers.
146
+ Graph-mode asset tags spawn tasks that resolve asynchronously, without
147
+ blocking the text pipeline.
148
+
149
+ ---
150
+
151
+ ## Documentation
152
+
153
+ | | |
154
+ |---|---|
155
+ | Theory | [First principles](docs/theory/first-principles.md) · [Bridge mechanism](docs/theory/bridge-mechanism.md) · [Streaming parse](docs/theory/streaming-parse.md) · [Asset generation](docs/theory/asset-generation.md) |
156
+ | Spec | [Phase 1 pipeline](docs/spec/exec-flow.md) · [XML elements](docs/spec/block-spec.md) · [Prompts](docs/spec/prompt-design.md) · [Data model](docs/spec/data-model.md) |
157
+ | Graph mode | [Design](docs/graph-mode-spec/design.md) |
158
+ | API | [GameSession](docs/api/session.md) · [Co-create](docs/api/co-create.md) |
159
+ | Log | [Engineering journal](docs/engineering-journal.md) |
160
+
161
+ ---
162
+
163
+ ## Development
164
+
165
+ ```bash
166
+ # Clone and install (the `test` extra adds pytest + babel for the test suite)
167
+ git clone https://github.com/SiriLee/Storyloom.git
168
+ cd Storyloom
169
+ pip install -e ".[desktop,bg,test]"
170
+
171
+ # Tests (no API key needed)
172
+ pytest
173
+
174
+ # Build
175
+ bash scripts/build.sh # standalone binary + wheel
176
+ bash scripts/pack_system_media.sh # system asset pack for release
177
+
178
+ # Generate system assets from source definitions (requires image API key)
179
+ python scripts/sysgen/generate_system_assets.py
180
+ python scripts/sysgen/generate_manifest.py
181
+ ```
182
+
183
+ **Conventions:** Python ≥ 3.10 · stdlib-first · Conventional Commits ·
184
+ English code & docs · mock tests (no real API calls).
185
+
186
+ **AI context:** [`AGENTS.md`](AGENTS.md) is the single source of truth for
187
+ coding-agent context (Claude Code, Cursor, Windsurf, Copilot, Gemini CLI, …).
188
+ [`CLAUDE.md`](CLAUDE.md) and
189
+ [`.github/copilot-instructions.md`](.github/copilot-instructions.md) are thin
190
+ compatibility bridges — keep shared knowledge in `AGENTS.md` only.
191
+
192
+ ---
193
+
194
+ [MIT](./LICENSE)
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "babel>=2.10"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "storyloom-engine"
7
+ version = "2.3.0"
8
+ description = "AI-powered real-time visual novel engine"
9
+ license = {text = "MIT"}
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "httpx>=0.28.0",
13
+ "fastapi>=0.115.0",
14
+ "uvicorn[standard]>=0.34.0",
15
+ ]
16
+
17
+ [project.optional-dependencies]
18
+ bg = ["onnxruntime>=1.18", "numpy>=1.24", "pillow>=10.0"]
19
+ desktop = ["pywebview>=5.0"]
20
+ test = ["babel>=2.10", "pytest>=7.0"]
21
+
22
+ [project.scripts]
23
+ storyloom-web = "storyloom.web.server:main"
24
+
25
+ [tool.setuptools.package-data]
26
+ storyloom = ["web/static/**/*", "core/lang_meta/*.json", "models/*.onnx", "locale/**/*", "content/**/*"]
27
+
28
+ [tool.setuptools.packages.find]
29
+ where = ["src"]
30
+
31
+ [tool.pytest.ini_options]
32
+ addopts = "--ignore=tests/prompt_lab"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,139 @@
1
+ """Build hook — compile .po → .mo + download ONNX model during ``pip install``.
2
+
3
+ Users never need ``msgfmt`` or any manual step. Everything happens
4
+ automatically inside the build phase.
5
+ """
6
+
7
+ from pathlib import Path
8
+
9
+ from setuptools import setup
10
+ from setuptools.command.build_py import build_py as _build_py
11
+ from setuptools.command.develop import develop as _develop
12
+ from setuptools.command.editable_wheel import editable_wheel as _editable_wheel
13
+
14
+
15
+ def _compile_mo_files() -> None:
16
+ """Compile all .po files under ``src/storyloom/locale/`` to .mo (Babel).
17
+
18
+ Frontend translations are a separate source (i18next JSON under
19
+ ``web/static/locales/``) and are not derived here.
20
+ """
21
+ project_root = Path(__file__).resolve().parent
22
+ src = project_root / "src"
23
+
24
+ # Load i18n_compile directly (avoids storyloom.__init__ → httpx import
25
+ # chain, which fails in isolated build environments like editable_wheel).
26
+ import importlib.util
27
+ spec = importlib.util.spec_from_file_location(
28
+ "storyloom.i18n_compile",
29
+ str(src / "storyloom" / "i18n_compile.py"),
30
+ )
31
+ mod = importlib.util.module_from_spec(spec)
32
+ spec.loader.exec_module(mod)
33
+ compile_all = mod.compile_all
34
+
35
+ locale_dir = project_root / "src" / "storyloom" / "locale"
36
+ compiled = compile_all(str(locale_dir))
37
+ if compiled:
38
+ print(f"[i18n] compiled {len(compiled)} .mo file(s)")
39
+
40
+
41
+ def _download_model() -> None:
42
+ """Download u2netp.onnx (~4.4 MB) into ``src/storyloom/models/``.
43
+
44
+ Idempotent — skips if already cached with the correct SHA256.
45
+ Network failures are warned, not fatal (the install continues).
46
+ """
47
+ import hashlib
48
+ import importlib.util
49
+ import urllib.request
50
+
51
+ project_root = Path(__file__).resolve().parent
52
+
53
+ # Load config.py standalone to read filename+SHA256 (avoids storyloom
54
+ # __init__ import chain, same pattern as _compile_mo_files above).
55
+ spec = importlib.util.spec_from_file_location(
56
+ "storyloom.config",
57
+ str(project_root / "src" / "storyloom" / "config.py"),
58
+ )
59
+ mod = importlib.util.module_from_spec(spec)
60
+ spec.loader.exec_module(mod)
61
+ sha256 = mod.BG_REMOVAL_MODEL_SHA256
62
+ filename = mod.BG_REMOVAL_MODEL_FILENAME
63
+
64
+ # URL is build-time only; kept here, not in production config.
65
+ url = (
66
+ "https://github.com/danielgatis/rembg/releases/download/"
67
+ "v0.0.0/u2netp.onnx"
68
+ )
69
+
70
+ target = project_root / "src" / "storyloom" / "models" / filename
71
+
72
+ # --- skip if already cached ---
73
+ if target.exists():
74
+ h = hashlib.sha256()
75
+ with open(target, "rb") as f:
76
+ for chunk in iter(lambda: f.read(65536), b""):
77
+ h.update(chunk)
78
+ if h.hexdigest() == sha256:
79
+ print(f"[model] {filename} cached (SHA256 ok)")
80
+ return
81
+ print(f"[model] {filename} exists but SHA256 mismatch — re-downloading")
82
+
83
+ # --- download ---
84
+ target.parent.mkdir(parents=True, exist_ok=True)
85
+ print(f"[model] downloading {filename} (4.4 MB)...")
86
+ try:
87
+ urllib.request.urlretrieve(url, target)
88
+ except Exception as e:
89
+ print(f"[model] WARNING: download failed ({e})")
90
+ print(f"[model] run `pip install -e .` again or place {filename} manually")
91
+ return
92
+
93
+ # --- verify ---
94
+ h = hashlib.sha256()
95
+ with open(target, "rb") as f:
96
+ for chunk in iter(lambda: f.read(65536), b""):
97
+ h.update(chunk)
98
+ if h.hexdigest() != sha256:
99
+ target.unlink()
100
+ print(f"[model] WARNING: SHA256 mismatch — corrupt download removed")
101
+ print(f"[model] run `pip install -e .` again to retry")
102
+ return
103
+
104
+ print(f"[model] downloaded {target}")
105
+
106
+
107
+ class build_py(_build_py):
108
+ """Custom build_py — compiles gettext catalogs + frontend JS dict
109
+ + downloads the background-removal model."""
110
+
111
+ def run(self) -> None:
112
+ _compile_mo_files()
113
+ _download_model()
114
+ super().run()
115
+
116
+
117
+ class develop(_develop):
118
+ """Custom develop — same hook for editable installs (legacy path)."""
119
+
120
+ def run(self) -> None:
121
+ _compile_mo_files()
122
+ _download_model()
123
+ super().run()
124
+
125
+
126
+ class editable_wheel(_editable_wheel):
127
+ """Custom editable_wheel — PEP 660 editable installs (pip install -e)."""
128
+
129
+ def run(self) -> None:
130
+ _compile_mo_files()
131
+ _download_model()
132
+ super().run()
133
+
134
+
135
+ setup(cmdclass={
136
+ "build_py": build_py,
137
+ "develop": develop,
138
+ "editable_wheel": editable_wheel,
139
+ })
@@ -0,0 +1,46 @@
1
+ """Storyloom — AI-powered real-time visual novel engine."""
2
+
3
+ __version__ = "2.3.0"
4
+
5
+ from storyloom.io.api_client import ApiClient, ApiError, ApiResult
6
+ from storyloom.config import WINDOW_SIZE, DEFAULT_MODEL
7
+ from storyloom.core.context_manager import ContextManager
8
+ from storyloom.core.game_loop import GameLoop, GameState, RoundResult, RoundRecord
9
+ from storyloom.core.prompt_builder import PromptBuilder
10
+ from storyloom.core.save_manager import SaveManager
11
+ from storyloom.core.session import GameSession
12
+ from storyloom.user_config import UserConfig
13
+
14
+ from storyloom.assets import Asset, AssetItem, AssetLibrary, AssetType, GameAssetRoster
15
+ from storyloom.parser import ParsedOutput, ParseError, Segment
16
+ from storyloom.tasks import Task, TaskGenerator, TaskPool, TaskType
17
+
18
+ __all__ = [
19
+ "ApiClient",
20
+ "ApiError",
21
+ "ApiResult",
22
+ "Asset",
23
+ "AssetItem",
24
+ "AssetLibrary",
25
+ "AssetType",
26
+ "ContextManager",
27
+ "DEFAULT_MODEL",
28
+ "GameAssetRoster",
29
+ "GameLoop",
30
+ "GameSession",
31
+ "GameState",
32
+ "ParsedOutput",
33
+ "ParseError",
34
+ "PromptBuilder",
35
+ "RoundRecord",
36
+ "RoundResult",
37
+ "SaveManager",
38
+ "Segment",
39
+ "Task",
40
+ "TaskGenerator",
41
+ "TaskPool",
42
+ "TaskType",
43
+ "UserConfig",
44
+
45
+ "WINDOW_SIZE",
46
+ ]
@@ -0,0 +1,19 @@
1
+ """Asset database layer — types, library, roster.
2
+
3
+ Per design.md §2: Three-layer abstraction:
4
+ AssetType / Asset / AssetItem — core data types
5
+ AssetLibrary — global asset registry (media/_asset_lib.json)
6
+ GameAssetRoster — per-game asset mapping (saves/{id}/_asset_roster.json)
7
+ """
8
+
9
+ from storyloom.assets._library import AssetLibrary
10
+ from storyloom.assets._roster import GameAssetRoster
11
+ from storyloom.assets._types import Asset, AssetItem, AssetType
12
+
13
+ __all__ = [
14
+ "Asset",
15
+ "AssetItem",
16
+ "AssetLibrary",
17
+ "AssetType",
18
+ "GameAssetRoster",
19
+ ]