dsh-novel-generator 0.1.1 → 0.1.3
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/client.js +39 -22
- package/index.js +4 -3
- package/package.json +3 -2
package/client.js
CHANGED
|
@@ -185,6 +185,7 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
185
185
|
.dsn-input:focus,.dsn-select:focus,.dsn-textarea:focus{border-color:var(--dsw-alias-brand-primary)}
|
|
186
186
|
.dsn-input.dsn-readonly{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);cursor:default}
|
|
187
187
|
.dsn-input.dsn-readonly:focus{border-color:var(--dsw-alias-border-l1)}
|
|
188
|
+
.dsn-input.dsn-input-invalid{border-color:var(--dsw-alias-state-warn-primary)}
|
|
188
189
|
.dsn-path{display:flex;gap:6px}
|
|
189
190
|
.dsn-list{display:flex;flex-direction:column;gap:8px}
|
|
190
191
|
.dsn-row{display:flex;align-items:center;gap:12px;padding:12px 14px;border-radius:12px;
|
|
@@ -425,7 +426,7 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
425
426
|
}, busy ? t('保存中…', "Saving…") : t('保存', "Save")))));
|
|
426
427
|
}
|
|
427
428
|
|
|
428
|
-
function ProjectsPage({ boot,
|
|
429
|
+
function ProjectsPage({ boot, resolveWorkspace, onOpen, onReload, onEditModel }) {
|
|
429
430
|
const [title, setTitle] = useState('');
|
|
430
431
|
const [cwd, setCwd] = useState('');
|
|
431
432
|
const [preset, setPreset] = useState('');
|
|
@@ -436,9 +437,10 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
436
437
|
const [busy, setBusy] = useState(false);
|
|
437
438
|
const [error, setError] = useState('');
|
|
438
439
|
|
|
439
|
-
// The title becomes a folder name inside the working directory
|
|
440
|
-
|
|
441
|
-
const
|
|
440
|
+
// The title becomes a folder name inside the working directory, and it is
|
|
441
|
+
// required: an empty name is reported when the user tries to create.
|
|
442
|
+
const trimmedTitle = title.trim();
|
|
443
|
+
const titleIssue = trimmedTitle === '' ? '' : titleProblem(trimmedTitle);
|
|
442
444
|
|
|
443
445
|
const groups = (boot.models && boot.models.groups) || [];
|
|
444
446
|
const models = useMemo(() => {
|
|
@@ -451,20 +453,31 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
451
453
|
}, [models]);
|
|
452
454
|
|
|
453
455
|
const pickDirectory = useCallback(async () => {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
+
// Resolved at click time: the workspace service may activate after this
|
|
457
|
+
// plugin, so an instance captured in `apply` can come back undefined.
|
|
458
|
+
const ui = typeof resolveWorkspace === 'function' ? resolveWorkspace() : null;
|
|
459
|
+
if (!ui || typeof ui.pickDirectory !== 'function') {
|
|
460
|
+
setError(t('目录选择器还没就绪,请稍后再点一次「选择」', 'The directory picker is not ready yet — try "Select" again in a moment.'));
|
|
456
461
|
return;
|
|
457
462
|
}
|
|
458
463
|
try {
|
|
459
|
-
const picked = await
|
|
464
|
+
const picked = await ui.pickDirectory();
|
|
460
465
|
if (picked) setCwd(picked);
|
|
461
466
|
} catch (pickError) {
|
|
462
467
|
setError(messageOf(pickError));
|
|
463
468
|
}
|
|
464
|
-
}, [
|
|
469
|
+
}, [resolveWorkspace]);
|
|
465
470
|
|
|
466
471
|
const create = useCallback(async () => {
|
|
467
472
|
setError('');
|
|
473
|
+
if (trimmedTitle === '') {
|
|
474
|
+
setError(t('作品名称不能为空', "Title cannot be empty."));
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (titleIssue) {
|
|
478
|
+
setError(titleIssue);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
468
481
|
if (!cwd.trim()) {
|
|
469
482
|
setError(t('请先点「选择」挑一个工作目录', "Pick a working directory with \"Select\" first."));
|
|
470
483
|
return;
|
|
@@ -472,7 +485,7 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
472
485
|
setBusy(true);
|
|
473
486
|
try {
|
|
474
487
|
const result = await api('/project/create', {
|
|
475
|
-
title:
|
|
488
|
+
title: trimmedTitle,
|
|
476
489
|
cwd: cwd.trim(),
|
|
477
490
|
agentPreset: preset || undefined,
|
|
478
491
|
model: provider && model ? { provider, model } : null,
|
|
@@ -485,7 +498,7 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
485
498
|
} finally {
|
|
486
499
|
setBusy(false);
|
|
487
500
|
}
|
|
488
|
-
}, [
|
|
501
|
+
}, [trimmedTitle, titleIssue, cwd, preset, provider, model, onOpen, onReload]);
|
|
489
502
|
|
|
490
503
|
const remove = useCallback(async (project) => {
|
|
491
504
|
if (!window.confirm(t('确定删除作品「{title}」?这会同时删除工作目录下的作品文件夹(正文、梗概、世界快照)。作品的会话本身保留。', 'Delete the work “{title}”? This also deletes its folder in the working directory (chapters, outlines, world snapshots). The session itself stays.', { title: project.title }))) return;
|
|
@@ -502,13 +515,12 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
502
515
|
h('label', { className: 'dsn-field' },
|
|
503
516
|
h('span', { className: 'dsn-label' }, t('作品名称', "Title")),
|
|
504
517
|
h('input', {
|
|
505
|
-
className: 'dsn-input'
|
|
518
|
+
className: `dsn-input${titleIssue ? ' dsn-input-invalid' : ''}`,
|
|
506
519
|
value: title,
|
|
507
520
|
placeholder: t('例如:长夜将尽', "e.g. The Long Night Ends"),
|
|
521
|
+
title: titleIssue || t('会成为工作目录下的文件夹名', "Becomes a folder name inside the working directory."),
|
|
508
522
|
onChange: (event) => setTitle(event.target.value),
|
|
509
|
-
}),
|
|
510
|
-
h('span', { className: `dsn-hint${titleIssue ? ' warn' : ''}` },
|
|
511
|
-
titleIssue || t('会成为工作目录下的文件夹名', "Becomes a folder name inside the working directory."))),
|
|
523
|
+
})),
|
|
512
524
|
h('label', { className: 'dsn-field' },
|
|
513
525
|
h('span', { className: 'dsn-label' }, t('工作目录', "Working directory")),
|
|
514
526
|
h('div', { className: 'dsn-path' },
|
|
@@ -552,8 +564,8 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
552
564
|
h('button', {
|
|
553
565
|
className: 'dsn-btn primary',
|
|
554
566
|
type: 'button',
|
|
555
|
-
|
|
556
|
-
|
|
567
|
+
// Enabled even when the form is incomplete: the click itself says why.
|
|
568
|
+
disabled: busy,
|
|
557
569
|
onClick: create,
|
|
558
570
|
}, busy ? t('创建中…', "Creating…") : t('创建作品并开会话', "Create work and open its session")))),
|
|
559
571
|
|
|
@@ -1654,7 +1666,7 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1654
1666
|
|
|
1655
1667
|
/* ------------------------------------------------------------ studio app */
|
|
1656
1668
|
|
|
1657
|
-
function StudioApp({
|
|
1669
|
+
function StudioApp({ resolveWorkspace }) {
|
|
1658
1670
|
// Subscribes every screen below to Settings → General → Language.
|
|
1659
1671
|
useLocaleId();
|
|
1660
1672
|
const [boot, setBoot] = useState(null);
|
|
@@ -1742,7 +1754,7 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1742
1754
|
? h('div', { className: 'dsn-scroll' },
|
|
1743
1755
|
h(ProjectsPage, {
|
|
1744
1756
|
boot,
|
|
1745
|
-
|
|
1757
|
+
resolveWorkspace,
|
|
1746
1758
|
onOpen: openProject,
|
|
1747
1759
|
onReload: loadBoot,
|
|
1748
1760
|
onEditModel: setModelTarget,
|
|
@@ -1772,12 +1784,12 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1772
1784
|
: null);
|
|
1773
1785
|
}
|
|
1774
1786
|
|
|
1775
|
-
function StudioOverlay({
|
|
1787
|
+
function StudioOverlay({ resolveWorkspace }) {
|
|
1776
1788
|
const open = useOverlayOpen();
|
|
1777
1789
|
if (!open) return null;
|
|
1778
1790
|
// No backdrop-click close: leaving with unsaved chapter text goes through
|
|
1779
1791
|
// the 关闭 button, which asks first.
|
|
1780
|
-
return h('div', { className: 'dsn-overlay' }, h(StudioApp, {
|
|
1792
|
+
return h('div', { className: 'dsn-overlay' }, h(StudioApp, { resolveWorkspace }));
|
|
1781
1793
|
}
|
|
1782
1794
|
|
|
1783
1795
|
/* ---------------------------------------------------------------- apply */
|
|
@@ -1785,7 +1797,12 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1785
1797
|
return {
|
|
1786
1798
|
inject: ['slots'],
|
|
1787
1799
|
apply(ctx) {
|
|
1788
|
-
|
|
1800
|
+
/**
|
|
1801
|
+
* Read at use time, not at activation: this plugin may mount before the
|
|
1802
|
+
* workspace UI that owns `uiWorkspace`, and a captured instance would
|
|
1803
|
+
* then be undefined for the life of the page.
|
|
1804
|
+
*/
|
|
1805
|
+
const resolveWorkspace = () => ctx.get('uiWorkspace') || null;
|
|
1789
1806
|
// Settings → General → Language drives every string that goes through
|
|
1790
1807
|
// the translator below; a host without the service stays Chinese.
|
|
1791
1808
|
localeService = ctx.get('locale') || null;
|
|
@@ -1798,7 +1815,7 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1798
1815
|
name: 'shell.overlay',
|
|
1799
1816
|
id: 'dsh-novel-generator-studio',
|
|
1800
1817
|
order: 40,
|
|
1801
|
-
}, () => h(StudioOverlay, {
|
|
1818
|
+
}, () => h(StudioOverlay, { resolveWorkspace })));
|
|
1802
1819
|
},
|
|
1803
1820
|
};
|
|
1804
1821
|
},
|
package/index.js
CHANGED
|
@@ -982,7 +982,7 @@ async function bootstrap(ctx, root) {
|
|
|
982
982
|
}
|
|
983
983
|
}
|
|
984
984
|
return {
|
|
985
|
-
revision: '2026-10-20.
|
|
985
|
+
revision: '2026-10-20.15',
|
|
986
986
|
projects,
|
|
987
987
|
models: {
|
|
988
988
|
default: modelCatalog.default || null,
|
|
@@ -1005,8 +1005,9 @@ async function bootstrap(ctx, root) {
|
|
|
1005
1005
|
async function createProject(ctx, root, body) {
|
|
1006
1006
|
const controller = ctx.get('sessionController');
|
|
1007
1007
|
if (controller === undefined) throw httpError(503, '当前部署缺少 sessionController 服务,无法创建会话');
|
|
1008
|
-
|
|
1009
|
-
//
|
|
1008
|
+
// The title is required and becomes a folder name inside the chosen
|
|
1009
|
+
// directory, so an empty or illegal one is refused rather than defaulted.
|
|
1010
|
+
const title = asText(body.title);
|
|
1010
1011
|
const titleIssue = titleProblem(title);
|
|
1011
1012
|
if (titleIssue !== null) throw httpError(400, titleIssue);
|
|
1012
1013
|
const cwd = asText(body.cwd);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-novel-generator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Novel studio for DeepSeek Harness: a draggable story timeline, world snapshots, chapter outlines, and chapters written by the work's own agent.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
"platform": "web",
|
|
52
52
|
"immediately": true,
|
|
53
53
|
"inject": [
|
|
54
|
-
"@deepseek-ai/dsh-client-locale"
|
|
54
|
+
"@deepseek-ai/dsh-client-locale",
|
|
55
|
+
"@deepseek-ai/dsh-client-ui-workspace"
|
|
55
56
|
]
|
|
56
57
|
}
|
|
57
58
|
}
|