dsh-novel-generator 0.1.16 → 0.1.17
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 +66 -11
- package/index.js +4 -1
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -1041,7 +1041,7 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1041
1041
|
|
|
1042
1042
|
/* ---------------------------------------------------------- outline page */
|
|
1043
1043
|
|
|
1044
|
-
function OutlinePage({ detail, chapter, isFirstChapter, onBack, onSaved, notify, onRegisterGuard, requestConfirm }) {
|
|
1044
|
+
function OutlinePage({ detail, chapter, isFirstChapter, onBack, onSaved, notify, onRegisterGuard, requestConfirm, onBusyChange }) {
|
|
1045
1045
|
const buildForm = useCallback(() => ({
|
|
1046
1046
|
title: chapter ? chapter.title || '' : '',
|
|
1047
1047
|
durationDays: chapter && chapter.outline ? chapter.outline.durationDays : (detail.timeline.unitDays || 30),
|
|
@@ -1182,6 +1182,22 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1182
1182
|
});
|
|
1183
1183
|
}, [requestConfirm, startJob]);
|
|
1184
1184
|
|
|
1185
|
+
// Report the running job upward so the studio can lock itself and warn
|
|
1186
|
+
// before the panel is closed mid-generation.
|
|
1187
|
+
useEffect(() => {
|
|
1188
|
+
if (typeof onBusyChange !== 'function') return;
|
|
1189
|
+
const running = job && job.state === 'running';
|
|
1190
|
+
if (!running) {
|
|
1191
|
+
onBusyChange(null);
|
|
1192
|
+
return undefined;
|
|
1193
|
+
}
|
|
1194
|
+
onBusyChange({
|
|
1195
|
+
label: job.kind === 'compose' ? t('正在自动推演…', 'Deducing…') : t('正在生成章节故事…', 'Generating the chapter…'),
|
|
1196
|
+
message: job.message || '',
|
|
1197
|
+
});
|
|
1198
|
+
return () => onBusyChange(null);
|
|
1199
|
+
}, [job ? job.jobId : null, job ? job.state : null, job ? job.message : null, onBusyChange]);
|
|
1200
|
+
|
|
1185
1201
|
useEffect(() => {
|
|
1186
1202
|
if (!job) return;
|
|
1187
1203
|
if (job.kind === 'generate' && job.state === 'done') {
|
|
@@ -1312,7 +1328,7 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1312
1328
|
* The only place a chapter is created: word bounds and the story length are
|
|
1313
1329
|
* required, and one button can deduce the synopsis and write the chapter.
|
|
1314
1330
|
*/
|
|
1315
|
-
function NewChapterDialog({ detail, startDay, onCancel, onCreated, notify }) {
|
|
1331
|
+
function NewChapterDialog({ detail, startDay, onCancel, onCreated, notify, onBusyChange }) {
|
|
1316
1332
|
const [form, setForm] = useState({
|
|
1317
1333
|
title: '',
|
|
1318
1334
|
minWords: 1200,
|
|
@@ -1337,6 +1353,20 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1337
1353
|
|
|
1338
1354
|
const composing = Boolean(job && job.state === 'running' && job.kind === 'compose');
|
|
1339
1355
|
|
|
1356
|
+
// A deducing dialog locks the studio behind it and warns before closing.
|
|
1357
|
+
useEffect(() => {
|
|
1358
|
+
if (typeof onBusyChange !== 'function') return undefined;
|
|
1359
|
+
if (!composing) {
|
|
1360
|
+
onBusyChange(null);
|
|
1361
|
+
return undefined;
|
|
1362
|
+
}
|
|
1363
|
+
onBusyChange({
|
|
1364
|
+
label: t('正在自动推演新章节…', 'Deducing a new chapter…'),
|
|
1365
|
+
message: (job && job.message) || '',
|
|
1366
|
+
});
|
|
1367
|
+
return () => onBusyChange(null);
|
|
1368
|
+
}, [composing, job ? job.message : null, onBusyChange]);
|
|
1369
|
+
|
|
1340
1370
|
const create = useCallback(async () => {
|
|
1341
1371
|
setBusy(true);
|
|
1342
1372
|
setError('');
|
|
@@ -1500,13 +1530,25 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1500
1530
|
|
|
1501
1531
|
/* ------------------------------------------------------------- workspace */
|
|
1502
1532
|
|
|
1503
|
-
function ProjectWorkspace({ detail, onReload, notify, onRegisterLeaveGuard }) {
|
|
1533
|
+
function ProjectWorkspace({ detail, onReload, notify, onRegisterLeaveGuard, onBusyChange }) {
|
|
1504
1534
|
const [view, setView] = useState('world');
|
|
1505
1535
|
const [chapterId, setChapterId] = useState(null);
|
|
1506
1536
|
// A running snapshot job: { label, message }. Non-null locks the studio,
|
|
1507
1537
|
// because the job keeps rewriting this work's snapshots.
|
|
1508
1538
|
const [snapshotJob, setSnapshotJob] = useState(null);
|
|
1509
|
-
|
|
1539
|
+
// The same idea for the chapter jobs (自动推演 / 生成章节故事), reported up
|
|
1540
|
+
// by the pages that own them.
|
|
1541
|
+
const [childBusy, setChildBusy] = useState(null);
|
|
1542
|
+
const busy = snapshotJob || childBusy;
|
|
1543
|
+
const snapshotBusy = Boolean(snapshotJob);
|
|
1544
|
+
|
|
1545
|
+
// Tell the shell whether a long operation is in flight, so closing the
|
|
1546
|
+
// panel can ask first.
|
|
1547
|
+
useEffect(() => {
|
|
1548
|
+
if (typeof onBusyChange !== 'function') return undefined;
|
|
1549
|
+
onBusyChange(busy ? { label: busy.label, message: busy.message } : null);
|
|
1550
|
+
return () => onBusyChange(null);
|
|
1551
|
+
}, [busy ? busy.label : null, busy ? busy.message : null, onBusyChange]);
|
|
1510
1552
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
1511
1553
|
const [text, setText] = useState('');
|
|
1512
1554
|
const [dirty, setDirty] = useState(false);
|
|
@@ -1706,14 +1748,14 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1706
1748
|
const trackIndex = view === 'world' ? 0 : view === 'story' ? 1 : 2;
|
|
1707
1749
|
|
|
1708
1750
|
return h('div', { className: 'dsn-stage' },
|
|
1709
|
-
//
|
|
1710
|
-
//
|
|
1711
|
-
|
|
1751
|
+
// Any running job owns the work: nothing else may be edited while it
|
|
1752
|
+
// rewrites the novel underneath.
|
|
1753
|
+
busy
|
|
1712
1754
|
? h('div', { className: 'dsn-lock' },
|
|
1713
1755
|
h('div', { className: 'dsn-lock-box' },
|
|
1714
|
-
h('div', { className: 'dsn-lock-title' },
|
|
1715
|
-
h('div', { className: 'dsn-lock-text' },
|
|
1716
|
-
h('div', { className: 'dsn-lock-hint' }, t('
|
|
1756
|
+
h('div', { className: 'dsn-lock-title' }, busy.label),
|
|
1757
|
+
h('div', { className: 'dsn-lock-text' }, busy.message),
|
|
1758
|
+
h('div', { className: 'dsn-lock-hint' }, t('生成期间界面不可操作,完成后会自动刷新', 'The studio is locked while this runs; it refreshes when done.'))))
|
|
1717
1759
|
: null,
|
|
1718
1760
|
h('div', { className: 'dsn-track', style: { transform: `translateX(-${trackIndex * 100}%)` } },
|
|
1719
1761
|
h('section', { className: 'dsn-page' },
|
|
@@ -1756,6 +1798,7 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1756
1798
|
notify,
|
|
1757
1799
|
onRegisterGuard: registerOutlineGuard,
|
|
1758
1800
|
requestConfirm: setConfirmRequest,
|
|
1801
|
+
onBusyChange: setChildBusy,
|
|
1759
1802
|
isFirstChapter: Boolean(chapter && chapters.length > 0 && chapter.chapterId === chapters[0].chapterId),
|
|
1760
1803
|
}))),
|
|
1761
1804
|
confirmRequest
|
|
@@ -1804,6 +1847,7 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1804
1847
|
onCancel: () => setDialogOpen(false),
|
|
1805
1848
|
onCreated: created,
|
|
1806
1849
|
notify,
|
|
1850
|
+
onBusyChange: setChildBusy,
|
|
1807
1851
|
})
|
|
1808
1852
|
: null);
|
|
1809
1853
|
}
|
|
@@ -1818,6 +1862,8 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1818
1862
|
const [projectId, setProjectId] = useState(null);
|
|
1819
1863
|
const [detail, setDetail] = useState(null);
|
|
1820
1864
|
const [toast, setToast] = useToast();
|
|
1865
|
+
// Set while a work runs a model job, so leaving the panel can warn first.
|
|
1866
|
+
const [busyNow, setBusyNow] = useState(null);
|
|
1821
1867
|
const [modelTarget, setModelTarget] = useState(null);
|
|
1822
1868
|
const leaveGuard = useRef(null);
|
|
1823
1869
|
const registerLeaveGuard = useCallback((guard) => {
|
|
@@ -1825,13 +1871,21 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1825
1871
|
}, []);
|
|
1826
1872
|
/** Every way out of the studio asks the workspace first. */
|
|
1827
1873
|
const requestLeave = useCallback((proceed) => {
|
|
1874
|
+
// A running generation outranks the editor guard: say it is still going
|
|
1875
|
+
// before anything closes.
|
|
1876
|
+
if (busyNow) {
|
|
1877
|
+
const label = busyNow.label || t('正在生成', 'Generation is running');
|
|
1878
|
+
if (!window.confirm(t('{label},现在离开吗?生成会在后台继续,回来时刷新即可看到结果。', '{label} — leave now? It keeps running in the background; reopen the panel later to see the result.', { label }))) return;
|
|
1879
|
+
proceed();
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1828
1882
|
const guard = leaveGuard.current;
|
|
1829
1883
|
if (typeof guard === 'function') {
|
|
1830
1884
|
guard(proceed);
|
|
1831
1885
|
return;
|
|
1832
1886
|
}
|
|
1833
1887
|
proceed();
|
|
1834
|
-
}, []);
|
|
1888
|
+
}, [busyNow]);
|
|
1835
1889
|
const close = useCallback(() => requestLeave(() => setOverlayOpen(false)), [requestLeave]);
|
|
1836
1890
|
|
|
1837
1891
|
const loadBoot = useCallback(async () => {
|
|
@@ -1911,6 +1965,7 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
1911
1965
|
onReload: reloadDetail,
|
|
1912
1966
|
notify,
|
|
1913
1967
|
onRegisterLeaveGuard: registerLeaveGuard,
|
|
1968
|
+
onBusyChange: setBusyNow,
|
|
1914
1969
|
})),
|
|
1915
1970
|
modelTarget
|
|
1916
1971
|
? h(ModelDialog, {
|
package/index.js
CHANGED
|
@@ -891,6 +891,9 @@ async function rebindSession(ctx, root, project) {
|
|
|
891
891
|
draft.sessionId = sessionId;
|
|
892
892
|
});
|
|
893
893
|
try {
|
|
894
|
+
// The memo skips projects already grouped; a rebind starts a new session
|
|
895
|
+
// that has never been grouped, so the entry has to go.
|
|
896
|
+
ensuredWorkspaces.delete(project.projectId);
|
|
894
897
|
await ensureWorkspace(ctx, root, project);
|
|
895
898
|
} catch {
|
|
896
899
|
// Grouping is cosmetic; the session works either way.
|
|
@@ -1361,7 +1364,7 @@ async function bootstrap(ctx, root) {
|
|
|
1361
1364
|
}
|
|
1362
1365
|
}
|
|
1363
1366
|
return {
|
|
1364
|
-
revision: '2026-10-20.
|
|
1367
|
+
revision: '2026-10-20.45',
|
|
1365
1368
|
projects,
|
|
1366
1369
|
models: {
|
|
1367
1370
|
default: modelCatalog.default || null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-novel-generator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
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",
|