← back to Wallco Ai
furnish: disk-backed cache (data/furnish-cache/<id>-<ctx>.json)
f4031b41d8b2fdc5f0b4e6f59b667d2def629c80 · 2026-05-13 14:10:05 -0700 · SteveStudio2
Per Steve's cost note — each click was hitting Gemini-text fresh. Now:
- First click for (design, context) → Gemini call + cache write
- Every subsequent click → instant cache hit, zero cost
- ?refresh=1 forces re-gen (admin escape hatch)
- TTL: forever — designs don't change; LLM output for same inputs is
stable enough to never expire
- Response includes cached:true so UI can show 'instant' if desired
Cost impact: at 1000 furnish clicks/mo with say 100 unique (design,ctx)
pairs, drops from $1/mo to $0.10/mo on text alone.
Files touched
M .gitignoreM data/designs.jsonM server.js
Diff
commit f4031b41d8b2fdc5f0b4e6f59b667d2def629c80
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Wed May 13 14:10:05 2026 -0700
furnish: disk-backed cache (data/furnish-cache/<id>-<ctx>.json)
Per Steve's cost note — each click was hitting Gemini-text fresh. Now:
- First click for (design, context) → Gemini call + cache write
- Every subsequent click → instant cache hit, zero cost
- ?refresh=1 forces re-gen (admin escape hatch)
- TTL: forever — designs don't change; LLM output for same inputs is
stable enough to never expire
- Response includes cached:true so UI can show 'instant' if desired
Cost impact: at 1000 furnish clicks/mo with say 100 unique (design,ctx)
pairs, drops from $1/mo to $0.10/mo on text alone.
---
.gitignore | 1 +
data/designs.json | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
server.js | 29 ++++++-
3 files changed, 249 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 5ec5329..d8a671a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@ data/generated_pre_seamless_backup/
data/moodboards.json
data/reviews.json
data/sample-requests.jsonl
+data/furnish-cache/
diff --git a/data/designs.json b/data/designs.json
index 42bfb19..b61e702 100644
--- a/data/designs.json
+++ b/data/designs.json
@@ -82801,5 +82801,225 @@
"user_scale_good": null,
"user_stars": null,
"room_mockups": []
+ },
+ {
+ "id": 3993,
+ "kind": "seamless_tile",
+ "category": "damask",
+ "dominant_hex": "#8a7053",
+ "saturation": 0.249,
+ "title": "Amber Atelier No.3993",
+ "handle": "wallco-3993",
+ "image_url": "/designs/img/1778706472331_1277553684.png",
+ "filename": "1778706472331_1277553684.png",
+ "generator": "wallco.ai",
+ "seed": 1277553684,
+ "is_published": false,
+ "created_at": "2026-05-13T21:08:09.673476+00:00",
+ "motifs": [],
+ "user_removed": false,
+ "user_color_good": null,
+ "user_style_good": null,
+ "user_scale_good": null,
+ "user_stars": null,
+ "room_mockups": []
+ },
+ {
+ "id": 3994,
+ "kind": "seamless_tile",
+ "category": "damask",
+ "dominant_hex": "#8a7053",
+ "saturation": 0.249,
+ "title": "Amber Salon No.3994",
+ "handle": "wallco-3994",
+ "image_url": "/designs/img/var_3993_hue+60_1778706489917.png",
+ "filename": "var_3993_hue+60_1778706489917.png",
+ "generator": "wallco.ai",
+ "seed": 0,
+ "is_published": false,
+ "created_at": "2026-05-13T21:08:15.85105+00:00",
+ "motifs": [],
+ "user_removed": false,
+ "user_color_good": null,
+ "user_style_good": null,
+ "user_scale_good": null,
+ "user_stars": null,
+ "room_mockups": []
+ },
+ {
+ "id": 3995,
+ "kind": "seamless_tile",
+ "category": "stoned-animals",
+ "dominant_hex": "#527345",
+ "saturation": 0.25,
+ "title": "Sage Hazefield No.3995",
+ "handle": "wallco-3995",
+ "image_url": "/designs/img/1778706485153_264367489.png",
+ "filename": "1778706485153_264367489.png",
+ "generator": "wallco.ai",
+ "seed": 264367489,
+ "is_published": false,
+ "created_at": "2026-05-13T21:08:15.853156+00:00",
+ "motifs": [],
+ "user_removed": false,
+ "user_color_good": null,
+ "user_style_good": null,
+ "user_scale_good": null,
+ "user_stars": null,
+ "room_mockups": []
+ },
+ {
+ "id": 3996,
+ "kind": "seamless_tile",
+ "category": "drunk-animals",
+ "dominant_hex": "#5f3b62",
+ "saturation": 0.248,
+ "title": "Plum Carouse No.3996",
+ "handle": "wallco-3996",
+ "image_url": "/designs/img/1778706485153_294943035.png",
+ "filename": "1778706485153_294943035.png",
+ "generator": "wallco.ai",
+ "seed": 294943035,
+ "is_published": false,
+ "created_at": "2026-05-13T21:08:17.12323+00:00",
+ "motifs": [],
+ "user_removed": false,
+ "user_color_good": null,
+ "user_style_good": null,
+ "user_scale_good": null,
+ "user_stars": null,
+ "room_mockups": []
+ },
+ {
+ "id": 3997,
+ "kind": "seamless_tile",
+ "category": "damask",
+ "dominant_hex": "#8a7053",
+ "saturation": 0.249,
+ "title": "Amber Heirloom No.3997",
+ "handle": "wallco-3997",
+ "image_url": "/designs/img/var_3993_hue+120_1778706495874.png",
+ "filename": "var_3993_hue+120_1778706495874.png",
+ "generator": "wallco.ai",
+ "seed": 0,
+ "is_published": false,
+ "created_at": "2026-05-13T21:08:18.538094+00:00",
+ "motifs": [],
+ "user_removed": false,
+ "user_color_good": null,
+ "user_style_good": null,
+ "user_scale_good": null,
+ "user_stars": null,
+ "room_mockups": []
+ },
+ {
+ "id": 3998,
+ "kind": "seamless_tile",
+ "category": "damask",
+ "dominant_hex": "#8a7053",
+ "saturation": 0.249,
+ "title": "Amber Atelier No.3998",
+ "handle": "wallco-3998",
+ "image_url": "/designs/img/var_3993_hue+180_1778706498543.png",
+ "filename": "var_3993_hue+180_1778706498543.png",
+ "generator": "wallco.ai",
+ "seed": 0,
+ "is_published": false,
+ "created_at": "2026-05-13T21:08:21.061595+00:00",
+ "motifs": [],
+ "user_removed": false,
+ "user_color_good": null,
+ "user_style_good": null,
+ "user_scale_good": null,
+ "user_stars": null,
+ "room_mockups": []
+ },
+ {
+ "id": 3999,
+ "kind": "seamless_tile",
+ "category": "damask",
+ "dominant_hex": "#8a7053",
+ "saturation": 0.249,
+ "title": "Amber Salon No.3999",
+ "handle": "wallco-3999",
+ "image_url": "/designs/img/var_3993_hue-60_1778706501074.png",
+ "filename": "var_3993_hue-60_1778706501074.png",
+ "generator": "wallco.ai",
+ "seed": 0,
+ "is_published": false,
+ "created_at": "2026-05-13T21:08:24.990778+00:00",
+ "motifs": [],
+ "user_removed": false,
+ "user_color_good": null,
+ "user_style_good": null,
+ "user_scale_good": null,
+ "user_stars": null,
+ "room_mockups": []
+ },
+ {
+ "id": 4000,
+ "kind": "seamless_tile",
+ "category": "damask",
+ "dominant_hex": "#8a7053",
+ "saturation": 0.249,
+ "title": "Amber Baroque No.4000",
+ "handle": "wallco-4000",
+ "image_url": "/designs/img/var_3993_mono_1778706505006.png",
+ "filename": "var_3993_mono_1778706505006.png",
+ "generator": "wallco.ai",
+ "seed": 0,
+ "is_published": false,
+ "created_at": "2026-05-13T21:08:28.434933+00:00",
+ "motifs": [],
+ "user_removed": false,
+ "user_color_good": null,
+ "user_style_good": null,
+ "user_scale_good": null,
+ "user_stars": null,
+ "room_mockups": []
+ },
+ {
+ "id": 4001,
+ "kind": "seamless_tile",
+ "category": "drunk-animals",
+ "dominant_hex": "#723e5d",
+ "saturation": 0.295,
+ "title": "Plum Tipple No.4001",
+ "handle": "wallco-4001",
+ "image_url": "/designs/img/1778706571763_1580835151.png",
+ "filename": "1778706571763_1580835151.png",
+ "generator": "wallco.ai",
+ "seed": 1580835151,
+ "is_published": false,
+ "created_at": "2026-05-13T21:09:40.313735+00:00",
+ "motifs": [],
+ "user_removed": false,
+ "user_color_good": null,
+ "user_style_good": null,
+ "user_scale_good": null,
+ "user_stars": null,
+ "room_mockups": []
+ },
+ {
+ "id": 4002,
+ "kind": "seamless_tile",
+ "category": "stoned-animals",
+ "dominant_hex": "#596b64",
+ "saturation": 0.092,
+ "title": "Marine Stardust No.4002",
+ "handle": "wallco-4002",
+ "image_url": "/designs/img/1778706571763_1200344699.png",
+ "filename": "1778706571763_1200344699.png",
+ "generator": "wallco.ai",
+ "seed": 1200344699,
+ "is_published": false,
+ "created_at": "2026-05-13T21:09:43.483698+00:00",
+ "motifs": [],
+ "user_removed": false,
+ "user_color_good": null,
+ "user_style_good": null,
+ "user_scale_good": null,
+ "user_stars": null,
+ "room_mockups": []
}
]
\ No newline at end of file
diff --git a/server.js b/server.js
index 3cd2579..6273943 100644
--- a/server.js
+++ b/server.js
@@ -12808,6 +12808,17 @@ app.get('/api/isolate/:designId', requireAdmin, (req, res) => {
// /commercial/hospitality/healthcare). Uses Gemini-text against the
// design's palette + motif + category. Read-only — no DB writes.
// furnish_route marker.
+// Disk-backed cache for /api/design/:id/furnish. The Gemini-text output
+// for a given (design × context) is stable — same palette + motif + use
+// case → same recommendations. Cache forever; manual purge via deleting
+// data/furnish-cache/<id>-<ctx>.json. Steve's 2026-05-13 cost note: each
+// click was hitting Gemini fresh; this drops repeat cost to zero.
+const FURNISH_CACHE_DIR = path.join(__dirname, 'data', 'furnish-cache');
+fs.mkdirSync(FURNISH_CACHE_DIR, { recursive: true });
+function furnishCachePath(id, ctx) {
+ return path.join(FURNISH_CACHE_DIR, `${id}-${ctx}.json`);
+}
+
app.post('/api/design/:id/furnish', async (req, res) => {
try {
const id = parseInt(req.params.id, 10);
@@ -12816,6 +12827,15 @@ app.post('/api/design/:id/furnish', async (req, res) => {
const validCtx = new Set(['residential','commercial','hospitality','healthcare']);
if (!validCtx.has(ctx)) return res.status(400).json({ ok: false, error: 'bad context' });
+ // Cache hit — skip Gemini, return saved JSON. ?refresh=1 forces re-gen.
+ const cachePath = furnishCachePath(id, ctx);
+ if (req.query.refresh !== '1' && fs.existsSync(cachePath)) {
+ try {
+ const cached = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
+ return res.json({ ...cached, cached: true });
+ } catch (e) { /* fall through to fresh gen on parse error */ }
+ }
+
const d = DESIGNS.find(x => x.id === id);
if (!d) return res.status(404).json({ ok: false, error: 'design not found' });
@@ -12858,7 +12878,14 @@ app.post('/api/design/:id/furnish', async (req, res) => {
const text = j.candidates?.[0]?.content?.parts?.find(p => p.text)?.text || '{}';
let parsed = {};
try { parsed = JSON.parse(text); } catch (e) { return res.status(502).json({ ok: false, error: 'parse failed', text: text.slice(0,200) }); }
- return res.json({ ok: true, design_id: id, context: ctx, categories: parsed.categories || [], note: parsed.note || '' });
+ const payload = { ok: true, design_id: id, context: ctx, categories: parsed.categories || [], note: parsed.note || '' };
+ // Write-through cache (best-effort; ignore disk errors)
+ try {
+ const tmp = cachePath + '.tmp';
+ fs.writeFileSync(tmp, JSON.stringify({ ...payload, generated_at: new Date().toISOString() }, null, 2));
+ fs.renameSync(tmp, cachePath);
+ } catch (e) { console.warn('[furnish-cache] write:', e.message); }
+ return res.json(payload);
} catch (e) {
return res.status(500).json({ ok: false, error: e.message });
}
← 0f93256 design page: add Share — Pinterest (Pin It) + Instagram (dow
·
back to Wallco Ai
·
design page: admin tone-on-tone color-wheel buttons + /api/d e52478f →