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 +5 -3
- package/README.zh.md +1 -1
- package/lib/client.js +136 -24
- package/lib/index.js +2 -2
- package/package.json +1 -1
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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.
|
|
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
|
-
|
|
3332
|
-
|
|
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 !== "
|
|
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
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
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 (
|
|
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
|
-
|
|
3440
|
+
abort.abort();
|
|
3356
3441
|
mounted?.dispose();
|
|
3357
3442
|
};
|
|
3358
|
-
}, [
|
|
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: [
|
|
3373
|
-
|
|
3374
|
-
|
|
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: [
|
|
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: "
|
|
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
|
-
|
|
81
|
-
|
|
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.
|
|
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",
|