@genkaku/dsh-worker-plugin 0.1.21 → 0.1.23

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.
Files changed (2) hide show
  1. package/lib/client.js +124 -43
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -91,6 +91,37 @@ var PANEL_CSS = `
91
91
  .gk-price input { text-align: right; }
92
92
  .gk-pull { width: 260px; max-width: 100%; }
93
93
  .gk-muted { color: var(--dsw-alias-label-tertiary); font-size: 12px; }
94
+ /* The panel can hold every model Ollama has (two dozen is normal). A bounded
95
+ list keeps the download box, Save and the Worker section reachable.
96
+ macOS auto-hides native scrollbars (and Chromium screenshots never paint
97
+ them), so the bar is drawn by us: always visible, and click-to-jump. */
98
+ .gk-models-wrap { position: relative; padding-right: 12px; }
99
+ .gk-models {
100
+ max-height: min(420px, 45vh);
101
+ overflow-y: auto;
102
+ overscroll-behavior: contain;
103
+ scrollbar-width: none;
104
+ }
105
+ .gk-models::-webkit-scrollbar { display: none; }
106
+ .gk-rail {
107
+ position: absolute;
108
+ top: 2px;
109
+ bottom: 2px;
110
+ right: 2px;
111
+ width: 8px;
112
+ border-radius: 4px;
113
+ background: rgba(127,137,153,.16);
114
+ cursor: pointer;
115
+ }
116
+ .gk-thumb {
117
+ position: absolute;
118
+ left: 0;
119
+ width: 8px;
120
+ min-height: 16px;
121
+ border-radius: 4px;
122
+ background: rgba(127,137,153,.7);
123
+ }
124
+ .gk-rail:hover .gk-thumb { background: rgba(127,137,153,.9); }
94
125
  `;
95
126
  function Icon({ size = 16 }) {
96
127
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "inline-flex", width: size, height: size }, "aria-hidden": "true", children: [
@@ -135,6 +166,44 @@ function seedDraft(status) {
135
166
  function draftSignature(draft) {
136
167
  return Object.entries(draft).map(([model, price]) => `${model}=${price}`).sort().join("|");
137
168
  }
169
+ function useScrollThumb() {
170
+ const [node, setNode] = (0, import_react.useState)(null);
171
+ const nodeRef = (0, import_react.useRef)(null);
172
+ const [thumb, setThumb] = (0, import_react.useState)(null);
173
+ const attach = (0, import_react.useCallback)((next) => {
174
+ nodeRef.current = next;
175
+ setNode(next);
176
+ }, []);
177
+ const update = (0, import_react.useCallback)(() => {
178
+ const el = nodeRef.current;
179
+ if (el == null) return;
180
+ const ratio = el.scrollHeight > el.clientHeight + 1 ? el.clientHeight / el.scrollHeight : 1;
181
+ setThumb(ratio >= 1 ? null : { top: el.scrollTop / el.scrollHeight * 100, height: Math.max(ratio * 100, 8) });
182
+ }, []);
183
+ (0, import_react.useEffect)(() => {
184
+ if (node == null) return;
185
+ update();
186
+ node.addEventListener("scroll", update, { passive: true });
187
+ const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(update);
188
+ ro?.observe(node);
189
+ const mo = typeof MutationObserver === "undefined" ? null : new MutationObserver(update);
190
+ mo?.observe(node, { childList: true, subtree: true });
191
+ return () => {
192
+ node.removeEventListener("scroll", update);
193
+ ro?.disconnect();
194
+ mo?.disconnect();
195
+ };
196
+ }, [node, update]);
197
+ const jump = (0, import_react.useCallback)((event) => {
198
+ const el = nodeRef.current;
199
+ const rail = event.currentTarget;
200
+ if (el == null || rail == null) return;
201
+ const rect = rail.getBoundingClientRect();
202
+ if (rect.height <= 0) return;
203
+ el.scrollTop = Math.max(0, (event.clientY - rect.top) / rect.height) * el.scrollHeight;
204
+ }, []);
205
+ return { attach, thumb, update, jump };
206
+ }
138
207
  function Section({ title, children }) {
139
208
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: box, children: [
140
209
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: caption, children: title }),
@@ -152,6 +221,7 @@ function Page() {
152
221
  const [pullModel, setPullModel] = (0, import_react.useState)("");
153
222
  const [pull, setPull] = (0, import_react.useState)(null);
154
223
  const seeded = (0, import_react.useRef)(false);
224
+ const models = useScrollThumb();
155
225
  const [origin, setOrigin] = (0, import_react.useState)(originOf(CANDIDATE_PORTS[0]));
156
226
  const originRef = (0, import_react.useRef)(origin);
157
227
  originRef.current = origin;
@@ -451,53 +521,64 @@ function Page() {
451
521
  "\u672A\u52FE\u9078\u7684\u6A21\u578B\u4ECD\u7559\u5728\u672C\u6A5F\uFF0C\u4F46\u4E0D\u6703\u4E0A\u67B6\u3002\u50F9\u683C\u70BA\u6BCF\u5C0F\u6642 USDC\uFF1B0 \u8868\u793A\u514D\u8CBB\u3002"
452
522
  ) }),
453
523
  names.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: dim, children: t("No local models yet. Download one below.", "\u672C\u6A5F\u9084\u6C92\u6709\u6A21\u578B\uFF0C\u53EF\u5728\u4E0B\u65B9\u4E0B\u8F09\u3002") }),
454
- names.map((name) => {
455
- const on = name in offered;
456
- const tier = tierOf.get(name);
457
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gk-row", children: [
458
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
459
- "input",
460
- {
461
- type: "checkbox",
462
- checked: on,
463
- "aria-label": name,
464
- onChange: () => {
465
- toggleOffer(name);
466
- }
467
- }
468
- ),
469
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: [
470
- name,
471
- tier !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...dim, fontSize: 11, marginLeft: 8 }, children: tier === "chat" ? t("not for agents", "\u4E0D\u9069\u7528\u65BC agent") : tier })
472
- ] }),
473
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gk-muted", style: { textAlign: "right" }, children: gb(sizeOf.get(name)) }),
474
- on ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
524
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gk-models-wrap", children: [
525
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gk-models", ref: models.attach, onScroll: models.update, children: names.map((name) => {
526
+ const on = name in offered;
527
+ const tier = tierOf.get(name);
528
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "gk-row", children: [
475
529
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
476
- import_dsh_client_ui_primitives.Input,
530
+ "input",
477
531
  {
478
- className: "gk-price",
479
- value: offered[name] ?? "",
480
- inputMode: "decimal",
481
- "aria-label": t(`Price per hour for ${name}`, `${name} \u6BCF\u5C0F\u6642\u50F9\u683C`),
482
- onChange: (e) => setDraft({ ...draft ?? {}, [name]: e.target.value })
532
+ type: "checkbox",
533
+ checked: on,
534
+ "aria-label": name,
535
+ onChange: () => {
536
+ toggleOffer(name);
537
+ }
483
538
  }
484
539
  ),
485
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gk-muted", children: "/h" })
486
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
487
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
488
- import_dsh_client_ui_primitives.Button,
489
- {
490
- variant: "ghost",
491
- size: "sm",
492
- disabled: busy === `remove:${name}`,
493
- onClick: () => {
494
- void removeModel(name);
495
- },
496
- children: t("delete", "\u522A\u9664")
497
- }
498
- )
499
- ] }, name);
500
- }),
540
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: [
541
+ name,
542
+ tier !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...dim, fontSize: 11, marginLeft: 8 }, children: tier === "chat" ? t("not for agents", "\u4E0D\u9069\u7528\u65BC agent") : tier })
543
+ ] }),
544
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gk-muted", style: { textAlign: "right" }, children: gb(sizeOf.get(name)) }),
545
+ on ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
546
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
547
+ import_dsh_client_ui_primitives.Input,
548
+ {
549
+ className: "gk-price",
550
+ value: offered[name] ?? "",
551
+ inputMode: "decimal",
552
+ "aria-label": t(`Price per hour for ${name}`, `${name} \u6BCF\u5C0F\u6642\u50F9\u683C`),
553
+ onChange: (e) => setDraft({ ...draft ?? {}, [name]: e.target.value })
554
+ }
555
+ ),
556
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gk-muted", children: "/h" })
557
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
558
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
559
+ import_dsh_client_ui_primitives.Button,
560
+ {
561
+ variant: "ghost",
562
+ size: "sm",
563
+ disabled: busy === `remove:${name}`,
564
+ onClick: () => {
565
+ void removeModel(name);
566
+ },
567
+ children: t("delete", "\u522A\u9664")
568
+ }
569
+ )
570
+ ] }, name);
571
+ }) }),
572
+ models.thumb != null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
573
+ "div",
574
+ {
575
+ className: "gk-rail",
576
+ "aria-hidden": "true",
577
+ onPointerDown: (e) => models.jump(e),
578
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "gk-thumb", style: { top: `${models.thumb.top}%`, height: `${models.thumb.height}%` } })
579
+ }
580
+ )
581
+ ] }),
501
582
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...row, justifyContent: "flex-start", gap: 8, marginTop: 12 }, children: [
502
583
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
503
584
  import_dsh_client_ui_primitives.Input,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genkaku/dsh-worker-plugin",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Share this machine's Ollama models on the GenKaKu network",
5
5
  "license": "MIT",
6
6
  "type": "module",