dsh-background-by-model 0.3.2 → 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 +16 -3
- package/README.zh.md +16 -3
- package/lib/client.js +279 -199
- package/lib/client.js.map +1 -1
- package/lib/index.js +158 -100
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,75 +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
|
-
rightbar: 0,
|
|
54
|
-
input: 0
|
|
55
|
-
},
|
|
56
|
-
settingsOpacity: 1,
|
|
57
|
-
chatTextOpacity: 0,
|
|
58
|
-
trajectoryOpacity: 1,
|
|
59
|
-
rightbarOpacity: null
|
|
25
|
+
const DEFAULT_PART_OPACITIES = {
|
|
26
|
+
bg: .85,
|
|
27
|
+
sidebar: .93,
|
|
28
|
+
card: 1,
|
|
29
|
+
input: 1
|
|
60
30
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
72
61
|
};
|
|
62
|
+
/** Slot names reach the filesystem, so they are strictly whitelisted. */
|
|
63
|
+
const SLOT_RE = /^[A-Za-z0-9_-]{1,32}$/;
|
|
73
64
|
function clamp(n, lo, hi, def) {
|
|
74
65
|
return typeof n === "number" && isFinite(n) ? Math.min(hi, Math.max(lo, n)) : def;
|
|
75
66
|
}
|
|
@@ -84,31 +75,29 @@ function normalizeBgState(s) {
|
|
|
84
75
|
ih: typeof v.ih === "number" && v.ih > 0 ? v.ih : 0
|
|
85
76
|
};
|
|
86
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
|
+
}
|
|
87
88
|
/** Coerce one persisted rule, or null when it lacks a usable id/slot. */
|
|
88
89
|
function normalizeRule(raw) {
|
|
89
90
|
const r = raw ?? {};
|
|
90
91
|
const id = typeof r.id === "string" && r.id !== "" ? r.id : null;
|
|
91
92
|
const slot = typeof r.slot === "string" && SLOT_RE.test(r.slot) ? r.slot : null;
|
|
92
93
|
if (id === null || slot === null) return null;
|
|
93
|
-
const
|
|
94
|
-
const color = Array.isArray(c) && c.length === 3 && c.every((n) => typeof n === "number" && isFinite(n)) ? [
|
|
95
|
-
clamp(c[0], 0, 360, 220),
|
|
96
|
-
clamp(c[1], 0, 1, .55),
|
|
97
|
-
clamp(c[2], 0, 1, .25)
|
|
98
|
-
] : null;
|
|
99
|
-
const mode = [
|
|
100
|
-
"fit",
|
|
101
|
-
"fill",
|
|
102
|
-
"stretch",
|
|
103
|
-
"tile",
|
|
104
|
-
"center"
|
|
105
|
-
].includes(r.bgMode) ? r.bgMode : "fit";
|
|
94
|
+
const mode = BG_MODES.includes(r.bgMode) ? r.bgMode : "fit";
|
|
106
95
|
return {
|
|
107
96
|
id,
|
|
108
97
|
slot,
|
|
109
98
|
match: typeof r.match === "string" ? r.match : "",
|
|
110
99
|
enabled: r.enabled !== false,
|
|
111
|
-
color,
|
|
100
|
+
color: normalizeHsl(r.color),
|
|
112
101
|
bgMode: mode,
|
|
113
102
|
wallpaperOpacity: clamp01(r.wallpaperOpacity, 1),
|
|
114
103
|
blur: clamp(r.blur, 0, 60, 0),
|
|
@@ -122,31 +111,66 @@ function normalizeConfig(raw) {
|
|
|
122
111
|
const ops = r.opacities ?? {};
|
|
123
112
|
const bl = r.blurs ?? {};
|
|
124
113
|
const blurs = {};
|
|
125
|
-
for (const k of [
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
"card",
|
|
129
|
-
"settings",
|
|
130
|
-
"chat",
|
|
131
|
-
"trajectory",
|
|
132
|
-
"rightbar",
|
|
133
|
-
"input"
|
|
134
|
-
]) 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]);
|
|
135
117
|
return {
|
|
136
118
|
rules,
|
|
137
|
-
opacities
|
|
138
|
-
bg: clamp01(ops.bg, DEFAULT_CONFIG.opacities.bg),
|
|
139
|
-
sidebar: clamp01(ops.sidebar, DEFAULT_CONFIG.opacities.sidebar),
|
|
140
|
-
card: clamp01(ops.card, DEFAULT_CONFIG.opacities.card),
|
|
141
|
-
input: clamp01(ops.input, DEFAULT_CONFIG.opacities.input)
|
|
142
|
-
},
|
|
119
|
+
opacities,
|
|
143
120
|
blurs,
|
|
144
|
-
settingsOpacity: clamp01(r.settingsOpacity,
|
|
145
|
-
chatTextOpacity: clamp01(r.chatTextOpacity,
|
|
146
|
-
trajectoryOpacity: clamp01(r.trajectoryOpacity,
|
|
147
|
-
rightbarOpacity: typeof r.rightbarOpacity === "number" && isFinite(r.rightbarOpacity) ? clamp01(r.rightbarOpacity, 1) : null
|
|
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
|
|
148
126
|
};
|
|
149
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
|
+
};
|
|
150
174
|
/**
|
|
151
175
|
* One-shot migration of the pre-0.3 store.
|
|
152
176
|
*
|
|
@@ -216,10 +240,44 @@ async function readConfig() {
|
|
|
216
240
|
}
|
|
217
241
|
return normalizeConfig(await migrateLegacy(raw));
|
|
218
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
|
+
}
|
|
219
275
|
async function writeConfig(config) {
|
|
220
276
|
await ensureDir();
|
|
221
277
|
try {
|
|
222
|
-
|
|
278
|
+
const normalized = normalizeConfig(config);
|
|
279
|
+
warnUnknownConfigKeys(config, normalized);
|
|
280
|
+
await writeFile(configPath(), JSON.stringify(normalized, null, 2), "utf8");
|
|
223
281
|
return true;
|
|
224
282
|
} catch (e) {
|
|
225
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",
|