dreamloop 0.1.1__tar.gz → 0.1.2__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.
- {dreamloop-0.1.1 → dreamloop-0.1.2}/CHANGELOG.md +12 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/PKG-INFO +28 -3
- {dreamloop-0.1.1 → dreamloop-0.1.2}/README.md +27 -2
- {dreamloop-0.1.1 → dreamloop-0.1.2}/README.zh-CN.md +25 -2
- {dreamloop-0.1.1 → dreamloop-0.1.2}/pyproject.toml +1 -1
- {dreamloop-0.1.1 → dreamloop-0.1.2}/src/dreamloop/__init__.py +1 -1
- {dreamloop-0.1.1 → dreamloop-0.1.2}/src/dreamloop/analysis.py +5 -1
- {dreamloop-0.1.1 → dreamloop-0.1.2}/src/dreamloop/cli.py +25 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/src/dreamloop/core.py +166 -0
- dreamloop-0.1.2/src/dreamloop/images.py +201 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/src/dreamloop/static/style.css +78 -14
- {dreamloop-0.1.1 → dreamloop-0.1.2}/src/dreamloop/templates/detail.html +13 -2
- {dreamloop-0.1.1 → dreamloop-0.1.2}/src/dreamloop/templates/index.html +49 -8
- {dreamloop-0.1.1 → dreamloop-0.1.2}/src/dreamloop/web.py +87 -2
- {dreamloop-0.1.1 → dreamloop-0.1.2}/tests/test_cli.py +11 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/tests/test_core_workflow.py +125 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/tests/test_readme_positioning.py +9 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/tests/test_web_api.py +88 -1
- {dreamloop-0.1.1 → dreamloop-0.1.2}/uv.lock +1 -1
- {dreamloop-0.1.1 → dreamloop-0.1.2}/.github/workflows/ci.yml +0 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/.github/workflows/publish.yml +0 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/.gitignore +0 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/LICENSE +0 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/docs/assets/dashboard-placeholder.svg +0 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/docs/assets/dashboard-screenshot.png +0 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/docs/assets/hero-dashboard.svg +0 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/docs/assets/social-preview.png +0 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/docs/demo-recording.md +0 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/scripts/start-dreamloop.cmd +0 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/tests/test_ai_config.py +0 -0
- {dreamloop-0.1.1 → dreamloop-0.1.2}/tests/test_packaging_release.py +0 -0
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.1.2 - 2026-06-14
|
|
4
|
+
|
|
5
|
+
DreamLoop's second patch tightens first-screen quality and introduces an explicit image-generation path:
|
|
6
|
+
|
|
7
|
+
- Reduced Dashboard hero title weight and spacing so the screenshot screen no longer visually overflows.
|
|
8
|
+
- Added subtle page-enter transitions and sidebar active-state motion, with `prefers-reduced-motion` support.
|
|
9
|
+
- Fixed the language toggle mojibake so Chinese renders as `中文`.
|
|
10
|
+
- Added optional image provider configuration: local visual cards by default, ComfyUI readiness checks, and a custom OpenAI-compatible cloud image endpoint.
|
|
11
|
+
- Added `dream_images` storage and `.dreamloop/assets/images/` output for generated image files.
|
|
12
|
+
- Added dream image web/API routes and `dreamloop image test`.
|
|
13
|
+
- Gallery now prefers real generated images and falls back to local visual memory cards.
|
|
14
|
+
|
|
3
15
|
## v0.1.1 - 2026-06-14
|
|
4
16
|
|
|
5
17
|
This patch focuses on first-impression reliability and the trust loop around interpretation quality:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dreamloop
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A local-first AI dream journal for people who want to notice patterns in their dreams.
|
|
5
5
|
Project-URL: Homepage, https://github.com/saime428/DreamLoop
|
|
6
6
|
Project-URL: Repository, https://github.com/saime428/DreamLoop
|
|
@@ -38,6 +38,7 @@ Description-Content-Type: text/markdown
|
|
|
38
38
|
- Free with Ollama. Optional DeepSeek/OpenAI or custom OpenAI-compatible endpoints.
|
|
39
39
|
- CLI-first, forkable, and built for Obsidian-minded knowledge workers.
|
|
40
40
|
- Detailed dream analysis that favors emotions, real-life context, and multiple verifiable interpretations.
|
|
41
|
+
- Optional dream images are explicit opt-in: local cards by default, OpenAI-compatible cloud endpoints only when configured, and local ComfyUI reserved for workflow-backed generation.
|
|
41
42
|
|
|
42
43
|
```bash
|
|
43
44
|
git clone https://github.com/saime428/DreamLoop.git
|
|
@@ -162,6 +163,19 @@ Provider defaults:
|
|
|
162
163
|
- `custom`: any OpenAI-compatible `/v1` endpoint, including local gateways
|
|
163
164
|
- `none`: capture-only local journal mode
|
|
164
165
|
|
|
166
|
+
## Image Providers
|
|
167
|
+
|
|
168
|
+
DreamLoop does not call an image API by default. Detail pages always support a local visual-memory card, and real image generation is opt-in. In v0.1.2 the cloud OpenAI-compatible path can generate files; the local ComfyUI option is a configuration and connectivity checkpoint until a workflow is attached.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
dreamloop image use local_card
|
|
172
|
+
dreamloop image use local_comfyui --base-url http://127.0.0.1:8188
|
|
173
|
+
dreamloop image use cloud_openai_compatible --model image-model --base-url https://images.example/v1
|
|
174
|
+
dreamloop image test
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Generated image files are stored under `.dreamloop/assets/images/`; the database stores local metadata such as provider, model, prompt, path, status, and error. ComfyUI readiness can be checked with `dreamloop image test`, but prompt submission stays disabled until a workflow is configured.
|
|
178
|
+
|
|
165
179
|
## Web App Loop
|
|
166
180
|
|
|
167
181
|
The FastAPI/Jinja dashboard is intentionally lightweight:
|
|
@@ -170,8 +184,8 @@ The FastAPI/Jinja dashboard is intentionally lightweight:
|
|
|
170
184
|
- Log: draft-first dream capture, optional reflection prompts, and AI analysis before saving
|
|
171
185
|
- Detail: original dream text, detailed interpretation, reality-grounded questions, raw JSON, and an opt-in visual-memory entry point
|
|
172
186
|
- Patterns: clickable calendar, recurring symbols, theme trends, and filters back into Log
|
|
173
|
-
- Gallery:
|
|
174
|
-
- Settings: provider
|
|
187
|
+
- Gallery: real generated images when configured, otherwise local visual cards derived from saved dreams
|
|
188
|
+
- Settings: AI provider, image provider, launch notes, local data directory, and privacy status
|
|
175
189
|
|
|
176
190
|
DreamLoop is currently launched with `uv run dreamloop web` from a checkout or `dreamloop web` after package install. A native desktop app is a later packaging task; v0.1 stays lightweight so the local-first core remains easy to inspect and fork.
|
|
177
191
|
|
|
@@ -183,7 +197,9 @@ The same app exposes JSON endpoints:
|
|
|
183
197
|
- `GET /api/dreams/{id}/similar`
|
|
184
198
|
- `POST /api/analyze/pending`
|
|
185
199
|
- `POST /api/dreams/{id}/feedback`
|
|
200
|
+
- `POST /api/dreams/{id}/image`
|
|
186
201
|
- `GET /api/feedback/summary`
|
|
202
|
+
- `GET /api/images/status`
|
|
187
203
|
- `POST /api/import/ics`
|
|
188
204
|
- `POST /api/weather/sync`
|
|
189
205
|
- `GET /api/insights/heatmap`
|
|
@@ -196,6 +212,7 @@ The same app exposes JSON endpoints:
|
|
|
196
212
|
dreamloop.sqlite3
|
|
197
213
|
config.json
|
|
198
214
|
secrets.env
|
|
215
|
+
assets/images/
|
|
199
216
|
chroma/
|
|
200
217
|
exports/
|
|
201
218
|
imports/
|
|
@@ -243,6 +260,14 @@ dreamloop web
|
|
|
243
260
|
- Local feedback buttons on interpretations: resonates, not accurate, unsure.
|
|
244
261
|
- Patterns summary for high-resonance themes.
|
|
245
262
|
|
|
263
|
+
### v0.1.2
|
|
264
|
+
|
|
265
|
+
- Dashboard first-screen polish for screenshot quality.
|
|
266
|
+
- Subtle page transitions with reduced-motion support.
|
|
267
|
+
- Optional image provider settings for local visual cards, ComfyUI readiness checks, and custom cloud image endpoints.
|
|
268
|
+
- Real generated image storage in `.dreamloop/assets/images/`.
|
|
269
|
+
- Gallery prefers real dream images and falls back to local visual cards.
|
|
270
|
+
|
|
246
271
|
### v0.2
|
|
247
272
|
|
|
248
273
|
- Markdown export.
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
- Free with Ollama. Optional DeepSeek/OpenAI or custom OpenAI-compatible endpoints.
|
|
13
13
|
- CLI-first, forkable, and built for Obsidian-minded knowledge workers.
|
|
14
14
|
- Detailed dream analysis that favors emotions, real-life context, and multiple verifiable interpretations.
|
|
15
|
+
- Optional dream images are explicit opt-in: local cards by default, OpenAI-compatible cloud endpoints only when configured, and local ComfyUI reserved for workflow-backed generation.
|
|
15
16
|
|
|
16
17
|
```bash
|
|
17
18
|
git clone https://github.com/saime428/DreamLoop.git
|
|
@@ -136,6 +137,19 @@ Provider defaults:
|
|
|
136
137
|
- `custom`: any OpenAI-compatible `/v1` endpoint, including local gateways
|
|
137
138
|
- `none`: capture-only local journal mode
|
|
138
139
|
|
|
140
|
+
## Image Providers
|
|
141
|
+
|
|
142
|
+
DreamLoop does not call an image API by default. Detail pages always support a local visual-memory card, and real image generation is opt-in. In v0.1.2 the cloud OpenAI-compatible path can generate files; the local ComfyUI option is a configuration and connectivity checkpoint until a workflow is attached.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
dreamloop image use local_card
|
|
146
|
+
dreamloop image use local_comfyui --base-url http://127.0.0.1:8188
|
|
147
|
+
dreamloop image use cloud_openai_compatible --model image-model --base-url https://images.example/v1
|
|
148
|
+
dreamloop image test
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Generated image files are stored under `.dreamloop/assets/images/`; the database stores local metadata such as provider, model, prompt, path, status, and error. ComfyUI readiness can be checked with `dreamloop image test`, but prompt submission stays disabled until a workflow is configured.
|
|
152
|
+
|
|
139
153
|
## Web App Loop
|
|
140
154
|
|
|
141
155
|
The FastAPI/Jinja dashboard is intentionally lightweight:
|
|
@@ -144,8 +158,8 @@ The FastAPI/Jinja dashboard is intentionally lightweight:
|
|
|
144
158
|
- Log: draft-first dream capture, optional reflection prompts, and AI analysis before saving
|
|
145
159
|
- Detail: original dream text, detailed interpretation, reality-grounded questions, raw JSON, and an opt-in visual-memory entry point
|
|
146
160
|
- Patterns: clickable calendar, recurring symbols, theme trends, and filters back into Log
|
|
147
|
-
- Gallery:
|
|
148
|
-
- Settings: provider
|
|
161
|
+
- Gallery: real generated images when configured, otherwise local visual cards derived from saved dreams
|
|
162
|
+
- Settings: AI provider, image provider, launch notes, local data directory, and privacy status
|
|
149
163
|
|
|
150
164
|
DreamLoop is currently launched with `uv run dreamloop web` from a checkout or `dreamloop web` after package install. A native desktop app is a later packaging task; v0.1 stays lightweight so the local-first core remains easy to inspect and fork.
|
|
151
165
|
|
|
@@ -157,7 +171,9 @@ The same app exposes JSON endpoints:
|
|
|
157
171
|
- `GET /api/dreams/{id}/similar`
|
|
158
172
|
- `POST /api/analyze/pending`
|
|
159
173
|
- `POST /api/dreams/{id}/feedback`
|
|
174
|
+
- `POST /api/dreams/{id}/image`
|
|
160
175
|
- `GET /api/feedback/summary`
|
|
176
|
+
- `GET /api/images/status`
|
|
161
177
|
- `POST /api/import/ics`
|
|
162
178
|
- `POST /api/weather/sync`
|
|
163
179
|
- `GET /api/insights/heatmap`
|
|
@@ -170,6 +186,7 @@ The same app exposes JSON endpoints:
|
|
|
170
186
|
dreamloop.sqlite3
|
|
171
187
|
config.json
|
|
172
188
|
secrets.env
|
|
189
|
+
assets/images/
|
|
173
190
|
chroma/
|
|
174
191
|
exports/
|
|
175
192
|
imports/
|
|
@@ -217,6 +234,14 @@ dreamloop web
|
|
|
217
234
|
- Local feedback buttons on interpretations: resonates, not accurate, unsure.
|
|
218
235
|
- Patterns summary for high-resonance themes.
|
|
219
236
|
|
|
237
|
+
### v0.1.2
|
|
238
|
+
|
|
239
|
+
- Dashboard first-screen polish for screenshot quality.
|
|
240
|
+
- Subtle page transitions with reduced-motion support.
|
|
241
|
+
- Optional image provider settings for local visual cards, ComfyUI readiness checks, and custom cloud image endpoints.
|
|
242
|
+
- Real generated image storage in `.dreamloop/assets/images/`.
|
|
243
|
+
- Gallery prefers real dream images and falls back to local visual cards.
|
|
244
|
+
|
|
220
245
|
### v0.2
|
|
221
246
|
|
|
222
247
|
- Markdown export.
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
- 支持 Ollama 零成本运行;需要云模型时再显式配置 DeepSeek、OpenAI 或 Custom OpenAI-compatible。
|
|
13
13
|
- CLI 优先、易 fork,适合开发者和 Obsidian 式知识工作流。
|
|
14
14
|
- 解梦更重视情绪、现实处境、多种可验证解释,而不是只给一两句玄学摘要。
|
|
15
|
+
- 梦境图像默认不调用外部 API;先生成本地视觉卡片,需要时再显式接入 OpenAI-compatible 云端图像接口,ComfyUI 暂作为工作流生图的配置预留。
|
|
15
16
|
|
|
16
17
|
```bash
|
|
17
18
|
git clone https://github.com/saime428/DreamLoop.git
|
|
@@ -83,8 +84,8 @@ DreamLoop v0.1 的产品逻辑是一个完整闭环:
|
|
|
83
84
|
- Log 录入:高频输入页,先写梦境,也可补充醒来感受、现实关联和个人联想,再点击 AI 分析。
|
|
84
85
|
- Detail 分析:查看梦境原文、详细解梦、现实问题、自我验证问题、原始 JSON,并能给每条解释打反馈。
|
|
85
86
|
- Patterns 规律:可点击梦境日历、符号趋势、主题趋势和高共鸣主题,图表能跳回 Log 过滤记录。
|
|
86
|
-
- Gallery
|
|
87
|
-
- Settings
|
|
87
|
+
- Gallery 记忆:优先展示真实生成图像;没有配置图像 provider 时展示本地视觉卡片。
|
|
88
|
+
- Settings 信任托底:配置 AI 提供方、图像提供方、查看本地数据目录、确认密钥不回显,并阅读隐私审计说明。
|
|
88
89
|
|
|
89
90
|
## 无 AI 模式也能用
|
|
90
91
|
|
|
@@ -125,6 +126,19 @@ Provider 说明:
|
|
|
125
126
|
- `custom`:任意 OpenAI-compatible `/v1` 端点,包括本地网关。
|
|
126
127
|
- `none`:只记录和浏览,不做 AI 分析。
|
|
127
128
|
|
|
129
|
+
## 图像 Provider
|
|
130
|
+
|
|
131
|
+
DreamLoop 默认不会调用图像 API。详情页始终可以生成本地视觉记忆卡片;真实图像生成需要你显式配置。v0.1.2 中,OpenAI-compatible 云端接口可以生成文件;本地 ComfyUI 先作为配置和连通性检测入口,等绑定 workflow 后再提交生图任务。
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
dreamloop image use local_card
|
|
135
|
+
dreamloop image use local_comfyui --base-url http://127.0.0.1:8188
|
|
136
|
+
dreamloop image use cloud_openai_compatible --model image-model --base-url https://images.example/v1
|
|
137
|
+
dreamloop image test
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
真实图像文件会写入 `.dreamloop/assets/images/`。数据库只保存 provider、model、prompt、路径、状态和错误信息等本地元数据。ComfyUI 可以用 `dreamloop image test` 检查服务是否在线,但在配置 workflow 前不会启用真实生图按钮。
|
|
141
|
+
|
|
128
142
|
## 常见排查
|
|
129
143
|
|
|
130
144
|
- 页面没有分析:先运行 `dreamloop doctor`,确认 provider 是否 ready。
|
|
@@ -156,6 +170,7 @@ Obsidian 路线图:
|
|
|
156
170
|
dreamloop.sqlite3
|
|
157
171
|
config.json
|
|
158
172
|
secrets.env
|
|
173
|
+
assets/images/
|
|
159
174
|
chroma/
|
|
160
175
|
exports/
|
|
161
176
|
imports/
|
|
@@ -184,6 +199,14 @@ SQLite 保存梦境、分析结果、用户反馈、日历事件和天气摘要
|
|
|
184
199
|
- Patterns 展示高共鸣主题。
|
|
185
200
|
- Settings 增加隐私审计说明。
|
|
186
201
|
|
|
202
|
+
### v0.1.2
|
|
203
|
+
|
|
204
|
+
- 修复 Dashboard 首屏视觉撑爆。
|
|
205
|
+
- 增加轻量页面切换动效,并支持 reduced motion。
|
|
206
|
+
- 增加本地视觉卡片、ComfyUI 连通性检查和自定义云端图像接口配置。
|
|
207
|
+
- 真实图像保存到 `.dreamloop/assets/images/`。
|
|
208
|
+
- Gallery 优先展示真实梦境图像,没有图像时回退到本地视觉卡片。
|
|
209
|
+
|
|
187
210
|
### v0.2
|
|
188
211
|
|
|
189
212
|
- Markdown/Obsidian 导出。
|
|
@@ -203,7 +203,11 @@ def save_ai_config(
|
|
|
203
203
|
config["base_url"] = base_url
|
|
204
204
|
path = dreamloop_dir(root) / "config.json"
|
|
205
205
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
206
|
-
|
|
206
|
+
payload = json.loads(path.read_text(encoding="utf-8")) if path.exists() else {}
|
|
207
|
+
if not isinstance(payload, dict):
|
|
208
|
+
payload = {}
|
|
209
|
+
payload["ai"] = config
|
|
210
|
+
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
207
211
|
ensure_gitignore(root)
|
|
208
212
|
return path
|
|
209
213
|
|
|
@@ -10,14 +10,17 @@ import typer
|
|
|
10
10
|
|
|
11
11
|
from .analysis import ai_status, save_ai_config, test_provider_connection
|
|
12
12
|
from .core import DreamLoop
|
|
13
|
+
from .images import image_status, save_image_config, test_image_provider_connection
|
|
13
14
|
|
|
14
15
|
app = typer.Typer(help="Local-first AI dream journal.")
|
|
15
16
|
import_app = typer.Typer(help="Import local data.")
|
|
16
17
|
weather_app = typer.Typer(help="Sync local context such as weather.")
|
|
17
18
|
ai_app = typer.Typer(help="Configure local and optional cloud AI providers.")
|
|
19
|
+
image_app = typer.Typer(help="Configure optional dream image generation.")
|
|
18
20
|
app.add_typer(import_app, name="import")
|
|
19
21
|
app.add_typer(weather_app, name="weather")
|
|
20
22
|
app.add_typer(ai_app, name="ai")
|
|
23
|
+
app.add_typer(image_app, name="image")
|
|
21
24
|
|
|
22
25
|
|
|
23
26
|
@app.command()
|
|
@@ -83,6 +86,12 @@ def doctor() -> None:
|
|
|
83
86
|
typer.echo(f"connection: {test_provider_connection(loop.root)}")
|
|
84
87
|
if status.warning:
|
|
85
88
|
typer.echo(f"warning: {status.warning}")
|
|
89
|
+
image = image_status(loop.root)
|
|
90
|
+
typer.echo(f"image_provider: {image.provider}")
|
|
91
|
+
typer.echo(f"image_model: {image.model or 'none'}")
|
|
92
|
+
typer.echo(f"image_ready: {image.ready}")
|
|
93
|
+
if image.warning:
|
|
94
|
+
typer.echo(f"image_warning: {image.warning}")
|
|
86
95
|
typer.echo("secrets: hidden")
|
|
87
96
|
|
|
88
97
|
|
|
@@ -163,5 +172,21 @@ def ai_test() -> None:
|
|
|
163
172
|
typer.echo(test_provider_connection())
|
|
164
173
|
|
|
165
174
|
|
|
175
|
+
@image_app.command("use")
|
|
176
|
+
def image_use(
|
|
177
|
+
provider: str,
|
|
178
|
+
model: Annotated[str | None, typer.Option("--model")] = None,
|
|
179
|
+
base_url: Annotated[str | None, typer.Option("--base-url")] = None,
|
|
180
|
+
) -> None:
|
|
181
|
+
save_image_config(provider=provider, model=model, base_url=base_url)
|
|
182
|
+
status = image_status()
|
|
183
|
+
typer.echo(f"Image provider set to {status.provider} ({status.model or 'local card'}).")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@image_app.command("test")
|
|
187
|
+
def image_test() -> None:
|
|
188
|
+
typer.echo(test_image_provider_connection())
|
|
189
|
+
|
|
190
|
+
|
|
166
191
|
def main() -> None:
|
|
167
192
|
app()
|
|
@@ -19,6 +19,7 @@ from .analysis import (
|
|
|
19
19
|
normalize_report_payload,
|
|
20
20
|
normalize_text_list,
|
|
21
21
|
)
|
|
22
|
+
from .images import ImageGenerator, build_image_generator, image_status
|
|
22
23
|
|
|
23
24
|
FEEDBACK_RATINGS = {"resonates", "not_accurate", "unsure"}
|
|
24
25
|
|
|
@@ -28,11 +29,13 @@ class DreamLoop:
|
|
|
28
29
|
self.root = Path(root or Path.cwd())
|
|
29
30
|
self.data_dir = self.root / ".dreamloop"
|
|
30
31
|
self.db_path = self.data_dir / "dreamloop.sqlite3"
|
|
32
|
+
self.images_dir = self.data_dir / "assets" / "images"
|
|
31
33
|
|
|
32
34
|
def init(self) -> None:
|
|
33
35
|
self.data_dir.mkdir(parents=True, exist_ok=True)
|
|
34
36
|
for name in ("chroma", "exports", "imports"):
|
|
35
37
|
(self.data_dir / name).mkdir(parents=True, exist_ok=True)
|
|
38
|
+
self.images_dir.mkdir(parents=True, exist_ok=True)
|
|
36
39
|
self._ensure_gitignore()
|
|
37
40
|
self._migrate()
|
|
38
41
|
|
|
@@ -118,6 +121,15 @@ class DreamLoop:
|
|
|
118
121
|
"SELECT * FROM dream_analyses WHERE dream_id = ? AND language = ?",
|
|
119
122
|
(dream_id, normalize_language(language)),
|
|
120
123
|
).fetchone()
|
|
124
|
+
image = db.execute(
|
|
125
|
+
"""
|
|
126
|
+
SELECT * FROM dream_images
|
|
127
|
+
WHERE dream_id = ? AND language = ?
|
|
128
|
+
ORDER BY CASE WHEN status = 'complete' THEN 0 ELSE 1 END, id DESC
|
|
129
|
+
LIMIT 1
|
|
130
|
+
""",
|
|
131
|
+
(dream_id, normalize_language(language)),
|
|
132
|
+
).fetchone()
|
|
121
133
|
if analysis:
|
|
122
134
|
raw_report = parse_json_any(analysis["raw_json"])
|
|
123
135
|
report = normalize_report_payload(raw_report) if isinstance(raw_report, dict) else {}
|
|
@@ -133,6 +145,7 @@ class DreamLoop:
|
|
|
133
145
|
}
|
|
134
146
|
else:
|
|
135
147
|
dream["analysis"] = None
|
|
148
|
+
dream["image"] = image_from_row(image) if image else None
|
|
136
149
|
return dream
|
|
137
150
|
|
|
138
151
|
def analyze_pending(
|
|
@@ -180,6 +193,7 @@ class DreamLoop:
|
|
|
180
193
|
self.init()
|
|
181
194
|
with self._connect() as db:
|
|
182
195
|
db.execute("DELETE FROM user_feedback WHERE dream_id = ?", (dream_id,))
|
|
196
|
+
db.execute("DELETE FROM dream_images WHERE dream_id = ?", (dream_id,))
|
|
183
197
|
db.execute("DELETE FROM dream_analyses WHERE dream_id = ?", (dream_id,))
|
|
184
198
|
cursor = db.execute("DELETE FROM dreams WHERE id = ?", (dream_id,))
|
|
185
199
|
return cursor.rowcount > 0
|
|
@@ -304,6 +318,74 @@ class DreamLoop:
|
|
|
304
318
|
)
|
|
305
319
|
return visual
|
|
306
320
|
|
|
321
|
+
def generate_dream_image(
|
|
322
|
+
self,
|
|
323
|
+
dream_id: int,
|
|
324
|
+
*,
|
|
325
|
+
language: str = "en",
|
|
326
|
+
generator: ImageGenerator | None = None,
|
|
327
|
+
) -> dict[str, Any]:
|
|
328
|
+
self.init()
|
|
329
|
+
dream = self.get_dream(dream_id, language=language)
|
|
330
|
+
prompt = build_dream_image_prompt(dream)
|
|
331
|
+
status = image_status(self.root)
|
|
332
|
+
generator = generator or build_image_generator(self.root)
|
|
333
|
+
provider = getattr(generator, "provider", status.provider) if generator else status.provider
|
|
334
|
+
model = getattr(generator, "model", status.model) if generator else status.model
|
|
335
|
+
created_at = datetime.now().isoformat(timespec="seconds")
|
|
336
|
+
if generator is None:
|
|
337
|
+
error = status.warning or "Image provider is not ready."
|
|
338
|
+
with self._connect() as db:
|
|
339
|
+
image_id = self._store_image_record(
|
|
340
|
+
db,
|
|
341
|
+
dream_id,
|
|
342
|
+
language=language,
|
|
343
|
+
provider=provider,
|
|
344
|
+
model=model or "",
|
|
345
|
+
prompt=prompt,
|
|
346
|
+
image_path="",
|
|
347
|
+
status="error",
|
|
348
|
+
error=error,
|
|
349
|
+
created_at=created_at,
|
|
350
|
+
)
|
|
351
|
+
raise RuntimeError(f"{error} (image record #{image_id})")
|
|
352
|
+
try:
|
|
353
|
+
image_bytes = generator.generate(prompt)
|
|
354
|
+
except Exception as exc:
|
|
355
|
+
with self._connect() as db:
|
|
356
|
+
self._store_image_record(
|
|
357
|
+
db,
|
|
358
|
+
dream_id,
|
|
359
|
+
language=language,
|
|
360
|
+
provider=provider,
|
|
361
|
+
model=model or "",
|
|
362
|
+
prompt=prompt,
|
|
363
|
+
image_path="",
|
|
364
|
+
status="error",
|
|
365
|
+
error=str(exc),
|
|
366
|
+
created_at=created_at,
|
|
367
|
+
)
|
|
368
|
+
raise
|
|
369
|
+
filename = f"dream-{dream_id}-{normalize_language(language)}-{datetime.now().strftime('%Y%m%d%H%M%S%f')}.png"
|
|
370
|
+
image_path = self.images_dir / filename
|
|
371
|
+
image_path.write_bytes(image_bytes)
|
|
372
|
+
relative_path = f"assets/images/{filename}"
|
|
373
|
+
with self._connect() as db:
|
|
374
|
+
image_id = self._store_image_record(
|
|
375
|
+
db,
|
|
376
|
+
dream_id,
|
|
377
|
+
language=language,
|
|
378
|
+
provider=provider,
|
|
379
|
+
model=model or "",
|
|
380
|
+
prompt=prompt,
|
|
381
|
+
image_path=relative_path,
|
|
382
|
+
status="complete",
|
|
383
|
+
error="",
|
|
384
|
+
created_at=created_at,
|
|
385
|
+
)
|
|
386
|
+
row = db.execute("SELECT * FROM dream_images WHERE id = ?", (image_id,)).fetchone()
|
|
387
|
+
return image_from_row(row)
|
|
388
|
+
|
|
307
389
|
def import_ics(self, path: str | Path) -> int:
|
|
308
390
|
self.init()
|
|
309
391
|
events = parse_ics(Path(path).read_text(encoding="utf-8"))
|
|
@@ -481,6 +563,40 @@ class DreamLoop:
|
|
|
481
563
|
)
|
|
482
564
|
db.execute("UPDATE dreams SET analysis_status = 'analyzed' WHERE id = ?", (dream_id,))
|
|
483
565
|
|
|
566
|
+
def _store_image_record(
|
|
567
|
+
self,
|
|
568
|
+
db: sqlite3.Connection,
|
|
569
|
+
dream_id: int,
|
|
570
|
+
*,
|
|
571
|
+
language: str,
|
|
572
|
+
provider: str,
|
|
573
|
+
model: str,
|
|
574
|
+
prompt: str,
|
|
575
|
+
image_path: str,
|
|
576
|
+
status: str,
|
|
577
|
+
error: str,
|
|
578
|
+
created_at: str,
|
|
579
|
+
) -> int:
|
|
580
|
+
cursor = db.execute(
|
|
581
|
+
"""
|
|
582
|
+
INSERT INTO dream_images (
|
|
583
|
+
dream_id, language, provider, model, prompt, image_path, status, error, created_at
|
|
584
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
585
|
+
""",
|
|
586
|
+
(
|
|
587
|
+
dream_id,
|
|
588
|
+
normalize_language(language),
|
|
589
|
+
provider,
|
|
590
|
+
model,
|
|
591
|
+
prompt,
|
|
592
|
+
image_path,
|
|
593
|
+
status,
|
|
594
|
+
error,
|
|
595
|
+
created_at,
|
|
596
|
+
),
|
|
597
|
+
)
|
|
598
|
+
return int(cursor.lastrowid)
|
|
599
|
+
|
|
484
600
|
def _migrate(self) -> None:
|
|
485
601
|
with self._connect() as db:
|
|
486
602
|
db.executescript(
|
|
@@ -526,6 +642,19 @@ class DreamLoop:
|
|
|
526
642
|
reason TEXT NOT NULL DEFAULT '',
|
|
527
643
|
created_at TEXT NOT NULL
|
|
528
644
|
);
|
|
645
|
+
|
|
646
|
+
CREATE TABLE IF NOT EXISTS dream_images (
|
|
647
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
648
|
+
dream_id INTEGER NOT NULL REFERENCES dreams(id) ON DELETE CASCADE,
|
|
649
|
+
language TEXT NOT NULL DEFAULT 'en',
|
|
650
|
+
provider TEXT NOT NULL,
|
|
651
|
+
model TEXT NOT NULL DEFAULT '',
|
|
652
|
+
prompt TEXT NOT NULL,
|
|
653
|
+
image_path TEXT NOT NULL DEFAULT '',
|
|
654
|
+
status TEXT NOT NULL,
|
|
655
|
+
error TEXT NOT NULL DEFAULT '',
|
|
656
|
+
created_at TEXT NOT NULL
|
|
657
|
+
);
|
|
529
658
|
"""
|
|
530
659
|
)
|
|
531
660
|
self._migrate_dreams_table(db)
|
|
@@ -667,6 +796,43 @@ def normalize_visual_memory(payload: dict[str, Any]) -> dict[str, Any]:
|
|
|
667
796
|
return visual
|
|
668
797
|
|
|
669
798
|
|
|
799
|
+
def image_from_row(row: sqlite3.Row | None) -> dict[str, Any] | None:
|
|
800
|
+
if row is None:
|
|
801
|
+
return None
|
|
802
|
+
image = dict(row)
|
|
803
|
+
image["image_url"] = image_url(image.get("image_path", ""))
|
|
804
|
+
return image
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
def image_url(image_path: str) -> str:
|
|
808
|
+
if not image_path:
|
|
809
|
+
return ""
|
|
810
|
+
if image_path.startswith("assets/"):
|
|
811
|
+
return f"/dreamloop-assets/{image_path.removeprefix('assets/')}"
|
|
812
|
+
return f"/dreamloop-assets/{image_path.lstrip('/')}"
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
def build_dream_image_prompt(dream: dict[str, Any]) -> str:
|
|
816
|
+
analysis = dream.get("analysis") or {}
|
|
817
|
+
report = analysis.get("report") or {}
|
|
818
|
+
parts = [
|
|
819
|
+
"Create a cinematic, dreamlike illustration based on this dream.",
|
|
820
|
+
f"Dream text: {dream.get('content', '')}",
|
|
821
|
+
]
|
|
822
|
+
if analysis.get("summary"):
|
|
823
|
+
parts.append(f"Interpretive summary: {analysis['summary']}")
|
|
824
|
+
if analysis.get("emotional_tone"):
|
|
825
|
+
parts.append(f"Core emotion: {analysis['emotional_tone']}")
|
|
826
|
+
symbols = normalize_text_list(analysis.get("symbols"))
|
|
827
|
+
if symbols:
|
|
828
|
+
parts.append(f"Important people, objects, and scenes: {', '.join(symbols[:6])}")
|
|
829
|
+
details = normalize_text_list(report.get("dream_details"))
|
|
830
|
+
if details:
|
|
831
|
+
parts.append(f"Concrete dream details to preserve: {', '.join(details[:4])}")
|
|
832
|
+
parts.append("Avoid text, logos, UI, gore, and literal JSON. Keep it atmospheric but specific.")
|
|
833
|
+
return " ".join(part for part in parts if part.strip())
|
|
834
|
+
|
|
835
|
+
|
|
670
836
|
def demo_samples() -> list[dict[str, Any]]:
|
|
671
837
|
return [
|
|
672
838
|
{
|