scenri 0.9.4 → 0.10.1
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.
- package/CHANGELOG.md +84 -0
- package/dist/{chunk-3ZRMKVBB.js → chunk-UUYMUOZQ.js} +148 -5
- package/dist/{cli-MQO66C5O.js → cli-3KKS7BE4.js} +3 -3
- package/dist/index.js +2 -2
- package/dist/serve.js +2266 -339
- package/dist/{src-3ITTV2UE.js → src-QZZDZM7I.js} +3 -3
- package/package.json +1 -1
- package/studio-dist/assets/facial-2-BmVnDzjJ.webp +0 -0
- package/studio-dist/assets/facial-3-DZtKCJBc.webp +0 -0
- package/studio-dist/assets/facial-4-D3bBk4Y-.webp +0 -0
- package/studio-dist/assets/facial-5-BZUOhBlt.webp +0 -0
- package/studio-dist/assets/facial-6-BjNlybGd.webp +0 -0
- package/studio-dist/assets/freckles-1-EZ0x3-vK.webp +0 -0
- package/studio-dist/assets/freckles-2-DuHEq26I.webp +0 -0
- package/studio-dist/assets/freckles-3-WGP6r0Fh.webp +0 -0
- package/studio-dist/assets/glasses-1-DPlo67j6.webp +0 -0
- package/studio-dist/assets/glasses-2-mRgaHYWV.webp +0 -0
- package/studio-dist/assets/glasses-3-DuTs0m8y.webp +0 -0
- package/studio-dist/assets/glasses-4-DGjoOjFS.webp +0 -0
- package/studio-dist/assets/glasses-5-DoEVdOaY.webp +0 -0
- package/studio-dist/assets/glasses-6-CcUVUrfz.webp +0 -0
- package/studio-dist/assets/index-D1MJJGXq.js +107 -0
- package/studio-dist/assets/{index-BGts89lc.css → index-ELZxH19c.css} +1 -1
- package/studio-dist/assets/length-1-androgynous-DiFL-HbJ.webp +0 -0
- package/studio-dist/assets/length-1-man-OI1u_7yA.webp +0 -0
- package/studio-dist/assets/length-1-woman-BbYkzjnG.webp +0 -0
- package/studio-dist/assets/length-2-androgynous-zBgi1XzH.webp +0 -0
- package/studio-dist/assets/length-2-man-Df3fVDUO.webp +0 -0
- package/studio-dist/assets/length-2-woman-BP1NSRcI.webp +0 -0
- package/studio-dist/assets/length-3-androgynous-BN_SLD6d.webp +0 -0
- package/studio-dist/assets/length-3-man-Dk_3OkKz.webp +0 -0
- package/studio-dist/assets/length-3-woman-BFV6b4If.webp +0 -0
- package/studio-dist/assets/length-4-androgynous-BblLWRi-.webp +0 -0
- package/studio-dist/assets/length-4-man-Cszq08t7.webp +0 -0
- package/studio-dist/assets/length-4-woman-B8gzWrbX.webp +0 -0
- package/studio-dist/assets/length-5-androgynous-DwxP_mgy.webp +0 -0
- package/studio-dist/assets/length-5-man-Yt6YRiWE.webp +0 -0
- package/studio-dist/assets/length-5-woman-BTz0YJMm.webp +0 -0
- package/studio-dist/assets/length-6-androgynous-BD7OoH6P.webp +0 -0
- package/studio-dist/assets/length-6-man-oceHSSPF.webp +0 -0
- package/studio-dist/assets/length-6-woman-BvX37wCz.webp +0 -0
- package/studio-dist/assets/makeup-1-CvGOSJO_.webp +0 -0
- package/studio-dist/assets/makeup-3-yoncgA8G.webp +0 -0
- package/studio-dist/assets/makeup-4-Buulx0wj.webp +0 -0
- package/studio-dist/assets/makeup-5-5Y_Qpn53.webp +0 -0
- package/studio-dist/assets/makeup-6-CSQm_m8e.webp +0 -0
- package/studio-dist/assets/prosthetic-1-C-bbKoKE.webp +0 -0
- package/studio-dist/assets/prosthetic-2-DCGXnKzb.webp +0 -0
- package/studio-dist/assets/prosthetic-3-BW_gdjlG.webp +0 -0
- package/studio-dist/assets/prosthetic-4-ec_5-7Tb.webp +0 -0
- package/studio-dist/assets/prosthetic-5-res5u7gj.webp +0 -0
- package/studio-dist/assets/prosthetic-6-0AvaEGMf.webp +0 -0
- package/studio-dist/assets/tattoo-3-33-3gd4C.webp +0 -0
- package/studio-dist/assets/tattoo-4-BW22hxdb.webp +0 -0
- package/studio-dist/assets/tattoo-5-BWJwjBfm.webp +0 -0
- package/studio-dist/index.html +2 -2
- package/studio-dist/assets/index-uiX1mW43.js +0 -104
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,89 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.1](https://github.com/tonygorb/Scenri/compare/v0.10.0...v0.10.1) (2026-09-16)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add placeTip function for hover card positioning ([de6d069](https://github.com/tonygorb/Scenri/commit/de6d069024b3d0c4b305f945a15322b1290d6458))
|
|
9
|
+
* add presenter duplication and deletion functionality ([11c83be](https://github.com/tonygorb/Scenri/commit/11c83be7ee167435fc8a6656c938380ec9b08c68))
|
|
10
|
+
* duplicate or delete a saved presenter from its card ([2e4dc00](https://github.com/tonygorb/Scenri/commit/2e4dc008f3bccbf9a9b2e444331bd2627775f1f4))
|
|
11
|
+
* enhance presenter view and retry functionality ([dbeb9e9](https://github.com/tonygorb/Scenri/commit/dbeb9e9e48c52510ebb9ca2949a27e0cebfb69ca))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* a fresh library keeps started_at through the status rebuild ([14aced1](https://github.com/tonygorb/Scenri/commit/14aced1fa5d0b841e8202b639e3ae95b615598ac))
|
|
17
|
+
* centre the Create first run and dock the assets panel from 1024 ([b102204](https://github.com/tonygorb/Scenri/commit/b102204d3e42107319a72923aea28de3e04298f2))
|
|
18
|
+
* retry in the same card, side profiles, chip fields and rails ([0fd3099](https://github.com/tonygorb/Scenri/commit/0fd309969320b0fe8f22cba0940cdf16f5bc68a4))
|
|
19
|
+
* type and lint errors in the chip field and the reference set ([53e5314](https://github.com/tonygorb/Scenri/commit/53e5314120de03cf7a5c4c3ef1d9946655e567a3))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Miscellaneous Chores
|
|
23
|
+
|
|
24
|
+
* **release:** pin the next version to 0.10.1 ([85a5df4](https://github.com/tonygorb/Scenri/commit/85a5df4711c81c30b60b18dfbd4a16098adff98b))
|
|
25
|
+
|
|
26
|
+
## [0.10.0](https://github.com/tonygorb/Scenri/compare/v0.9.4...v0.10.0) (2026-09-16)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Features
|
|
30
|
+
|
|
31
|
+
* presenter creation as a conversation, with a view and an editor ([39e1a36](https://github.com/tonygorb/Scenri/commit/39e1a3633ab27712a628508fd5c1db51c3bcfb51))
|
|
32
|
+
* **presenter:** the studio arrives the way everything else you make here does ([7fee9b7](https://github.com/tonygorb/Scenri/commit/7fee9b77160475490c5e4dc39dd19725f7e2ba53))
|
|
33
|
+
* **studio:** show an import where you are, and fill the wall as it lands ([667d8ac](https://github.com/tonygorb/Scenri/commit/667d8acd913818400b5ea7fee0502439e06fc0e9))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Bug Fixes
|
|
37
|
+
|
|
38
|
+
* **brand:** teach the brand scraper the manners the crawler already had ([b62f4da](https://github.com/tonygorb/Scenri/commit/b62f4da7d929dc9a3fdbdba5c4840e5bba3c9c84))
|
|
39
|
+
* **catalog:** a card asks for a thumbnail, not the picture it will import ([2a91200](https://github.com/tonygorb/Scenri/commit/2a91200a96cc240e7b0e144ece6670747efccee3))
|
|
40
|
+
* **catalog:** capture the site's own icon, and open the import from its dock ([cb76556](https://github.com/tonygorb/Scenri/commit/cb76556c1b85bd001deffbc9e1f3f35595bca679))
|
|
41
|
+
* **catalog:** count a catalogue the budget can actually reach ([8ba5d85](https://github.com/tonygorb/Scenri/commit/8ba5d85111e46c6c4a0afb7c48c9b972c067a168))
|
|
42
|
+
* **catalog:** count the pictures a run owes, not the ones it has looked at ([7913f21](https://github.com/tonygorb/Scenri/commit/7913f21949ea58823f0eab1a19bd175a84ae376c))
|
|
43
|
+
* **catalog:** count what a person is missing, in the unit they asked in ([e763b93](https://github.com/tonygorb/Scenri/commit/e763b93cdf0cfce12a84fe6a5e703e55bc7db973))
|
|
44
|
+
* **catalog:** read a guarded endpoint as guarded, not as a busy one ([1643170](https://github.com/tonygorb/Scenri/commit/1643170dcb93c80cc06d5be565e85d6aa7dfdc9a))
|
|
45
|
+
* **catalog:** reliable website import — accurate counts, guarded endpoints, live progress ([37658f5](https://github.com/tonygorb/Scenri/commit/37658f53a846d42bc87b7fc522c68c589aef20a6))
|
|
46
|
+
* **cli:** a revision does not carry a picture of the face it replaced ([072a353](https://github.com/tonygorb/Scenri/commit/072a353f8c43328df3e8b066a1f78637398c5394))
|
|
47
|
+
* **cli:** putting a picture back is a swap, not a decision to build the next view ([12d1787](https://github.com/tonygorb/Scenri/commit/12d17879e711a8275e34f27879030f5082226439))
|
|
48
|
+
* **composer:** a chip for a presenter who is gone says so ([58de149](https://github.com/tonygorb/Scenri/commit/58de1497623ab4b512eca3a3ac7e77d73a067f24))
|
|
49
|
+
* **core:** a draft becoming a saved presenter is one write ([970c0dd](https://github.com/tonygorb/Scenri/commit/970c0dd2c1a571ba524a719bf05c4b06d653f454))
|
|
50
|
+
* **presenter:** "Describe someone instead" describes someone ([b115d2e](https://github.com/tonygorb/Scenri/commit/b115d2e70a1e0642b4350d74c1149ba3804539b8))
|
|
51
|
+
* **presenter:** a record from before the studio opens, reads right, and can be drawn ([24f6ae1](https://github.com/tonygorb/Scenri/commit/24f6ae1dfaa3a92148c9caad2c915965eb702755))
|
|
52
|
+
* **presenter:** a reload is not a way of holding state ([3f7231d](https://github.com/tonygorb/Scenri/commit/3f7231dd6d5e51ae71ac46dbf58dcd2dc6df77fa))
|
|
53
|
+
* **presenter:** a view drawn from a face that moved lands stale, not current ([ce7d5fe](https://github.com/tonygorb/Scenri/commit/ce7d5fe6b40c93173a937095902089803352fe4b))
|
|
54
|
+
* **presenter:** an answer is installed only for the draft it was asked about ([c62ae1a](https://github.com/tonygorb/Scenri/commit/c62ae1ac9a261d319295f30e76f113bec1a6f845))
|
|
55
|
+
* **presenter:** arriving at a draft that is drawing reads its conversation ([edec9e3](https://github.com/tonygorb/Scenri/commit/edec9e35d213a0e6db30f78792cd1cffd098daff))
|
|
56
|
+
* **presenter:** Create presenter is a new presenter, every time ([4188c42](https://github.com/tonygorb/Scenri/commit/4188c4230865981366fb3ccccfe4c55ea40788b3))
|
|
57
|
+
* **presenter:** deleting a presenter reaches every surface, and takes its session with it ([73ceea2](https://github.com/tonygorb/Scenri/commit/73ceea292f3c124aeb85903dc0b40677e1cdf4c7))
|
|
58
|
+
* **presenter:** opening a draft does not ask a question it is about to withdraw ([4e4c6a4](https://github.com/tonygorb/Scenri/commit/4e4c6a427797f721cdb936474f3d7778a4de3a6b))
|
|
59
|
+
* **presenter:** the close on a phone head sits on the screen, and in the middle of the bar ([54634bd](https://github.com/tonygorb/Scenri/commit/54634bdcd5a050ad3cabc032bef67a68d22e001f))
|
|
60
|
+
* **presenter:** the editor gets the latch the creation flow already has ([56119c7](https://github.com/tonygorb/Scenri/commit/56119c7731ab71d69806a99944531b33235eedf3))
|
|
61
|
+
* **presenter:** the picture is simply there, and a picture put back brings its view up ([8665646](https://github.com/tonygorb/Scenri/commit/866564696cab338829c77dee9851bb65a69f7cef))
|
|
62
|
+
* **presenter:** the question over a picture put back says what happened to it ([d678a96](https://github.com/tonygorb/Scenri/commit/d678a969aa92260d0d02f6dcb615b78eb477dd80))
|
|
63
|
+
* **presenter:** the stage is the shape of the picture, so a bad draw is not a crop ([52f6bde](https://github.com/tonygorb/Scenri/commit/52f6bde7b6dfbade987b1e24324315c62a950a48))
|
|
64
|
+
* **presenter:** throwing an unfinished person away asks first ([9226470](https://github.com/tonygorb/Scenri/commit/9226470fe7cb0001695699f446d5e4c2f16ad579))
|
|
65
|
+
* **presenter:** without an engine, the photo is the face rather than a dead end ([8ddae49](https://github.com/tonygorb/Scenri/commit/8ddae49a74a191e126024d65340ee89ab78d84c9))
|
|
66
|
+
* **studio:** a discarded draft hands its place on, instead of dropping focus ([5180237](https://github.com/tonygorb/Scenri/commit/5180237bcbf18d2d76eff0cbe64f6644084ad071))
|
|
67
|
+
* **studio:** a kit you walk away from is not a brand you made ([03b3613](https://github.com/tonygorb/Scenri/commit/03b36139699e665669e75d7722c170930c8ec87b))
|
|
68
|
+
* **studio:** close the silent paths through onboarding and import ([441d310](https://github.com/tonygorb/Scenri/commit/441d310ecdd6e30ab63f117c19507036bff06513))
|
|
69
|
+
* **studio:** count one thing in the task row, and say what a stop kept ([0aba7e5](https://github.com/tonygorb/Scenri/commit/0aba7e5ee42b1a05b9a4635a2557c3a0bca2a5c3))
|
|
70
|
+
* **studio:** offer the products a listing found, not only the ones read ([4ea36ef](https://github.com/tonygorb/Scenri/commit/4ea36ef0890a854e19375a865e2c8190e97b78c7))
|
|
71
|
+
* **studio:** one press is one act, for Enter and for a confirm ([d43a8c7](https://github.com/tonygorb/Scenri/commit/d43a8c7f99a083f2d8d559d02475f89e03952464))
|
|
72
|
+
* **studio:** say what was kept, not only what went wrong ([70f1855](https://github.com/tonygorb/Scenri/commit/70f1855ce4945d72f56bf43ece8080b5050cafa5))
|
|
73
|
+
* **studio:** say when the look for a shop did not finish ([be0e2f6](https://github.com/tonygorb/Scenri/commit/be0e2f600673f3f1c8f8aef36d80b7cba0430212))
|
|
74
|
+
* **studio:** Start over keeps drawn work, and lets go of a draft with none ([f5d1328](https://github.com/tonygorb/Scenri/commit/f5d132824d030ab604cf52a97f71c5a73118464f))
|
|
75
|
+
* **studio:** the import pill wears the brand's mark and a clock ([4b78b1a](https://github.com/tonygorb/Scenri/commit/4b78b1a6e93780e5389ce2990af929c3dc515638))
|
|
76
|
+
* **studio:** the presenter studio says what it will spend, like the other two flows ([3bb039c](https://github.com/tonygorb/Scenri/commit/3bb039cf22c5950fd5226f22b93405477a216a4c))
|
|
77
|
+
* **test:** keep the private repo out of the battery drivers' defaults ([cd0f674](https://github.com/tonygorb/Scenri/commit/cd0f674d4f7b98490bf70c840ef8c9292c5f8f53))
|
|
78
|
+
* **test:** read the reference image's basename, not its POSIX split ([442cc07](https://github.com/tonygorb/Scenri/commit/442cc07bdedb02c62a695b9c22ac090caee2d890))
|
|
79
|
+
* **test:** the analyzer's imagePaths assertion had the same split('/') bug ([7877088](https://github.com/tonygorb/Scenri/commit/787708802c94f46be158c4f3e93c147e0973ee97))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
### Performance Improvements
|
|
83
|
+
|
|
84
|
+
* **catalog:** ask the listing for a chosen set, not a page each ([cfe9a3c](https://github.com/tonygorb/Scenri/commit/cfe9a3c3aa7411ae1e8fe6d5002a2f1b4397de53))
|
|
85
|
+
* **catalog:** keep the catalogue the store already handed over ([9630378](https://github.com/tonygorb/Scenri/commit/9630378ed2e076f6cb9b7ce50dde28c68e615548))
|
|
86
|
+
|
|
3
87
|
## [0.9.4](https://github.com/tonygorb/Scenri/compare/v0.9.3...v0.9.4) (2026-09-14)
|
|
4
88
|
|
|
5
89
|
|
|
@@ -2,7 +2,7 @@ import { homedir } from 'os';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import Database from 'better-sqlite3';
|
|
4
4
|
import { randomUUID, createHash } from 'crypto';
|
|
5
|
-
import { mkdirSync, existsSync, chmodSync, readFileSync, writeFileSync, readdirSync
|
|
5
|
+
import { mkdirSync, existsSync, chmodSync, rmSync, readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
6
6
|
|
|
7
7
|
// ../core/src/index.ts
|
|
8
8
|
|
|
@@ -173,6 +173,14 @@ CREATE TABLE IF NOT EXISTS import_jobs (
|
|
|
173
173
|
finished_at TEXT
|
|
174
174
|
);
|
|
175
175
|
CREATE INDEX IF NOT EXISTS idx_import_jobs_brand ON import_jobs(brand_id);
|
|
176
|
+
CREATE TABLE IF NOT EXISTS presenter_drafts (
|
|
177
|
+
id TEXT PRIMARY KEY,
|
|
178
|
+
brand_id TEXT NOT NULL REFERENCES brands(id) ON DELETE CASCADE,
|
|
179
|
+
json TEXT NOT NULL,
|
|
180
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
181
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
182
|
+
);
|
|
183
|
+
CREATE INDEX IF NOT EXISTS idx_presenter_drafts_brand ON presenter_drafts(brand_id);
|
|
176
184
|
`;
|
|
177
185
|
function widenNodeStatusCheck(db) {
|
|
178
186
|
const row = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='nodes'").get();
|
|
@@ -197,12 +205,13 @@ function widenNodeStatusCheck(db) {
|
|
|
197
205
|
brief TEXT,
|
|
198
206
|
archived INTEGER NOT NULL DEFAULT 0,
|
|
199
207
|
duration_ms INTEGER,
|
|
208
|
+
started_at TEXT,
|
|
200
209
|
batch_id TEXT,
|
|
201
210
|
batch_index INTEGER NOT NULL DEFAULT 0
|
|
202
211
|
);
|
|
203
212
|
INSERT INTO nodes_new
|
|
204
213
|
SELECT id, project_id, parent_id, kind, prompt, engine_id, status, images, cost_usd, kept, error,
|
|
205
|
-
created_at, overlays, brief, archived, duration_ms, batch_id, batch_index
|
|
214
|
+
created_at, overlays, brief, archived, duration_ms, started_at, batch_id, batch_index
|
|
206
215
|
FROM nodes;
|
|
207
216
|
DROP TABLE nodes;
|
|
208
217
|
ALTER TABLE nodes_new RENAME TO nodes;
|
|
@@ -517,6 +526,9 @@ function openDb(homeDir) {
|
|
|
517
526
|
if (!nodeCols.includes("duration_ms")) {
|
|
518
527
|
db.exec("ALTER TABLE nodes ADD COLUMN duration_ms INTEGER");
|
|
519
528
|
}
|
|
529
|
+
if (!nodeCols.includes("started_at")) {
|
|
530
|
+
db.exec("ALTER TABLE nodes ADD COLUMN started_at TEXT");
|
|
531
|
+
}
|
|
520
532
|
if (!nodeCols.includes("batch_id")) {
|
|
521
533
|
db.exec("ALTER TABLE nodes ADD COLUMN batch_id TEXT");
|
|
522
534
|
}
|
|
@@ -577,6 +589,12 @@ function createImageStore(homeDir) {
|
|
|
577
589
|
},
|
|
578
590
|
has(hash) {
|
|
579
591
|
return /^[a-f0-9]{32}$/.test(hash) && EXTS.some((ext) => existsSync(fileFor(hash, ext)));
|
|
592
|
+
},
|
|
593
|
+
remove(hash) {
|
|
594
|
+
const file = this.pathFor(hash);
|
|
595
|
+
if (!existsSync(file)) return false;
|
|
596
|
+
rmSync(file, { force: true });
|
|
597
|
+
return true;
|
|
580
598
|
}
|
|
581
599
|
};
|
|
582
600
|
}
|
|
@@ -674,6 +692,9 @@ function uniqueSetSlug(db, brandId, name, id) {
|
|
|
674
692
|
return firstFree(slugifyWithId(name, id, "set"), (c) => !!stmt.get(brandId, c, id));
|
|
675
693
|
}
|
|
676
694
|
var SET_NAME_SEP = String.fromCharCode(31);
|
|
695
|
+
function rowToPresenterDraft(r) {
|
|
696
|
+
return { id: r.id, brandId: r.brand_id, json: JSON.parse(r.json), createdAt: r.created_at, updatedAt: r.updated_at };
|
|
697
|
+
}
|
|
677
698
|
function rowToSet(r) {
|
|
678
699
|
return {
|
|
679
700
|
id: r.id,
|
|
@@ -690,7 +711,7 @@ var LINEAGE_SIBLINGS_RADIUS = 25;
|
|
|
690
711
|
var LINEAGE_CHILDREN_MAX = 60;
|
|
691
712
|
var LINEAGE_HISTORY_MAX = 60;
|
|
692
713
|
var FEED_COLS = `n.id, n.project_id, n.parent_id, n.kind, substr(n.prompt, 1, ${PROMPT_HEAD_CHARS}) AS prompt_head,
|
|
693
|
-
n.engine_id, n.status, n.images, n.cost_usd, n.duration_ms, n.kept, n.error, n.created_at, n.brief, n.archived,
|
|
714
|
+
n.engine_id, n.status, n.images, n.cost_usd, n.duration_ms, n.kept, n.error, n.created_at, n.started_at, n.brief, n.archived,
|
|
694
715
|
n.batch_id, n.batch_index, ${CHILD_COUNT_SQL} AS child_count`;
|
|
695
716
|
function rowToFeedNode(r) {
|
|
696
717
|
return {
|
|
@@ -707,6 +728,7 @@ function rowToFeedNode(r) {
|
|
|
707
728
|
kept: !!r.kept,
|
|
708
729
|
error: r.error,
|
|
709
730
|
createdAt: r.created_at,
|
|
731
|
+
startedAt: r.started_at ?? r.created_at,
|
|
710
732
|
brief: r.brief ? JSON.parse(r.brief) : null,
|
|
711
733
|
archived: !!r.archived,
|
|
712
734
|
batchId: r.batch_id ?? null,
|
|
@@ -850,6 +872,21 @@ function createStore(db) {
|
|
|
850
872
|
);
|
|
851
873
|
return this.getBrand(id);
|
|
852
874
|
},
|
|
875
|
+
/**
|
|
876
|
+
* A draft becoming a saved asset: the document and the row move together.
|
|
877
|
+
*
|
|
878
|
+
* These were two statements with nothing holding them. A crash between
|
|
879
|
+
* them left the presenter in the brand AND its draft row alive, so the
|
|
880
|
+
* wall showed a finished person and an unfinished one, and saving that
|
|
881
|
+
* draft again appended a second copy of the same person.
|
|
882
|
+
*/
|
|
883
|
+
updateBrandAndDropPresenterDraft(id, json, draftId) {
|
|
884
|
+
return db.transaction(() => {
|
|
885
|
+
const row = this.updateBrand(id, json);
|
|
886
|
+
this.deletePresenterDraft(draftId);
|
|
887
|
+
return row;
|
|
888
|
+
})();
|
|
889
|
+
},
|
|
853
890
|
deleteBrand(id) {
|
|
854
891
|
db.prepare("DELETE FROM brands WHERE id=?").run(id);
|
|
855
892
|
},
|
|
@@ -972,6 +1009,42 @@ function createStore(db) {
|
|
|
972
1009
|
membersOf(setId) {
|
|
973
1010
|
return db.prepare("SELECT node_id FROM set_nodes WHERE set_id=? ORDER BY added_at, node_id").all(setId).map((r) => r.node_id);
|
|
974
1011
|
},
|
|
1012
|
+
// presenter drafts
|
|
1013
|
+
/** Upsert: the same id replaces the json and moves updated_at forward; created_at stays. */
|
|
1014
|
+
putPresenterDraft(row) {
|
|
1015
|
+
db.prepare(
|
|
1016
|
+
// Millisecond stamps, the nodes table's own format: a draft is touched
|
|
1017
|
+
// on every step, and two touches inside one second must still order.
|
|
1018
|
+
`INSERT INTO presenter_drafts (id, brand_id, json, created_at, updated_at)
|
|
1019
|
+
VALUES (?,?,?, strftime('%Y-%m-%d %H:%M:%f','now'), strftime('%Y-%m-%d %H:%M:%f','now'))
|
|
1020
|
+
ON CONFLICT(id) DO UPDATE SET json=excluded.json, updated_at=strftime('%Y-%m-%d %H:%M:%f','now')`
|
|
1021
|
+
).run(row.id, row.brandId, JSON.stringify(row.json));
|
|
1022
|
+
return this.getPresenterDraft(row.id);
|
|
1023
|
+
},
|
|
1024
|
+
getPresenterDraft(id) {
|
|
1025
|
+
const r = db.prepare("SELECT * FROM presenter_drafts WHERE id=?").get(id);
|
|
1026
|
+
return r ? rowToPresenterDraft(r) : null;
|
|
1027
|
+
},
|
|
1028
|
+
/** Most recently touched first, the same order sets keep. */
|
|
1029
|
+
listPresenterDrafts(brandId) {
|
|
1030
|
+
return db.prepare("SELECT * FROM presenter_drafts WHERE brand_id=? ORDER BY updated_at DESC, created_at DESC, id").all(brandId).map(rowToPresenterDraft);
|
|
1031
|
+
},
|
|
1032
|
+
deletePresenterDraft(id) {
|
|
1033
|
+
db.prepare("DELETE FROM presenter_drafts WHERE id=?").run(id);
|
|
1034
|
+
},
|
|
1035
|
+
/**
|
|
1036
|
+
* Whether any row still points at a stored image: a shot's images, a
|
|
1037
|
+
* shot's brief (source image, attachments), an imported catalog image, or
|
|
1038
|
+
* another presenter draft. The store is content-addressed, so the same
|
|
1039
|
+
* photo uploaded twice is one file; whoever removes an image asks this
|
|
1040
|
+
* first, and the brand documents are the caller's half of the answer.
|
|
1041
|
+
*/
|
|
1042
|
+
imageReferenced(hash) {
|
|
1043
|
+
if (!/^[a-f0-9]{32}$/.test(hash)) return false;
|
|
1044
|
+
const like = `%${hash}%`;
|
|
1045
|
+
const hit = (sql, ...args) => !!db.prepare(sql).get(...args);
|
|
1046
|
+
return hit("SELECT 1 FROM nodes WHERE images LIKE ? LIMIT 1", `%"${hash}"%`) || hit("SELECT 1 FROM nodes WHERE brief LIKE ? LIMIT 1", like) || hit("SELECT 1 FROM catalog_images WHERE asset_ref LIKE ? LIMIT 1", like) || hit("SELECT 1 FROM presenter_drafts WHERE json LIKE ? LIMIT 1", like);
|
|
1047
|
+
},
|
|
975
1048
|
// nodes / version tree
|
|
976
1049
|
addNode(input) {
|
|
977
1050
|
if (input.parentId) {
|
|
@@ -1044,6 +1117,19 @@ function createStore(db) {
|
|
|
1044
1117
|
cancelNode(id) {
|
|
1045
1118
|
db.prepare("UPDATE nodes SET status='cancelled' WHERE id=?").run(id);
|
|
1046
1119
|
},
|
|
1120
|
+
/**
|
|
1121
|
+
* Run this shot again on the same row. The card keeps its place: id,
|
|
1122
|
+
* brief, prompt, parent and created_at stay put. Status goes back to
|
|
1123
|
+
* running, the old error and pictures go, so the tile becomes the wait.
|
|
1124
|
+
* started_at moves to now so the clock on the card is this run, not the
|
|
1125
|
+
* minutes the first attempt already spent.
|
|
1126
|
+
*/
|
|
1127
|
+
reopenNode(id) {
|
|
1128
|
+
db.prepare(
|
|
1129
|
+
"UPDATE nodes SET status='running', error=NULL, images='[]', duration_ms=NULL, cost_usd=0, started_at=strftime('%Y-%m-%d %H:%M:%f','now') WHERE id=?"
|
|
1130
|
+
).run(id);
|
|
1131
|
+
return this.getNode(id);
|
|
1132
|
+
},
|
|
1047
1133
|
getNode(id) {
|
|
1048
1134
|
const r = db.prepare(`SELECT n.*, ${CHILD_COUNT_SQL} AS child_count FROM nodes n WHERE n.id=?`).get(id);
|
|
1049
1135
|
return r ? rowToNode(r) : null;
|
|
@@ -1711,6 +1797,24 @@ function productImportMethods(db) {
|
|
|
1711
1797
|
WHERE product_id=? AND source_url=?`
|
|
1712
1798
|
).run(assetRef, meta?.width ?? null, meta?.height ?? null, productId, sourceUrl);
|
|
1713
1799
|
},
|
|
1800
|
+
/**
|
|
1801
|
+
* How many pictures are still owed, for a progress that can be trusted.
|
|
1802
|
+
*
|
|
1803
|
+
* The importer used to add up the rounds it had fetched and call that the
|
|
1804
|
+
* total, so the total was really "pictures discovered so far": it grew by
|
|
1805
|
+
* sixty every round, and the fraction fell back every time it did. A run
|
|
1806
|
+
* went 60/60, then 64/120, then 122/172 - a bar sliding backwards twice
|
|
1807
|
+
* while nothing had gone wrong. Counting what is left is one query and it
|
|
1808
|
+
* is the actual answer.
|
|
1809
|
+
*/
|
|
1810
|
+
countImagesNeedingAssets(brandId, imagesPerProduct) {
|
|
1811
|
+
const row = db.prepare(
|
|
1812
|
+
`SELECT COUNT(*) AS n FROM catalog_images i
|
|
1813
|
+
JOIN catalog_products p ON p.id = i.product_id
|
|
1814
|
+
WHERE p.brand_id=? AND (i.asset_ref IS NULL OR i.asset_ref='') AND i.position < ?`
|
|
1815
|
+
).get(brandId, imagesPerProduct);
|
|
1816
|
+
return row?.n ?? 0;
|
|
1817
|
+
},
|
|
1714
1818
|
listImagesNeedingAssets(brandId, limit = 500) {
|
|
1715
1819
|
return db.prepare(
|
|
1716
1820
|
`SELECT i.* FROM catalog_images i
|
|
@@ -1730,6 +1834,38 @@ function productImportMethods(db) {
|
|
|
1730
1834
|
excluded: !!r.excluded
|
|
1731
1835
|
}));
|
|
1732
1836
|
},
|
|
1837
|
+
/**
|
|
1838
|
+
* Hide pictures that turn up on product after product.
|
|
1839
|
+
*
|
|
1840
|
+
* A photograph of a thing belongs to that thing. A picture the crawl found
|
|
1841
|
+
* on eighteen different product pages is the shop talking, not the
|
|
1842
|
+
* product: a promotion flash, a collection sticker, a delivery badge.
|
|
1843
|
+
* Measured on a real storefront, three addresses out of 265 were shared
|
|
1844
|
+
* past this threshold and all three were merchandising furniture, while
|
|
1845
|
+
* every genuine packshot appeared once.
|
|
1846
|
+
*
|
|
1847
|
+
* Names nothing and knows nothing about any platform, which is the point:
|
|
1848
|
+
* the badges that caused this carry no word a filter could match, and the
|
|
1849
|
+
* next store's will be different words.
|
|
1850
|
+
*
|
|
1851
|
+
* Excluded rather than deleted, the same state the picture chooser uses,
|
|
1852
|
+
* so anything wrongly caught is still there to be put back.
|
|
1853
|
+
*/
|
|
1854
|
+
excludeSharedImages(sourceId, minProducts = 3) {
|
|
1855
|
+
const r = db.prepare(
|
|
1856
|
+
`UPDATE catalog_images SET excluded=1
|
|
1857
|
+
WHERE excluded=0
|
|
1858
|
+
AND product_id IN (SELECT id FROM catalog_products WHERE source_id=?)
|
|
1859
|
+
AND source_url IN (
|
|
1860
|
+
SELECT ci.source_url FROM catalog_images ci
|
|
1861
|
+
JOIN catalog_products cp ON cp.id = ci.product_id
|
|
1862
|
+
WHERE cp.source_id=?
|
|
1863
|
+
GROUP BY ci.source_url
|
|
1864
|
+
HAVING COUNT(DISTINCT ci.product_id) >= ?
|
|
1865
|
+
)`
|
|
1866
|
+
).run(sourceId, sourceId, minProducts);
|
|
1867
|
+
return r.changes;
|
|
1868
|
+
},
|
|
1733
1869
|
markMissingUnavailable(sourceId, seenExternalKeys) {
|
|
1734
1870
|
if (!seenExternalKeys.length) {
|
|
1735
1871
|
const r2 = db.prepare(
|
|
@@ -1952,6 +2088,12 @@ function createCatalogStore(db) {
|
|
|
1952
2088
|
var REFERENCE_ROLE_DIRECTIVE = {
|
|
1953
2089
|
product: "the exact product \u2014 preserve its label, shape, colors and design faithfully; do not redesign it",
|
|
1954
2090
|
character: "the exact person \u2014 match their face, facial structure, skin, hair and build exactly; their clothing, pose and background are capture context, not styling to reproduce",
|
|
2091
|
+
// One thing in the picture, and nothing else in it. A person uploads a pair
|
|
2092
|
+
// of glasses they like, or the exact tattoo, and every other role would have
|
|
2093
|
+
// handed the model a second face to blend with the first: `character` says
|
|
2094
|
+
// "this is who they are", `style` takes the mood of the whole frame. This
|
|
2095
|
+
// says take the object and leave the rest of the photograph behind.
|
|
2096
|
+
detail: "a reference for one detail only \u2014 match the design, shape, proportions, colours and finish of the thing itself, exactly as drawn or made. Take nothing else from this image: not the identity of anyone in it, not their face, skin, hair, body or build, not their clothing, not the background, not the lighting. The person in the picture being drawn is somebody else entirely",
|
|
1955
2097
|
brand: "the brand's own mark \u2014 if the direction calls for the mark to appear, reproduce it exactly as drawn, same colours, letterforms and proportions, every character down to the smallest secondary lettering, in its original script and reading direction, never translated, transliterated or re-spelled; otherwise take only its colour and treatment, and never its subject, geometry or composition",
|
|
1956
2098
|
// Only a figure-led scene attaches one of these now, so this says what that
|
|
1957
2099
|
// case actually needs. It used to read "environment and light only - take no
|
|
@@ -1976,6 +2118,7 @@ var REFERENCE_ROLE_DIRECTIVE = {
|
|
|
1976
2118
|
var EDIT_REFERENCE_ROLE_DIRECTIVE = {
|
|
1977
2119
|
product: "the exact product: keep or restore its label, shape and design faithfully",
|
|
1978
2120
|
character: "the exact person: keep their face, facial structure, skin, hair and build faithfully; take no clothing, pose or background from this reference, and keep the source image's existing outfit unless the instruction changes it",
|
|
2121
|
+
detail: "one detail to match: its design, shape, colours and finish only. Take no identity, skin, hair, body, clothing or background from it",
|
|
1979
2122
|
brand: "the brand's own mark: reproduce it exactly as drawn wherever it appears \u2014 every character down to the smallest secondary lettering, in its original script and reading direction \u2014 never redrawn, re-lettered, translated or transliterated",
|
|
1980
2123
|
scene: "a reference for environment, light and treatment only \u2014 take no identity from any person in it",
|
|
1981
2124
|
composition: "a reference for framing and pose only",
|
|
@@ -2032,5 +2175,5 @@ function createCore(homeDir = defaultHome()) {
|
|
|
2032
2175
|
}
|
|
2033
2176
|
|
|
2034
2177
|
export { ASPECT_TOLERANCE, BUDGET_EXHAUSTED, EDIT_REFERENCE_ROLE_DIRECTIVE, REFERENCE_ROLE_DIRECTIVE, SCHEMA_VERSION, STEM_MIN, SchemaTooNewError, SpendCapError, TRIGRAM_MIN, budgetSize, createCatalogStore, createCore, createStore, defaultHome, fold, ftsMatch, matchesQuery, ratioLabel, searchTerms, termMatches, uniqueProjectSlug, uniqueSetSlug, uniqueSlug };
|
|
2035
|
-
//# sourceMappingURL=chunk-
|
|
2036
|
-
//# sourceMappingURL=chunk-
|
|
2178
|
+
//# sourceMappingURL=chunk-UUYMUOZQ.js.map
|
|
2179
|
+
//# sourceMappingURL=chunk-UUYMUOZQ.js.map
|
|
@@ -463,7 +463,7 @@ async function addOrRepair(ownEntry, say, over) {
|
|
|
463
463
|
}
|
|
464
464
|
async function rememberDecline(home) {
|
|
465
465
|
try {
|
|
466
|
-
const { createCore } = await import('./src-
|
|
466
|
+
const { createCore } = await import('./src-QZZDZM7I.js');
|
|
467
467
|
const core = createCore(home);
|
|
468
468
|
try {
|
|
469
469
|
core.store.setSetting("desktop.prompt", "declined");
|
|
@@ -530,5 +530,5 @@ function tail(path, bytes = 4096) {
|
|
|
530
530
|
}
|
|
531
531
|
|
|
532
532
|
export { addToDesktop, assetsDirFor, installDeps, runDesktopCommand, runExecFile, runOpenCommand };
|
|
533
|
-
//# sourceMappingURL=cli-
|
|
534
|
-
//# sourceMappingURL=cli-
|
|
533
|
+
//# sourceMappingURL=cli-3KKS7BE4.js.map
|
|
534
|
+
//# sourceMappingURL=cli-3KKS7BE4.js.map
|
package/dist/index.js
CHANGED
|
@@ -86,12 +86,12 @@ try {
|
|
|
86
86
|
break;
|
|
87
87
|
}
|
|
88
88
|
case "desktop": {
|
|
89
|
-
const { runDesktopCommand } = await import('./cli-
|
|
89
|
+
const { runDesktopCommand } = await import('./cli-3KKS7BE4.js');
|
|
90
90
|
process.exit(await runDesktopCommand(command, fileURLToPath(import.meta.url)));
|
|
91
91
|
break;
|
|
92
92
|
}
|
|
93
93
|
case "open": {
|
|
94
|
-
const { runOpenCommand } = await import('./cli-
|
|
94
|
+
const { runOpenCommand } = await import('./cli-3KKS7BE4.js');
|
|
95
95
|
process.exit(await runOpenCommand(fileURLToPath(import.meta.url)));
|
|
96
96
|
break;
|
|
97
97
|
}
|