@musnows/scriverse 0.4.11 → 0.5.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/dist/ai.js +383 -20
- package/dist/ai.js.map +1 -1
- package/dist/app.js +69 -12
- package/dist/app.js.map +1 -1
- package/dist/database.js +55 -0
- package/dist/database.js.map +1 -1
- package/dist/pagination.js +1 -1
- package/dist/public/ai-message-meta.js +7 -2
- package/dist/public/app.js +575 -81
- package/dist/public/global-search.d.ts +12 -0
- package/dist/public/global-search.js +23 -0
- package/dist/public/index.html +18 -5
- package/dist/public/relationship-filters.d.ts +10 -0
- package/dist/public/relationship-filters.js +11 -0
- package/dist/public/relationship-graph.js +59 -3
- package/dist/public/styles.css +247 -2
- package/dist/store.js +123 -30
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +12 -0
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
package/dist/store.js
CHANGED
|
@@ -6,6 +6,27 @@ import { paginated, paginationSql } from "./pagination.js";
|
|
|
6
6
|
import { currentRequestActor } from "./request-context.js";
|
|
7
7
|
import { classifyWorkModulePermissions, emptyWorkModulePermissions, fullWorkModulePermissions, storedWorkModulePermissions } from "./work-permissions.js";
|
|
8
8
|
import { countWords, documentShortSearchTerms, id, json, normalizeDocumentSearchText, normalizeParagraphSpacing, now, splitDocumentParagraphs } from "./utils.js";
|
|
9
|
+
const defaultPlatformPageSizes = {
|
|
10
|
+
characters: 30,
|
|
11
|
+
analysisTasks: 30,
|
|
12
|
+
fileVersions: 30
|
|
13
|
+
};
|
|
14
|
+
function platformPageSizes(value) {
|
|
15
|
+
const stored = typeof value === "string"
|
|
16
|
+
? json(value, {})
|
|
17
|
+
: value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
18
|
+
const pageSize = (key) => {
|
|
19
|
+
const candidate = Number(stored[key]);
|
|
20
|
+
return Number.isInteger(candidate) && candidate >= 10 && candidate <= 100
|
|
21
|
+
? candidate
|
|
22
|
+
: defaultPlatformPageSizes[key];
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
characters: pageSize("characters"),
|
|
26
|
+
analysisTasks: pageSize("analysisTasks"),
|
|
27
|
+
fileVersions: pageSize("fileVersions")
|
|
28
|
+
};
|
|
29
|
+
}
|
|
9
30
|
function settingsFromInput(settingsMarkdown, settings, fallback = []) {
|
|
10
31
|
if (settingsMarkdown !== undefined)
|
|
11
32
|
return settingsMarkdown.trim() ? [settingsMarkdown] : [];
|
|
@@ -571,16 +592,23 @@ export class Store {
|
|
|
571
592
|
const row = this.db.get("SELECT * FROM platform_ui_settings WHERE id = 1");
|
|
572
593
|
return {
|
|
573
594
|
toastPosition: String(row?.toast_position) === "top-right" ? "top-right" : "bottom-right",
|
|
595
|
+
pageSizes: platformPageSizes(row?.page_sizes_json),
|
|
574
596
|
updatedAt: String(row?.updated_at ?? "")
|
|
575
597
|
};
|
|
576
598
|
}
|
|
577
599
|
updatePlatformUiSettings(input) {
|
|
578
600
|
const timestamp = now();
|
|
601
|
+
const current = this.getPlatformUiSettings();
|
|
602
|
+
const currentPageSizes = platformPageSizes(current.pageSizes);
|
|
603
|
+
const pageSizes = platformPageSizes(JSON.stringify({ ...currentPageSizes, ...input.pageSizes }));
|
|
604
|
+
const toastPosition = input.toastPosition ?? (current.toastPosition === "top-right" ? "top-right" : "bottom-right");
|
|
579
605
|
this.db.transaction(() => {
|
|
580
|
-
this.db.run(`INSERT INTO platform_ui_settings (id, toast_position, updated_at) VALUES (1, ?, ?)
|
|
581
|
-
ON CONFLICT(id) DO UPDATE SET toast_position = excluded.toast_position,
|
|
606
|
+
this.db.run(`INSERT INTO platform_ui_settings (id, toast_position, page_sizes_json, updated_at) VALUES (1, ?, ?, ?)
|
|
607
|
+
ON CONFLICT(id) DO UPDATE SET toast_position = excluded.toast_position,
|
|
608
|
+
page_sizes_json = excluded.page_sizes_json, updated_at = excluded.updated_at`, toastPosition, JSON.stringify(pageSizes), timestamp);
|
|
582
609
|
this.audit(PLATFORM_AI_WORK_ID, "platform.ui-settings.updated", "platform-ui-settings", "platform-ui-settings", {
|
|
583
|
-
toastPosition
|
|
610
|
+
toastPosition,
|
|
611
|
+
pageSizes
|
|
584
612
|
});
|
|
585
613
|
});
|
|
586
614
|
return this.getPlatformUiSettings();
|
|
@@ -3605,8 +3633,21 @@ export class Store {
|
|
|
3605
3633
|
this.getWork(workId);
|
|
3606
3634
|
const taskId = id("task");
|
|
3607
3635
|
const timestamp = now();
|
|
3608
|
-
const scope = input.scope ?? { type: "book" };
|
|
3636
|
+
const scope = { ...(input.scope ?? { type: "book" }) };
|
|
3609
3637
|
const sourceVersions = {};
|
|
3638
|
+
const targetCharacters = [];
|
|
3639
|
+
if (Array.isArray(scope.characterIds)) {
|
|
3640
|
+
for (const characterId of scope.characterIds) {
|
|
3641
|
+
if (typeof characterId !== "string")
|
|
3642
|
+
throw new AppError(400, "CHARACTER_REQUIRED", "被分析角色标识无效");
|
|
3643
|
+
const character = this.getCharacter(characterId);
|
|
3644
|
+
if (character.workId !== workId)
|
|
3645
|
+
throw new AppError(400, "CHARACTER_WORK_MISMATCH", "被分析角色不属于当前作品");
|
|
3646
|
+
targetCharacters.push({ id: characterId, name: String(character.name) });
|
|
3647
|
+
}
|
|
3648
|
+
if (targetCharacters.length > 0)
|
|
3649
|
+
scope.targetCharacters = targetCharacters;
|
|
3650
|
+
}
|
|
3610
3651
|
if (typeof scope.chapterId === "string") {
|
|
3611
3652
|
const chapter = this.getChapter(scope.chapterId);
|
|
3612
3653
|
if (chapter.workId !== workId)
|
|
@@ -3631,20 +3672,16 @@ export class Store {
|
|
|
3631
3672
|
this.notifyAnalysisTaskQueued(workId);
|
|
3632
3673
|
return this.getTask(taskId);
|
|
3633
3674
|
}
|
|
3634
|
-
listTasks(workId) {
|
|
3635
|
-
this.getWork(workId);
|
|
3636
|
-
return this.db.all("SELECT * FROM analysis_tasks WHERE work_id = ? ORDER BY created_at DESC, id DESC", workId).map((row) => this.mapTask(row));
|
|
3637
|
-
}
|
|
3638
|
-
listTasksPage(workId, pagination) {
|
|
3639
|
-
this.getWork(workId);
|
|
3640
|
-
const page = paginationSql(pagination);
|
|
3641
|
-
const rows = this.db.all(`SELECT * FROM analysis_tasks WHERE work_id = ? ORDER BY created_at DESC, id DESC${page.sql}`, workId, ...page.params);
|
|
3642
|
-
return paginated(rows.map((row) => this.mapTask(row)), pagination);
|
|
3643
|
-
}
|
|
3644
3675
|
listTaskSummariesPage(workId, pagination) {
|
|
3645
3676
|
this.getWork(workId);
|
|
3677
|
+
const statsRow = this.db.get(`SELECT COUNT(*) AS total,
|
|
3678
|
+
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count,
|
|
3679
|
+
SUM(CASE WHEN status = 'running' THEN 1 ELSE 0 END) AS running_count,
|
|
3680
|
+
AVG(CASE WHEN status = 'running' THEN progress ELSE NULL END) AS running_progress
|
|
3681
|
+
FROM analysis_tasks WHERE work_id = ?`, workId) ?? {};
|
|
3682
|
+
const total = numberValue(statsRow, "total");
|
|
3646
3683
|
const page = paginationSql(pagination);
|
|
3647
|
-
const rows = this.db.all(`SELECT id,
|
|
3684
|
+
const rows = this.db.all(`SELECT id, task_type, scope_json, status, progress, created_at, updated_at
|
|
3648
3685
|
FROM analysis_tasks WHERE work_id = ? ORDER BY created_at DESC, id DESC${page.sql}`, workId, ...page.params);
|
|
3649
3686
|
const chapterSummaries = new Map(this.db.all(`SELECT chapter.id, chapter.title, volume.title AS volume_title
|
|
3650
3687
|
FROM chapters chapter JOIN volumes volume ON volume.id = chapter.volume_id
|
|
@@ -3653,7 +3690,16 @@ export class Store {
|
|
|
3653
3690
|
`${requiredString(row, "volume_title")} · ${requiredString(row, "title")}`
|
|
3654
3691
|
]));
|
|
3655
3692
|
const volumeTitles = new Map(this.db.all("SELECT id, title FROM volumes WHERE work_id = ?", workId).map((row) => [requiredString(row, "id"), requiredString(row, "title")]));
|
|
3656
|
-
|
|
3693
|
+
const characterNames = this.taskCharacterNames(workId, rows.map((row) => json(requiredString(row, "scope_json"), {})));
|
|
3694
|
+
return {
|
|
3695
|
+
...paginated(rows.map((row) => this.mapTaskSummary(row, chapterSummaries, volumeTitles, characterNames)), pagination, total),
|
|
3696
|
+
stats: {
|
|
3697
|
+
total,
|
|
3698
|
+
pendingCount: numberValue(statsRow, "pending_count"),
|
|
3699
|
+
runningCount: numberValue(statsRow, "running_count"),
|
|
3700
|
+
runningProgress: numberValue(statsRow, "running_progress")
|
|
3701
|
+
}
|
|
3702
|
+
};
|
|
3657
3703
|
}
|
|
3658
3704
|
getTask(taskId) {
|
|
3659
3705
|
const row = this.db.get("SELECT * FROM analysis_tasks WHERE id = ?", taskId);
|
|
@@ -3728,12 +3774,14 @@ export class Store {
|
|
|
3728
3774
|
mapTask(row) {
|
|
3729
3775
|
const workId = requiredString(row, "work_id");
|
|
3730
3776
|
const scope = json(requiredString(row, "scope_json"), {});
|
|
3777
|
+
const characterNames = this.taskCharacterNames(workId, [scope]);
|
|
3731
3778
|
return {
|
|
3732
3779
|
id: requiredString(row, "id"),
|
|
3733
3780
|
workId,
|
|
3734
3781
|
taskType: requiredString(row, "task_type"),
|
|
3735
3782
|
scope,
|
|
3736
|
-
scopeSummary: this.taskScopeSummary(workId, scope),
|
|
3783
|
+
scopeSummary: this.taskScopeSummary(workId, scope, characterNames),
|
|
3784
|
+
scopeSummaryWithoutCharacterNames: this.taskScopeSummary(workId, scope, new Map(), false),
|
|
3737
3785
|
scopeDetails: this.taskScopeDetails(workId, scope),
|
|
3738
3786
|
status: requiredString(row, "status"),
|
|
3739
3787
|
progress: numberValue(row, "progress"),
|
|
@@ -3744,51 +3792,96 @@ export class Store {
|
|
|
3744
3792
|
updatedAt: requiredString(row, "updated_at")
|
|
3745
3793
|
};
|
|
3746
3794
|
}
|
|
3747
|
-
mapTaskSummary(row, chapterSummaries, volumeTitles) {
|
|
3795
|
+
mapTaskSummary(row, chapterSummaries, volumeTitles, characterNames) {
|
|
3748
3796
|
const scope = json(requiredString(row, "scope_json"), {});
|
|
3749
3797
|
return {
|
|
3750
3798
|
id: requiredString(row, "id"),
|
|
3751
|
-
workId: requiredString(row, "work_id"),
|
|
3752
3799
|
taskType: requiredString(row, "task_type"),
|
|
3753
|
-
scope,
|
|
3754
|
-
|
|
3800
|
+
scopeSummary: this.taskScopeSummaryFromMaps(scope, chapterSummaries, volumeTitles, characterNames),
|
|
3801
|
+
scopeSummaryWithoutCharacterNames: this.taskScopeSummaryFromMaps(scope, chapterSummaries, volumeTitles, new Map(), false),
|
|
3755
3802
|
status: requiredString(row, "status"),
|
|
3756
3803
|
progress: numberValue(row, "progress"),
|
|
3757
3804
|
createdAt: requiredString(row, "created_at"),
|
|
3758
3805
|
updatedAt: requiredString(row, "updated_at")
|
|
3759
3806
|
};
|
|
3760
3807
|
}
|
|
3761
|
-
taskScopeSummaryFromMaps(scope, chapterSummaries, volumeTitles) {
|
|
3808
|
+
taskScopeSummaryFromMaps(scope, chapterSummaries, volumeTitles, characterNames, includeCharacterNames = true) {
|
|
3809
|
+
const targetedSuffix = this.taskTargetedSuffix(scope, characterNames, includeCharacterNames);
|
|
3762
3810
|
if (typeof scope.chapterId === "string")
|
|
3763
|
-
return chapterSummaries.get(scope.chapterId) ?? "章节已删除"
|
|
3811
|
+
return `${chapterSummaries.get(scope.chapterId) ?? "章节已删除"}${targetedSuffix}`;
|
|
3764
3812
|
if (scope.type === "volume" && typeof scope.volumeId === "string") {
|
|
3765
3813
|
const title = volumeTitles.get(scope.volumeId);
|
|
3766
|
-
return title ? `分卷 · ${title}` : "分卷已删除"
|
|
3814
|
+
return `${title ? `分卷 · ${title}` : "分卷已删除"}${targetedSuffix}`;
|
|
3767
3815
|
}
|
|
3768
3816
|
if (scope.type === "book" || Object.keys(scope).length === 0)
|
|
3769
|
-
return "全书"
|
|
3817
|
+
return `${scope.includeAllSettings === true ? "全书 + 所有设定" : "全书"}${targetedSuffix}`;
|
|
3770
3818
|
return "未指定范围";
|
|
3771
3819
|
}
|
|
3772
|
-
taskScopeSummary(workId, scope) {
|
|
3820
|
+
taskScopeSummary(workId, scope, characterNames, includeCharacterNames = true) {
|
|
3821
|
+
const targetedSuffix = this.taskTargetedSuffix(scope, characterNames, includeCharacterNames);
|
|
3773
3822
|
if (typeof scope.chapterId === "string") {
|
|
3774
3823
|
const chapter = this.db.get(`SELECT chapter.title AS title, volume.title AS volume_title
|
|
3775
3824
|
FROM chapters chapter
|
|
3776
3825
|
JOIN volumes volume ON volume.id = chapter.volume_id
|
|
3777
3826
|
WHERE chapter.id = ? AND chapter.work_id = ?`, scope.chapterId, workId);
|
|
3778
3827
|
if (!chapter)
|
|
3779
|
-
return
|
|
3828
|
+
return `章节已删除${targetedSuffix}`;
|
|
3780
3829
|
const title = requiredString(chapter, "title");
|
|
3781
3830
|
const volumeTitle = requiredString(chapter, "volume_title");
|
|
3782
|
-
return `${volumeTitle} · ${title}`;
|
|
3831
|
+
return `${volumeTitle} · ${title}${targetedSuffix}`;
|
|
3783
3832
|
}
|
|
3784
3833
|
if (scope.type === "volume" && typeof scope.volumeId === "string") {
|
|
3785
3834
|
const volume = this.db.get("SELECT title FROM volumes WHERE id = ? AND work_id = ?", scope.volumeId, workId);
|
|
3786
|
-
return volume ? `分卷 · ${requiredString(volume, "title")}` : "分卷已删除"
|
|
3835
|
+
return `${volume ? `分卷 · ${requiredString(volume, "title")}` : "分卷已删除"}${targetedSuffix}`;
|
|
3787
3836
|
}
|
|
3788
3837
|
if (scope.type === "book" || Object.keys(scope).length === 0)
|
|
3789
|
-
return "全书"
|
|
3838
|
+
return `${scope.includeAllSettings === true ? "全书 + 所有设定" : "全书"}${targetedSuffix}`;
|
|
3790
3839
|
return "未指定范围";
|
|
3791
3840
|
}
|
|
3841
|
+
taskCharacterNames(workId, scopes) {
|
|
3842
|
+
const characterIds = [...new Set(scopes.flatMap((scope) => {
|
|
3843
|
+
const snapshotNames = this.taskCharacterSnapshotNames(scope);
|
|
3844
|
+
return Array.isArray(scope.characterIds)
|
|
3845
|
+
? scope.characterIds.filter((characterId) => typeof characterId === "string" && !snapshotNames.has(characterId))
|
|
3846
|
+
: [];
|
|
3847
|
+
}))];
|
|
3848
|
+
const characterNames = new Map();
|
|
3849
|
+
for (let offset = 0; offset < characterIds.length; offset += 400) {
|
|
3850
|
+
const batch = characterIds.slice(offset, offset + 400);
|
|
3851
|
+
const placeholders = batch.map(() => "?").join(", ");
|
|
3852
|
+
for (const row of this.db.all(`SELECT id, name FROM characters WHERE work_id = ? AND id IN (${placeholders})`, workId, ...batch)) {
|
|
3853
|
+
characterNames.set(requiredString(row, "id"), requiredString(row, "name"));
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
return characterNames;
|
|
3857
|
+
}
|
|
3858
|
+
taskTargetedSuffix(scope, characterNames, includeCharacterNames = true) {
|
|
3859
|
+
const characterIds = Array.isArray(scope.characterIds)
|
|
3860
|
+
? scope.characterIds.filter((characterId) => typeof characterId === "string")
|
|
3861
|
+
: [];
|
|
3862
|
+
if (characterIds.length === 0)
|
|
3863
|
+
return "";
|
|
3864
|
+
const overwriteSuffix = scope.replaceExistingRelationships === true ? " · 覆盖已有关系" : "";
|
|
3865
|
+
if (!includeCharacterNames)
|
|
3866
|
+
return ` · 定向 ${characterIds.length} 人${overwriteSuffix}`;
|
|
3867
|
+
const snapshotNames = this.taskCharacterSnapshotNames(scope);
|
|
3868
|
+
const names = characterIds.map((characterId) => snapshotNames.get(characterId) ?? characterNames.get(characterId) ?? "已删除角色");
|
|
3869
|
+
return ` · 定向 ${characterIds.length} 人:${names.join("、")}${overwriteSuffix}`;
|
|
3870
|
+
}
|
|
3871
|
+
taskCharacterSnapshotNames(scope) {
|
|
3872
|
+
const snapshotNames = new Map();
|
|
3873
|
+
if (Array.isArray(scope.targetCharacters)) {
|
|
3874
|
+
for (const item of scope.targetCharacters) {
|
|
3875
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
3876
|
+
continue;
|
|
3877
|
+
const target = item;
|
|
3878
|
+
if (typeof target.id === "string" && typeof target.name === "string" && target.name.trim()) {
|
|
3879
|
+
snapshotNames.set(target.id, target.name);
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
return snapshotNames;
|
|
3884
|
+
}
|
|
3792
3885
|
taskScopeDetails(workId, scope) {
|
|
3793
3886
|
if (typeof scope.chapterId === "string") {
|
|
3794
3887
|
const chapter = this.db.get(`SELECT chapter.id AS id, chapter.title AS title, chapter.version_no AS version_no,
|