dsh-read-animation 0.1.0 → 0.1.2
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/CHANGELOG.md +169 -103
- package/COMPATIBILITY.md +100 -0
- package/README.md +20 -16
- package/README.zh.md +12 -10
- package/assets/icon.svg +32 -0
- package/lib/admission.js +33 -35
- package/lib/bytes.js +31 -0
- package/lib/display.js +2 -17
- package/lib/index.js +64 -6
- package/lib/model.js +34 -6
- package/lib/pacing.js +217 -0
- package/lib/patch.js +18 -0
- package/lib/read.js +76 -5
- package/package.json +6 -3
- package/scripts/check-dsh-compat.py +121 -0
- package/scripts/preview-sheet.mjs +20 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,103 +1,169 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 0.1.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
0
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
the
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.2 — 2026-11-08
|
|
4
|
+
|
|
5
|
+
- Fix [#7](https://github.com/NeoHotaru/dsh-read-animation/issues/7). Capture
|
|
6
|
+
uploaded GIF and animated WebP bytes at `attachments.saveImages`. DSH 0.2.2
|
|
7
|
+
normalizes uploads before prompt admission, so its stored references previously
|
|
8
|
+
bypassed capture. Original animation display, model frame sheets, and
|
|
9
|
+
normalized-copy reads now use the captured source.
|
|
10
|
+
- Add raw-upload regressions through the real attachment store and, on DSH
|
|
11
|
+
0.2.2, its upload service and receipt resolution. Update the integration fixture
|
|
12
|
+
for the separate native tool executor and batch execution API.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
- **The supported DSH range is measured rather than assumed.** The plugin wraps
|
|
16
|
+
internal service methods, and `engines.dsh` claimed `>=0.1.7-alpha.1` on the
|
|
17
|
+
strength of one working install. The integration suite now runs against
|
|
18
|
+
`0.1.3-alpha.2` (the oldest release exposing every member), `0.1.7-alpha.2`,
|
|
19
|
+
`0.2.0-rc.2` — which is `latest` — and `0.2.1-alpha.3`, all 38/38, and
|
|
20
|
+
`scripts/check-dsh-compat.py` reads the type declarations of all 32 published
|
|
21
|
+
releases to find the floor: **0.1.3-alpha.2**. See
|
|
22
|
+
[COMPATIBILITY.md](COMPATIBILITY.md), including what is *not* claimed.
|
|
23
|
+
- **A host missing a member now says so.** Nothing checked the members the plugin
|
|
24
|
+
calls before calling them: a host with `readImage` but no `saveImages` mounted
|
|
25
|
+
cleanly and then threw a `TypeError` from inside the tool. Mount reports every
|
|
26
|
+
missing member with the release it arrived in, the tool names the reason in its
|
|
27
|
+
own error, and a test covers the check itself.
|
|
28
|
+
- **`preview-sheet` printed a path that does not exist.** It spelled the store
|
|
29
|
+
layout as `files/<name>` where the store uses `files/<2>/<digest>/<name>`. The
|
|
30
|
+
plugin asks `fileHostPath` and was always right; only the script's second copy of
|
|
31
|
+
the layout could drift.
|
|
32
|
+
- **The integration harness needed `maxPixels` in a request target.** DSH
|
|
33
|
+
0.1.3-alpha.2 rejects a target without it; later releases default it. A test
|
|
34
|
+
artefact, not a compatibility problem, but it meant the suite could not run on
|
|
35
|
+
the floor version at all.
|
|
36
|
+
|
|
37
|
+
## 0.1.1 — 2026-10-14
|
|
38
|
+
|
|
39
|
+
Fixed after the first release, from what a live session reported back.
|
|
40
|
+
|
|
41
|
+
- **`read_animation` follows the path it advertises.** Every image a model receives
|
|
42
|
+
is shown as a normalized copy at `attachments/v1/objects/<2>/<digest>`, and that
|
|
43
|
+
is the path a model reaches for when it wants to look at the picture again. For a
|
|
44
|
+
pasted animation that copy is a single frame, so the tool answered "this picture
|
|
45
|
+
has one frame": true of that file, and useless as an answer. The tool now resolves
|
|
46
|
+
that path to the animation this plugin kept, and reports the file the bytes
|
|
47
|
+
actually came from. A bare `sha256:<digest>` is accepted too.
|
|
48
|
+
- **The sheet says where the animation lives.** Its header ends with the path the
|
|
49
|
+
store keeps the original at, so a model that wants more than the eight samples can
|
|
50
|
+
read that path with explicit frame indices instead of searching the attachment
|
|
51
|
+
store for the file behind the picture it was sent.
|
|
52
|
+
|
|
53
|
+
## 0.1.0 — 2026-10-14
|
|
54
|
+
|
|
55
|
+
First release.
|
|
56
|
+
|
|
57
|
+
Note for anyone publishing a later version, because both releases so far looked like
|
|
58
|
+
failures while they were simply still in flight:
|
|
59
|
+
|
|
60
|
+
- `npm publish` answers `PUT … 202` and prints "Your package is being processed and
|
|
61
|
+
may take a few minutes to become available", then exits 0. For a brand-new package
|
|
62
|
+
name the registry also carries a public `0.0.0-stage` placeholder alongside it.
|
|
63
|
+
Nothing needs approving in either case.
|
|
64
|
+
- Measured on 0.1.1: the version appeared in the packument **2m41s** after the PUT,
|
|
65
|
+
and its tarball became downloadable **7m05s** after it. In between, the packument
|
|
66
|
+
either 404s or lists the version while its tarball still 404s.
|
|
67
|
+
- Check the tarball, not just `npm view versions`, and check it with a GET: a HEAD
|
|
68
|
+
request to the tarball URL answers 404 even once the tarball is live.
|
|
69
|
+
- A mirror lags further. `registry.npmmirror.com` still served 0.1.0 well after
|
|
70
|
+
0.1.1 was live on npmjs.org, and a profile configured against it keeps installing
|
|
71
|
+
the previous version until it syncs.
|
|
72
|
+
|
|
73
|
+
### Fixed after an adversarial audit
|
|
74
|
+
|
|
75
|
+
Four independent reviews (blast radius, plugin code, packaging and lifecycle, then
|
|
76
|
+
an adversarial pass over every high-severity finding) ran before this was
|
|
77
|
+
published. What they found, and what changed:
|
|
78
|
+
|
|
79
|
+
- **The frame sheet's `variantId` was not a content digest.** It was a hand-rolled
|
|
80
|
+
`sha256:filmanim…` string, and the DeepSeek adapter validates persisted ids
|
|
81
|
+
against `/^sha256:[0-9a-f]{64}$/` when it loads its upload index — then swallows
|
|
82
|
+
the failure into an *empty* index. The effect was not a visible error: it
|
|
83
|
+
discarded every other session's cached file id and re-uploaded every image.
|
|
84
|
+
Reproduced against the real exported index class (plugin id: written 1, reloaded
|
|
85
|
+
0; conforming id: 1/1). It is now the SHA-256 of the bytes being returned, which
|
|
86
|
+
also stops one on-disk cache entry from aliasing two different byte streams.
|
|
87
|
+
- **The degraded path was worse than the missing feature.** The store reaches the
|
|
88
|
+
frame it re-encodes through its own `readImage` call, which this plugin wraps, so
|
|
89
|
+
falling back to the store's request read could hand the model the *whole
|
|
90
|
+
animation* where it expects one frame. The request path now suspends the
|
|
91
|
+
animation substitution around that call, and reads its own tiles through the
|
|
92
|
+
unwrapped read. Pinned by an integration check that breaks a stored frame and
|
|
93
|
+
asserts the fallback is exactly one frame.
|
|
94
|
+
- **No decoded-pixel ceiling on the `read_animation` path.** `limitInputPixels`
|
|
95
|
+
was disabled with nothing in its place: a 60 KB file declaring a 9000x9000
|
|
96
|
+
canvas was refused by the deployment's own attachment store and decoded here
|
|
97
|
+
anyway, at about 310 MiB. Both the probe and the frame extraction now carry the
|
|
98
|
+
deployment's `imageLimits.maxImagePixels`.
|
|
99
|
+
- **The sheet cache ignored the size the caller asked for**, so a caller with an
|
|
100
|
+
8 KiB budget could be served a 28 KB sheet built for a 4 MiB one. The target is
|
|
101
|
+
now part of the cache key, and a tile is never larger than a single frame would
|
|
102
|
+
have been for that caller.
|
|
103
|
+
- **The measured header wrap could still be defeated.** A floor of 16 characters
|
|
104
|
+
kept the loop from reaching a budget that fit, so on a narrow canvas it gave up
|
|
105
|
+
and returned ink wider than the sheet. The floor is now low enough to be
|
|
106
|
+
satisfiable, and a canvas too narrow for legible text gets no header at all.
|
|
107
|
+
- **The canvas was composed before it was shrunk**, building a 6156x6306 (about
|
|
108
|
+
111 MiB) image to throw away. Tile size and grid are settled first.
|
|
109
|
+
- **Torn state files could poison the next record.** A crash part way through an
|
|
110
|
+
append left a line with no newline, so the following record concatenated onto it
|
|
111
|
+
and became unreadable too. A torn tail is now detected and compacted away.
|
|
112
|
+
- **The store's bound applied to memory but not to what was read back**, and the
|
|
113
|
+
log was never compacted. Both fixed; the file is rewritten when it grows past
|
|
114
|
+
twice the entries worth keeping.
|
|
115
|
+
- **Teardown was last-writer-wins.** Each install captured whatever was on the
|
|
116
|
+
method, so disposing one left another's patch removed and could reinstall a
|
|
117
|
+
disposed closure as a permanent own property. Three wrappers now share one
|
|
118
|
+
installer that resolves its delegate per call, removes cleanly (deleting an
|
|
119
|
+
inherited property rather than leaving a shadow behind), and reports whether it
|
|
120
|
+
actually took effect — the previous identity check compared a value that proxy
|
|
121
|
+
reads always re-wrap, so it could never detect a failed install.
|
|
122
|
+
- **`npm test` did not run the invariant checks.** The no-`@deepseek-ai`-import
|
|
123
|
+
rule and the bundle/manifest consistency check lived only in the integration
|
|
124
|
+
harness, which prints SKIP and exits 0 without `DSH_HARNESS_MODULES`. Both now
|
|
125
|
+
run in `npm test`, which is also the `prepublishOnly` gate.
|
|
126
|
+
- **`files[]` omitted `scripts/`** while `package.json` advertised two npm scripts
|
|
127
|
+
that live there, so the published package's own scripts were broken.
|
|
128
|
+
|
|
129
|
+
- **A pasted animation keeps playing in the UI, and the message is left alone.**
|
|
130
|
+
The browser already uploads an animated GIF/WebP byte-for-byte and labelled as
|
|
131
|
+
such; the Host flattens it to a single
|
|
132
|
+
frame during prompt admission and keeps no copy of the source. The plugin wraps
|
|
133
|
+
`ctx.attachments.admitPromptContent` to capture the frames and the original file
|
|
134
|
+
at that moment, and `ctx.attachments.readImage` so the UI is served the animation
|
|
135
|
+
itself (the browser animates whatever bytes that one read returns). What you send
|
|
136
|
+
is what you send: the chat message is unchanged.
|
|
137
|
+
- **The model now receives a labelled sheet of the frames.** The delivery step is
|
|
138
|
+
`ctx.attachments.readImageRequest(ref, target, signal)`, the method each adapter
|
|
139
|
+
calls to build its own copy of one image. It answers with exactly one image, so
|
|
140
|
+
the frames are composed into a grid chosen to be as square as the sheet budget
|
|
141
|
+
allows — every tile at its native size, with the frame index, duration and start
|
|
142
|
+
time printed under each, and the animation's own facts in a wrapped header. The
|
|
143
|
+
sheet is built once per animation and cached.
|
|
144
|
+
- Rewriting the request from an `llm/stream` listener is **not** used and would not
|
|
145
|
+
work: a loop-built request arrives deep-frozen ("listeners read it, never rewrite
|
|
146
|
+
it"), so that earlier attempt silently did nothing.
|
|
147
|
+
- Captured frames and the original are persisted per `$DSH_HOME` in
|
|
148
|
+
`read-animation/v1/pasted.jsonl`, keyed by the message's attachment id, so a
|
|
149
|
+
session resumed in a new process still displays — and still delivers — an
|
|
150
|
+
animation pasted before the restart.
|
|
151
|
+
- A frame's index and start time ride in its attachment display name
|
|
152
|
+
(`x.gif#frame16@1960ms`), which the harness renders beside the picture once the
|
|
153
|
+
frames are delivered — so the timing never has to be repeated as message text.
|
|
154
|
+
(An earlier version of this feature put a header plus one caption per frame into
|
|
155
|
+
the user's own message; consecutive text parts are concatenated by the UI with
|
|
156
|
+
no separator, so a one-image message became an unreadable wall.)
|
|
157
|
+
- `read_animation` tool: decodes an animated GIF or animated WebP, samples frames
|
|
158
|
+
across the timeline (or by explicit index / instant), commits each frame to the
|
|
159
|
+
attachment store, and returns a header plus one captioned image per frame.
|
|
160
|
+
- Refuses animated PNG by name instead of silently returning its first frame,
|
|
161
|
+
which is what the host image library does with one.
|
|
162
|
+
- Refuses static images and points at `read_image`; answers a single-frame GIF or
|
|
163
|
+
WebP with a note that it is not animated.
|
|
164
|
+
- Discloses decode-time substitutions in the model-facing text: zero frame
|
|
165
|
+
delays treated as 100 ms, missing delays, transparency flattened onto white.
|
|
166
|
+
- The package imports no `@deepseek-ai/*` module at runtime, so it loads both as
|
|
167
|
+
an installed package and as a `link:`ed checkout. The compiled schemas ship as
|
|
168
|
+
literals beside their authoring specs, and the integration harness keeps the
|
|
169
|
+
two in step.
|
package/COMPATIBILITY.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Compatibility
|
|
2
|
+
|
|
3
|
+
## Supported range
|
|
4
|
+
|
|
5
|
+
**DSH 0.1.3-alpha.2 and later.** Upload compatibility rechecked on
|
|
6
|
+
2026-11-08 against DSH 0.2.2-rc.1 and 0.1.7-alpha.2.
|
|
7
|
+
|
|
8
|
+
## What was measured
|
|
9
|
+
|
|
10
|
+
### The integration suite, actually run
|
|
11
|
+
|
|
12
|
+
`test/integration/harness.mjs` boots the real `dsh-fs-local`,
|
|
13
|
+
`dsh-attachment-local`, `dsh-system-prompt` and `dsh-tools` plugins in-process,
|
|
14
|
+
registers the tool, and exercises the paste path through a real `llm.stream`
|
|
15
|
+
dispatch. Pointed at four versions spanning the range:
|
|
16
|
+
|
|
17
|
+
| DSH | checks |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| `0.1.3-alpha.2` — the oldest release with the full API | 38/38 |
|
|
20
|
+
| `0.1.7-alpha.2` — the version this was developed against | 38/38 |
|
|
21
|
+
| `0.2.0-rc.2` — `latest` on that date | 38/38 |
|
|
22
|
+
| `0.2.1-alpha.3` — `alpha` on that date, newest | 38/38 |
|
|
23
|
+
|
|
24
|
+
The table above records the 2026-10-14 baseline. The upload regression checks
|
|
25
|
+
added on 2026-11-08 produced the following results:
|
|
26
|
+
|
|
27
|
+
| DSH | checks |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| `0.1.7-alpha.2` — encoded-prompt and raw image-storage paths | 40/40 |
|
|
30
|
+
| `0.2.2-rc.1` — includes the raw upload service and stored-reference admission | 41/41 |
|
|
31
|
+
|
|
32
|
+
DSH 0.2.2 normalizes uploaded images before prompt admission. Capture now wraps
|
|
33
|
+
`saveImages`, which receives raw bytes for both upload and encoded-prompt paths.
|
|
34
|
+
The regressions check GIF and animated WebP display, frame sheets, and reads of
|
|
35
|
+
normalized-copy paths. The 0.2.2 case also calls the real
|
|
36
|
+
`FileUploads.uploadImageStream` and resolves its receipt into a stored image
|
|
37
|
+
reference. The test harness mounts the native tool executor on hosts that expose
|
|
38
|
+
that separate service.
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
DSH_HARNESS_MODULES=/path/to/dsh/node_modules node test/integration/harness.mjs
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### The API surface, read from every published release
|
|
45
|
+
|
|
46
|
+
`scripts/check-dsh-compat.py` downloads the type declarations of
|
|
47
|
+
`@deepseek-ai/dsh-attachment` and `@deepseek-ai/dsh-attachment-local` for every
|
|
48
|
+
published DSH version and reports which of the members this plugin calls are
|
|
49
|
+
present. DSH publishes its whole monorepo at one version, so `dsh@X` pins
|
|
50
|
+
`dsh-attachment-local@X`.
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
python scripts/check-dsh-compat.py
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The result over all 32 published releases: every member is present from
|
|
57
|
+
**0.1.3-alpha.2** onward and absent before it. The members, and the release each
|
|
58
|
+
first appears in:
|
|
59
|
+
|
|
60
|
+
| member | since |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| `saveImages` | `0.1.0-rc.7` |
|
|
63
|
+
| `readImageRequest` | `0.1.2-alpha.3` |
|
|
64
|
+
| `readFileStream` | `0.1.2-alpha.3` |
|
|
65
|
+
| `saveFile` | `0.1.3-alpha.2` |
|
|
66
|
+
| `fileHostPath` | `0.1.3-alpha.2` — optional; the plugin works without it |
|
|
67
|
+
|
|
68
|
+
So the floor is set by `saveFile`, which is what keeps the original animation's
|
|
69
|
+
bytes for the sheet, and by `fileHostPath`. Below `0.1.3-alpha.2` the plugin still
|
|
70
|
+
mounts and still reads animations from files; pasted animations degrade to a
|
|
71
|
+
single frame, and it says which member is missing and when that member arrived.
|
|
72
|
+
|
|
73
|
+
## What is not claimed
|
|
74
|
+
|
|
75
|
+
- **Four of the seventeen releases in the range were executed.** Every release in
|
|
76
|
+
the range was checked to expose the full member list, but a release whose types
|
|
77
|
+
are complete can still behave differently. The four points span floor to ceiling.
|
|
78
|
+
- **The upper bound is a date, not a guarantee.** This plugin wraps
|
|
79
|
+
`saveImages`, `readImage`, and `readImageRequest` — internal service
|
|
80
|
+
methods. A DSH release that changes their signatures or the shape of a request
|
|
81
|
+
target will break it, and nothing here can detect that in advance.
|
|
82
|
+
- **The `engines.dsh` field is documentation.** npm does not enforce a
|
|
83
|
+
non-standard `engines` key, and DSH does not read it. It records the measured
|
|
84
|
+
floor; it does not stop an install.
|
|
85
|
+
|
|
86
|
+
## Re-measuring after a DSH release
|
|
87
|
+
|
|
88
|
+
1. `python scripts/check-dsh-compat.py` — does the new release still expose
|
|
89
|
+
everything?
|
|
90
|
+
2. Install it somewhere and run the suite against it:
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
mkdir /tmp/dsh-probe && cd /tmp/dsh-probe
|
|
94
|
+
npm init -y && npm i @deepseek-ai/dsh@<version>
|
|
95
|
+
cd <this repo>
|
|
96
|
+
DSH_HARNESS_MODULES=/tmp/dsh-probe/node_modules node test/integration/harness.mjs
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
3. If it fails, the check that fails names the method. The messages raised at mount
|
|
100
|
+
and from the tool also name the missing member and the release it arrived in.
|
package/README.md
CHANGED
|
@@ -52,13 +52,13 @@ the chunk stream and refuses it by name instead of returning frame 0.
|
|
|
52
52
|
paste handler hands the clipboard's own `File` straight to the upload path, and
|
|
53
53
|
that path encodes `image/gif` and `image/webp` unchanged (`imageMediaType()`
|
|
54
54
|
accepts both and returns them as-is) — no canvas, no `drawImage`, no `toBlob`.
|
|
55
|
-
**The bytes reach the Host whole.**
|
|
56
|
-
`
|
|
57
|
-
|
|
55
|
+
**The bytes reach the Host whole.** The Host normalizes them through
|
|
56
|
+
`attachments.saveImages`. In DSH 0.2.2 this happens during upload, so prompt
|
|
57
|
+
admission receives an already-stored image reference.
|
|
58
58
|
|
|
59
|
-
`
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
The plugin wraps `saveImages`, captures the animation from its raw inputs, and
|
|
60
|
+
stores the original and decoded frames against the returned image reference.
|
|
61
|
+
This covers raw uploads and older encoded-prompt admission:
|
|
62
62
|
|
|
63
63
|
```
|
|
64
64
|
you paste 1 animation
|
|
@@ -143,22 +143,26 @@ the gaps are gaps.
|
|
|
143
143
|
|
|
144
144
|
## Install
|
|
145
145
|
|
|
146
|
+
Needs **DSH 0.1.3-alpha.2 or later** — see [COMPATIBILITY.md](COMPATIBILITY.md) for
|
|
147
|
+
the measured range, how it was measured, and what is not claimed.
|
|
148
|
+
|
|
146
149
|
```sh
|
|
147
150
|
dsh plugin --profile web add dsh-read-animation
|
|
148
151
|
```
|
|
149
152
|
|
|
150
|
-
Replace `web` with your profile name.
|
|
151
|
-
|
|
153
|
+
Replace `web` with your profile name. Both routes are verified in a real profile:
|
|
154
|
+
the install lands, `dsh plugin add` registers the bundle, and the `sharp` native
|
|
155
|
+
binary resolves from the package's own `node_modules`.
|
|
152
156
|
|
|
153
157
|
```sh
|
|
154
|
-
dsh plugin --profile web add
|
|
158
|
+
dsh plugin --profile web add dsh-read-animation # npm
|
|
159
|
+
dsh plugin --profile web add github:NeoHotaru/dsh-read-animation#v0.1.0 # a tagged commit
|
|
155
160
|
```
|
|
156
161
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
exists.)
|
|
162
|
+
The git route copies the whole repository, tests and fixtures included, which is
|
|
163
|
+
why npm is the nicer install. (A brand-new package name goes through npm's staged
|
|
164
|
+
publishing on its first release, so the version you install may briefly not exist
|
|
165
|
+
yet while the registry processes it.)
|
|
162
166
|
|
|
163
167
|
Or from a checkout:
|
|
164
168
|
|
|
@@ -238,8 +242,8 @@ tool refuses to run when the resolved model route does not declare image input,
|
|
|
238
242
|
for the same reason `read_image` does.
|
|
239
243
|
|
|
240
244
|
**A pasted animation** is handled in three steps, none of which touch your
|
|
241
|
-
message: **capture** at
|
|
242
|
-
|
|
245
|
+
message: **capture** at image storage (`ctx.attachments.saveImages` receives the
|
|
246
|
+
original bytes), **display** at `ctx.attachments.readImage` (the
|
|
243
247
|
one read the UI makes, served the original so it plays), and **delivery** at
|
|
244
248
|
`ctx.attachments.readImageRequest` (the model's copy, composed into one labelled
|
|
245
249
|
sheet). All three are **monkey-patches of core service methods**, so they do
|
package/README.zh.md
CHANGED
|
@@ -47,10 +47,11 @@ APNG 得的是同一种病,而且更隐蔽:libvips 8.18.7 根本不认 `acTL
|
|
|
47
47
|
**入口一 · 贴进聊天框。** 浏览器那段路本身是干净的——粘贴处理器把 `clipboardData` 里的原始
|
|
48
48
|
`File` 直接交出去,上传路径对 `image/gif` / `image/webp` 原样编码(`imageMediaType()` 明确
|
|
49
49
|
接受这两种并原样返回),没有 canvas、没有 `drawImage`、没有 `toBlob`。**字节完好地到达宿主。**
|
|
50
|
-
|
|
50
|
+
宿主通过 `attachments.saveImages` 将图片归一化。DSH 0.2.2 在上传阶段就完成这一步,
|
|
51
|
+
之后消息接收入口拿到的是已存储的图片引用。
|
|
51
52
|
|
|
52
|
-
`
|
|
53
|
-
|
|
53
|
+
本插件在 `saveImages` 保存图片时,用仍可访问的原始字节解码动画,并将原件和帧关联到
|
|
54
|
+
返回的图片引用。这同时覆盖原始文件上传和旧版编码图片入口:
|
|
54
55
|
|
|
55
56
|
```
|
|
56
57
|
你贴 1 张动图
|
|
@@ -121,15 +122,16 @@ frame 9 of 24 (sample 3) · starts at 450 ms · displayed for 50 ms
|
|
|
121
122
|
dsh plugin --profile web add dsh-read-animation
|
|
122
123
|
```
|
|
123
124
|
|
|
124
|
-
`web` 换成你自己的 profile
|
|
125
|
+
`web` 换成你自己的 profile 名。**两条路都在真实 profile 里验证过**:装得上、`dsh plugin add` 会把它
|
|
126
|
+
注册成 bundle、`sharp` 的原生二进制也能从包自己的 `node_modules` 里解析出来。
|
|
125
127
|
|
|
126
128
|
```sh
|
|
127
|
-
dsh plugin --profile web add
|
|
129
|
+
dsh plugin --profile web add dsh-read-animation # npm
|
|
130
|
+
dsh plugin --profile web add github:NeoHotaru/dsh-read-animation#v0.1.0 # 钉住某个 tag
|
|
128
131
|
```
|
|
129
132
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
所以等上面那个 npm 包存在之后,npm 那条路更干净。)
|
|
133
|
+
git 那条会把整个仓库(含测试和 fixture)拷下来,所以 npm 更干净。(全新包名的**首次发布**会走
|
|
134
|
+
npm 的 staged publishing,所以刚发完那一小段时间里,registry 上可能还查不到那个版本号。)
|
|
133
135
|
|
|
134
136
|
或者从本地检出:
|
|
135
137
|
|
|
@@ -200,8 +202,8 @@ PNG,所以 harness 的归一化管线对它无事可做。
|
|
|
200
202
|
`read_image` 完全一致。当解析出的模型路由未声明图片输入时,工具会拒绝执行——理由和
|
|
201
203
|
`read_image` 相同。
|
|
202
204
|
|
|
203
|
-
|
|
204
|
-
(`ctx.attachments.
|
|
205
|
+
**贴进聊天框的动图**分三步,都不碰你的消息:**捕获**在图片存储层
|
|
206
|
+
(`ctx.attachments.saveImages` 仍能访问上传原件),**显示**在
|
|
205
207
|
`ctx.attachments.readImage`(界面取图的那一次读,返回原件所以会动),**交付**在
|
|
206
208
|
`ctx.attachments.readImageRequest`(模型那份,拼成一张带标注的胶片条)。三步都是
|
|
207
209
|
**猴补核心服务的方法**,所以写得很保守——
|
package/assets/icon.svg
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" width="96" height="96" role="img" aria-label="Four frames of one animation, with the subject growing across them">
|
|
2
|
+
<!--
|
|
3
|
+
The plugin's whole idea in one mark: one animation shown as a grid of frames
|
|
4
|
+
over time, with the subject changing across them.
|
|
5
|
+
|
|
6
|
+
Drawn for the size it is actually rendered at, roughly 40px on a bundle card:
|
|
7
|
+
four bold shapes, no gradients, no text, nothing that turns to mush. The
|
|
8
|
+
background is a mid-tone tile so the mark reads on both a light and a dark
|
|
9
|
+
card, and the frames inside carry the contrast.
|
|
10
|
+
-->
|
|
11
|
+
<rect width="96" height="96" rx="21" fill="#26333F"/>
|
|
12
|
+
<g fill="#EDF2F8">
|
|
13
|
+
<rect x="10" y="10" width="36" height="36" rx="7"/>
|
|
14
|
+
<rect x="50" y="10" width="36" height="36" rx="7"/>
|
|
15
|
+
<rect x="10" y="50" width="36" height="36" rx="7"/>
|
|
16
|
+
<rect x="50" y="50" width="36" height="36" rx="7"/>
|
|
17
|
+
</g>
|
|
18
|
+
<!--
|
|
19
|
+
The subject grows across the four moments. Size is a property the eye reads
|
|
20
|
+
even when it scans a grid out of order, which a position change is not: in a
|
|
21
|
+
2x2 layout, reading order jumps from the top right back to the bottom left, so
|
|
22
|
+
four dots each moving inside their own frame read as four unrelated things.
|
|
23
|
+
-->
|
|
24
|
+
<g fill="#26333F">
|
|
25
|
+
<circle cx="28" cy="28" r="5"/>
|
|
26
|
+
<circle cx="68" cy="28" r="7"/>
|
|
27
|
+
<circle cx="28" cy="68" r="9"/>
|
|
28
|
+
</g>
|
|
29
|
+
<!-- The last sampled moment is the one the sheet calls out, so it is the one
|
|
30
|
+
shape in the mark that is not greyscale. -->
|
|
31
|
+
<circle cx="68" cy="68" r="11" fill="#C9761B"/>
|
|
32
|
+
</svg>
|