@musnows/scriverse 0.4.12 → 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 +175 -4
- package/dist/ai.js.map +1 -1
- package/dist/app.js +30 -11
- 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/app.js +400 -74
- package/dist/public/global-search.d.ts +12 -0
- package/dist/public/global-search.js +23 -0
- package/dist/public/index.html +17 -4
- 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 +209 -2
- package/dist/store.js +110 -32
- 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,9 @@ 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 = [];
|
|
3610
3639
|
if (Array.isArray(scope.characterIds)) {
|
|
3611
3640
|
for (const characterId of scope.characterIds) {
|
|
3612
3641
|
if (typeof characterId !== "string")
|
|
@@ -3614,7 +3643,10 @@ export class Store {
|
|
|
3614
3643
|
const character = this.getCharacter(characterId);
|
|
3615
3644
|
if (character.workId !== workId)
|
|
3616
3645
|
throw new AppError(400, "CHARACTER_WORK_MISMATCH", "被分析角色不属于当前作品");
|
|
3646
|
+
targetCharacters.push({ id: characterId, name: String(character.name) });
|
|
3617
3647
|
}
|
|
3648
|
+
if (targetCharacters.length > 0)
|
|
3649
|
+
scope.targetCharacters = targetCharacters;
|
|
3618
3650
|
}
|
|
3619
3651
|
if (typeof scope.chapterId === "string") {
|
|
3620
3652
|
const chapter = this.getChapter(scope.chapterId);
|
|
@@ -3640,20 +3672,16 @@ export class Store {
|
|
|
3640
3672
|
this.notifyAnalysisTaskQueued(workId);
|
|
3641
3673
|
return this.getTask(taskId);
|
|
3642
3674
|
}
|
|
3643
|
-
listTasks(workId) {
|
|
3644
|
-
this.getWork(workId);
|
|
3645
|
-
return this.db.all("SELECT * FROM analysis_tasks WHERE work_id = ? ORDER BY created_at DESC, id DESC", workId).map((row) => this.mapTask(row));
|
|
3646
|
-
}
|
|
3647
|
-
listTasksPage(workId, pagination) {
|
|
3648
|
-
this.getWork(workId);
|
|
3649
|
-
const page = paginationSql(pagination);
|
|
3650
|
-
const rows = this.db.all(`SELECT * FROM analysis_tasks WHERE work_id = ? ORDER BY created_at DESC, id DESC${page.sql}`, workId, ...page.params);
|
|
3651
|
-
return paginated(rows.map((row) => this.mapTask(row)), pagination);
|
|
3652
|
-
}
|
|
3653
3675
|
listTaskSummariesPage(workId, pagination) {
|
|
3654
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");
|
|
3655
3683
|
const page = paginationSql(pagination);
|
|
3656
|
-
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
|
|
3657
3685
|
FROM analysis_tasks WHERE work_id = ? ORDER BY created_at DESC, id DESC${page.sql}`, workId, ...page.params);
|
|
3658
3686
|
const chapterSummaries = new Map(this.db.all(`SELECT chapter.id, chapter.title, volume.title AS volume_title
|
|
3659
3687
|
FROM chapters chapter JOIN volumes volume ON volume.id = chapter.volume_id
|
|
@@ -3662,7 +3690,16 @@ export class Store {
|
|
|
3662
3690
|
`${requiredString(row, "volume_title")} · ${requiredString(row, "title")}`
|
|
3663
3691
|
]));
|
|
3664
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")]));
|
|
3665
|
-
|
|
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
|
+
};
|
|
3666
3703
|
}
|
|
3667
3704
|
getTask(taskId) {
|
|
3668
3705
|
const row = this.db.get("SELECT * FROM analysis_tasks WHERE id = ?", taskId);
|
|
@@ -3737,12 +3774,14 @@ export class Store {
|
|
|
3737
3774
|
mapTask(row) {
|
|
3738
3775
|
const workId = requiredString(row, "work_id");
|
|
3739
3776
|
const scope = json(requiredString(row, "scope_json"), {});
|
|
3777
|
+
const characterNames = this.taskCharacterNames(workId, [scope]);
|
|
3740
3778
|
return {
|
|
3741
3779
|
id: requiredString(row, "id"),
|
|
3742
3780
|
workId,
|
|
3743
3781
|
taskType: requiredString(row, "task_type"),
|
|
3744
3782
|
scope,
|
|
3745
|
-
scopeSummary: this.taskScopeSummary(workId, scope),
|
|
3783
|
+
scopeSummary: this.taskScopeSummary(workId, scope, characterNames),
|
|
3784
|
+
scopeSummaryWithoutCharacterNames: this.taskScopeSummary(workId, scope, new Map(), false),
|
|
3746
3785
|
scopeDetails: this.taskScopeDetails(workId, scope),
|
|
3747
3786
|
status: requiredString(row, "status"),
|
|
3748
3787
|
progress: numberValue(row, "progress"),
|
|
@@ -3753,57 +3792,96 @@ export class Store {
|
|
|
3753
3792
|
updatedAt: requiredString(row, "updated_at")
|
|
3754
3793
|
};
|
|
3755
3794
|
}
|
|
3756
|
-
mapTaskSummary(row, chapterSummaries, volumeTitles) {
|
|
3795
|
+
mapTaskSummary(row, chapterSummaries, volumeTitles, characterNames) {
|
|
3757
3796
|
const scope = json(requiredString(row, "scope_json"), {});
|
|
3758
3797
|
return {
|
|
3759
3798
|
id: requiredString(row, "id"),
|
|
3760
|
-
workId: requiredString(row, "work_id"),
|
|
3761
3799
|
taskType: requiredString(row, "task_type"),
|
|
3762
|
-
scope,
|
|
3763
|
-
|
|
3800
|
+
scopeSummary: this.taskScopeSummaryFromMaps(scope, chapterSummaries, volumeTitles, characterNames),
|
|
3801
|
+
scopeSummaryWithoutCharacterNames: this.taskScopeSummaryFromMaps(scope, chapterSummaries, volumeTitles, new Map(), false),
|
|
3764
3802
|
status: requiredString(row, "status"),
|
|
3765
3803
|
progress: numberValue(row, "progress"),
|
|
3766
3804
|
createdAt: requiredString(row, "created_at"),
|
|
3767
3805
|
updatedAt: requiredString(row, "updated_at")
|
|
3768
3806
|
};
|
|
3769
3807
|
}
|
|
3770
|
-
taskScopeSummaryFromMaps(scope, chapterSummaries, volumeTitles) {
|
|
3771
|
-
const targetedSuffix =
|
|
3772
|
-
? ` · 定向 ${scope.characterIds.length} 人${scope.replaceExistingRelationships === true ? " · 覆盖已有关系" : ""}`
|
|
3773
|
-
: "";
|
|
3808
|
+
taskScopeSummaryFromMaps(scope, chapterSummaries, volumeTitles, characterNames, includeCharacterNames = true) {
|
|
3809
|
+
const targetedSuffix = this.taskTargetedSuffix(scope, characterNames, includeCharacterNames);
|
|
3774
3810
|
if (typeof scope.chapterId === "string")
|
|
3775
3811
|
return `${chapterSummaries.get(scope.chapterId) ?? "章节已删除"}${targetedSuffix}`;
|
|
3776
3812
|
if (scope.type === "volume" && typeof scope.volumeId === "string") {
|
|
3777
3813
|
const title = volumeTitles.get(scope.volumeId);
|
|
3778
|
-
return title ? `分卷 · ${title}
|
|
3814
|
+
return `${title ? `分卷 · ${title}` : "分卷已删除"}${targetedSuffix}`;
|
|
3779
3815
|
}
|
|
3780
3816
|
if (scope.type === "book" || Object.keys(scope).length === 0)
|
|
3781
3817
|
return `${scope.includeAllSettings === true ? "全书 + 所有设定" : "全书"}${targetedSuffix}`;
|
|
3782
3818
|
return "未指定范围";
|
|
3783
3819
|
}
|
|
3784
|
-
taskScopeSummary(workId, scope) {
|
|
3785
|
-
const targetedSuffix =
|
|
3786
|
-
? ` · 定向 ${scope.characterIds.length} 人${scope.replaceExistingRelationships === true ? " · 覆盖已有关系" : ""}`
|
|
3787
|
-
: "";
|
|
3820
|
+
taskScopeSummary(workId, scope, characterNames, includeCharacterNames = true) {
|
|
3821
|
+
const targetedSuffix = this.taskTargetedSuffix(scope, characterNames, includeCharacterNames);
|
|
3788
3822
|
if (typeof scope.chapterId === "string") {
|
|
3789
3823
|
const chapter = this.db.get(`SELECT chapter.title AS title, volume.title AS volume_title
|
|
3790
3824
|
FROM chapters chapter
|
|
3791
3825
|
JOIN volumes volume ON volume.id = chapter.volume_id
|
|
3792
3826
|
WHERE chapter.id = ? AND chapter.work_id = ?`, scope.chapterId, workId);
|
|
3793
3827
|
if (!chapter)
|
|
3794
|
-
return
|
|
3828
|
+
return `章节已删除${targetedSuffix}`;
|
|
3795
3829
|
const title = requiredString(chapter, "title");
|
|
3796
3830
|
const volumeTitle = requiredString(chapter, "volume_title");
|
|
3797
3831
|
return `${volumeTitle} · ${title}${targetedSuffix}`;
|
|
3798
3832
|
}
|
|
3799
3833
|
if (scope.type === "volume" && typeof scope.volumeId === "string") {
|
|
3800
3834
|
const volume = this.db.get("SELECT title FROM volumes WHERE id = ? AND work_id = ?", scope.volumeId, workId);
|
|
3801
|
-
return volume ? `分卷 · ${requiredString(volume, "title")}
|
|
3835
|
+
return `${volume ? `分卷 · ${requiredString(volume, "title")}` : "分卷已删除"}${targetedSuffix}`;
|
|
3802
3836
|
}
|
|
3803
3837
|
if (scope.type === "book" || Object.keys(scope).length === 0)
|
|
3804
3838
|
return `${scope.includeAllSettings === true ? "全书 + 所有设定" : "全书"}${targetedSuffix}`;
|
|
3805
3839
|
return "未指定范围";
|
|
3806
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
|
+
}
|
|
3807
3885
|
taskScopeDetails(workId, scope) {
|
|
3808
3886
|
if (typeof scope.chapterId === "string") {
|
|
3809
3887
|
const chapter = this.db.get(`SELECT chapter.id AS id, chapter.title AS title, chapter.version_no AS version_no,
|