dsh-her 0.1.0-beta.1 → 0.1.0
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 +66 -10
- package/lib/core/workshop.js +65 -3
- package/lib/dsh/workshop.js +14 -4
- package/lib/types/core/workshop.d.ts +28 -2
- package/lib/types/dsh/workshop.d.ts +3 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -182,6 +182,33 @@ function screenColor(card) {
|
|
|
182
182
|
return /绿/.test(`${card.outfit}${card.extra}${card.sentence}`) ? { name: "\u7EAF\u7070\u8272", hex: "#808080" } : { name: "\u7EAF\u7EFF\u8272", hex: "#00B140" };
|
|
183
183
|
}
|
|
184
184
|
var POSE_WORDS = { stand: "\u7AD9\u7740", sit: "\u5750\u7740", lie: "\u8EBA\u7740" };
|
|
185
|
+
var WEB_HOW = '\u4E00\u6BB5\u6BB5\u751F\u6210\uFF1A\u4E0A\u4F20\u9996\u5E27\uFF0C\u7C98\u8D34\u63D0\u793A\u8BCD\uFF08\u6709"\u8D1F\u9762\u63D0\u793A\u8BCD"\u6846\u5C31\u628A\u8D1F\u9762\u63D0\u793A\u8BCD\u4E5F\u7C98\u8FDB\u53BB\uFF0C\u6709"\u5C3E\u5E27"\u5C31\u4E5F\u653E\u9996\u5E27\u90A3\u5F20\u56FE\uFF09\uFF0C\u4E0B\u8F7D\u540E\u6309\u6587\u4EF6\u540D\u5B58\u8FDB\u5BF9\u5E94\u7684\u6587\u4EF6\u5939\u3002\u300C\u751F\u6210\u6E05\u5355.md\u300D\u91CC\u6BCF\u6BB5\u90FD\u5217\u597D\u4E86\u3002';
|
|
186
|
+
var GENERATORS = {
|
|
187
|
+
minimax: {
|
|
188
|
+
name: "MiniMax Code",
|
|
189
|
+
label: "MiniMax Code\uFF08\u7ED9\u5B83\u4EFB\u52A1\u6587\u4EF6\uFF0C\u81EA\u52A8\u751F\u6210\u4E0B\u8F7D\uFF09",
|
|
190
|
+
settings: "MiniMax-Hailuo-2.3 \u56FE\u751F\u89C6\u9891\uFF0C1080P\uFF0C6 \u79D2\u3002",
|
|
191
|
+
how: '\u5728 MiniMax Code \u91CC\u8BF4\uFF1A"\u6309 <\u9879\u76EE\u6587\u4EF6\u5939>/MiniMax\u751F\u6210\u4EFB\u52A1.md \u751F\u6210"\uFF0C\u5B83\u4F1A\u4E00\u6BB5\u6BB5\u751F\u6210\u3001\u4E0B\u8F7D\u3001\u5B58\u597D\u3002'
|
|
192
|
+
},
|
|
193
|
+
comfyui: {
|
|
194
|
+
name: "ComfyUI",
|
|
195
|
+
label: "ComfyUI\uFF08\u672C\u5730\uFF0CWan 2.2 \u7B49\u56FE\u751F\u89C6\u9891\u5DE5\u4F5C\u6D41\uFF09",
|
|
196
|
+
settings: "\u56FE\u751F\u89C6\u9891\u5DE5\u4F5C\u6D41\uFF08\u6BD4\u5982 Wan 2.2 I2V\uFF09\uFF0C\u5C3A\u5BF8\u548C\u9996\u5E27\u540C\u6BD4\u4F8B\u3001\u77ED\u8FB9 720 \u4EE5\u4E0A\uFF0C\u7EA6 5 \u79D2\uFF0816 fps \u65F6 81 \u5E27\uFF09\uFF0C\u8D1F\u9762\u63D0\u793A\u8BCD\u653E\u8FDB\u8D1F\u9762\u8282\u70B9\uFF0C\u6BCF\u6BB5\u7528\u540C\u4E00\u5957\u53C2\u6570\u3002",
|
|
197
|
+
how: "\u7528\u4F60\u7684\u56FE\u751F\u89C6\u9891\u5DE5\u4F5C\u6D41\u4E00\u6BB5\u6BB5\u8DD1\uFF0C\u6216\u8005\u7528\u811A\u672C\u8BFB\u300CComfyUI\u751F\u6210\u6E05\u5355.json\u300D\uFF08\u6BCF\u6BB5\u7684\u9996\u5E27\u3001\u63D0\u793A\u8BCD\u3001\u8D1F\u9762\u63D0\u793A\u8BCD\u3001\u4FDD\u5B58\u8DEF\u5F84\uFF09\u6279\u91CF\u8DD1\u3002\u8F93\u51FA\u5B58\u6210\u6E05\u5355\u91CC\u7684\u6587\u4EF6\u540D\u3002"
|
|
198
|
+
},
|
|
199
|
+
jimeng: { name: "\u5373\u68A6", label: "\u5373\u68A6\uFF08\u7F51\u9875 / App\uFF09", settings: "\u56FE\u751F\u89C6\u9891\uFF0C1080P\uFF0C5 \u79D2\u3002", how: `\u5728\u5373\u68A6\u91CC${WEB_HOW}` },
|
|
200
|
+
kling: { name: "\u53EF\u7075", label: "\u53EF\u7075\uFF08\u7F51\u9875 / App\uFF09", settings: "\u56FE\u751F\u89C6\u9891\uFF0C1080P\uFF0C5 \u79D2\u3002", how: `\u5728\u53EF\u7075\u91CC${WEB_HOW}` },
|
|
201
|
+
hailuo: { name: "\u6D77\u87BA AI", label: "\u6D77\u87BA AI\uFF08MiniMax \u7684\u7F51\u9875\u7248\uFF09", settings: "\u56FE\u751F\u89C6\u9891\uFF0C1080P\uFF0C6 \u79D2\u3002", how: `\u5728\u6D77\u87BA AI \u91CC${WEB_HOW}` },
|
|
202
|
+
wanx: { name: "\u901A\u4E49\u4E07\u76F8", label: "\u901A\u4E49\u4E07\u76F8\uFF08\u7F51\u9875 / \u963F\u91CC\u4E91\u767E\u70BC\uFF09", settings: "\u56FE\u751F\u89C6\u9891\uFF0C1080P\uFF0C5 \u79D2\u3002", how: `\u5728\u901A\u4E49\u4E07\u76F8\u91CC${WEB_HOW}` },
|
|
203
|
+
vidu: { name: "Vidu", label: "Vidu\uFF08\u7F51\u9875\uFF09", settings: "\u56FE\u751F\u89C6\u9891\uFF0C1080P\uFF0C4\u20135 \u79D2\u3002", how: `\u5728 Vidu \u91CC${WEB_HOW}` }
|
|
204
|
+
};
|
|
205
|
+
var MANUAL_LABEL = "\u624B\u5DE5\u586B\u5199\uFF08\u522B\u7684\u5DE5\u5177\uFF0C\u81EA\u5DF1\u5199\u540D\u5B57\u548C\u8BBE\u7F6E\uFF09";
|
|
206
|
+
function generatorInfo(project) {
|
|
207
|
+
if (project.generator !== "manual" && project.generator in GENERATORS) return GENERATORS[project.generator];
|
|
208
|
+
const name = project.custom?.name.trim() || "\u4F60\u7528\u7684\u5DE5\u5177";
|
|
209
|
+
const settings = project.custom?.settings.trim() || "\u56FE\u751F\u89C6\u9891\uFF0C\u548C\u9996\u5E27\u540C\u6BD4\u4F8B\uFF0C1080P\uFF0C5\u20136 \u79D2\uFF0C\u6BCF\u6BB5\u7528\u540C\u4E00\u5957\u8BBE\u7F6E\u3002";
|
|
210
|
+
return { name, label: MANUAL_LABEL, settings, how: `\u5728${name}\u91CC${WEB_HOW}` };
|
|
211
|
+
}
|
|
185
212
|
var PRESETS = [
|
|
186
213
|
{ role: "idle", n: 1, required: true, action: "\u5B89\u9759\u5730{pose}\uFF0C\u81EA\u7136\u547C\u5438\uFF0C\u5076\u5C14\u7728\u773C\uFF0C\u5934\u6781\u8F7B\u5FAE\u5730\u6643\u52A8\uFF0C\u5634\u5DF4\u95ED\u5408\uFF0C\u6D45\u6D45\u5730\u7B11" },
|
|
187
214
|
{ role: "talk", n: 1, required: true, action: "\u770B\u7740\u955C\u5934\u6E29\u67D4\u5730\u8BF4\u8BDD\uFF0C\u5634\u5DF4\u81EA\u7136\u6301\u7EED\u5F00\u5408\uFF0C\u8F7B\u8F7B\u70B9\u5934\uFF0C\u624B\u6709\u5F88\u5C0F\u7684\u52A8\u4F5C\u3002\u6700\u540E\u4E00\u79D2\u95ED\u5634\u56DE\u5230\u539F\u59FF\u52BF" },
|
|
@@ -235,7 +262,8 @@ function rules(project, kind, scene) {
|
|
|
235
262
|
].join("\u3002") + "\u3002";
|
|
236
263
|
}
|
|
237
264
|
function clipPrompt(project, kind, clip, scene) {
|
|
238
|
-
|
|
265
|
+
const prefix = project.generator === "manual" ? project.custom?.prefix.trim() ?? "" : "";
|
|
266
|
+
return `${prefix === "" ? "" : `${prefix.replace(/[,。,.\s]+$/, "")}\u3002`}${rules(project, kind, scene)}\u5979\u662F\uFF1A${describe(project.card)}\u3002${clip.action}\u3002`;
|
|
239
267
|
}
|
|
240
268
|
function referencePrompt(project) {
|
|
241
269
|
const color = screenColor(project.card);
|
|
@@ -391,7 +419,7 @@ function Workshop({ state, apply: apply2 }) {
|
|
|
391
419
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
392
420
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "her-card", children: [
|
|
393
421
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "\u5F62\u8C61\u5DE5\u574A" }),
|
|
394
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "her-muted", children: "\u505A\u4E00\u4E2A\u4F60\u81EA\u5DF1\u7684\u5979\uFF1A\u9009\u597D\u5979\u7684\u6837\u5B50\u3001\u5979\u4F4F\u5728\u54EA\u4E9B\u573A\u666F\u91CC\uFF0C\u8FD9\u91CC\u4F1A\u628A\u6BCF\u4E00\u5F20\u56FE\u3001\u6BCF\u4E00\u6BB5\u89C6\u9891\u7684\u63D0\u793A\u8BCD\u90FD\u5199\u597D\uFF0C\u653E\u8FDB\u300C\u6587\u7A3F / dsh-her \u7D20\u6750\u300D\u91CC\u7684\u4E00\u4E2A\u6587\u4EF6\u5939\u3002\u4F60\u7528\
|
|
422
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "her-muted", children: "\u505A\u4E00\u4E2A\u4F60\u81EA\u5DF1\u7684\u5979\uFF1A\u9009\u597D\u5979\u7684\u6837\u5B50\u3001\u5979\u4F4F\u5728\u54EA\u4E9B\u573A\u666F\u91CC\uFF0C\u8FD9\u91CC\u4F1A\u628A\u6BCF\u4E00\u5F20\u56FE\u3001\u6BCF\u4E00\u6BB5\u89C6\u9891\u7684\u63D0\u793A\u8BCD\u90FD\u5199\u597D\uFF0C\u653E\u8FDB\u300C\u6587\u7A3F / dsh-her \u7D20\u6750\u300D\u91CC\u7684\u4E00\u4E2A\u6587\u4EF6\u5939\u3002\u4F60\u7528\u5373\u68A6\u3001\u53EF\u7075\u3001MiniMax\u3001ComfyUI \u8FD9\u4E9B\u5DE5\u5177\u751F\u6210\uFF0C\u653E\u56DE\u6587\u4EF6\u5939\uFF0C\u518D\u4E00\u952E\u5BFC\u5165\u3002" }),
|
|
395
423
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "her-grid", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { className: `her-choice ${item.project.id === open ? "on" : ""}`, onClick: () => choose(item), children: [
|
|
396
424
|
item.project.name,
|
|
397
425
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: progressText(item.status) })
|
|
@@ -428,12 +456,37 @@ function Workshop({ state, apply: apply2 }) {
|
|
|
428
456
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "her-label", children: "\u7528\u4EC0\u4E48\u751F\u6210\u89C6\u9891" }),
|
|
429
457
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "her-select", value: draft.generator, onChange: (e) => edit((p) => {
|
|
430
458
|
p.generator = e.target.value;
|
|
459
|
+
if (p.generator === "manual") p.custom ??= { name: "", settings: "", prefix: "" };
|
|
431
460
|
}), children: [
|
|
432
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value:
|
|
433
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "manual", children:
|
|
461
|
+
Object.keys(GENERATORS).map((id) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: id, children: GENERATORS[id].label }, id)),
|
|
462
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "manual", children: MANUAL_LABEL })
|
|
434
463
|
] })
|
|
435
464
|
] })
|
|
436
465
|
] }),
|
|
466
|
+
draft.generator === "manual" && draft.custom !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "her-row", children: [
|
|
467
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: 1, minWidth: 140 }, children: [
|
|
468
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "her-label", children: "\u5DE5\u5177\u540D\u5B57" }),
|
|
469
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "her-input", value: draft.custom.name, placeholder: "\u6BD4\u5982 Runway\u3001PixVerse\u3001\u81EA\u5DF1\u7684\u5DE5\u4F5C\u6D41", onChange: (e) => edit((p) => {
|
|
470
|
+
p.custom.name = e.target.value;
|
|
471
|
+
}) })
|
|
472
|
+
] }),
|
|
473
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: 2, minWidth: 220 }, children: [
|
|
474
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "her-label", children: "\u7EDF\u4E00\u8BBE\u7F6E\uFF08\u5199\u8FDB\u6BCF\u4E2A\u63D0\u793A\u8BCD\u6587\u4EF6\u7684\u5F00\u5934\uFF09" }),
|
|
475
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "her-input", value: draft.custom.settings, placeholder: generatorInfo({ generator: "manual" }).settings, onChange: (e) => edit((p) => {
|
|
476
|
+
p.custom.settings = e.target.value;
|
|
477
|
+
}) })
|
|
478
|
+
] }),
|
|
479
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: "1 1 100%" }, children: [
|
|
480
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "her-label", children: "\u6BCF\u6BB5\u63D0\u793A\u8BCD\u524D\u9762\u52A0\uFF08\u53EF\u4EE5\u4E0D\u586B\uFF1B\u6BD4\u5982 LoRA \u89E6\u53D1\u8BCD\u3001\u753B\u98CE\u8BCD\uFF09" }),
|
|
481
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "her-input", value: draft.custom.prefix, onChange: (e) => edit((p) => {
|
|
482
|
+
p.custom.prefix = e.target.value;
|
|
483
|
+
}) })
|
|
484
|
+
] })
|
|
485
|
+
] }),
|
|
486
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "her-muted", children: [
|
|
487
|
+
"\u7EDF\u4E00\u8BBE\u7F6E\uFF1A",
|
|
488
|
+
generatorInfo(draft).settings
|
|
489
|
+
] }),
|
|
437
490
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardEditor, { card: draft.card, onChange: (card) => edit((p) => {
|
|
438
491
|
p.card = card;
|
|
439
492
|
}) }),
|
|
@@ -503,11 +556,11 @@ function Workshop({ state, apply: apply2 }) {
|
|
|
503
556
|
fold("import", "5. \u751F\u6210\u3001\u5BFC\u5165", status.importable ? "\u53EF\u4EE5\u5BFC\u5165\u4E86" : "\u7247\u6BB5\u8FD8\u4E0D\u591F", false, /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
504
557
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ol", { className: "her-muted", style: { margin: "4px 0", paddingLeft: 18 }, children: [
|
|
505
558
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
|
|
506
|
-
'\u6BCF\u4E00\u6B65\u586B\u5B8C\u70B9"\u4FDD\u5B58\u8FD9\u4E00\u6B65"\uFF1A\u63D0\u793A\u8BCD\u5199\u8FDB\u9879\u76EE\u6587\u4EF6\u5939',
|
|
507
|
-
draft.generator === "minimax" ? "\uFF0C\u8FD8\u6709\u7ED9 MiniMax Code \u7684\u300CMiniMax\u751F\u6210\u4EFB\u52A1.md\u300D" : "",
|
|
559
|
+
'\u6BCF\u4E00\u6B65\u586B\u5B8C\u70B9"\u4FDD\u5B58\u8FD9\u4E00\u6B65"\uFF1A\u63D0\u793A\u8BCD\u5199\u8FDB\u9879\u76EE\u6587\u4EF6\u5939\uFF0C\u6240\u6709\u7247\u6BB5\u5217\u5728\u300C\u751F\u6210\u6E05\u5355.md\u300D',
|
|
560
|
+
draft.generator === "minimax" ? "\uFF0C\u8FD8\u6709\u7ED9 MiniMax Code \u7684\u300CMiniMax\u751F\u6210\u4EFB\u52A1.md\u300D" : draft.generator === "comfyui" ? "\uFF0C\u8FD8\u6709\u7ED9\u811A\u672C\u7528\u7684\u300CComfyUI\u751F\u6210\u6E05\u5355.json\u300D" : "",
|
|
508
561
|
"\u3002"
|
|
509
562
|
] }),
|
|
510
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: draft.
|
|
563
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: generatorInfo(draft).how }),
|
|
511
564
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: '\u573A\u666F\u7684\u7167\u7247\u653E\u597D\u540E\u70B9"\u6846\u4EBA\u7269\u533A"\uFF1B\u7247\u6BB5\u9F50\u4E86\u5C31"\u5BFC\u5165\u8FD9\u4E2A\u5F62\u8C61"\u3002\u6BCF\u90E8\u5206\u81F3\u5C11\u8981 3 \u6BB5\uFF08\u5F85\u673A\u3001\u8BF4\u8BDD \xD72\uFF09\uFF0C\u5176\u4ED6\u7684\u4EE5\u540E\u8865\uFF0C\u518D\u5BFC\u5165\u4E00\u6B21\u5C31\u4F1A\u66F4\u65B0\u3002' })
|
|
512
565
|
] }),
|
|
513
566
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "her-row", children: [
|
|
@@ -539,7 +592,7 @@ function storeFolds(folds) {
|
|
|
539
592
|
function Fold({ title, summary, done, open, onToggle, children }) {
|
|
540
593
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "her-card", children: [
|
|
541
594
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { className: "her-fold", onClick: onToggle, "aria-expanded": open, children: [
|
|
542
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className:
|
|
595
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `her-fold-arrow${open ? " open" : ""}`, "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 16 16", width: "14", height: "14", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6 3.5 10.5 8 6 12.5", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }),
|
|
543
596
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: title }),
|
|
544
597
|
!open && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `her-fold-sum ${done ? "her-ok" : "her-muted"}`, children: summary })
|
|
545
598
|
] }),
|
|
@@ -1458,9 +1511,12 @@ var STYLES = `
|
|
|
1458
1511
|
.her-card h2{font-size:15px;font-weight:600;margin:0 0 6px}
|
|
1459
1512
|
.her-card p{margin:4px 0}
|
|
1460
1513
|
.her-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:8px 0}
|
|
1461
|
-
.her-fold{display:flex;align-items:
|
|
1514
|
+
.her-fold{display:flex;align-items:center;gap:10px;width:100%;border:none;background:none;padding:0;color:${TEXT};font:inherit;cursor:pointer;text-align:left}
|
|
1462
1515
|
.her-fold h2{margin:0}
|
|
1463
|
-
.her-fold-arrow{
|
|
1516
|
+
.her-fold-arrow{flex:none;display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:50%;background:${LAYER2};color:${ACCENT};transition:transform .15s,background .15s}
|
|
1517
|
+
.her-fold-arrow.open{transform:rotate(90deg)}
|
|
1518
|
+
.her-fold:hover .her-fold-arrow{background:${HOVER}}
|
|
1519
|
+
.her-fold:focus-visible{outline:2px solid ${ACCENT};outline-offset:4px;border-radius:6px}
|
|
1464
1520
|
.her-fold-sum{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}
|
|
1465
1521
|
.her-examples{margin-top:12px}
|
|
1466
1522
|
.her-example-row{display:flex;gap:10px;flex-wrap:wrap}
|
package/lib/core/workshop.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* (dsh/workshop.ts), the "她" page edits the project.
|
|
5
5
|
*
|
|
6
6
|
* Prompts come in three layers:
|
|
7
|
-
* - rules: fixed, by kind (desk pet or scene)
|
|
7
|
+
* - rules: fixed, by kind (desk pet or scene);
|
|
8
8
|
* - who she is: one sentence from the character card, the same in every
|
|
9
9
|
* image and every clip, so she stays the same person;
|
|
10
10
|
* - the action: one per clip, preset or the user's own.
|
|
@@ -62,6 +62,33 @@ export function screenColor(card) {
|
|
|
62
62
|
return /绿/.test(`${card.outfit}${card.extra}${card.sentence}`) ? { name: '纯灰色', hex: '#808080' } : { name: '纯绿色', hex: '#00B140' };
|
|
63
63
|
}
|
|
64
64
|
export const POSE_WORDS = { stand: '站着', sit: '坐着', lie: '躺着' };
|
|
65
|
+
const WEB_HOW = '一段段生成:上传首帧,粘贴提示词(有"负面提示词"框就把负面提示词也粘进去,有"尾帧"就也放首帧那张图),下载后按文件名存进对应的文件夹。「生成清单.md」里每段都列好了。';
|
|
66
|
+
export const GENERATORS = {
|
|
67
|
+
minimax: {
|
|
68
|
+
name: 'MiniMax Code', label: 'MiniMax Code(给它任务文件,自动生成下载)',
|
|
69
|
+
settings: 'MiniMax-Hailuo-2.3 图生视频,1080P,6 秒。',
|
|
70
|
+
how: '在 MiniMax Code 里说:"按 <项目文件夹>/MiniMax生成任务.md 生成",它会一段段生成、下载、存好。',
|
|
71
|
+
},
|
|
72
|
+
comfyui: {
|
|
73
|
+
name: 'ComfyUI', label: 'ComfyUI(本地,Wan 2.2 等图生视频工作流)',
|
|
74
|
+
settings: '图生视频工作流(比如 Wan 2.2 I2V),尺寸和首帧同比例、短边 720 以上,约 5 秒(16 fps 时 81 帧),负面提示词放进负面节点,每段用同一套参数。',
|
|
75
|
+
how: '用你的图生视频工作流一段段跑,或者用脚本读「ComfyUI生成清单.json」(每段的首帧、提示词、负面提示词、保存路径)批量跑。输出存成清单里的文件名。',
|
|
76
|
+
},
|
|
77
|
+
jimeng: { name: '即梦', label: '即梦(网页 / App)', settings: '图生视频,1080P,5 秒。', how: `在即梦里${WEB_HOW}` },
|
|
78
|
+
kling: { name: '可灵', label: '可灵(网页 / App)', settings: '图生视频,1080P,5 秒。', how: `在可灵里${WEB_HOW}` },
|
|
79
|
+
hailuo: { name: '海螺 AI', label: '海螺 AI(MiniMax 的网页版)', settings: '图生视频,1080P,6 秒。', how: `在海螺 AI 里${WEB_HOW}` },
|
|
80
|
+
wanx: { name: '通义万相', label: '通义万相(网页 / 阿里云百炼)', settings: '图生视频,1080P,5 秒。', how: `在通义万相里${WEB_HOW}` },
|
|
81
|
+
vidu: { name: 'Vidu', label: 'Vidu(网页)', settings: '图生视频,1080P,4–5 秒。', how: `在 Vidu 里${WEB_HOW}` },
|
|
82
|
+
};
|
|
83
|
+
export const MANUAL_LABEL = '手工填写(别的工具,自己写名字和设置)';
|
|
84
|
+
/** The project's tool, a custom one filled in from `custom`. */
|
|
85
|
+
export function generatorInfo(project) {
|
|
86
|
+
if (project.generator !== 'manual' && project.generator in GENERATORS)
|
|
87
|
+
return GENERATORS[project.generator];
|
|
88
|
+
const name = project.custom?.name.trim() || '你用的工具';
|
|
89
|
+
const settings = project.custom?.settings.trim() || '图生视频,和首帧同比例,1080P,5–6 秒,每段用同一套设置。';
|
|
90
|
+
return { name, label: MANUAL_LABEL, settings, how: `在${name}里${WEB_HOW}` };
|
|
91
|
+
}
|
|
65
92
|
/** Preset actions; `{pose}` reads 站着 / 坐着 / 躺着. */
|
|
66
93
|
const PRESETS = [
|
|
67
94
|
{ role: 'idle', n: 1, required: true, action: '安静地{pose},自然呼吸,偶尔眨眼,头极轻微地晃动,嘴巴闭合,浅浅地笑' },
|
|
@@ -124,7 +151,8 @@ export function rules(project, kind, scene) {
|
|
|
124
151
|
export const NEGATIVE = '换脸, 身份漂移, 发型变化, 服装变化, 年龄变化, 多余人物, 多余手指, 手部畸形, 肢体扭曲, 脚步移动, 走出画面, 瞬移, 镜头移动, 变焦, 切镜, 背景变化, 光线闪烁, 阴影出现, 文字, 字幕, 水印, logo, 夸张表情, 卡通化, 塑料皮肤, 模糊, 低清';
|
|
125
152
|
/** The full prompt of one clip: rules, who she is, the action. */
|
|
126
153
|
export function clipPrompt(project, kind, clip, scene) {
|
|
127
|
-
|
|
154
|
+
const prefix = project.generator === 'manual' ? project.custom?.prefix.trim() ?? '' : '';
|
|
155
|
+
return `${prefix === '' ? '' : `${prefix.replace(/[,。,.\s]+$/, '')}。`}${rules(project, kind, scene)}她是:${describe(project.card)}。${clip.action}。`;
|
|
128
156
|
}
|
|
129
157
|
/** The reference picture: her on a plain screen, full length. It is also the desk pet's anchor. */
|
|
130
158
|
export function referencePrompt(project) {
|
|
@@ -173,7 +201,8 @@ export function promptsFile(project, part) {
|
|
|
173
201
|
return [
|
|
174
202
|
`${project.name} · ${part === 'desk' ? '桌宠' : part.name} · 视频提示词`,
|
|
175
203
|
'',
|
|
176
|
-
|
|
204
|
+
`用${generatorInfo(project).name}:${generatorInfo(project).settings}`,
|
|
205
|
+
`首帧(和尾帧,工具支持的话):${first}。比例和首帧一致。`,
|
|
177
206
|
'生成好的视频按下面的文件名命名(.mp4),放进这个文件夹。',
|
|
178
207
|
'',
|
|
179
208
|
`负面提示词:${NEGATIVE}`,
|
|
@@ -181,6 +210,35 @@ export function promptsFile(project, part) {
|
|
|
181
210
|
...clips.filter(c => c.off !== true).flatMap(c => [`${c.file}.mp4${c.required === true ? '(必须)' : ''}`, clipPrompt(project, kind, c, scene), '']),
|
|
182
211
|
].join('\n');
|
|
183
212
|
}
|
|
213
|
+
/** 生成清单.md: every clip with its first frame, where it goes and its prompt, for any tool. */
|
|
214
|
+
export function clipList(project, root) {
|
|
215
|
+
const tool = generatorInfo(project);
|
|
216
|
+
const lines = [
|
|
217
|
+
`# ${project.name} · 视频片段清单(${tool.name})`,
|
|
218
|
+
'',
|
|
219
|
+
`统一设置:${tool.settings}`,
|
|
220
|
+
'提示词原样使用;首尾接不上没关系,导入时会自动处理成循环。已经有的 mp4 不用重做。',
|
|
221
|
+
'',
|
|
222
|
+
`负面提示词(工具有这个框就用):${NEGATIVE}`,
|
|
223
|
+
'',
|
|
224
|
+
];
|
|
225
|
+
for (const item of plannedClips(project)) {
|
|
226
|
+
lines.push(`## ${item.part} / ${item.clip.file}${item.clip.required === true ? '(必须)' : ''}`, `- 首帧:\`${root}/${item.first}\``, `- 保存到:\`${root}/${item.folder}/${item.clip.file}.mp4\``, `- 提示词:${item.prompt}`, '');
|
|
227
|
+
}
|
|
228
|
+
return lines.join('\n');
|
|
229
|
+
}
|
|
230
|
+
/** ComfyUI生成清单.json: the same list for a script that drives a ComfyUI workflow. */
|
|
231
|
+
export function comfyuiList(project, root) {
|
|
232
|
+
return `${JSON.stringify({
|
|
233
|
+
format: 'dsh-her-clips@1',
|
|
234
|
+
settings: GENERATORS.comfyui.settings,
|
|
235
|
+
negative: NEGATIVE,
|
|
236
|
+
clips: plannedClips(project).map(item => ({
|
|
237
|
+
part: item.part, name: item.clip.file, required: item.clip.required === true,
|
|
238
|
+
image: `${root}/${item.first}`, output: `${root}/${item.folder}/${item.clip.file}.mp4`, prompt: item.prompt,
|
|
239
|
+
})),
|
|
240
|
+
}, null, 2)}\n`;
|
|
241
|
+
}
|
|
184
242
|
/** The task file for MiniMax Code: one uniform setting, at most 5 at a time, download as each finishes. */
|
|
185
243
|
export function minimaxTask(project, root) {
|
|
186
244
|
const lines = [
|
|
@@ -238,6 +296,10 @@ export function validateProject(value) {
|
|
|
238
296
|
throw new Error('项目内容不完整');
|
|
239
297
|
if (!p.desk.on && p.scenes.length === 0)
|
|
240
298
|
throw new Error('桌宠和场景至少要有一个');
|
|
299
|
+
if (p.generator !== 'manual' && !(p.generator in GENERATORS))
|
|
300
|
+
throw new Error('不认识这个生成工具');
|
|
301
|
+
if (p.custom !== undefined && (typeof p.custom !== 'object' || p.custom === null || [p.custom.name, p.custom.settings, p.custom.prefix].some(v => typeof v !== 'string')))
|
|
302
|
+
throw new Error('手工填写的工具信息不对');
|
|
241
303
|
const names = new Set();
|
|
242
304
|
for (const scene of p.scenes) {
|
|
243
305
|
if (typeof scene.id !== 'string' || typeof scene.name !== 'string' || scene.name.trim() === '' || !Array.isArray(scene.clips))
|
package/lib/dsh/workshop.js
CHANGED
|
@@ -5,18 +5,20 @@
|
|
|
5
5
|
* 形象.json the project (character card, scenes, clips)
|
|
6
6
|
* 参考图提示词.txt how to make 参考图.png, her "ID photo"
|
|
7
7
|
* 参考图.png (the user's) — also the desk pet's anchor
|
|
8
|
-
*
|
|
8
|
+
* 生成清单.md every clip: first frame, file, prompt
|
|
9
|
+
* MiniMax生成任务.md the same, as a task for MiniMax Code (when that is the tool)
|
|
10
|
+
* ComfyUI生成清单.json the same, for a ComfyUI script (when that is the tool)
|
|
9
11
|
* 桌宠/提示词.txt + the desk pet's clips
|
|
10
12
|
* 场景-卧室/提示词.txt + 照片提示词.txt, plate.png, scene.json, crop.png, clips
|
|
11
13
|
*
|
|
12
14
|
* The prompts are rewritten on every save; the user's pictures and clips are
|
|
13
15
|
* never touched. Importing hands the folders to the body (same computer).
|
|
14
16
|
*/
|
|
15
|
-
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
16
18
|
import { homedir } from 'node:os';
|
|
17
19
|
import { basename, dirname, extname, join, resolve } from 'node:path';
|
|
18
20
|
import { fileURLToPath } from 'node:url';
|
|
19
|
-
import { DESK_DIR, EXAMPLE_FILES, EXAMPLE_SLOTS, minimaxTask, PROJECT_FILE, promptsFile, REFERENCE_FILE, referencePrompt, safeName, sceneDir, scenePhotoPrompt, validateProject, } from "../core/workshop.js";
|
|
21
|
+
import { clipList, comfyuiList, DESK_DIR, EXAMPLE_FILES, EXAMPLE_SLOTS, minimaxTask, PROJECT_FILE, promptsFile, REFERENCE_FILE, referencePrompt, safeName, sceneDir, scenePhotoPrompt, validateProject, } from "../core/workshop.js";
|
|
20
22
|
export const MATERIALS_DIR = join(homedir(), 'Documents', 'dsh-her 素材');
|
|
21
23
|
function readProject(dir) {
|
|
22
24
|
try {
|
|
@@ -91,7 +93,15 @@ export function saveProject(input, screen, root = MATERIALS_DIR) {
|
|
|
91
93
|
referencePrompt(project),
|
|
92
94
|
'',
|
|
93
95
|
].join('\n'));
|
|
94
|
-
write(join(dir, '
|
|
96
|
+
write(join(dir, '生成清单.md'), clipList(project, dir));
|
|
97
|
+
// The tool's own task file; another tool's, left from before, goes.
|
|
98
|
+
const taskFiles = [['MiniMax生成任务.md', 'minimax', minimaxTask], ['ComfyUI生成清单.json', 'comfyui', comfyuiList]];
|
|
99
|
+
for (const [file, generator, make] of taskFiles) {
|
|
100
|
+
if (project.generator === generator)
|
|
101
|
+
write(join(dir, file), make(project, dir));
|
|
102
|
+
else
|
|
103
|
+
rmSync(join(dir, file), { force: true });
|
|
104
|
+
}
|
|
95
105
|
if (project.desk.on) {
|
|
96
106
|
mkdirSync(join(dir, DESK_DIR), { recursive: true });
|
|
97
107
|
write(join(dir, DESK_DIR, '提示词.txt'), promptsFile(project, 'desk'));
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* (dsh/workshop.ts), the "她" page edits the project.
|
|
5
5
|
*
|
|
6
6
|
* Prompts come in three layers:
|
|
7
|
-
* - rules: fixed, by kind (desk pet or scene)
|
|
7
|
+
* - rules: fixed, by kind (desk pet or scene);
|
|
8
8
|
* - who she is: one sentence from the character card, the same in every
|
|
9
9
|
* image and every clip, so she stays the same person;
|
|
10
10
|
* - the action: one per clip, preset or the user's own.
|
|
@@ -65,7 +65,27 @@ export interface ProjectScene {
|
|
|
65
65
|
user?: 'active' | 'idle';
|
|
66
66
|
clips: ClipPlan[];
|
|
67
67
|
}
|
|
68
|
-
|
|
68
|
+
/** The tool the clips are made with. `manual` is any other tool, described in `custom`. */
|
|
69
|
+
export type Generator = 'minimax' | 'comfyui' | 'jimeng' | 'kling' | 'hailuo' | 'wanx' | 'vidu' | 'manual';
|
|
70
|
+
export interface GeneratorInfo {
|
|
71
|
+
name: string;
|
|
72
|
+
/** One line in the picker. */
|
|
73
|
+
label: string;
|
|
74
|
+
/** The settings every clip uses, written at the top of each 提示词.txt. */
|
|
75
|
+
settings: string;
|
|
76
|
+
/** How the clips get made, in the last card. */
|
|
77
|
+
how: string;
|
|
78
|
+
}
|
|
79
|
+
export declare const GENERATORS: Record<Exclude<Generator, 'manual'>, GeneratorInfo>;
|
|
80
|
+
/** For `manual`: the tool's name, its settings, and words every clip's prompt starts with (a LoRA trigger word, a style). */
|
|
81
|
+
export interface CustomGenerator {
|
|
82
|
+
name: string;
|
|
83
|
+
settings: string;
|
|
84
|
+
prefix: string;
|
|
85
|
+
}
|
|
86
|
+
export declare const MANUAL_LABEL = "\u624B\u5DE5\u586B\u5199\uFF08\u522B\u7684\u5DE5\u5177\uFF0C\u81EA\u5DF1\u5199\u540D\u5B57\u548C\u8BBE\u7F6E\uFF09";
|
|
87
|
+
/** The project's tool, a custom one filled in from `custom`. */
|
|
88
|
+
export declare function generatorInfo(project: Pick<LookProject, 'generator' | 'custom'>): GeneratorInfo;
|
|
69
89
|
export interface LookProject {
|
|
70
90
|
format: typeof PROJECT_FORMAT;
|
|
71
91
|
id: string;
|
|
@@ -79,6 +99,8 @@ export interface LookProject {
|
|
|
79
99
|
/** The scene when no rule matches (an id in `scenes`). */
|
|
80
100
|
default?: string;
|
|
81
101
|
generator: Generator;
|
|
102
|
+
/** Filled in when `generator` is `manual`. */
|
|
103
|
+
custom?: CustomGenerator;
|
|
82
104
|
createdAt: string;
|
|
83
105
|
}
|
|
84
106
|
/** The preset clips for a pose: the minimum ticked, the rest offered unticked. */
|
|
@@ -109,6 +131,10 @@ export declare function plannedClips(project: LookProject): Array<{
|
|
|
109
131
|
}>;
|
|
110
132
|
/** 提示词.txt of one part (the desk pet, or a scene): every clip's prompt, ready to copy. */
|
|
111
133
|
export declare function promptsFile(project: LookProject, part: 'desk' | ProjectScene): string;
|
|
134
|
+
/** 生成清单.md: every clip with its first frame, where it goes and its prompt, for any tool. */
|
|
135
|
+
export declare function clipList(project: LookProject, root: string): string;
|
|
136
|
+
/** ComfyUI生成清单.json: the same list for a script that drives a ComfyUI workflow. */
|
|
137
|
+
export declare function comfyuiList(project: LookProject, root: string): string;
|
|
112
138
|
/** The task file for MiniMax Code: one uniform setting, at most 5 at a time, download as each finishes. */
|
|
113
139
|
export declare function minimaxTask(project: LookProject, root: string): string;
|
|
114
140
|
/** An age that is clearly grown up: 二十多岁, 三十岁左右, 25 岁, 成年 … */
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* 形象.json the project (character card, scenes, clips)
|
|
6
6
|
* 参考图提示词.txt how to make 参考图.png, her "ID photo"
|
|
7
7
|
* 参考图.png (the user's) — also the desk pet's anchor
|
|
8
|
-
*
|
|
8
|
+
* 生成清单.md every clip: first frame, file, prompt
|
|
9
|
+
* MiniMax生成任务.md the same, as a task for MiniMax Code (when that is the tool)
|
|
10
|
+
* ComfyUI生成清单.json the same, for a ComfyUI script (when that is the tool)
|
|
9
11
|
* 桌宠/提示词.txt + the desk pet's clips
|
|
10
12
|
* 场景-卧室/提示词.txt + 照片提示词.txt, plate.png, scene.json, crop.png, clips
|
|
11
13
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-her",
|
|
3
|
-
"version": "0.1.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "A desktop companion for DeepSeek Harness (dsh): she lives on your real desktop (on top of your windows or inside the wallpaper), talks with you by voice and remembers you. · DSH 桌面女友:住在你真正的桌面上,能语音聊天、记得你。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|