dsh-background-by-model 0.3.1 → 0.4.0
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/README.md +255 -236
- package/README.zh.md +255 -236
- package/lib/client.js +343 -188
- package/lib/client.js.map +1 -1
- package/lib/index.js +160 -97
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,73 +1,66 @@
|
|
|
1
1
|
import { access, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dshHomePath } from "@deepseek-ai/dsh-home-paths";
|
|
3
|
-
//#region src/
|
|
3
|
+
//#region src/schema.ts
|
|
4
|
+
/** Every accepted `bgMode`, in UI order. */
|
|
5
|
+
const BG_MODES = [
|
|
6
|
+
"fit",
|
|
7
|
+
"fill",
|
|
8
|
+
"stretch",
|
|
9
|
+
"tile",
|
|
10
|
+
"center"
|
|
11
|
+
];
|
|
4
12
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* theme-config.json the ordered rule list + the global interface settings
|
|
12
|
-
* modelbg-<slot> one background image per rule (raw bytes, no extension;
|
|
13
|
-
* the MIME is sniffed from the magic bytes when served)
|
|
14
|
-
*
|
|
15
|
-
* Images travel as data URLs over the RPC channel — there is no separate HTTP
|
|
16
|
-
* image route. A legacy single-wallpaper / video-background store is migrated
|
|
17
|
-
* to rule 1 on first read.
|
|
13
|
+
* Main interface opacities (0..1), global (Interface page):
|
|
14
|
+
* `bg` main background (`--dsw-alias-bg-base`)
|
|
15
|
+
* `sidebar` sidebar (`--dsw-specific-sidebar-fill`)
|
|
16
|
+
* `card` cards/panels (`--dsw-alias-bg-layer-1/2/3`, `--dsw-specific-menu`)
|
|
17
|
+
* `input` input/control surfaces (`--dsw-specific-input-major`)
|
|
18
18
|
*/
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const LEGACY_WALLPAPER = "wallpaper.jpg";
|
|
25
|
-
const LEGACY_FILES = [
|
|
26
|
-
"wallpaper.mp4",
|
|
27
|
-
"wallpaper.webm",
|
|
28
|
-
"wallpaper.ogv",
|
|
29
|
-
"wallpaper.mov",
|
|
30
|
-
"wallpaper.mkv",
|
|
31
|
-
"wallpaper.video",
|
|
32
|
-
"wallpaper.upload.tmp"
|
|
19
|
+
const PART_OPACITY_KEYS = [
|
|
20
|
+
"bg",
|
|
21
|
+
"sidebar",
|
|
22
|
+
"card",
|
|
23
|
+
"input"
|
|
33
24
|
];
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
rules: [],
|
|
40
|
-
opacities: {
|
|
41
|
-
bg: .85,
|
|
42
|
-
sidebar: .93,
|
|
43
|
-
card: 1,
|
|
44
|
-
input: 1
|
|
45
|
-
},
|
|
46
|
-
blurs: {
|
|
47
|
-
bg: 0,
|
|
48
|
-
sidebar: 0,
|
|
49
|
-
card: 0,
|
|
50
|
-
settings: 0,
|
|
51
|
-
chat: 0,
|
|
52
|
-
trajectory: 0,
|
|
53
|
-
input: 0
|
|
54
|
-
},
|
|
55
|
-
settingsOpacity: 1,
|
|
56
|
-
chatTextOpacity: 0,
|
|
57
|
-
trajectoryOpacity: 1
|
|
25
|
+
const DEFAULT_PART_OPACITIES = {
|
|
26
|
+
bg: .85,
|
|
27
|
+
sidebar: .93,
|
|
28
|
+
card: 1,
|
|
29
|
+
input: 1
|
|
58
30
|
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Interface blur (px, 0..60), global (Interface page):
|
|
33
|
+
* `bg` main background (AppFrame grid)
|
|
34
|
+
* `sidebar` sidebar column
|
|
35
|
+
* `card` cards/panels (center + details columns, dialog option panels)
|
|
36
|
+
* `settings` settings panel
|
|
37
|
+
* `chat` conversation text region (message column of the chat view)
|
|
38
|
+
* `trajectory` trajectory view surface
|
|
39
|
+
* `rightbar` file-preview panel — the right sidebar a file click opens
|
|
40
|
+
* `input` input/control surfaces ([data-composer-card], [data-cordis-panel])
|
|
41
|
+
*/
|
|
42
|
+
const PART_BLUR_KEYS = [
|
|
43
|
+
"bg",
|
|
44
|
+
"sidebar",
|
|
45
|
+
"card",
|
|
46
|
+
"settings",
|
|
47
|
+
"chat",
|
|
48
|
+
"trajectory",
|
|
49
|
+
"rightbar",
|
|
50
|
+
"input"
|
|
51
|
+
];
|
|
52
|
+
const DEFAULT_PART_BLURS = {
|
|
53
|
+
bg: 0,
|
|
54
|
+
sidebar: 0,
|
|
55
|
+
card: 0,
|
|
56
|
+
settings: 0,
|
|
57
|
+
chat: 0,
|
|
58
|
+
trajectory: 0,
|
|
59
|
+
rightbar: 0,
|
|
60
|
+
input: 0
|
|
70
61
|
};
|
|
62
|
+
/** Slot names reach the filesystem, so they are strictly whitelisted. */
|
|
63
|
+
const SLOT_RE = /^[A-Za-z0-9_-]{1,32}$/;
|
|
71
64
|
function clamp(n, lo, hi, def) {
|
|
72
65
|
return typeof n === "number" && isFinite(n) ? Math.min(hi, Math.max(lo, n)) : def;
|
|
73
66
|
}
|
|
@@ -82,31 +75,29 @@ function normalizeBgState(s) {
|
|
|
82
75
|
ih: typeof v.ih === "number" && v.ih > 0 ? v.ih : 0
|
|
83
76
|
};
|
|
84
77
|
}
|
|
78
|
+
/** One `[h, s, l]` triple, or null when the value is not a complete finite triple. */
|
|
79
|
+
function normalizeHsl(raw) {
|
|
80
|
+
if (!Array.isArray(raw) || raw.length !== 3) return null;
|
|
81
|
+
if (!raw.every((n) => typeof n === "number" && isFinite(n))) return null;
|
|
82
|
+
return [
|
|
83
|
+
clamp(raw[0], 0, 360, 220),
|
|
84
|
+
clamp(raw[1], 0, 1, .55),
|
|
85
|
+
clamp(raw[2], 0, 1, .25)
|
|
86
|
+
];
|
|
87
|
+
}
|
|
85
88
|
/** Coerce one persisted rule, or null when it lacks a usable id/slot. */
|
|
86
89
|
function normalizeRule(raw) {
|
|
87
90
|
const r = raw ?? {};
|
|
88
91
|
const id = typeof r.id === "string" && r.id !== "" ? r.id : null;
|
|
89
92
|
const slot = typeof r.slot === "string" && SLOT_RE.test(r.slot) ? r.slot : null;
|
|
90
93
|
if (id === null || slot === null) return null;
|
|
91
|
-
const
|
|
92
|
-
const color = Array.isArray(c) && c.length === 3 && c.every((n) => typeof n === "number" && isFinite(n)) ? [
|
|
93
|
-
clamp(c[0], 0, 360, 220),
|
|
94
|
-
clamp(c[1], 0, 1, .55),
|
|
95
|
-
clamp(c[2], 0, 1, .25)
|
|
96
|
-
] : null;
|
|
97
|
-
const mode = [
|
|
98
|
-
"fit",
|
|
99
|
-
"fill",
|
|
100
|
-
"stretch",
|
|
101
|
-
"tile",
|
|
102
|
-
"center"
|
|
103
|
-
].includes(r.bgMode) ? r.bgMode : "fit";
|
|
94
|
+
const mode = BG_MODES.includes(r.bgMode) ? r.bgMode : "fit";
|
|
104
95
|
return {
|
|
105
96
|
id,
|
|
106
97
|
slot,
|
|
107
98
|
match: typeof r.match === "string" ? r.match : "",
|
|
108
99
|
enabled: r.enabled !== false,
|
|
109
|
-
color,
|
|
100
|
+
color: normalizeHsl(r.color),
|
|
110
101
|
bgMode: mode,
|
|
111
102
|
wallpaperOpacity: clamp01(r.wallpaperOpacity, 1),
|
|
112
103
|
blur: clamp(r.blur, 0, 60, 0),
|
|
@@ -120,29 +111,66 @@ function normalizeConfig(raw) {
|
|
|
120
111
|
const ops = r.opacities ?? {};
|
|
121
112
|
const bl = r.blurs ?? {};
|
|
122
113
|
const blurs = {};
|
|
123
|
-
for (const k of [
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
"card",
|
|
127
|
-
"settings",
|
|
128
|
-
"chat",
|
|
129
|
-
"trajectory",
|
|
130
|
-
"input"
|
|
131
|
-
]) blurs[k] = clamp(bl[k], 0, 60, DEFAULT_CONFIG.blurs[k]);
|
|
114
|
+
for (const k of PART_BLUR_KEYS) blurs[k] = clamp(bl[k], 0, 60, DEFAULT_PART_BLURS[k]);
|
|
115
|
+
const opacities = {};
|
|
116
|
+
for (const k of PART_OPACITY_KEYS) opacities[k] = clamp01(ops[k], DEFAULT_PART_OPACITIES[k]);
|
|
132
117
|
return {
|
|
133
118
|
rules,
|
|
134
|
-
opacities
|
|
135
|
-
bg: clamp01(ops.bg, DEFAULT_CONFIG.opacities.bg),
|
|
136
|
-
sidebar: clamp01(ops.sidebar, DEFAULT_CONFIG.opacities.sidebar),
|
|
137
|
-
card: clamp01(ops.card, DEFAULT_CONFIG.opacities.card),
|
|
138
|
-
input: clamp01(ops.input, DEFAULT_CONFIG.opacities.input)
|
|
139
|
-
},
|
|
119
|
+
opacities,
|
|
140
120
|
blurs,
|
|
141
|
-
settingsOpacity: clamp01(r.settingsOpacity,
|
|
142
|
-
chatTextOpacity: clamp01(r.chatTextOpacity,
|
|
143
|
-
trajectoryOpacity: clamp01(r.trajectoryOpacity,
|
|
121
|
+
settingsOpacity: clamp01(r.settingsOpacity, 1),
|
|
122
|
+
chatTextOpacity: clamp01(r.chatTextOpacity, 0),
|
|
123
|
+
trajectoryOpacity: clamp01(r.trajectoryOpacity, 1),
|
|
124
|
+
rightbarOpacity: typeof r.rightbarOpacity === "number" && isFinite(r.rightbarOpacity) ? clamp01(r.rightbarOpacity, 1) : null,
|
|
125
|
+
autoExtract: r.autoExtract !== false
|
|
144
126
|
};
|
|
145
127
|
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/index.ts
|
|
130
|
+
/**
|
|
131
|
+
* Node half of dsh-background-by-model: file-backed rule persistence.
|
|
132
|
+
*
|
|
133
|
+
* Owns the `~/.dsh/.dsh-background-by-model-data/` store and exposes a small RPC
|
|
134
|
+
* surface on the dedicated `/dsh-background-by-model` channel (never the shared
|
|
135
|
+
* `/api`, so slash commands stay intact).
|
|
136
|
+
*
|
|
137
|
+
* theme-config.json the ordered rule list + the global interface settings
|
|
138
|
+
* modelbg-<slot> one background image per rule (raw bytes, no extension;
|
|
139
|
+
* the MIME is sniffed from the magic bytes when served)
|
|
140
|
+
*
|
|
141
|
+
* Images travel as data URLs over the RPC channel — there is no separate HTTP
|
|
142
|
+
* image route. A legacy single-wallpaper / video-background store is migrated
|
|
143
|
+
* to rule 1 on first read.
|
|
144
|
+
*/
|
|
145
|
+
const name = "dsh-background-by-model";
|
|
146
|
+
const inject = ["connection", "webServer"];
|
|
147
|
+
const DATA_DIR = ".dsh-background-by-model-data";
|
|
148
|
+
const CONFIG_FILE = "theme-config.json";
|
|
149
|
+
const IMAGE_PREFIX = "modelbg-";
|
|
150
|
+
const LEGACY_WALLPAPER = "wallpaper.jpg";
|
|
151
|
+
const LEGACY_FILES = [
|
|
152
|
+
"wallpaper.mp4",
|
|
153
|
+
"wallpaper.webm",
|
|
154
|
+
"wallpaper.ogv",
|
|
155
|
+
"wallpaper.mov",
|
|
156
|
+
"wallpaper.mkv",
|
|
157
|
+
"wallpaper.video",
|
|
158
|
+
"wallpaper.upload.tmp"
|
|
159
|
+
];
|
|
160
|
+
const WALLPAPER_FETCH_MAX = 26214400;
|
|
161
|
+
const WALLPAPER_FETCH_TIMEOUT = 2e4;
|
|
162
|
+
const dataDir = () => dshHomePath(DATA_DIR);
|
|
163
|
+
const configPath = () => dshHomePath(DATA_DIR, CONFIG_FILE);
|
|
164
|
+
const legacyWallpaperPath = () => dshHomePath(DATA_DIR, LEGACY_WALLPAPER);
|
|
165
|
+
const imagePath = (slot) => dshHomePath(DATA_DIR, `${IMAGE_PREFIX}${slot}`);
|
|
166
|
+
const exists = async (p) => {
|
|
167
|
+
try {
|
|
168
|
+
await access(p);
|
|
169
|
+
return true;
|
|
170
|
+
} catch {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
146
174
|
/**
|
|
147
175
|
* One-shot migration of the pre-0.3 store.
|
|
148
176
|
*
|
|
@@ -185,7 +213,8 @@ async function migrateLegacy(raw) {
|
|
|
185
213
|
blurs: r.blurs,
|
|
186
214
|
settingsOpacity: r.settingsOpacity,
|
|
187
215
|
chatTextOpacity: r.chatTextOpacity,
|
|
188
|
-
trajectoryOpacity: r.trajectoryOpacity
|
|
216
|
+
trajectoryOpacity: r.trajectoryOpacity,
|
|
217
|
+
rightbarOpacity: r.rightbarOpacity
|
|
189
218
|
};
|
|
190
219
|
try {
|
|
191
220
|
await writeFile(configPath(), JSON.stringify(next, null, 2), "utf8");
|
|
@@ -211,10 +240,44 @@ async function readConfig() {
|
|
|
211
240
|
}
|
|
212
241
|
return normalizeConfig(await migrateLegacy(raw));
|
|
213
242
|
}
|
|
243
|
+
const LEGACY_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
244
|
+
"color",
|
|
245
|
+
"bgMode",
|
|
246
|
+
"wallpaperOpacity",
|
|
247
|
+
"blur",
|
|
248
|
+
"bgState"
|
|
249
|
+
]);
|
|
250
|
+
const warnedConfigKeys = /* @__PURE__ */ new Set();
|
|
251
|
+
function warnUnknownConfigKeys(raw, normalized) {
|
|
252
|
+
if (raw === null || typeof raw !== "object") return;
|
|
253
|
+
const r = raw;
|
|
254
|
+
const warned = (id) => {
|
|
255
|
+
if (warnedConfigKeys.has(id)) return;
|
|
256
|
+
warnedConfigKeys.add(id);
|
|
257
|
+
console.warn(`dsh-background-by-model: ignoring unknown config field "${id}" (declared in one half only?)`);
|
|
258
|
+
};
|
|
259
|
+
const known = new Set(Object.keys(normalized));
|
|
260
|
+
for (const key of Object.keys(r)) if (!known.has(key) && !LEGACY_CONFIG_KEYS.has(key)) warned(key);
|
|
261
|
+
const groups = [["blurs", normalized.blurs], ["opacities", normalized.opacities]];
|
|
262
|
+
for (const [group, have] of groups) {
|
|
263
|
+
const got = r[group];
|
|
264
|
+
if (got === null || typeof got !== "object") continue;
|
|
265
|
+
const keys = new Set(Object.keys(have));
|
|
266
|
+
for (const key of Object.keys(got)) if (!keys.has(key)) warned(`${group}.${key}`);
|
|
267
|
+
}
|
|
268
|
+
const sent = Array.isArray(r.rules) ? r.rules[0] : void 0;
|
|
269
|
+
const have = normalized.rules[0];
|
|
270
|
+
if (sent !== null && typeof sent === "object" && have !== void 0) {
|
|
271
|
+
const keys = new Set(Object.keys(have));
|
|
272
|
+
for (const key of Object.keys(sent)) if (!keys.has(key)) warned(`rules[].${key}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
214
275
|
async function writeConfig(config) {
|
|
215
276
|
await ensureDir();
|
|
216
277
|
try {
|
|
217
|
-
|
|
278
|
+
const normalized = normalizeConfig(config);
|
|
279
|
+
warnUnknownConfigKeys(config, normalized);
|
|
280
|
+
await writeFile(configPath(), JSON.stringify(normalized, null, 2), "utf8");
|
|
218
281
|
return true;
|
|
219
282
|
} catch (e) {
|
|
220
283
|
console.error(`dsh-background-by-model: failed to write "${CONFIG_FILE}"`, e);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-background-by-model",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Appearance plugin for DeepSeek Harness: an ordered list of model rules, each carrying its own wallpaper, theme color, layout mode, opacity and blur — the background follows the model you switch to.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|