meshy-dsh 0.2.0 → 0.2.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 CHANGED
@@ -32,11 +32,13 @@ exported `MESHY_API_KEY` does not reach the CLI; the browser login does.
32
32
  In Web and Desktop, a **Meshy** entry appears in the left sidebar. It opens a Meshy page with your
33
33
  account and a few example requests: pick one and a new session opens with it filled in, ready to
34
34
  edit and send. **Settings → Meshy** (also the plugin's page under **Plugins**) shows the
35
- Meshy CLI version, whether you are signed in, which site (meshy.ai or meshy.cn) and your
36
- credits. **Sign in to Meshy** shows the approval link with a QR code for your phone and the code
35
+ Meshy CLI version, whether and how you are signed in (browser or API key), which site (meshy.ai
36
+ or meshy.cn) and your credits. **Sign in to Meshy** shows the approval link with a QR code for your phone and the code
37
37
  to check; the page picks up the approval by itself. Clicking a `.glb` file in the right sidebar, or
38
38
  the card of a model the agent delivers, opens an interactive 3D preview (drag to orbit, scroll to
39
- zoom, reset the view or let it turn) with its file size and triangle count. tui and headless have
39
+ zoom, reset the view or let it turn) with its file size and triangle count. Models up to 256 MB
40
+ open, past dsh's 32 MB limit for previewing a whole file, because the preview reads them in 2 MB
41
+ pieces. tui and headless have
40
42
  neither; the skills work the same everywhere.
41
43
 
42
44
  ## How it works
package/README.zh.md CHANGED
@@ -18,7 +18,7 @@ dsh plugin --profile web add github:meshy-dev/meshy-dsh
18
18
 
19
19
  ## 设置页与 3D 预览
20
20
 
21
- 在 Web 和 Desktop 里,左侧栏会出现 **Meshy** 入口,打开后是 Meshy 页面:显示账号,并给出几个示例需求,点一个就会新开会话并填好需求,改一改再发送。**设置 → Meshy**(插件页里也有)会显示 Meshy CLI 版本、是否已登录、所在站点(meshy.ai 或 meshy.cn)和积分。点**登录 Meshy**会显示批准链接、二维码和核对用的验证码,可以用手机扫码批准,页面会自动发现批准完成。在右侧栏点一个 `.glb` 文件,或点 agent 交付的模型卡片,会打开可交互的 3D 预览(拖动旋转,滚轮缩放,可复位视角或自动旋转),并显示文件大小和三角面数。tui 和 headless 没有这些界面,skill 在任何界面都一样能用。
21
+ 在 Web 和 Desktop 里,左侧栏会出现 **Meshy** 入口,打开后是 Meshy 页面:显示账号,并给出几个示例需求,点一个就会新开会话并填好需求,改一改再发送。**设置 → Meshy**(插件页里也有)会显示 Meshy CLI 版本、是否已登录及登录方式(浏览器或 API key)、所在站点(meshy.ai 或 meshy.cn)和积分。点**登录 Meshy**会显示批准链接、二维码和核对用的验证码,可以用手机扫码批准,页面会自动发现批准完成。在右侧栏点一个 `.glb` 文件,或点 agent 交付的模型卡片,会打开可交互的 3D 预览(拖动旋转,滚轮缩放,可复位视角或自动旋转),并显示文件大小和三角面数。预览会按 2 MB 分块读取模型,所以不受 dsh 整文件预览 32 MB 的上限限制,最大可预览 256 MB。tui 和 headless 没有这些界面,skill 在任何界面都一样能用。
22
22
 
23
23
  ## 工作方式
24
24
 
package/lib/client.js CHANGED
@@ -2542,6 +2542,12 @@ const name = "meshy-dsh-client";
2542
2542
  const inject = ["slots", "locale"];
2543
2543
  const NS = "meshy";
2544
2544
  const GLB_ID = "meshy-dsh/glb";
2545
+ /**
2546
+ * dsh refuses a complete-file read above 32 MiB (`maxFileBytes`), so the preview reads 2 MiB
2547
+ * windows (`maxBytes`, the per-window cap) and stops at its own limit instead.
2548
+ */
2549
+ const WINDOW = 2 << 20;
2550
+ const MAX_GLB = 256 << 20;
2545
2551
  /** The left-sidebar entry and its page in the main column share this id. */
2546
2552
  const PAGE_ID = "meshy";
2547
2553
  const EXAMPLES = [
@@ -2612,7 +2618,9 @@ const en = {
2612
2618
  "site.cn": "China",
2613
2619
  credits: "Credits",
2614
2620
  "credits.get": "Get credits",
2615
- account: "Account",
2621
+ method: "Signed in with",
2622
+ "method.browser": "Browser",
2623
+ "method.apiKey": "API key",
2616
2624
  more: "Manage account",
2617
2625
  signIn: "Sign in to Meshy",
2618
2626
  "signIn.hint": "Approve in your browser once; the Meshy skills then use this sign-in.",
@@ -2639,6 +2647,7 @@ const en = {
2639
2647
  "viewer.title": "3D model",
2640
2648
  "viewer.loading": "Loading model…",
2641
2649
  "viewer.failed": "This model cannot be shown",
2650
+ "viewer.tooLarge": "The model is {size}, over the {limit} preview limit. Remesh it to fewer faces to preview it.",
2642
2651
  "viewer.triangles": "{count} triangles",
2643
2652
  "viewer.reset": "Reset view",
2644
2653
  "viewer.spin": "Auto-rotate",
@@ -2674,7 +2683,9 @@ const zh = {
2674
2683
  "site.cn": "中国站",
2675
2684
  credits: "积分",
2676
2685
  "credits.get": "获取积分",
2677
- account: "账号",
2686
+ method: "登录方式",
2687
+ "method.browser": "浏览器登录",
2688
+ "method.apiKey": "API key",
2678
2689
  more: "管理账号",
2679
2690
  signIn: "登录 Meshy",
2680
2691
  "signIn.hint": "在浏览器里批准一次,Meshy 技能之后都会使用这个登录。",
@@ -2701,6 +2712,7 @@ const zh = {
2701
2712
  "viewer.title": "3D 模型",
2702
2713
  "viewer.loading": "正在加载模型…",
2703
2714
  "viewer.failed": "无法显示这个模型",
2715
+ "viewer.tooLarge": "模型有 {size},超过 {limit} 的预览上限。减面后再预览。",
2704
2716
  "viewer.triangles": "{count} 个三角面",
2705
2717
  "viewer.reset": "重置视角",
2706
2718
  "viewer.spin": "自动旋转",
@@ -3057,9 +3069,9 @@ function MeshyPanel({ t, view }) {
3057
3069
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "meshy-divider" }),
3058
3070
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3059
3071
  className: "meshy-row",
3060
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("account") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3072
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("method") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3061
3073
  className: "meshy-muted",
3062
- children: auth.credential ?? auth.source ?? "—"
3074
+ children: auth.method ? t(`method.${auth.method}`) : "—"
3063
3075
  })]
3064
3076
  }),
3065
3077
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "meshy-divider" }),
@@ -3328,34 +3340,111 @@ function MeshyPage({ t }) {
3328
3340
  })
3329
3341
  });
3330
3342
  }
3331
- /** The keyed `sidebar.right.tab.document` body for .glb, fed the file's complete bytes. */
3332
- function GlbBody({ content, t }) {
3343
+ let workspaceFiles;
3344
+ var TooLarge = class extends Error {
3345
+ size;
3346
+ constructor(size) {
3347
+ super("too large");
3348
+ this.size = size;
3349
+ }
3350
+ };
3351
+ /** A preview tab's `dsh-resource://file/session/<id>/<path>` address (dsh-util-workspace-path's grammar). */
3352
+ function sessionFileOf(address) {
3353
+ const m = /^dsh-resource:\/\/file\/session\/([^/?#]+)\/([^?#]+)/.exec(address);
3354
+ return m ? {
3355
+ sessionId: decodeURIComponent(m[1]),
3356
+ path: m[2].split("/").map(decodeURIComponent).join("/")
3357
+ } : void 0;
3358
+ }
3359
+ /** Read the whole file window by window; a change mid-read starts over on the new version. */
3360
+ async function readWindows(address, signal, progress) {
3361
+ const file = sessionFileOf(address);
3362
+ if (!file || !workspaceFiles) throw new Error(`cannot read ${address}`);
3363
+ let chunks = [];
3364
+ let offset = 0;
3365
+ let version;
3366
+ let length = WINDOW;
3367
+ for (;;) {
3368
+ const res = await workspaceFiles.readBytes(file.sessionId, file.path, { range: {
3369
+ offset,
3370
+ length
3371
+ } }, signal);
3372
+ signal.throwIfAborted();
3373
+ if (!res.ok) {
3374
+ const limit = res.error.details?.limit;
3375
+ if (res.error.code === "workspace-file/too-large" && limit && limit < length) {
3376
+ length = limit;
3377
+ continue;
3378
+ }
3379
+ throw new Error(res.error.message ?? res.error.code);
3380
+ }
3381
+ const w = res.value;
3382
+ if (version !== void 0 && w.version !== version) {
3383
+ chunks = [];
3384
+ offset = 0;
3385
+ version = void 0;
3386
+ continue;
3387
+ }
3388
+ version = w.version;
3389
+ if ((w.bytes ?? 0) > MAX_GLB) throw new TooLarge(w.bytes);
3390
+ chunks.push(w.data);
3391
+ offset += w.data.byteLength;
3392
+ if (offset > MAX_GLB) throw new TooLarge(offset);
3393
+ progress(offset, w.bytes ?? offset);
3394
+ if (w.eof || w.data.byteLength === 0) break;
3395
+ }
3396
+ const data = new Uint8Array(offset);
3397
+ let at = 0;
3398
+ for (const c of chunks) {
3399
+ data.set(c, at);
3400
+ at += c.byteLength;
3401
+ }
3402
+ return {
3403
+ data,
3404
+ version
3405
+ };
3406
+ }
3407
+ /** The keyed `sidebar.right.tab.document` body for .glb; it reads the file itself (renderer loading). */
3408
+ function GlbBody({ content, resourceAddress, t }) {
3333
3409
  const host = (0, react.useRef)(null);
3334
3410
  const [viewer, setViewer] = (0, react.useState)();
3335
3411
  const [error, setError] = (0, react.useState)();
3336
3412
  const [spinning, setSpinning] = (0, react.useState)(false);
3413
+ const [size, setSize] = (0, react.useState)(0);
3414
+ const [progress, setProgress] = (0, react.useState)();
3337
3415
  (0, react.useEffect)(() => {
3338
3416
  const el = host.current;
3339
- if (content.kind !== "bytes" || !content.data || !el) return;
3417
+ if (content.kind !== "renderer" || !el) return;
3418
+ const abort = new AbortController();
3340
3419
  let mounted;
3341
- let cancelled = false;
3342
3420
  setError(void 0);
3343
3421
  setViewer(void 0);
3344
3422
  setSpinning(false);
3345
- require.async("./client.viewer.js").then((module) => module.mountViewer(el, content.data)).then((v) => {
3346
- if (cancelled) v.dispose();
3347
- else {
3348
- mounted = v;
3349
- setViewer(v);
3350
- }
3423
+ setProgress(void 0);
3424
+ readWindows(resourceAddress, abort.signal, (read, total) => setProgress([read, total])).then(async ({ data, version }) => {
3425
+ const v = await (await require.async("./client.viewer.js")).mountViewer(el, data);
3426
+ if (abort.signal.aborted) return v.dispose();
3427
+ mounted = v;
3428
+ setSize(data.byteLength);
3429
+ setViewer(v);
3430
+ content.loaded?.(version);
3351
3431
  }).catch((err) => {
3352
- if (!cancelled) setError(String(err?.message ?? err));
3432
+ if (abort.signal.aborted) return;
3433
+ setError(err instanceof TooLarge ? t("viewer.tooLarge", {
3434
+ size: (0, _deepseek_ai_dsh_client_ui_primitives.fileSizeText)(err.size),
3435
+ limit: (0, _deepseek_ai_dsh_client_ui_primitives.fileSizeText)(MAX_GLB)
3436
+ }) : String(err?.message ?? err));
3437
+ content.failed?.();
3353
3438
  });
3354
3439
  return () => {
3355
- cancelled = true;
3440
+ abort.abort();
3356
3441
  mounted?.dispose();
3357
3442
  };
3358
- }, [content]);
3443
+ }, [
3444
+ content.kind,
3445
+ content.revision,
3446
+ resourceAddress
3447
+ ]);
3359
3448
  const toggle = () => {
3360
3449
  viewer?.setAutoRotate(!spinning);
3361
3450
  setSpinning(!spinning);
@@ -3369,10 +3458,21 @@ function GlbBody({ content, t }) {
3369
3458
  }),
3370
3459
  !viewer && !error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3371
3460
  className: "meshy-overlay meshy-muted",
3372
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Logo, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.TextShimmer, {
3373
- active: true,
3374
- children: t("viewer.loading")
3375
- })]
3461
+ children: [
3462
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Logo, {}),
3463
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.TextShimmer, {
3464
+ active: true,
3465
+ children: t("viewer.loading")
3466
+ }),
3467
+ progress && progress[1] > WINDOW && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3468
+ className: "meshy-small",
3469
+ children: [
3470
+ (0, _deepseek_ai_dsh_client_ui_primitives.fileSizeText)(progress[0]),
3471
+ " / ",
3472
+ (0, _deepseek_ai_dsh_client_ui_primitives.fileSizeText)(progress[1])
3473
+ ]
3474
+ })
3475
+ ]
3376
3476
  }),
3377
3477
  error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3378
3478
  className: "meshy-overlay",
@@ -3390,7 +3490,11 @@ function GlbBody({ content, t }) {
3390
3490
  className: "meshy-bar",
3391
3491
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3392
3492
  className: "meshy-chip",
3393
- children: [content.data ? `${(0, _deepseek_ai_dsh_client_ui_primitives.fileSizeText)(content.data.byteLength)} · ` : "", t("viewer.triangles", { count: viewer.triangles.toLocaleString() })]
3493
+ children: [
3494
+ (0, _deepseek_ai_dsh_client_ui_primitives.fileSizeText)(size),
3495
+ " · ",
3496
+ t("viewer.triangles", { count: viewer.triangles.toLocaleString() })
3497
+ ]
3394
3498
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3395
3499
  className: "meshy-tools",
3396
3500
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
@@ -3468,15 +3572,23 @@ function apply(ctx) {
3468
3572
  ctx.inject([
3469
3573
  "documentPreviews",
3470
3574
  "slots",
3471
- "locale"
3575
+ "locale",
3576
+ "remote",
3577
+ "remote.workspaceFiles"
3472
3578
  ], (ctx) => {
3579
+ ctx.effect(() => {
3580
+ workspaceFiles = ctx.remote.workspaceFiles;
3581
+ return () => {
3582
+ workspaceFiles = void 0;
3583
+ };
3584
+ }, "meshy-dsh: workspace file reads");
3473
3585
  ctx.effect(() => ctx.documentPreviews.register({
3474
3586
  id: GLB_ID,
3475
3587
  extensions: ["glb"],
3476
3588
  binaryExtensions: ["glb"],
3477
3589
  priority: "extension",
3478
3590
  title: () => ctx.locale.bind(NS)("viewer.title"),
3479
- loading: "bytes-complete"
3591
+ loading: "renderer"
3480
3592
  }), "meshy-dsh: glb preview");
3481
3593
  ctx.effect(() => ctx.slots.inject("sidebar.right.tab.document", () => ctx.slots.register({
3482
3594
  name: "sidebar.right.tab.document",
package/lib/index.js CHANGED
@@ -77,8 +77,8 @@ export async function status() {
77
77
  auth: {
78
78
  authenticated: r.authenticated === true,
79
79
  verified: r.verified === true,
80
- source: r.source ?? null,
81
- credential: r.credential ?? null,
80
+ // How it signed in, never the credential: an OAuth access token starts msy_at_.
81
+ method: r.authenticated !== true ? null : String(r.credential ?? '').startsWith('msy_at_') ? 'browser' : 'apiKey',
82
82
  region: typeof r.base_url_v1 === 'string' && r.base_url_v1.includes('meshy.cn') ? 'cn' : 'global',
83
83
  balance: typeof r.balance?.balance === 'number' ? r.balance.balance : null,
84
84
  hint: r.hint ?? null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meshy-dsh",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Meshy skills for DeepSeek Harness: textured, rigged and animated 3D models or print-ready files from text or images, through meshy-cli, with a Meshy settings page and a 3D preview for GLB files.",
5
5
  "keywords": [
6
6
  "dsh-plugin",