dsh-llm-roster 0.5.5 → 0.5.6
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 +4 -4
- package/README.zh-CN.md +3 -3
- package/package.json +2 -2
- package/plugin/server.js +1 -1
- package/plugin/thumbs.js +16 -13
package/README.md
CHANGED
|
@@ -416,10 +416,10 @@ want it:
|
|
|
416
416
|
work too. A picture in a .zip is at most 16 MB, and one you upload at most 4 MB.
|
|
417
417
|
|
|
418
418
|
**Thumbnails.** Where pictures show small (the roster's cards, the album, the summon's cards, the pickers), the roster
|
|
419
|
-
sends a thumbnail 320 wide, made from the picture with [
|
|
419
|
+
sends a thumbnail 320 wide, made from the picture with [@napi-rs/image](https://image.napi.rs) and kept in DSH's home
|
|
420
420
|
(`llm-roster/thumbs/`). A pack's are made in the background as it is added or chosen, any other as it is first shown;
|
|
421
421
|
a picture that changes gets a new one. Her page, the summon's banner and the album's viewer show the picture itself.
|
|
422
|
-
|
|
422
|
+
@napi-rs/image is an optional dependency, installed with the roster where it has a build for the machine (Linux, macOS and
|
|
423
423
|
Windows on x64 and Arm); without it, every picture is sent whole.
|
|
424
424
|
|
|
425
425
|
To make a pack by hand, export one (or start a new empty one, choose pictures and export it), change what you like,
|
|
@@ -489,7 +489,7 @@ its own album (the Earned ✦ are DSH's, in `stars.json`, so every browser share
|
|
|
489
489
|
| [`plugin/client.js`](plugin/client.js) | The web half (React from DSH). It puts the avatar in the `conversation.input.right` slot and renders the roster dialog and the Plugins page. It switches models through `modelDirectories.directoryFor(session).select()`. It writes model lists and `apiKeyEnv` with `remote.settings.mutate`, stores keys with `remote.credentials.set`, and reads catalogs with `remote.llm.discoverModels`. |
|
|
490
490
|
| [`plugin/index.js`](plugin/index.js) | The DSH plugin (`llm-roster`). It mounts the routes on DSH's API, resolves keys through DSH's credential store, then the environment, and watches DSH's model calls (`llm/stream`, passed along untouched) for why they fail. |
|
|
491
491
|
| [`plugin/stars.js`](plugin/stars.js) | The summon's Earned ✦: dropped by model calls that work, spent on summons, added by cheat codes. |
|
|
492
|
-
| [`plugin/thumbs.js`](plugin/thumbs.js) | The pictures' thumbnails, made with
|
|
492
|
+
| [`plugin/thumbs.js`](plugin/thumbs.js) | The pictures' thumbnails, made with @napi-rs/image (when it is installed) and kept in DSH's home. |
|
|
493
493
|
| [`plugin/failures.js`](plugin/failures.js) | Why calls failed lately: kept by provider and model, counted, let go when a call succeeds again. |
|
|
494
494
|
| [`plugin/server.js`](plugin/server.js) | The routes, below. |
|
|
495
495
|
| [`plugin/roster.js`](plugin/roster.js) | Which character and tier each model is, the lineup, the picture and avatar each one wears, the pack in use, set-aside providers, and the provider each character goes through. |
|
|
@@ -537,7 +537,7 @@ The routes are on DSH's API, behind its sign-in and for its owner only:
|
|
|
537
537
|
## Development
|
|
538
538
|
|
|
539
539
|
```sh
|
|
540
|
-
npm install #
|
|
540
|
+
npm install # @napi-rs/image, for the thumbnails (optional: their tests are skipped without it)
|
|
541
541
|
npm test # node --test: the server half, art packs and the summon odds, no DSH or network needed
|
|
542
542
|
python3 tools/import-art.py ~/Projects/DiscordStudyBot # art/ again from a ChimeraArchiveHarness checkout
|
|
543
543
|
python3 tools/draw-art.py --dry-run # the other characters' tiers: what it would draw
|
package/README.zh-CN.md
CHANGED
|
@@ -265,7 +265,7 @@ knights/
|
|
|
265
265
|
|
|
266
266
|
`.webp` 能让立绘包最小(内置卡面每张约 100–450 KB,质量 86);`.png`、`.jpg` 和 `.gif` 也可以。.zip 中的一张图片最大 16 MB,你上传的图片最大 4 MB。
|
|
267
267
|
|
|
268
|
-
**缩略图。** 在图片显示得很小的地方(名册卡片、图鉴、召唤卡片、选择器),名册会发送宽 320 的缩略图,用 [
|
|
268
|
+
**缩略图。** 在图片显示得很小的地方(名册卡片、图鉴、召唤卡片、选择器),名册会发送宽 320 的缩略图,用 [@napi-rs/image](https://image.napi.rs) 从原图生成并保存在 DSH 主目录(`llm-roster/thumbs/`)。立绘包的缩略图会在添加或选择该立绘包时于后台生成,其他图片在首次显示时生成;图片变化后会生成新的缩略图。她的页面、召唤横幅和图鉴查看器会显示原图本身。@napi-rs/image 是可选依赖,在名册有对应机器构建的地方随名册一起安装(Linux、macOS 和 Windows 的 x64 与 Arm);没有它时,每张图片都会原样发送。
|
|
269
269
|
|
|
270
270
|
要手工制作立绘包,可以先导出一个(或新建一个空白立绘包,选择图片后导出),改成你喜欢的样子,重新压缩并导入。要分享立绘包,把它的文件夹放到 GitHub 上;任何人都可以通过链接拉取。你拥有的立绘包保存在 `~/.dsh/llm-roster/packs/<name>/`,你的改动保存在 `~/.dsh/llm-roster/changes/<name>/`;立绘包的文件也可以在那里直接编辑,名册下次打开时会显示这些改动。
|
|
271
271
|
|
|
@@ -322,7 +322,7 @@ knights/
|
|
|
322
322
|
| [`plugin/client.js`](plugin/client.js) | 网页端部分(使用 DSH 的 React)。它把头像放进 `conversation.input.right` 插槽,并渲染名册对话框和“插件”页面。它通过 `modelDirectories.directoryFor(session).select()` 切换模型,通过 `remote.settings.mutate` 写入模型列表和 `apiKeyEnv`,通过 `remote.credentials.set` 保存密钥,并通过 `remote.llm.discoverModels` 读取目录。 |
|
|
323
323
|
| [`plugin/index.js`](plugin/index.js) | DSH 插件(`llm-roster`)。它把路由挂载到 DSH 的 API 上,先通过 DSH 的凭据存储解析密钥,再尝试环境变量,并监视 DSH 的模型调用(`llm/stream`,原样转发)以了解失败原因。 |
|
|
324
324
|
| [`plugin/stars.js`](plugin/stars.js) | 召唤的赚取 ✦:由成功的模型调用掉落,在召唤中扣除,通过作弊码增加。 |
|
|
325
|
-
| [`plugin/thumbs.js`](plugin/thumbs.js) | 图片的缩略图:用
|
|
325
|
+
| [`plugin/thumbs.js`](plugin/thumbs.js) | 图片的缩略图:用 @napi-rs/image(安装时)生成,保存在 DSH 主目录中。 |
|
|
326
326
|
| [`plugin/failures.js`](plugin/failures.js) | 最近调用失败原因:按服务商和模型保存、计数,调用成功后放掉。 |
|
|
327
327
|
| [`plugin/server.js`](plugin/server.js) | 下面的路由。 |
|
|
328
328
|
| [`plugin/roster.js`](plugin/roster.js) | 每个模型对应哪个角色和层级、阵容、每个模型所用的立绘和头像、当前使用的立绘包、搁置的服务商,以及每个角色经哪个服务商使用。 |
|
|
@@ -368,7 +368,7 @@ knights/
|
|
|
368
368
|
## 开发
|
|
369
369
|
|
|
370
370
|
```sh
|
|
371
|
-
npm install #
|
|
371
|
+
npm install # @napi-rs/image,用于缩略图(可选:没有它时相关测试会被跳过)
|
|
372
372
|
npm test # node --test:测试服务端部分、立绘包和召唤概率,不需要 DSH 或网络
|
|
373
373
|
python3 tools/import-art.py ~/Projects/DiscordStudyBot # 从 ChimeraArchiveHarness 检出重新生成 art/
|
|
374
374
|
python3 tools/draw-art.py --dry-run # 其他角色的层级:预览将绘制的内容
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-llm-roster",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"description": "A gacha-style character roster for DeepSeek Harness: an avatar beside the model picker, and a roster of your models as characters, with their art, Artificial Analysis rankings, keys, balance and which models are in use.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT AND CC-BY-NC-SA-4.0",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"test": "node --test"
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
|
-
"
|
|
40
|
+
"@napi-rs/image": "^1.16.0"
|
|
41
41
|
},
|
|
42
42
|
"dsh": {
|
|
43
43
|
"bundle": {
|
package/plugin/server.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// POST /api/llm-roster.unpark { provider } → what was kept of it
|
|
20
20
|
// POST /api/llm-roster.route { character, provider | null }: the provider her lineup goes through
|
|
21
21
|
// GET /api/llm-roster.image ?art=<folder>/<file> (a picture of the pack in use: `deepseek/high.webp`; or of the
|
|
22
|
-
// pack `&pack=<id>`); `&size=thumb`: its thumbnail (320 wide, when
|
|
22
|
+
// pack `&pack=<id>`); `&size=thumb`: its thumbnail (320 wide, when @napi-rs/image is there)
|
|
23
23
|
// GET /api/llm-roster.packs your art packs, the one in use, and the ones that come with the roster
|
|
24
24
|
// POST /api/llm-roster.packs { use: '<id>' } | { create: '<name>' } | { remove: '<id>' } | { discard: '<id>' } |
|
|
25
25
|
// { fetch: '<GitHub or .zip link>' } | { add: '<file>' }: one that comes with the
|
package/plugin/thumbs.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Thumbnails of the art packs' pictures, for what shows them small (the roster's cards, the album, the summon's cards,
|
|
2
|
-
// the pickers); the full pictures are for a character's page, the banner and the viewer. They are made with
|
|
3
|
-
// it is installed (an optional dependency
|
|
2
|
+
// the pickers); the full pictures are for a character's page, the banner and the viewer. They are made with
|
|
3
|
+
// @napi-rs/image, when it is installed (an optional dependency, prebuilt, with no install script: pnpm refuses one that
|
|
4
|
+
// has one; without it, every picture is served whole), kept in DSH's home
|
|
4
5
|
// (llm-roster/thumbs/), each named by its picture's file, size and time, so a changed picture gets a new one; a pack's
|
|
5
6
|
// are made in the background as it is added or chosen, and any other as it is first asked for.
|
|
6
7
|
import crypto from 'node:crypto'
|
|
@@ -11,17 +12,17 @@ export const WIDTH = 320 // wide enough for a card twice over on a sharp screen
|
|
|
11
12
|
const QUALITY = 80
|
|
12
13
|
const KEPT = 2000 // thumbnails kept at most (the oldest go first)
|
|
13
14
|
|
|
14
|
-
let found //
|
|
15
|
-
const
|
|
15
|
+
let found // @napi-rs/image's Transformer, once looked for (null: none)
|
|
16
|
+
const imageModule = async () => {
|
|
16
17
|
if (found === undefined) {
|
|
17
|
-
try { found = (await import('
|
|
18
|
+
try { found = (await import('@napi-rs/image')).Transformer ?? null } catch { found = null }
|
|
18
19
|
}
|
|
19
20
|
return found
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export class Thumbs {
|
|
23
|
-
/** `load`: how
|
|
24
|
-
constructor (folder, { load =
|
|
24
|
+
/** `load`: how the image library is found (for the tests: one that finds none). */
|
|
25
|
+
constructor (folder, { load = imageModule } = {}) {
|
|
25
26
|
this.root = folder ? path.join(folder, 'thumbs') : null
|
|
26
27
|
this.load = load
|
|
27
28
|
this.making = new Map() // a thumbnail's file → its making
|
|
@@ -30,8 +31,8 @@ export class Thumbs {
|
|
|
30
31
|
this.warming = false
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
/** The thumbnail's file for a picture's (`file`): made now if need be; null to serve the picture itself (no
|
|
34
|
-
* picture already small, an animated GIF, or one it cannot read). */
|
|
34
|
+
/** The thumbnail's file for a picture's (`file`): made now if need be; null to serve the picture itself (no image
|
|
35
|
+
* library, a picture already small, an animated GIF, or one it cannot read). */
|
|
35
36
|
async of (file) {
|
|
36
37
|
if (!this.root || !file || /\.gif$/i.test(file) || this.whole.has(file)) return null
|
|
37
38
|
let stat
|
|
@@ -41,13 +42,15 @@ export class Thumbs {
|
|
|
41
42
|
if (fs.existsSync(target)) return target
|
|
42
43
|
if (this.making.has(target)) return this.making.get(target)
|
|
43
44
|
const making = (async () => {
|
|
44
|
-
const
|
|
45
|
-
if (!
|
|
46
|
-
const
|
|
45
|
+
const Transformer = await this.load()
|
|
46
|
+
if (!Transformer) return null
|
|
47
|
+
const bytes = fs.readFileSync(file)
|
|
48
|
+
const about = await new Transformer(bytes).metadata()
|
|
47
49
|
if (!about.width || about.width <= WIDTH) { this.whole.add(file); return null }
|
|
50
|
+
const small = await new Transformer(bytes).resize(WIDTH).webp(QUALITY)
|
|
48
51
|
fs.mkdirSync(this.root, { recursive: true })
|
|
49
52
|
const temporary = `${target}.${process.pid}.tmp`
|
|
50
|
-
|
|
53
|
+
fs.writeFileSync(temporary, small)
|
|
51
54
|
fs.renameSync(temporary, target)
|
|
52
55
|
return target
|
|
53
56
|
})().catch(() => null).finally(() => this.making.delete(target))
|