@deepblend/dsh-blender-ui 0.2.0 → 0.2.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/lib/client.js +15 -0
- package/lib/index.js +101 -0
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -127,6 +127,10 @@ window.__ModuleLoader__.load({
|
|
|
127
127
|
state: '/deepblend/state',
|
|
128
128
|
projects: '/deepblend/projects',
|
|
129
129
|
capabilities: '/deepblend/capabilities',
|
|
130
|
+
// The one route here that is not fetched by the panel: it is the `href` of the export link in
|
|
131
|
+
// the header, so the BROWSER makes the request and writes the file. `contract/diagnostics.test.mjs`
|
|
132
|
+
// holds these four paths to the route table the Host actually matches.
|
|
133
|
+
diagnostics: '/deepblend/diagnostics',
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
/** Route for one project's own surfaces. */
|
|
@@ -1363,6 +1367,17 @@ window.__ModuleLoader__.load({
|
|
|
1363
1367
|
? Badge({ tone: 'warn', name: 'api', children: `hostApiVersion ${state.hostApiVersion} ≠ ${EXPECTED_HOST_API}` })
|
|
1364
1368
|
: null,
|
|
1365
1369
|
el('span', { style: { flex: 1 } }),
|
|
1370
|
+
// AN ANCHOR, NOT A BUTTON. The export is a download, and a download is what an anchor with
|
|
1371
|
+
// `download` does: the browser fetches the route and writes the file itself, in both faces
|
|
1372
|
+
// (React passes `href`/`download` through, the DOM binding sets them as attributes). A button
|
|
1373
|
+
// would need imperative blob plumbing in a renderer that has no place to put it.
|
|
1374
|
+
el('a', {
|
|
1375
|
+
className: 'db-btn',
|
|
1376
|
+
href: ROUTES.diagnostics,
|
|
1377
|
+
download: 'deepblend-diagnostics.json',
|
|
1378
|
+
'data-action': 'export-diagnostics',
|
|
1379
|
+
title: '导出一份可以附在问题里的诊断信息(版本、配置、项目与任务的摘要)',
|
|
1380
|
+
}, '导出诊断'),
|
|
1366
1381
|
Button({ action: 'reload', onClick: actions.reload, children: '刷新' }),
|
|
1367
1382
|
),
|
|
1368
1383
|
el('div', { className: 'db-nav' }, VIEWS.map(entry => el('button', {
|
package/lib/index.js
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
* Plane: Host composition (the Web Client half is `./client.js`)
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
+
import { homedir } from 'node:os'
|
|
27
|
+
|
|
26
28
|
import { Service } from '@deepseek-ai/cordis'
|
|
27
29
|
import z from '@deepseek-ai/schemastery'
|
|
28
30
|
|
|
@@ -39,6 +41,9 @@ import {
|
|
|
39
41
|
buildRevisionDiff,
|
|
40
42
|
buildSceneTree,
|
|
41
43
|
buildSettingsCard,
|
|
44
|
+
buildDiagnosticsBundle,
|
|
45
|
+
DIAGNOSTICS_JOB_LIMIT,
|
|
46
|
+
DIAGNOSTICS_FAILURE_LIMIT,
|
|
42
47
|
matchUiRoute,
|
|
43
48
|
assertKnownConfigKeys,
|
|
44
49
|
} from '@deepblend/dsh-blender-contracts'
|
|
@@ -476,6 +481,102 @@ export function createHandlers(ctx) {
|
|
|
476
481
|
}
|
|
477
482
|
},
|
|
478
483
|
|
|
484
|
+
/**
|
|
485
|
+
* The shareable diagnostic bundle (ledger C10).
|
|
486
|
+
*
|
|
487
|
+
* IT PROBES BLENDER, AND THAT IS THE POINT. The first question a maintainer asks about a report is
|
|
488
|
+
* "does Blender work on that machine", and a bundle that answered "not probed" would send the
|
|
489
|
+
* conversation back to the user. The probe is the same one the settings card already makes
|
|
490
|
+
* (coalesced, cached for `capabilitiesCacheMs`), and a FAILED probe is a structured value here
|
|
491
|
+
* rather than an error page — a machine without Blender is exactly the machine whose bundle
|
|
492
|
+
* somebody is about to send.
|
|
493
|
+
*
|
|
494
|
+
* `config` IS AN ALLOWLIST. Every key below is named on purpose. A spread would publish whatever
|
|
495
|
+
* configuration key is added next, and a diagnostic bundle is the last place that should happen.
|
|
496
|
+
*/
|
|
497
|
+
diagnostics: async () => {
|
|
498
|
+
let capabilities = null
|
|
499
|
+
let probeError = null
|
|
500
|
+
try {
|
|
501
|
+
capabilities = await studio().describeCapabilities({})
|
|
502
|
+
} catch (cause) {
|
|
503
|
+
const error = cause instanceof BlenderError
|
|
504
|
+
? cause
|
|
505
|
+
: new BlenderError(
|
|
506
|
+
BlenderErrorCode.CAPABILITY_PROBE_FAILED,
|
|
507
|
+
cause instanceof Error ? cause.message : String(cause),
|
|
508
|
+
)
|
|
509
|
+
probeError = { code: error.code ?? BlenderErrorCode.CAPABILITY_PROBE_FAILED, message: error.message ?? '' }
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const listed = await studio().listProjects()
|
|
513
|
+
const projects = []
|
|
514
|
+
const failures = []
|
|
515
|
+
for (const record of listed.projects) {
|
|
516
|
+
const jobs = await studio().listJobs({ projectId: record.projectId })
|
|
517
|
+
const byStatus = {}
|
|
518
|
+
for (const job of jobs.jobs) byStatus[job.status] = (byStatus[job.status] ?? 0) + 1
|
|
519
|
+
const failed = jobs.jobs.filter(job => job.status === 'failed').slice(-DIAGNOSTICS_JOB_LIMIT)
|
|
520
|
+
for (const job of failed) {
|
|
521
|
+
failures.push({
|
|
522
|
+
projectId: record.projectId,
|
|
523
|
+
jobId: job.jobId,
|
|
524
|
+
type: job.type ?? null,
|
|
525
|
+
errorCode: job.errorCode ?? null,
|
|
526
|
+
message: job.error ?? null,
|
|
527
|
+
frames: job.frames ?? null,
|
|
528
|
+
updatedAt: job.updatedAt ?? null,
|
|
529
|
+
})
|
|
530
|
+
}
|
|
531
|
+
projects.push({
|
|
532
|
+
projectId: record.projectId,
|
|
533
|
+
title: record.title ?? null,
|
|
534
|
+
currentRevision: record.currentRevision ?? null,
|
|
535
|
+
revisionCount: record.revisionCount ?? 0,
|
|
536
|
+
updatedAt: record.updatedAt ?? null,
|
|
537
|
+
jobs: {
|
|
538
|
+
total: jobs.jobs.length,
|
|
539
|
+
byStatus,
|
|
540
|
+
unfinished: jobs.unfinished?.length ?? 0,
|
|
541
|
+
recoveryFindings: jobs.recovery?.length ?? 0,
|
|
542
|
+
recent: jobs.jobs.slice(-DIAGNOSTICS_JOB_LIMIT).map(job => ({
|
|
543
|
+
jobId: job.jobId,
|
|
544
|
+
type: job.type ?? null,
|
|
545
|
+
status: job.status,
|
|
546
|
+
revision: job.revision ?? null,
|
|
547
|
+
frames: job.frames ?? null,
|
|
548
|
+
errorCode: job.errorCode ?? null,
|
|
549
|
+
updatedAt: job.updatedAt ?? null,
|
|
550
|
+
})),
|
|
551
|
+
},
|
|
552
|
+
})
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// The Host projects its own configuration (the keys are declared there, and this half's schema
|
|
556
|
+
// declares none of them), and the same projection supplies the workspace root below — one read,
|
|
557
|
+
// so the two fields cannot disagree about where this deployment keeps its state.
|
|
558
|
+
const projected = studio().describeConfiguration?.() ?? {}
|
|
559
|
+
|
|
560
|
+
return buildDiagnosticsBundle({
|
|
561
|
+
generatedAt: new Date().toISOString(),
|
|
562
|
+
product: { name: 'DeepBlend Studio', version: studio().productVersion?.() ?? null, hostApiVersion: HOST_API_VERSION },
|
|
563
|
+
environment: { node: process.version, platform: process.platform, arch: process.arch, home: homedir() },
|
|
564
|
+
blender: {
|
|
565
|
+
probed: true,
|
|
566
|
+
note: 'Blender was probed for this export (the same probe the settings card makes).',
|
|
567
|
+
capabilities,
|
|
568
|
+
error: probeError,
|
|
569
|
+
},
|
|
570
|
+
config: projected,
|
|
571
|
+
store: { projectsRoot: listed.projectsRoot ?? null, workspaceRoot: projected.workspaceRoot ?? null, projects },
|
|
572
|
+
failures,
|
|
573
|
+
notes: [
|
|
574
|
+
`jobs per project are capped at ${DIAGNOSTICS_JOB_LIMIT}`,
|
|
575
|
+
`failures are capped at ${DIAGNOSTICS_FAILURE_LIMIT}`,
|
|
576
|
+
],
|
|
577
|
+
})
|
|
578
|
+
},
|
|
579
|
+
|
|
479
580
|
/** Everything the panel needs to render itself from scratch, in one request. */
|
|
480
581
|
state: async ({ query }) => {
|
|
481
582
|
const listed = await studio().listProjects()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepblend/dsh-blender-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DeepBlend workbench UI: Host half (typed HTTP API) and Web Client half (sidebar, panels, tool cards).",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@deepblend/dsh-blender-contracts": "0.2.
|
|
22
|
+
"@deepblend/dsh-blender-contracts": "0.2.2"
|
|
23
23
|
},
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"repository": {
|