dsh-novel-generator 0.1.5 → 0.1.6
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 -4
- package/client.js +13 -10
- package/index.js +26 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,13 +29,14 @@ dsh plugin --profile web add dsh-novel-generator
|
|
|
29
29
|
|
|
30
30
|
## 世界快照怎么来
|
|
31
31
|
|
|
32
|
-
- 时间线以「天」计(0 =
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
32
|
+
- 时间线以「天」计(0 = 创建作品那天),你选的「快照间隔」(7 / 15 / 30 / 90 / 180 / 365 天,可手输)决定了一条**网格**:第 15、30、45… 天。
|
|
33
|
+
- **手动**:把时间线拖到网格点上(第 N 天且 N 能被间隔整除),标题栏出现「生成世界快照 · 第 N 天」;该点已有快照时按钮变成「重新生成快照 · 第 N 天」,会替换旧的那份。两种情况都先弹确认。
|
|
34
|
+
- **自动**:每次生成章节后检查两个条件,缺一不可:① 本章结束时间**正好落在网格点上**;② **上一格**(该点 − 一个间隔)已经有快照(第 0 天视为已覆盖)。满足才自动生成这一格的快照。
|
|
35
|
+
- 这样改间隔不会把旧快照重排:网格随当前间隔移动,自动生成只沿着「上一格存在」的链条向前推进,断档的地方留给你手动补。
|
|
36
36
|
|
|
37
37
|
## 数据与接口
|
|
38
38
|
|
|
39
|
+
|
|
39
40
|
- **索引文件**:`<DSH_HOME>/novel-studio/projects/<项目id>/project.json`(`DSH_HOME` 缺省为 `~/.dsh`,本机即 `C:\Users\Admin\.dsh\novel-studio`)。时间线、大纲、快照字段都记在这里;`dataDir` 配置可改这块的位置。
|
|
40
41
|
- **正文、梗概、快照全部写到工作目录下、以作品名称命名的文件夹里**:
|
|
41
42
|
|
package/client.js
CHANGED
|
@@ -748,13 +748,16 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
748
748
|
|
|
749
749
|
const snapshot = snapshotAt(detail, day);
|
|
750
750
|
const chapters = detail.chapters || [];
|
|
751
|
-
//
|
|
752
|
-
//
|
|
753
|
-
//
|
|
754
|
-
const
|
|
751
|
+
// The button belongs on the interval grid: the cursor must sit exactly on
|
|
752
|
+
// a multiple of the current interval. There it either fills a missing
|
|
753
|
+
// snapshot or replaces the one already written for that day.
|
|
754
|
+
const onGridPoint = unitDays >= 1 && day > 0 && day % unitDays === 0;
|
|
755
|
+
const hasSnapshotHere = (detail.snapshots || []).some((entry) => entry.day === day);
|
|
755
756
|
const canSnapshot = chapters.length > 0
|
|
756
|
-
&&
|
|
757
|
-
|
|
757
|
+
&& onGridPoint
|
|
758
|
+
&& (hasSnapshotHere || day <= storyEnd);
|
|
759
|
+
const regenerating = hasSnapshotHere;
|
|
760
|
+
const snapshotLabel = regenerating
|
|
758
761
|
? t('重新生成快照 · 第 {day} 天', 'Regenerate snapshot · day {day}', { day })
|
|
759
762
|
: t('生成世界快照 · 第 {day} 天', 'Generate snapshot · day {day}', { day });
|
|
760
763
|
const nearest = useMemo(() => {
|
|
@@ -773,18 +776,18 @@ body[data-ds-dark-theme] .dsn-shell{color-scheme:dark}
|
|
|
773
776
|
|
|
774
777
|
const askSnapshot = useCallback(() => {
|
|
775
778
|
requestConfirm({
|
|
776
|
-
title:
|
|
779
|
+
title: regenerating
|
|
777
780
|
? t('是否重新生成第 {day} 天的世界快照?', 'Regenerate the world snapshot for day {day}?', { day })
|
|
778
781
|
: t('是否为当前时间生成世界快照?', "Generate a world snapshot for this point in time?"),
|
|
779
|
-
text:
|
|
782
|
+
text: regenerating
|
|
780
783
|
? t('会以第 {day} 天之前那版快照和这段时间的章节梗概重写这一天,原有快照被替换。', 'It rewrites day {day} from the snapshot before it and the synopses of this stretch; the existing snapshot is replaced.', { day })
|
|
781
784
|
: t('将为第 {day} 天生成一份世界快照:以上一份快照和这段时间的章节梗概为依据,时间线会延伸到这一天。', 'A world snapshot will be generated for day {day} from the previous snapshot and the synopses of this stretch; the timeline extends to that day.', { day }),
|
|
782
|
-
confirmLabel:
|
|
785
|
+
confirmLabel: regenerating
|
|
783
786
|
? t('重新生成', 'Regenerate')
|
|
784
787
|
: t('生成快照', "Generate snapshot"),
|
|
785
788
|
action: () => onRefreshSnapshot(day),
|
|
786
789
|
});
|
|
787
|
-
}, [requestConfirm, day, detail, onRefreshSnapshot,
|
|
790
|
+
}, [requestConfirm, day, detail, onRefreshSnapshot, regenerating]);
|
|
788
791
|
|
|
789
792
|
return h(React.Fragment, null,
|
|
790
793
|
h('div', { className: 'dsn-pagehead' },
|
package/index.js
CHANGED
|
@@ -1073,7 +1073,7 @@ async function bootstrap(ctx, root) {
|
|
|
1073
1073
|
}
|
|
1074
1074
|
}
|
|
1075
1075
|
return {
|
|
1076
|
-
revision: '2026-10-20.
|
|
1076
|
+
revision: '2026-10-20.19',
|
|
1077
1077
|
projects,
|
|
1078
1078
|
models: {
|
|
1079
1079
|
default: modelCatalog.default || null,
|
|
@@ -1349,15 +1349,22 @@ async function ensureWorkspace(ctx, project) {
|
|
|
1349
1349
|
return workspace;
|
|
1350
1350
|
}
|
|
1351
1351
|
|
|
1352
|
-
/**
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1352
|
+
/**
|
|
1353
|
+
* Whether a chapter ending at `day` earns an automatic snapshot.
|
|
1354
|
+
*
|
|
1355
|
+
* The rule ignores how many snapshots exist and which interval produced them:
|
|
1356
|
+
* a point counts only when the chapter lands exactly on a multiple of the
|
|
1357
|
+
* *current* interval, and the point one interval earlier is already covered
|
|
1358
|
+
* (day 0 — the story's start — counts as covered). Changing the interval
|
|
1359
|
+
* therefore moves the grid, and generation only continues along an unbroken
|
|
1360
|
+
* chain rather than back-filling gaps by itself.
|
|
1361
|
+
*/
|
|
1362
|
+
function snapshotPointDue(project, day) {
|
|
1356
1363
|
const unit = Math.max(1, project.timeline.unitDays || 30);
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
return
|
|
1364
|
+
if (day <= 0 || day % unit !== 0) return false;
|
|
1365
|
+
const previous = day - unit;
|
|
1366
|
+
if (previous <= 0) return true;
|
|
1367
|
+
return (project.snapshots || []).some((snapshot) => snapshot.day === previous);
|
|
1361
1368
|
}
|
|
1362
1369
|
|
|
1363
1370
|
/** Story day where the next chapter continues: the end of the last chapter. */
|
|
@@ -1385,8 +1392,15 @@ function withTimelineFacts(project) {
|
|
|
1385
1392
|
unitDays,
|
|
1386
1393
|
latestSnapshotDay,
|
|
1387
1394
|
storyEnd: lastChapterEnd(project),
|
|
1388
|
-
|
|
1395
|
+
// The next point on the *current* interval's grid, not "last + unit":
|
|
1396
|
+
// after an interval change the grid moves under the existing snapshots.
|
|
1397
|
+
nextSnapshotDay: Math.floor(latestSnapshotDay / unitDays) * unitDays + unitDays,
|
|
1389
1398
|
snapshotDays: snapshots.map((snapshot) => snapshot.day),
|
|
1399
|
+
// Every grid point up to the story's end, so the Client can mark them.
|
|
1400
|
+
gridDays: Array.from(
|
|
1401
|
+
{ length: Math.max(0, Math.floor(lastChapterEnd(project) / unitDays)) },
|
|
1402
|
+
(_, index) => (index + 1) * unitDays,
|
|
1403
|
+
),
|
|
1390
1404
|
},
|
|
1391
1405
|
};
|
|
1392
1406
|
}
|
|
@@ -1465,7 +1479,7 @@ function startGenerate(ctx, root, registry, body) {
|
|
|
1465
1479
|
await exportChapter(project, saved, content);
|
|
1466
1480
|
const window = chapterWindow(project, chapter);
|
|
1467
1481
|
let snapshotJobId = null;
|
|
1468
|
-
if (
|
|
1482
|
+
if (snapshotPointDue(project, window.end)) {
|
|
1469
1483
|
const snapshotJob = registerSnapshot(ctx, root, registry, projectId, window.end);
|
|
1470
1484
|
snapshotJobId = snapshotJob.jobId;
|
|
1471
1485
|
}
|
|
@@ -1582,7 +1596,7 @@ function startCompose(ctx, root, registry, body) {
|
|
|
1582
1596
|
if (written) await exportChapter(after, written, content);
|
|
1583
1597
|
const window = chapterWindow(after, target);
|
|
1584
1598
|
let snapshotJobId = null;
|
|
1585
|
-
if (
|
|
1599
|
+
if (snapshotPointDue(after, window.end)) {
|
|
1586
1600
|
snapshotJobId = registerSnapshot(ctx, root, registry, projectId, window.end).jobId;
|
|
1587
1601
|
}
|
|
1588
1602
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-novel-generator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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",
|