dsh-web-icon-indicator 0.5.0 → 0.5.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 +16 -0
- package/README.md +113 -43
- package/README.zh.md +92 -31
- package/icons/plugin.svg +6 -0
- package/lib/client.js +141 -32
- package/lib/index.js +202 -94
- package/lib/types/client/index.d.ts +13 -7
- package/lib/types/index.d.ts +14 -5
- package/package.json +4 -2
- package/test/verify.js +511 -78
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.2](https://github.com/waknow/dsh-web-icon-indicator/compare/v0.5.1...v0.5.2) (2026-09-24)
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
* give the official Plugins page its own artwork ([332896b](https://github.com/waknow/dsh-web-icon-indicator/commit/332896b4b05af94877e63a33ad9c4895b94ad7dd))
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
* repaint the favicon on DSH 0.1.7's theme-scoped link pair ([a57f78f](https://github.com/waknow/dsh-web-icon-indicator/commit/a57f78ff98acd4d9ae41c03c3b0519113e357bb2))
|
|
19
|
+
|
|
20
|
+
## [0.5.1](https://github.com/waknow/dsh-web-icon-indicator/compare/v0.5.0...v0.5.1) (2026-09-22)
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
* load on DSH 0.1.7 and keep the legacy settings contract ([6e8dbb5](https://github.com/waknow/dsh-web-icon-indicator/commit/6e8dbb5402b9847eecc33029d68316407f767288))
|
|
25
|
+
|
|
10
26
|
## [0.5.0](https://github.com/waknow/dsh-web-icon-indicator/compare/v0.4.2...v0.5.0) (2026-09-17)
|
|
11
27
|
|
|
12
28
|
### Added
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/dsh-web-icon-indicator)
|
|
8
8
|
[](./LICENSE)
|
|
9
9
|
|
|
10
|
-
> **⚠️ DSH version support** — requires **DSH ≥ 0.1.2** (
|
|
10
|
+
> **⚠️ DSH version support** — requires **DSH ≥ 0.1.2-rc.1**. One bundle serves both settings generations: **modern** (≥ 0.1.7-alpha.1: exported `Config` schema + `configForms` + `plugins.row.config`) and **legacy** (≤ 0.1.6-alpha.1: `settings.installSection` + `settingsScope` + `settings.plugin.item`). Verified on **DSH 0.1.5-rc.3** and **DSH 0.1.7-alpha.1**; on any older host the favicon still works even if the settings page is not reachable.
|
|
11
11
|
|
|
12
12
|
Browser tab favicon reflects the current DSH session state — `idle` / `running` / `asking` / `done` — so you can see at a glance whether a session needs your attention, even when the tab is in the background.
|
|
13
13
|
|
|
@@ -19,7 +19,7 @@ Browser tab favicon reflects the current DSH session state — `idle` / `running
|
|
|
19
19
|
- **One SVG, recolored & animated in the browser** — ships a single whale template ([`icons/base.svg`](./icons/base.svg)); every state, color and frame is rendered client-side as a `data:image/svg+xml` URI. No per-color icon files.
|
|
20
20
|
- **Six built-in effects** — `static`, `blink`, `breath`, `rainbow`, `heartbeat`, `bounce` — all driven by JavaScript, since favicons don't play SVG CSS animations.
|
|
21
21
|
- **Fully configurable, applied live** — every state's color, effect and cycle speed, plus the asking/done hold timings, apply to the running tab within ~1 s — no reload, no restart.
|
|
22
|
-
- **Built-in settings UI, zero YAML** —
|
|
22
|
+
- **Built-in settings UI, zero YAML** — the *Favicon indicator* page edits the whole config with live color-swatch previews and persists it for you (modern hosts: the profile patch; legacy hosts: the profile `settings.yaml` — paths below).
|
|
23
23
|
- **Background-tab & restart-proof** — animated states keep a wall-clock fallback while `requestAnimationFrame` is paused in hidden tabs, and the status poll self-heals across host restarts. Returning to a tab repaints immediately: a `visibilitychange` listener fires an instant status fetch, so a state that flipped while the tab was hidden (e.g. the `done` hold expiring) shows at once instead of waiting for the next — possibly throttled — poll tick. When the backend is stopped, the tab never loses its icon: the outage restores the shell's own favicon from an offline-safe `data:`-URI copy (or keeps the last painted frame), and the live icon returns on the first successful poll.
|
|
24
24
|
- **Active-agent count at a glance** — while **more than one** agent is active (non-idle: `asking` / `running` / `done`), the favicon switches from the whale to a **full-frame number block** showing the live count (up to `99+`), colored and animated exactly like the whale would be in that state; back to the whale when 0–1 agents are active. (Same visual language as the *满幅数字* channel in [`demo/badge.html`](./demo/badge.html).)
|
|
25
25
|
|
|
@@ -27,12 +27,14 @@ Browser tab favicon reflects the current DSH session state — `idle` / `running
|
|
|
27
27
|
|
|
28
28
|
| # | Step |
|
|
29
29
|
| --- | --- |
|
|
30
|
-
| 1 | Open the DSH Web GUI and go to **Settings /
|
|
31
|
-
| 2 |
|
|
32
|
-
| 3 |
|
|
30
|
+
| 1 | Open the DSH Web GUI and go to **Settings / 设置** → **Plugins / 插件**. |
|
|
31
|
+
| 2 | On ≥ 0.1.7: open the **dsh-web-icon-indicator** bundle and click **Configure / 配置** on its `dsh-web-icon-indicator` row. On ≤ 0.1.6-alpha.1: open **Plugin config / 插件配置** in that tab. |
|
|
32
|
+
| 3 | The **Favicon indicator / 标签页图标指示器** page opens with the full form. |
|
|
33
33
|
| 4 | Set **Default icon color / 默认图标颜色** for the idle whale (its only knob — idle paints one color and never animates), then expand a state row (`running` / `asking` / `done`) to edit **Effect / 特效**, **Colors / 颜色** (each swatch is a native color picker) and **Cycle (ms) / 周期(毫秒)** (shown only for animated states — static states have no cycle); use **Asking hold / 提问驻留** and **Done hold / 完成驻留** for the two timings. |
|
|
34
34
|
|
|
35
|
-
Changes are saved through the settings transport into the profile
|
|
35
|
+
Changes are saved through the settings transport into the profile patch and applied to the running tab within ~1 s — no reload, no restart. See [Configure](#configure) for the full key reference.
|
|
36
|
+
|
|
37
|
+
> 🐳 **The Plugins page shows this plugin's own artwork** — on DSH ≥ 0.1.7 the **Plugins / 插件** page draws a 48 px icon on every bundle card (a 40 px one on every row) from `package.json` → `icon`. This repo declares `"icon": "icons/plugin.svg"`, so the card shows the whale instead of the generic placeholder. The icon is read by the host (`readPluginMeta` → `iconOf`) and shipped to the browser as a base64 `data:` URL; **a missing or malformed icon never blanks the card — it turns the plugin's `meta.error` on and the card shows a problem tag**, so keep the path relative, inside the package, and ≤ 256 KiB. Adding `icon` needs no settings, no code, and no host restart — only a new install or publish; see [Plugin artwork on the Plugins page](#plugin-artwork-on-the-plugins-page).
|
|
36
38
|
|
|
37
39
|
## 🎬 Default configuration, visualized
|
|
38
40
|
|
|
@@ -115,12 +117,18 @@ dsh plugin --profile web add <path-or-tarball>
|
|
|
115
117
|
|
|
116
118
|
Or drop the directory into `~/.dsh/profiles/web/node_modules/<name>/` and ship a `cordis.patch.yml` that matches the one shipped here.
|
|
117
119
|
|
|
120
|
+
> ℹ️ The official **Plugins** page reads the artwork from **`package.json.icon` of the
|
|
121
|
+
> installed package**, so it appears only after an install (or a publish) — a page
|
|
122
|
+
> that was already open keeps its last snapshot until you reload it. See
|
|
123
|
+
> [Plugin artwork on the Plugins page](#plugin-artwork-on-the-plugins-page).
|
|
124
|
+
|
|
118
125
|
## Configure
|
|
119
126
|
|
|
120
127
|
All keys are optional; defaults shown. `statusPath` and `iconPathPrefix` are
|
|
121
128
|
**registration-time** keys: set them in the composition entry only — they are
|
|
122
129
|
baked into the route table and the injected script when the plugin mounts, so
|
|
123
|
-
they are intentionally **not** part of the settings
|
|
130
|
+
they are intentionally **not** part of the settings page's live form (they are
|
|
131
|
+
`Config` fields, but not `.volatile()`).
|
|
124
132
|
|
|
125
133
|
| Key | Default | Meaning |
|
|
126
134
|
| --- | --- | --- |
|
|
@@ -132,6 +140,10 @@ they are intentionally **not** part of the settings surface (`settings.yaml`).
|
|
|
132
140
|
| `defaultColor` | *(unset)* | Default icon color (the idle whale's primary) — tell multiple DSH instances apart. Warns when it is too close to another state's color |
|
|
133
141
|
| `states` | see below | Per-state visual config |
|
|
134
142
|
|
|
143
|
+
> The Plugins-page artwork is **not** a config key — it comes from
|
|
144
|
+
> `package.json.icon` (see [Plugin artwork on the Plugins page](#plugin-artwork-on-the-plugins-page)).
|
|
145
|
+
> `iconsDir` only tells the *favicon* routes where `base.svg` lives.
|
|
146
|
+
|
|
135
147
|
Each entry in `states` is one object per state: `{ effect, colors[], speed? }`:
|
|
136
148
|
|
|
137
149
|
```yaml
|
|
@@ -150,8 +162,8 @@ config:
|
|
|
150
162
|
`idle` is special: its color is the `defaultColor` key, and the settings card
|
|
151
163
|
offers **no per-state entry** for it (one color, no animation, no cycle). A
|
|
152
164
|
`states.idle` entry is still honored when it arrives from the composition entry
|
|
153
|
-
or
|
|
154
|
-
not editable from the
|
|
165
|
+
or the settings document's user layer — backward compatibility only; it is
|
|
166
|
+
simply not editable from the page.
|
|
155
167
|
|
|
156
168
|
Entries are shallow-merged over the defaults, so you can override only a few states. Example:
|
|
157
169
|
|
|
@@ -183,8 +195,8 @@ immediately distinguishable — no need to touch the per-state palette:
|
|
|
183
195
|
color you configured; the other states keep their signal colors. Unset (the
|
|
184
196
|
default) means "the idle state's own color", i.e. today's behavior.
|
|
185
197
|
- It is a **per-DSH-instance** setting, not per browser tab: every tab of one
|
|
186
|
-
instance shares it, while another instance (its own profile
|
|
187
|
-
`
|
|
198
|
+
instance shares it, while another instance (its own profile, e.g.
|
|
199
|
+
`dsh web --port 3081`) can use a different color.
|
|
188
200
|
- **Reset** removes your overrides back to the composition entry. When the
|
|
189
201
|
color comes from that entry (the `base` layer, which an `unset` cannot
|
|
190
202
|
reach), the card writes the idle state's own color instead — so "Reset to
|
|
@@ -203,14 +215,21 @@ immediately distinguishable — no need to touch the per-state palette:
|
|
|
203
215
|
with each other — only the default color against them. A malformed value is
|
|
204
216
|
ignored (and reported) rather than painted.
|
|
205
217
|
|
|
206
|
-
### Settings page
|
|
207
|
-
|
|
208
|
-
The plugin
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
218
|
+
### Settings page (DSH ≥ 0.1.2-rc.1, both settings generations)
|
|
219
|
+
|
|
220
|
+
The plugin exports the whole config surface above as its Cordis `Config`
|
|
221
|
+
schema (a schemastery schema in `lib/index.js`) **and** registers the same
|
|
222
|
+
schema through the legacy settings service when the running host still exposes
|
|
223
|
+
it — the two paths are feature-detected at mount, so one bundle serves both.
|
|
224
|
+
The namespace differs by generation: **≥ 0.1.7** keys every live form by
|
|
225
|
+
**profile entry id**, so it is `dsh-web-icon-indicator` (the row id the bundle
|
|
226
|
+
patch declares); **≤ 0.1.6-alpha.1** uses the plugin-chosen
|
|
227
|
+
`web-icon-indicator`, the same string 0.5.x used, so an existing section keeps
|
|
228
|
+
resolving:
|
|
229
|
+
|
|
230
|
+
- **Web GUI:** open **设置 → 插件**, expand the **dsh-web-icon-indicator**
|
|
231
|
+
bundle and configure its row. The *Favicon indicator* page edits the same
|
|
232
|
+
keys: asking/done hold, the **default icon color** (with a
|
|
214
233
|
live palette preview and the similarity warning), and the per-state effect /
|
|
215
234
|
colors / cycle for `running` / `asking` / `done`. Each of those states is a
|
|
216
235
|
collapsible row whose header shows one **color chip** per state — split in two
|
|
@@ -230,34 +249,84 @@ service under the `web-icon-indicator` namespace (a schemastery schema in
|
|
|
230
249
|
`colors[0]`). `idle` deliberately gets **no row** — it paints one
|
|
231
250
|
color and never animates, so the default-color field is its whole
|
|
232
251
|
configuration. Everything is staged and saved through the settings transport.
|
|
233
|
-
- **Persistence:** values land in the profile
|
|
234
|
-
`~/.dsh/
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
252
|
+
- **Persistence:** on ≥ 0.1.7 values land in the profile patch
|
|
253
|
+
(`~/.dsh/profiles/<profile>/cordis.patch.yml`) under the
|
|
254
|
+
`dsh-web-icon-indicator` row's `config:` block; on ≤ 0.1.6-alpha.1 they land
|
|
255
|
+
in `~/.dsh/settings.yaml` as a `web-icon-indicator:` section. In both cases
|
|
256
|
+
the composition entry stays the `base` layer; resolution order is schema
|
|
257
|
+
defaults → composition entry → user layer.
|
|
258
|
+
- **No server restart, no tab reload** for settings-page saves. On ≥ 0.1.7 a
|
|
259
|
+
live write is committed into the running plugin's config references
|
|
260
|
+
(`loader/volatile-update` — the loader does not restart the plugin); on the
|
|
261
|
+
legacy line the service calls the registered `onChange` hook. Either way
|
|
262
|
+
`askingHoldMs` / `doneHoldMs` apply host-side immediately and per-state visual
|
|
263
|
+
config (effect / colors / cycle) is synced into the running tab through the
|
|
264
|
+
status poll within ~1 s. Only code-level default changes in `lib/index.js`
|
|
265
|
+
need a tab reload (or a DSH web rebuild).
|
|
266
|
+
- **Route paths are not live settings.** `statusPath` / `iconPathPrefix` are
|
|
267
|
+
validated by `Config` but deliberately **not** `.volatile()`, so they are
|
|
268
|
+
absent from the live form: they are baked into the route table and the
|
|
269
|
+
injected script at registration time, and honoring an edit there would point
|
|
270
|
+
the browser at a path the server never serves. They are composition-entry
|
|
271
|
+
only (see the table above); changing them re-applies the plugin.
|
|
272
|
+
- The live surface therefore covers `askingHoldMs`, `doneHoldMs`, `iconsDir`,
|
|
273
|
+
`defaultColor` and `states`. `iconsDir` has no schema default, so it is
|
|
274
|
+
omitted from the resolved form until a user sets it.
|
|
251
275
|
- The browser half is a hand-written `lib/client.js` (ModuleLoader factory
|
|
252
|
-
format — no build step, no runtime deps beyond the shell's `react`).
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
276
|
+
format — no build step, no runtime deps beyond the shell's `react`). It
|
|
277
|
+
registers on both page slots (`plugins.row.config` — gated on the served
|
|
278
|
+
namespace — and the legacy `settings.plugin.item`) and resolves whichever
|
|
279
|
+
settings provider the host exposes (`configForms` or `settingsScope`) at
|
|
280
|
+
render time, so neither is a hard dependency of the module. The DSH client
|
|
281
|
+
scanner picks a new `dsh.client` declaration up on the next profile start.
|
|
282
|
+
- Deployments without a settings service are unaffected: the plugin keeps
|
|
283
|
+
running on the composition entry + schema defaults it was mounted with.
|
|
284
|
+
|
|
285
|
+
## Plugin artwork on the Plugins page
|
|
286
|
+
|
|
287
|
+
DSH ≥ 0.1.7 renders a **per-plugin icon on the official Plugins page**: 48 px on
|
|
288
|
+
each bundle card, 40 px on each row (`PackageArtwork` in
|
|
289
|
+
`@deepseek-ai/dsh-client-ui-plugin-manager`, which falls back to a generic
|
|
290
|
+
placeholder when a package declares none). The artwork comes straight from the
|
|
291
|
+
package manifest, so shipping one is pure metadata — **no code, no settings, no
|
|
292
|
+
host restart**:
|
|
293
|
+
|
|
294
|
+
```jsonc
|
|
295
|
+
// package.json
|
|
296
|
+
{
|
|
297
|
+
"name": "dsh-web-icon-indicator",
|
|
298
|
+
"icon": "icons/plugin.svg" // relative to package.json, and inside the package
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
The host reads it before evaluating any plugin code
|
|
303
|
+
(`readPluginMeta` → `iconOf` in `@deepseek-ai/dsh-app-boot`):
|
|
304
|
+
|
|
305
|
+
| Rule | Value |
|
|
306
|
+
| --- | --- |
|
|
307
|
+
| Field | top-level `package.json.icon` (a sibling of `name`/`version` — **not** under `dsh`) |
|
|
308
|
+
| Path form | relative to the manifest's directory; absolute paths, Windows drive letters (`C:\…`) and URLs are rejected |
|
|
309
|
+
| Containment | must stay inside the manifest directory after `realpath` resolution (`..` escapes are rejected) |
|
|
310
|
+
| Formats | `.svg` → `image/svg+xml`, `.png` → `image/png`, `.jpg` / `.jpeg` → `image/jpeg`, `.webp` → `image/webp` |
|
|
311
|
+
| Size | ≤ 256 KiB (checked both on `stat` and on the bytes actually read) |
|
|
312
|
+
| Delivery | read as bytes and shipped to the browser as a base64 `data:` URL on the wire (`PluginLocalizedMeta.icon`) |
|
|
313
|
+
|
|
314
|
+
> ⚠️ **A bad icon is worse than no icon.** Any violation is a *metadata error*,
|
|
315
|
+
> not a silent fallback: the reader returns `meta.error`, the host surfaces it,
|
|
316
|
+
> and the card shows a **problem tag** for the plugin. Keep the file in the
|
|
317
|
+
> package (`files` ships `icons/`) and make sure it is standalone — a raw
|
|
318
|
+
> `icons/base.svg` would be **invalid**, because its `#p { fill: __COLOR__ }`
|
|
319
|
+
> placeholder is not a colour. This repo therefore ships the pre-filled
|
|
320
|
+
> [`icons/plugin.svg`](./icons/plugin.svg) (`base.svg` with `__COLOR__` →
|
|
321
|
+
> `#1a1a1a`) and asserts the whole contract in `test/verify.js` (F62–F65).
|
|
322
|
+
|
|
323
|
+
Because the icon is read from the **installed** package, a local edit takes
|
|
324
|
+
effect only after reinstalling the plugin (`dsh plugin --profile web add
|
|
325
|
+
<path>`) or publishing a new version.
|
|
257
326
|
|
|
258
327
|
## How it works
|
|
259
328
|
|
|
260
|
-
- Host plugin with a small browser half: registers routes on the existing `webServer` — the status JSON endpoint, a static `/dsh-web-icon-indicator/base.svg` (the whale template), and one `tapIndex` that injects a small browser script into every served `index.html`. The config surface is
|
|
329
|
+
- Host plugin with a small browser half: registers routes on the existing `webServer` — the status JSON endpoint, a static `/dsh-web-icon-indicator/base.svg` (the whale template), and one `tapIndex` that injects a small browser script into every served `index.html`. The config surface is the plugin's exported `Config` schema, which the DSH settings service projects into a live page keyed by the `dsh-web-icon-indicator` entry id (see above).
|
|
261
330
|
- Status is aggregated across live `agents.list()` with priority `asking > running > done > idle`. The aggregation runs a `reconcile()` step on every request to detect running → idle transitions, because `agent/status`'s idle delivery is not guaranteed at turn end. The status endpoint also reports `active` — the number of non-idle agents — and while that count is **> 1** the injected script renders a full-frame count block (the *满幅数字* channel of [`demo/badge.html`](./demo/badge.html): a rounded block filled with the same per-frame state color/effect as the whale, bold white count sized 31%–52% of the icon, capped at `99+`) instead of the whale, so the tab shows how many agents are busy at once even in a pinned 16px tab.
|
|
262
331
|
- `ask_user_question` tool calls (via `tools/pre-execute` / `tools/result`) flip the session into `asking` with a configurable minimum-hold so the icon stays visible even when the user answers immediately.
|
|
263
332
|
- Permission / **sandbox-interception** waits are also surfaced as `asking`: when the agent hits a sandbox denial and escalates (`sandbox_permissions` + `justification`), or any other tool asks for approval, the approval service appends an `approval/asked` session event and blocks the agent until you decide. The plugin watches `session/event` (with an authoritative fold over the live session log as a fallback) and pins the session into the `asking` state for that whole wait, clearing it on `approval/decided`.
|
|
@@ -277,6 +346,7 @@ The favicon is a plain image, so browsers never run the SVG's own CSS/JS animati
|
|
|
277
346
|
Known limitations (current as of Safari 26.3):
|
|
278
347
|
|
|
279
348
|
- **Favicons have their own cache.** Chrome keeps a favicon database, Firefox a `favicons.sqlite`, and **Safari a system-level icon cache** — none of which a normal *clear cache* touches, and WebKit even caches the "no icon" case. That is why a changed icon can linger for an existing tab. The plugin already mitigates this: it serves `base.svg` and the status endpoint with `Cache-Control: no-store`, bundles a freshness query (`?t=Date.now()`) on its fetches, and replaces the `<link rel=icon>` node on each state change.
|
|
349
|
+
- **A theme-scoped favicon pair must be collapsed first (DSH ≥ 0.1.7).** The shell's `index.html` ships *two* `rel=icon` links — `favicon-dark.svg` for `prefers-color-scheme: dark` and `favicon.svg` for light — because up to `0.1.6-alpha.2` there was exactly one. A browser resolves the favicon to the **last connected link whose `media` matches**, so repainting only the first one left the tab showing the shell's icon no matter what the session did. The plugin now normalizes the set to a single always-matching link at startup (keeping the variant your scheme selects) before painting. If you are on a version before 0.5.2 and the tab icon simply never changes, this is why.
|
|
280
350
|
- **Safari renders SVG favicons but ignores their internal CSS** — no `@media`, no `prefers-color-scheme`, no CSS animation. So all recoloring must be baked into each frame's markup (which the plugin does) rather than driven by CSS variables.
|
|
281
351
|
- **`data:`-URI SVG favicons are unreliable in Safari** (WebKit bug 236616, still open; reproduced on Safari 17.6). The plugin currently builds each frame as a `data:image/svg+xml` URI, so on Safari the tab icon may not render at all — the biggest known gap.
|
|
282
352
|
- **Dynamic JS updates in Safari are hit-or-miss;** they may require a reload, and Safari "locks onto" the first icon it sees. There is no guaranteed, spec-supported way to swap a favicon live in Safari today.
|
package/README.zh.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/dsh-web-icon-indicator)
|
|
8
8
|
[](./LICENSE)
|
|
9
9
|
|
|
10
|
-
> **⚠️ DSH 版本支持** —— 需要 **DSH ≥ 0.1.2
|
|
10
|
+
> **⚠️ DSH 版本支持** —— 需要 **DSH ≥ 0.1.2-rc.1**。同一份插件同时兼容两代 settings 契约:**新一代**(≥ 0.1.7-alpha.1:导出 `Config` schema + `configForms` + `plugins.row.config`)与**旧一代**(≤ 0.1.6-alpha.1:`settings.installSection` + `settingsScope` + `settings.plugin.item`)。已在 **DSH 0.1.5-rc.3** 与 **DSH 0.1.7-alpha.1** 上验证;更早的宿主上 favicon 仍可用,只是配置页可能不可达。
|
|
11
11
|
|
|
12
12
|
浏览器标签页 favicon 实时反映 DSH 会话状态——`待机` / `运行中` / `提问` / `完成`——让你在标签页置于后台时也能一眼看出是否有会话需要处理。
|
|
13
13
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
- **单个 SVG,浏览器内上色与动画** —— 只内置一个鲸鱼模板([`icons/base.svg`](./icons/base.svg));每个状态、颜色、每一帧都在客户端渲染为 `data:image/svg+xml` URI,不再有按颜色拆分的图标文件。
|
|
20
20
|
- **六种内置特效** —— `static`(静止)、`blink`(闪烁)、`breath`(呼吸)、`rainbow`(彩虹)、`heartbeat`(心跳)、`bounce`(跳动),全部由 JavaScript 驱动(favicon 不会播放 SVG CSS 动画)。
|
|
21
21
|
- **完全可配置、即时生效** —— 每个状态的颜色、特效、周期,以及提问 / 完成驻留时长,改动约 1 秒内同步到已打开的标签页——无需刷新、无需重启。
|
|
22
|
-
- **内置配置 UI,无需手写 YAML** ——
|
|
22
|
+
- **内置配置 UI,无需手写 YAML** —— *标签页图标指示器* 页面可编辑整套配置,带实时色块预览并自动持久化(新宿主:profile patch;旧宿主:profile `settings.yaml`,路径见下)。
|
|
23
23
|
- **后台标签页与重启抗性** —— 隐藏标签页中 `requestAnimationFrame` 被暂停时,动画态会按墙钟时间补帧;状态轮询还能扛住 host 重启、后端停止:故障期间标签页图标绝不丢失(还原启动时缓存的原始图标 `data:`-URI 副本,或保留最后一帧插件图标),端点恢复后自动回到实时状态。切回前台时会立刻触发一次状态拉取并重绘——后台标签页的定时器会被浏览器节流,轮询可能滞后,所以回到标签页的瞬间就刷新最新状态(比如 `done` 保持期在隐藏期间过期、图标应退回 `idle` 的情况)。
|
|
24
24
|
- **多 agent 一目了然** —— 当同时有**超过一个**活动 agent(非待机:`asking` / `running` / `done`)时,favicon 从鲸鱼切换为**占满整帧的数字块**,实时显示活动数(上限 `99+`),颜色与动画和该状态下鲸鱼完全一致;活动数回到 0–1 时恢复鲸鱼。(视觉与 [`demo/badge.html`](./demo/badge.html) 的「满幅数字」通道一致。)
|
|
25
25
|
|
|
@@ -27,12 +27,14 @@
|
|
|
27
27
|
|
|
28
28
|
| # | 步骤 |
|
|
29
29
|
| --- | --- |
|
|
30
|
-
| 1 | 打开 DSH Web GUI,进入
|
|
31
|
-
| 2 |
|
|
32
|
-
| 3 |
|
|
30
|
+
| 1 | 打开 DSH Web GUI,进入 **设置** → **插件**。 |
|
|
31
|
+
| 2 | ≥ 0.1.7:展开 **dsh-web-icon-indicator** bundle,在其 `dsh-web-icon-indicator` 行上点击 **配置**;≤ 0.1.6-alpha.1:在该选项卡里打开 **插件配置**。 |
|
|
32
|
+
| 3 | 打开 **标签页图标指示器(Favicon indicator)** 配置页。 |
|
|
33
33
|
| 4 | 先用 **默认图标颜色** 设置待机鲸鱼的颜色(这是待机唯一的设置项——待机只画一种颜色、不做动画),再展开状态行(`running` / `asking` / `done`)编辑 **特效**、**颜色**(每个色块即原生取色器),以及**周期(毫秒)**——仅动画状态显示,静态状态无周期;用 **提问驻留** / **完成驻留** 调整两个时长。 |
|
|
34
34
|
|
|
35
|
-
改动会通过 settings 传输层持久化到 profile
|
|
35
|
+
改动会通过 settings 传输层持久化到 profile patch,约 1 秒内应用到已打开的标签页——无需刷新、无需重启。完整键说明见 [配置](#配置)。
|
|
36
|
+
|
|
37
|
+
> 🐳 **插件页会显示本插件自己的图标** —— DSH ≥ 0.1.7 的 **设置 → 插件** 页面会为每个 bundle 卡片画一个 48px 图标(每一行 40px),来源是 `package.json` → `icon`。本仓库声明了 `"icon": "icons/plugin.svg"`,所以卡片显示的是鲸鱼而不是通用占位图。图标由宿主读取(`readPluginMeta` → `iconOf`),并以 base64 `data:` URL 传给浏览器;**图标缺失或不合规不会把卡片变空——而是点亮该插件的 `meta.error`,卡片上出现「问题」标签**,所以路径必须相对、必须在包内、且 ≤ 256 KiB。加这个字段不需要任何配置、代码或宿主重启,只需重新安装或发一个新版本;详见[插件页图标](#插件页图标)。
|
|
36
38
|
|
|
37
39
|
## 🎬 默认配置,可视化
|
|
38
40
|
|
|
@@ -110,11 +112,15 @@ dsh plugin --profile web add <路径或tarball>
|
|
|
110
112
|
|
|
111
113
|
或将目录放进 `~/.dsh/profiles/web/node_modules/<name>/`,并附带与包内一致的 `cordis.patch.yml`。
|
|
112
114
|
|
|
115
|
+
> ℹ️ 官方**插件页**的图标读取的是**已安装包的 `package.json.icon`**,因此只有在安装
|
|
116
|
+
> (或发布新版本)之后才会出现——已经打开的插件页会一直显示上一次的快照,直到你刷新。
|
|
117
|
+
> 详见[插件页图标](#插件页图标)。
|
|
118
|
+
|
|
113
119
|
## 配置
|
|
114
120
|
|
|
115
121
|
所有键均可选,默认值如下。`statusPath` 与 `iconPathPrefix` 是**注册期**键:
|
|
116
122
|
只能在合成条目(composition entry)里设置——它们在插件挂载时就被烘进路由表与注入
|
|
117
|
-
|
|
123
|
+
脚本,因此刻意**不**进入配置页的实时表单(它们是 `Config` 字段,但非 `.volatile()`)。
|
|
118
124
|
|
|
119
125
|
| 键 | 默认值 | 含义 |
|
|
120
126
|
| --- | --- | --- |
|
|
@@ -126,6 +132,10 @@ dsh plugin --profile web add <路径或tarball>
|
|
|
126
132
|
| `defaultColor` | *(未设置)* | 默认图标颜色(待机鲸鱼的主色)—— 用于区分多个 DSH 实例;与其它状态颜色过于接近时会告警 |
|
|
127
133
|
| `states` | 见下 | 每个状态的视觉配置 |
|
|
128
134
|
|
|
135
|
+
> 插件页的图标**不是**配置键——它来自 `package.json.icon`
|
|
136
|
+
> (见[插件页图标](#插件页图标))。`iconsDir` 只告诉 *favicon* 路由去哪里取
|
|
137
|
+
> `base.svg`。
|
|
138
|
+
|
|
129
139
|
`states` 中每个状态是一个对象:`{ effect, colors[], speed? }`:
|
|
130
140
|
|
|
131
141
|
```yaml
|
|
@@ -143,7 +153,7 @@ config:
|
|
|
143
153
|
|
|
144
154
|
`idle` 比较特殊:它的颜色就是 `defaultColor` 键,设置卡片**不为它提供状态条目**
|
|
145
155
|
(一种颜色、不做动画、也没有周期)。若 `states.idle` 来自合成条目或手写的
|
|
146
|
-
|
|
156
|
+
设置文档的用户层,仍然会被沿用——这属于向后兼容,只是无法在配置页里编辑。
|
|
147
157
|
|
|
148
158
|
每个状态条目会在默认值之上做浅合并,因此只需覆盖少量状态。示例:
|
|
149
159
|
|
|
@@ -173,7 +183,7 @@ config:
|
|
|
173
183
|
因此 idle 仍保留自己配置的特效与第二色,其它状态的颜色语义(黄=运行、红/黄=提问、
|
|
174
184
|
绿=完成)不受影响;不设置(默认)即等于「沿用 idle 自己的颜色」,行为与之前完全一致。
|
|
175
185
|
- 这是**按 DSH 实例**生效的设置,不是按标签页:同一实例的所有标签页共用它;另一个实例
|
|
176
|
-
(自己的 profile
|
|
186
|
+
(自己的 profile,例如 `dsh web --port 3081`)可以用另一种颜色。
|
|
177
187
|
- **恢复默认**会把你的覆盖清回合成条目。当颜色本来就来自合成条目(`base` 层,
|
|
178
188
|
用户层的 `unset` 触及不到)时,卡片改为写入 idle 自己的颜色——这样「恢复默认」真的
|
|
179
189
|
能让图标回到朴素的鲸鱼色,而合成条目里配置的值仍可通过「清除覆盖」一键取回。
|
|
@@ -186,13 +196,18 @@ config:
|
|
|
186
196
|
共用 `#FACC15`,所以**状态之间互不比较**,只把默认色与它们逐一比较。格式非法的值会被
|
|
187
197
|
忽略并上报,而不会被画到图标上。
|
|
188
198
|
|
|
189
|
-
###
|
|
199
|
+
### 设置页(DSH ≥ 0.1.2-rc.1,兼容两代 settings 契约)
|
|
190
200
|
|
|
191
|
-
|
|
192
|
-
|
|
201
|
+
插件把上面整套配置作为 Cordis `Config` schema 导出(`lib/index.js` 中的
|
|
202
|
+
schemastery schema),**并在宿主仍提供旧版服务时**用同一份 schema 走
|
|
203
|
+
`settings.installSection` 注册——两条路径在挂载时按能力探测,因此同一份 bundle
|
|
204
|
+
两代宿主都能用。命名空间随代际不同:**≥ 0.1.7** 按 **profile entry id** 组织实时
|
|
205
|
+
表单,即 `dsh-web-icon-indicator`(bundle patch 声明的行 id);**≤ 0.1.6-alpha.1**
|
|
206
|
+
使用插件自定的 `web-icon-indicator`,与 0.5.x 保持一致,老用户的 section 继续生效:
|
|
193
207
|
|
|
194
|
-
- **Web GUI:** 打开 **设置 →
|
|
195
|
-
|
|
208
|
+
- **Web GUI:** 打开 **设置 → 插件**,展开 **dsh-web-icon-indicator** bundle 并
|
|
209
|
+
配置它的行,即出现 *标签页图标指示器* 页面,可编辑:提问/完成驻留、
|
|
210
|
+
**默认图标颜色**(带配色一览与相似度告警),以及
|
|
196
211
|
`running` / `asking` / `done` 三个状态各自的特效 / 颜色 / 周期。这三个状态
|
|
197
212
|
各占一行可折叠条目,行首是一个**色块**——多色状态(asking)会左右分格同时显示
|
|
198
213
|
红黄两色,旁边是一行「特效 · 周期」摘要(如 `Blink · 400ms`)。点击色块即打开
|
|
@@ -206,27 +221,72 @@ config:
|
|
|
206
221
|
色块(也就是存下的 `colors[0]`)。`idle` 刻意**不占
|
|
207
222
|
一行**——它只画一种颜色、不做动画,默认图标颜色就是它的全部配置。所有修改都通过
|
|
208
223
|
settings 传输层暂存并保存。
|
|
209
|
-
- **持久化:**
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
-
|
|
221
|
-
|
|
224
|
+
- **持久化:** ≥ 0.1.7 写入 profile patch
|
|
225
|
+
(`~/.dsh/profiles/<profile>/cordis.patch.yml`)中 `dsh-web-icon-indicator`
|
|
226
|
+
行的 `config:` 段;≤ 0.1.6-alpha.1 写入 `~/.dsh/settings.yaml` 的
|
|
227
|
+
`web-icon-indicator:` 段。两者都保持合成条目为 `base` 层;解析顺序为
|
|
228
|
+
schema 默认值 → 合成条目 → 用户层。
|
|
229
|
+
- **无需重启服务器、无需刷新标签页**即可让配置页的修改生效:≥ 0.1.7 时实时写入
|
|
230
|
+
直接提交到运行中插件的配置引用(`loader/volatile-update`——loader 不会重启插件);
|
|
231
|
+
旧一代则由 settings 服务回调注册的 `onChange`。两种情况 `askingHoldMs` /
|
|
232
|
+
`doneHoldMs` 都在主机侧即时生效;各状态的视觉配置(特效 / 颜色 / 周期)会随状态
|
|
233
|
+
轮询同步进正在运行的标签页,约 1 秒内生效。只有改 `lib/index.js` 里的代码级默认值
|
|
234
|
+
才需要重载标签页(或重新构建 DSH Web)。
|
|
235
|
+
- **路由路径不是实时设置项。** `statusPath` / `iconPathPrefix` 由 `Config` 校验,但
|
|
236
|
+
刻意**不加** `.volatile()`,因此不出现在实时表单里:它们已被烘进路由表与注入脚本,
|
|
237
|
+
在那里生效只会让浏览器去请求服务器根本没有提供的路径。它们只存在于合成条目
|
|
238
|
+
(见上方表格),改动会重新挂载插件。
|
|
239
|
+
- 因此实时设置面覆盖 `askingHoldMs`、`doneHoldMs`、`iconsDir`、`defaultColor`
|
|
240
|
+
与 `states`。`iconsDir` 没有 schema 默认值,用户未设置时不会出现在解析后的表单中。
|
|
222
241
|
- 浏览器半区是手写的 `lib/client.js`(ModuleLoader factory 格式——无构建步骤、
|
|
223
|
-
无额外运行期依赖,仅用 shell 自带的 `react
|
|
224
|
-
|
|
225
|
-
|
|
242
|
+
无额外运行期依赖,仅用 shell 自带的 `react`)。它同时往两代页面插槽注册
|
|
243
|
+
(`plugins.row.config`——按「Host 是否提供该命名空间」门控;以及旧的
|
|
244
|
+
`settings.plugin.item`),并在渲染时按宿主实际提供的服务解析表单
|
|
245
|
+
(`configForms` 或 `settingsScope`),因此两者都不是模块的硬依赖。DSH 客户端
|
|
246
|
+
扫描器会在下次启动 profile 时识别新的 `dsh.client` 声明。
|
|
247
|
+
- 未组合 settings 服务的部署不受影响:插件继续使用挂载时的合成条目 + schema 默认值。
|
|
248
|
+
|
|
249
|
+
## 插件页图标
|
|
250
|
+
|
|
251
|
+
DSH ≥ 0.1.7 会在官方**插件页**为每个插件渲染图标:bundle 卡片 48px、每一行
|
|
252
|
+
40px(`@deepseek-ai/dsh-client-ui-plugin-manager` 的 `PackageArtwork`,未声明时
|
|
253
|
+
回退到通用占位图)。图标直接来自包清单,所以给出它纯粹是元数据——**不需要改代码、
|
|
254
|
+
不需要配置、不需要重启宿主**:
|
|
255
|
+
|
|
256
|
+
```jsonc
|
|
257
|
+
// package.json
|
|
258
|
+
{
|
|
259
|
+
"name": "dsh-web-icon-indicator",
|
|
260
|
+
"icon": "icons/plugin.svg" // 相对 package.json,且必须留在包内
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
宿主在**不执行任何插件代码**的前提下读取它(`@deepseek-ai/dsh-app-boot` 的
|
|
265
|
+
`readPluginMeta` → `iconOf`):
|
|
266
|
+
|
|
267
|
+
| 规则 | 取值 |
|
|
268
|
+
| --- | --- |
|
|
269
|
+
| 字段 | 顶层 `package.json.icon`(与 `name`/`version` 同级,**不是**放在 `dsh` 下) |
|
|
270
|
+
| 路径形式 | 相对清单所在目录;绝对路径、Windows 盘符(`C:\…`)与 URL 都会被拒绝 |
|
|
271
|
+
| 包含关系 | 经 `realpath` 解析后必须仍在清单目录内(`..` 越界会被拒绝) |
|
|
272
|
+
| 格式 | `.svg` → `image/svg+xml`,`.png` → `image/png`,`.jpg` / `.jpeg` → `image/jpeg`,`.webp` → `image/webp` |
|
|
273
|
+
| 大小 | ≤ 256 KiB(`stat` 与实际读到的字节数各校验一次) |
|
|
274
|
+
| 传输 | 读成字节后以 base64 `data:` URL 随元数据下发(`PluginLocalizedMeta.icon`) |
|
|
275
|
+
|
|
276
|
+
> ⚠️ **坏的图标比没有图标更糟。** 任何不合规都不是静默回退,而是**元数据错误**:
|
|
277
|
+
> 读取器返回 `meta.error`,宿主把它暴露出来,卡片上会给这个插件打上**问题标签**。
|
|
278
|
+
> 所以文件要留在包内(`files` 里已包含 `icons/`),并且必须是独立可用的 SVG——
|
|
279
|
+
> 直接用 `icons/base.svg` 是**无效**的,因为它里面的 `#p { fill: __COLOR__ }`
|
|
280
|
+
> 占位符并不是颜色。本仓库因此提供预填好的
|
|
281
|
+
> [`icons/plugin.svg`](./icons/plugin.svg)(即 `base.svg` 把 `__COLOR__` 换成
|
|
282
|
+
> `#1a1a1a`),并在 `test/verify.js` 里把整套约束都断言下来(F62–F65)。
|
|
283
|
+
|
|
284
|
+
由于图标是从**已安装**的包读取的,本地改动只有在重新安装插件
|
|
285
|
+
(`dsh plugin --profile web add <path>`)或发布新版本之后才会生效。
|
|
226
286
|
|
|
227
287
|
## 实现原理
|
|
228
288
|
|
|
229
|
-
- Host 插件 + 一个小型浏览器半区:在现有 `webServer` 上注册路由——状态 JSON 端点、静态 `/dsh-web-icon-indicator/base.svg`(鲸鱼模板),以及一个 `tapIndex` 向每个 `index.html`
|
|
289
|
+
- Host 插件 + 一个小型浏览器半区:在现有 `webServer` 上注册路由——状态 JSON 端点、静态 `/dsh-web-icon-indicator/base.svg`(鲸鱼模板),以及一个 `tapIndex` 向每个 `index.html` 注入小段浏览器脚本。整套配置就是插件导出的 `Config` schema,DSH settings 服务按 `dsh-web-icon-indicator` entry id 把它投射为实时配置页(见上)。
|
|
230
290
|
- 状态按 `agents.list()` 聚合,优先级 `asking > running > done > idle`。每次请求都会执行一次 `reconcile()` 检测 running → idle 的转换,因为 `agent/status` 的 idle 事件在回合结束时并不保证送达。状态端点还会上报 `active`——非待机 agent 数——当该数 **> 1** 时,注入脚本改为渲染占满整帧的数字块([`demo/badge.html`](./demo/badge.html) 的「满幅数字」通道:圆角色块,填充色与鲸鱼同源的逐帧状态色/特效,白色粗体数字约占图标高度 31%–52%,上限 `99+`),而不是鲸鱼,这样即使在 16px 的固定标签页里也能一眼看出同时有几个 agent 在忙。
|
|
231
291
|
- `ask_user_question` 工具调用(通过 `tools/pre-execute` / `tools/result`)把会话置为 `asking`,带可配置的最小保持时长,即使你立刻回答,图标也会保持可见。
|
|
232
292
|
- 权限 / **沙箱拦截**等待同样会显示为 `asking`:当 agent 命中沙箱拒绝并请求提权(`sandbox_permissions` + `justification`),或其他工具需要征得同意时,审批服务会先写入一条 `approval/asked` 会话事件并阻塞 agent,直到你做出决定。插件监听 `session/event`(并以实时会话日志的权威折叠作为兜底)在整个等待期间将会话置为 `asking` 状态,收到 `approval/decided` 后清除。
|
|
@@ -246,6 +306,7 @@ favicon 本质是一张图片,浏览器不会在标签页 UI 里运行 SVG 自
|
|
|
246
306
|
已知限制(截至 Safari 26.3):
|
|
247
307
|
|
|
248
308
|
- **favicon 有专属缓存。** Chrome 用 favicon 数据库、Firefox 用 `favicons.sqlite`、**Safari 用系统级图标缓存**——清普通缓存都清不掉,WebKit 甚至会把「无图标」这一状态也缓存起来。这就是改了图标后,已打开的标签页还可能显示旧图标的原因。本插件已通过「给 `base.svg` 与状态端点设置 `Cache-Control: no-store`、请求携带 freshness 参数(`?t=Date.now()`)、每次切换状态时重建 `<link rel=icon>` 节点」来缓解。
|
|
309
|
+
- **必须先折叠「按主题配对」的两个 favicon link(DSH ≥ 0.1.7)。** 宿主的 `index.html` 里有**两个** `rel=icon`——`favicon-dark.svg` 对应 `prefers-color-scheme: dark`、`favicon.svg` 对应浅色(0.1.6-alpha.2 及更早只有一个)。浏览器取的是**最后一个 media 命中的已连接 link**,所以只重绘第一个的话,无论会话状态怎么变,标签页始终显示宿主自己的图标。插件现在会在首次绘制前把这一组规范成**唯一一个不带 media 的 link**(保留你当前配色方案对应的那个)。如果你用的是 0.5.2 之前的版本、且标签页图标完全不变化,原因就在这里。
|
|
249
310
|
- **Safari 渲染 SVG favicon,但忽略其内部 CSS**——不支持 `@media`、`prefers-color-scheme`、CSS 动画。所以所有上色必须烘焙进每一帧的标记(本插件正是这么做的),而不能依赖 CSS 变量。
|
|
250
311
|
- **`data:` URI 的 SVG favicon 在 Safari 不可靠**(WebKit bug 236616,仍未关闭;Safari 17.6 复现)。本插件当前每帧都生成 `data:image/svg+xml` URI,因此在 Safari 上标签页图标可能完全不显示——这是最大的已知缺口。
|
|
251
312
|
- **Safari 的动态 JS 更新为 hit-or-miss**,可能需要刷新一次;Safari 会「锁定」它首次看到的图标。目前没有保证可靠的、符合规范的手段能在 Safari 中实时更换 favicon。
|
package/icons/plugin.svg
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50" fill="none">
|
|
2
|
+
<style>
|
|
3
|
+
#p { fill: #1a1a1a; }
|
|
4
|
+
</style>
|
|
5
|
+
<path id="p" fill="#1a1a1a" d="M48.8354 10.0479C48.3232 9.79199 48.1025 10.2798 47.8032 10.5278C47.7007 10.6079 47.6143 10.7119 47.5273 10.8076C46.7793 11.624 45.9048 12.1597 44.7622 12.0957C43.0923 12 41.666 12.5356 40.4058 13.8398C40.1377 12.2319 39.2476 11.272 37.8926 10.6558C37.1836 10.3359 36.4668 10.0156 35.9702 9.31982C35.6235 8.82373 35.5293 8.27197 35.356 7.72754C35.2456 7.3999 35.1353 7.06396 34.7651 7.00781C34.3633 6.94385 34.2056 7.2876 34.0479 7.57568C33.418 8.75195 33.1733 10.0479 33.1973 11.3599C33.2524 14.312 34.4736 16.6641 36.8999 18.3359C37.1758 18.5278 37.2466 18.7197 37.1597 19C36.9946 19.5757 36.7974 20.1357 36.624 20.7119C36.5137 21.0801 36.3486 21.1597 35.9624 21C34.6309 20.4321 33.481 19.5918 32.4644 18.5757C30.7393 16.8721 29.1792 14.9917 27.2334 13.52C26.7764 13.1758 26.3193 12.856 25.8467 12.5518C23.8618 10.584 26.1069 8.96777 26.627 8.77588C27.1704 8.57568 26.8159 7.8877 25.0591 7.896C23.3022 7.90381 21.6953 8.50391 19.647 9.30371C19.3477 9.42383 19.0322 9.51172 18.7095 9.58398C16.8501 9.22363 14.9199 9.14355 12.9033 9.37598C9.10596 9.80762 6.07275 11.6396 3.84326 14.7681C1.16455 18.5278 0.53418 22.7998 1.30664 27.2559C2.11768 31.9521 4.46582 35.8398 8.07373 38.8799C11.8159 42.0322 16.1255 43.5762 21.041 43.2803C24.0269 43.104 27.3516 42.6963 31.1016 39.4561C32.0469 39.936 33.0396 40.1279 34.686 40.272C35.9546 40.3921 37.1758 40.208 38.1211 40.0078C39.6021 39.688 39.4995 38.2881 38.9639 38.0322C34.623 35.9678 35.5762 36.8081 34.71 36.1279C36.9155 33.4639 40.2402 30.6958 41.54 21.728C41.6426 21.0161 41.5557 20.5679 41.54 19.9917C41.5322 19.6396 41.6108 19.5039 42.0049 19.4639C43.0923 19.3359 44.1479 19.0317 45.1167 18.4878C47.9292 16.9199 49.064 14.3438 49.3315 11.2559C49.3711 10.7837 49.3237 10.2959 48.8354 10.0479ZM24.3262 37.8398C20.1196 34.4639 18.0791 33.3521 17.2358 33.3999C16.4482 33.4482 16.5898 34.3682 16.7632 34.9678C16.9443 35.5601 17.1812 35.9683 17.5117 36.4878C17.7402 36.832 17.8979 37.3442 17.2832 37.728C15.9282 38.584 13.5728 37.4399 13.4624 37.3838C10.7207 35.7358 8.42822 33.5601 6.81348 30.584C5.25342 27.7197 4.34766 24.6479 4.19775 21.3677C4.1582 20.5757 4.38672 20.2959 5.15869 20.1519C6.17529 19.96 7.22314 19.9199 8.23926 20.0718C12.5327 20.7119 16.1885 22.6719 19.2529 25.7759C21.002 27.5439 22.3252 29.6558 23.6885 31.7202C25.1377 33.9121 26.6978 36 28.6831 37.7119C29.3843 38.312 29.9434 38.7681 30.479 39.104C28.8643 39.2881 26.1699 39.3281 24.3262 37.8398ZM26.3433 24.6001C26.3433 24.248 26.6191 23.9678 26.9658 23.9678C27.0444 23.9678 27.1152 23.9839 27.1782 24.0078C27.2651 24.04 27.3438 24.0879 27.4067 24.1602C27.5171 24.272 27.5801 24.4321 27.5801 24.6001C27.5801 24.9521 27.3042 25.2319 26.9575 25.2319C26.6108 25.2319 26.3433 24.9521 26.3433 24.6001ZM32.6064 27.8799C32.2046 28.0479 31.8027 28.1919 31.4165 28.208C30.8179 28.2397 30.1641 27.9922 29.8096 27.688C29.2583 27.2158 28.8643 26.9521 28.6987 26.1279C28.6279 25.7759 28.6675 25.2319 28.7305 24.9199C28.8721 24.248 28.7144 23.8159 28.2495 23.4238C27.8716 23.104 27.3911 23.0161 26.8633 23.0161C26.666 23.0161 26.4849 22.9277 26.3511 22.856C26.1304 22.7441 25.9492 22.4639 26.1226 22.1201C26.1777 22.0078 26.4458 21.7358 26.5088 21.688C27.2256 21.272 28.0527 21.4077 28.8169 21.7197C29.5259 22.0161 30.0615 22.5601 30.834 23.3281C31.6216 24.2559 31.7632 24.5117 32.2124 25.208C32.5669 25.752 32.8901 26.312 33.1104 26.9521C33.2446 27.3521 33.0713 27.6802 32.6064 27.8799Z"/>
|
|
6
|
+
</svg>
|