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.
- storyloom_engine-2.3.0/LICENSE +21 -0
- storyloom_engine-2.3.0/MANIFEST.in +4 -0
- storyloom_engine-2.3.0/PKG-INFO +20 -0
- storyloom_engine-2.3.0/README.md +194 -0
- storyloom_engine-2.3.0/pyproject.toml +32 -0
- storyloom_engine-2.3.0/setup.cfg +4 -0
- storyloom_engine-2.3.0/setup.py +139 -0
- storyloom_engine-2.3.0/src/storyloom/__init__.py +46 -0
- storyloom_engine-2.3.0/src/storyloom/assets/__init__.py +19 -0
- storyloom_engine-2.3.0/src/storyloom/assets/_library.py +507 -0
- storyloom_engine-2.3.0/src/storyloom/assets/_manifest.py +83 -0
- storyloom_engine-2.3.0/src/storyloom/assets/_roster.py +251 -0
- storyloom_engine-2.3.0/src/storyloom/assets/_types.py +153 -0
- storyloom_engine-2.3.0/src/storyloom/config.py +105 -0
- storyloom_engine-2.3.0/src/storyloom/content/en/guide.md +124 -0
- storyloom_engine-2.3.0/src/storyloom/content/zh-CN/guide.md +122 -0
- storyloom_engine-2.3.0/src/storyloom/content/zh-TW/guide.md +122 -0
- storyloom_engine-2.3.0/src/storyloom/core/__init__.py +42 -0
- storyloom_engine-2.3.0/src/storyloom/core/co_create.py +1346 -0
- storyloom_engine-2.3.0/src/storyloom/core/context_manager.py +162 -0
- storyloom_engine-2.3.0/src/storyloom/core/event_dispatcher.py +228 -0
- storyloom_engine-2.3.0/src/storyloom/core/game_loop.py +1593 -0
- storyloom_engine-2.3.0/src/storyloom/core/lang_meta/en.json +7 -0
- storyloom_engine-2.3.0/src/storyloom/core/lang_meta/zh-CN.json +7 -0
- storyloom_engine-2.3.0/src/storyloom/core/lang_meta/zh-TW.json +7 -0
- storyloom_engine-2.3.0/src/storyloom/core/prebuild.py +999 -0
- storyloom_engine-2.3.0/src/storyloom/core/prompt_builder.py +1129 -0
- storyloom_engine-2.3.0/src/storyloom/core/save_manager.py +436 -0
- storyloom_engine-2.3.0/src/storyloom/core/session.py +360 -0
- storyloom_engine-2.3.0/src/storyloom/core/state_manager.py +616 -0
- storyloom_engine-2.3.0/src/storyloom/core/update_manager.py +559 -0
- storyloom_engine-2.3.0/src/storyloom/dev_cli/__init__.py +24 -0
- storyloom_engine-2.3.0/src/storyloom/dev_cli/__main__.py +4 -0
- storyloom_engine-2.3.0/src/storyloom/dev_cli/_terminal.py +112 -0
- storyloom_engine-2.3.0/src/storyloom/dev_cli/game_driver.py +773 -0
- storyloom_engine-2.3.0/src/storyloom/dev_cli/observer.py +164 -0
- storyloom_engine-2.3.0/src/storyloom/i18n.py +110 -0
- storyloom_engine-2.3.0/src/storyloom/i18n_compile.py +39 -0
- storyloom_engine-2.3.0/src/storyloom/io/__init__.py +41 -0
- storyloom_engine-2.3.0/src/storyloom/io/_types.py +54 -0
- storyloom_engine-2.3.0/src/storyloom/io/api_client.py +403 -0
- storyloom_engine-2.3.0/src/storyloom/io/img_api_client.py +681 -0
- storyloom_engine-2.3.0/src/storyloom/io/img_prompts.py +70 -0
- storyloom_engine-2.3.0/src/storyloom/io/img_utils.py +436 -0
- storyloom_engine-2.3.0/src/storyloom/io/thinking.py +256 -0
- storyloom_engine-2.3.0/src/storyloom/launcher.py +116 -0
- storyloom_engine-2.3.0/src/storyloom/locale/README.md +30 -0
- storyloom_engine-2.3.0/src/storyloom/locale/zh_CN/LC_MESSAGES/storyloom.po +19 -0
- storyloom_engine-2.3.0/src/storyloom/locale/zh_TW/LC_MESSAGES/storyloom.po +19 -0
- storyloom_engine-2.3.0/src/storyloom/models/u2netp.onnx +0 -0
- storyloom_engine-2.3.0/src/storyloom/parser/__init__.py +24 -0
- storyloom_engine-2.3.0/src/storyloom/parser/stream_parser.py +688 -0
- storyloom_engine-2.3.0/src/storyloom/tasks/__init__.py +21 -0
- storyloom_engine-2.3.0/src/storyloom/tasks/_generator.py +136 -0
- storyloom_engine-2.3.0/src/storyloom/tasks/_llm_generate.py +619 -0
- storyloom_engine-2.3.0/src/storyloom/tasks/_llm_match.py +260 -0
- storyloom_engine-2.3.0/src/storyloom/tasks/_pool.py +72 -0
- storyloom_engine-2.3.0/src/storyloom/tasks/_types.py +94 -0
- storyloom_engine-2.3.0/src/storyloom/user_config.py +342 -0
- storyloom_engine-2.3.0/src/storyloom/web/__init__.py +13 -0
- storyloom_engine-2.3.0/src/storyloom/web/__main__.py +3 -0
- storyloom_engine-2.3.0/src/storyloom/web/server.py +1726 -0
- storyloom_engine-2.3.0/src/storyloom/web/sessions.py +291 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/css/graph.css +336 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/css/main.css +3515 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/css/theme-dark.css +45 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/css/theme-light.css +45 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/css/variables.css +68 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/index.html +34 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/js/adventure-log.js +225 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/js/api.js +55 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/js/assets.js +325 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/js/co-create.js +1100 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/js/credits.js +29 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/js/display.js +468 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/js/game.js +824 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/js/graph-renderer.js +574 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/js/icons.js +304 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/js/router.js +1708 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/js/sse-client.js +196 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/js/state.js +463 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/js/vendor/i18next.min.js +1 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/js/vendor/i18nextHttpBackend.min.js +1 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/locales/en.json +1 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/locales/zh-CN.json +201 -0
- storyloom_engine-2.3.0/src/storyloom/web/static/locales/zh-TW.json +201 -0
- storyloom_engine-2.3.0/src/storyloom_engine.egg-info/PKG-INFO +20 -0
- storyloom_engine-2.3.0/src/storyloom_engine.egg-info/SOURCES.txt +116 -0
- storyloom_engine-2.3.0/src/storyloom_engine.egg-info/dependency_links.txt +1 -0
- storyloom_engine-2.3.0/src/storyloom_engine.egg-info/entry_points.txt +2 -0
- storyloom_engine-2.3.0/src/storyloom_engine.egg-info/requires.txt +15 -0
- storyloom_engine-2.3.0/src/storyloom_engine.egg-info/top_level.txt +1 -0
- storyloom_engine-2.3.0/tests/test_api_client.py +215 -0
- storyloom_engine-2.3.0/tests/test_assets.py +1964 -0
- storyloom_engine-2.3.0/tests/test_co_create.py +1164 -0
- storyloom_engine-2.3.0/tests/test_context_manager.py +188 -0
- storyloom_engine-2.3.0/tests/test_game_loop.py +1614 -0
- storyloom_engine-2.3.0/tests/test_graph_mode_pipeline.py +475 -0
- storyloom_engine-2.3.0/tests/test_i18n.py +70 -0
- storyloom_engine-2.3.0/tests/test_i18n_compile.py +42 -0
- storyloom_engine-2.3.0/tests/test_img_api_client.py +454 -0
- storyloom_engine-2.3.0/tests/test_img_utils.py +447 -0
- storyloom_engine-2.3.0/tests/test_integration.py +263 -0
- storyloom_engine-2.3.0/tests/test_launcher.py +135 -0
- storyloom_engine-2.3.0/tests/test_llm_generate.py +1080 -0
- storyloom_engine-2.3.0/tests/test_llm_match.py +733 -0
- storyloom_engine-2.3.0/tests/test_manifest.py +204 -0
- storyloom_engine-2.3.0/tests/test_pipeline_integration.py +767 -0
- storyloom_engine-2.3.0/tests/test_prebuild.py +1207 -0
- storyloom_engine-2.3.0/tests/test_prompt_builder.py +339 -0
- storyloom_engine-2.3.0/tests/test_save_manager.py +365 -0
- storyloom_engine-2.3.0/tests/test_session.py +240 -0
- storyloom_engine-2.3.0/tests/test_state_manager.py +471 -0
- storyloom_engine-2.3.0/tests/test_stream_parser.py +661 -0
- storyloom_engine-2.3.0/tests/test_task_framework.py +1051 -0
- storyloom_engine-2.3.0/tests/test_update_manager.py +550 -0
- storyloom_engine-2.3.0/tests/test_user_config.py +360 -0
- 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,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
|
+
[](https://www.python.org/)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+
[](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,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
|
+
]
|