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