@musnows/scriverse 0.7.2 → 0.7.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.
Files changed (48) hide show
  1. package/README.md +5 -0
  2. package/dist/ai.js +248 -250
  3. package/dist/ai.js.map +1 -1
  4. package/dist/app.js +184 -39
  5. package/dist/app.js.map +1 -1
  6. package/dist/attachment-storage.js +28 -12
  7. package/dist/attachment-storage.js.map +1 -1
  8. package/dist/backup-encryption.js +129 -0
  9. package/dist/backup-encryption.js.map +1 -0
  10. package/dist/collaboration-presence.js +200 -18
  11. package/dist/collaboration-presence.js.map +1 -1
  12. package/dist/database.js +162 -3
  13. package/dist/database.js.map +1 -1
  14. package/dist/docx-export.js +1 -1
  15. package/dist/docx-export.js.map +1 -1
  16. package/dist/domain.js +18 -0
  17. package/dist/domain.js.map +1 -1
  18. package/dist/image-metadata.js +17 -2
  19. package/dist/image-metadata.js.map +1 -1
  20. package/dist/presence-store.js +160 -0
  21. package/dist/presence-store.js.map +1 -0
  22. package/dist/public/ai-context-meter.js +27 -0
  23. package/dist/public/ai-mentions.js +14 -0
  24. package/dist/public/app.js +1017 -291
  25. package/dist/public/background-task-center.js +1 -1
  26. package/dist/public/chapter-editor-virtualization.js +52 -0
  27. package/dist/public/index.html +43 -21
  28. package/dist/public/presence-client-id.d.ts +10 -0
  29. package/dist/public/presence-client-id.js +31 -0
  30. package/dist/public/s3-backup-ui.d.ts +10 -0
  31. package/dist/public/s3-backup-ui.js +29 -0
  32. package/dist/public/setting-filters.d.ts +16 -0
  33. package/dist/public/setting-filters.js +19 -0
  34. package/dist/public/styles.css +37 -7
  35. package/dist/s3-backup.js +197 -12
  36. package/dist/s3-backup.js.map +1 -1
  37. package/dist/security.js +82 -20
  38. package/dist/security.js.map +1 -1
  39. package/dist/server-runtime.js +36 -18
  40. package/dist/server-runtime.js.map +1 -1
  41. package/dist/store.js +75 -17
  42. package/dist/store.js.map +1 -1
  43. package/dist/upload-limits.js +35 -0
  44. package/dist/upload-limits.js.map +1 -0
  45. package/dist/user-auth.js +11 -1
  46. package/dist/user-auth.js.map +1 -1
  47. package/dist/version.js +1 -1
  48. package/package.json +3 -2
@@ -0,0 +1,160 @@
1
+ function requiredString(value) {
2
+ return typeof value === "string" && value.length > 0 ? value : null;
3
+ }
4
+ function optionalString(value) {
5
+ return typeof value === "string" && value.length > 0 ? value : undefined;
6
+ }
7
+ function parsePage(row) {
8
+ const kind = requiredString(row.page_kind);
9
+ if (kind === "welcome" || kind === "settings")
10
+ return { kind };
11
+ if (kind === "editor") {
12
+ const resourceId = requiredString(row.page_resource_id);
13
+ return resourceId ? { kind, resourceId } : null;
14
+ }
15
+ if (kind === "module") {
16
+ const module = requiredString(row.page_module);
17
+ return module ? { kind, module } : null;
18
+ }
19
+ if (kind === "entity-editor") {
20
+ const module = requiredString(row.page_module);
21
+ const resourceId = optionalString(row.page_resource_id);
22
+ return module ? { kind, module, ...(resourceId ? { resourceId } : {}) } : null;
23
+ }
24
+ return null;
25
+ }
26
+ function uniqueRecipients(recipients) {
27
+ return recipients.filter((recipient, index, values) => values.findIndex((candidate) => (candidate.userId === recipient.userId && candidate.clientId === recipient.clientId)) === index);
28
+ }
29
+ function parseRecipients(value) {
30
+ if (typeof value !== "string")
31
+ return [];
32
+ try {
33
+ const parsed = JSON.parse(value);
34
+ if (!Array.isArray(parsed))
35
+ return [];
36
+ const recipients = [];
37
+ for (const item of parsed) {
38
+ if (!item || typeof item !== "object" || Array.isArray(item))
39
+ continue;
40
+ const candidate = item;
41
+ const userId = requiredString(candidate.userId);
42
+ const clientId = requiredString(candidate.clientId);
43
+ if (userId && clientId)
44
+ recipients.push({ userId, clientId });
45
+ }
46
+ return uniqueRecipients(recipients);
47
+ }
48
+ catch {
49
+ return [];
50
+ }
51
+ }
52
+ export class PresenceStore {
53
+ database;
54
+ constructor(database) {
55
+ this.database = database;
56
+ }
57
+ loadEntries(activeSince) {
58
+ const entries = [];
59
+ for (const row of this.database.all(`SELECT work_id, client_id, user_id, username, display_name, avatar_url,
60
+ page_kind, page_module, page_resource_id, last_seen_at
61
+ FROM presence_entries
62
+ WHERE last_seen_at >= ?
63
+ ORDER BY last_seen_at ASC, work_id ASC, client_id ASC`, activeSince)) {
64
+ const workId = requiredString(row.work_id);
65
+ const clientId = requiredString(row.client_id);
66
+ const userId = requiredString(row.user_id);
67
+ const username = requiredString(row.username);
68
+ const displayName = requiredString(row.display_name);
69
+ const lastSeenAt = requiredString(row.last_seen_at);
70
+ const page = parsePage(row);
71
+ if (!workId || !clientId || !userId || !username || !displayName || !lastSeenAt || !page)
72
+ continue;
73
+ entries.push({
74
+ workId,
75
+ clientId,
76
+ userId,
77
+ username,
78
+ displayName,
79
+ avatarUrl: typeof row.avatar_url === "string" ? row.avatar_url : null,
80
+ page,
81
+ lastSeenAt
82
+ });
83
+ }
84
+ return entries;
85
+ }
86
+ loadChanges(activeSince, limit) {
87
+ const resolvedLimit = Math.max(0, Math.trunc(limit));
88
+ if (resolvedLimit === 0)
89
+ return [];
90
+ const changes = [];
91
+ for (const row of this.database.all(`SELECT id, work_id, page_key, label, action, page_deleted, actor_user_id, actor_display_name,
92
+ saved_at, recipient_client_ids_json
93
+ FROM presence_changes
94
+ WHERE saved_at >= ?
95
+ ORDER BY saved_at DESC, id DESC
96
+ LIMIT ?`, activeSince, resolvedLimit)) {
97
+ const id = requiredString(row.id);
98
+ const workId = requiredString(row.work_id);
99
+ const pageKey = requiredString(row.page_key);
100
+ const label = requiredString(row.label);
101
+ const action = row.action === "delete" ? "delete" : "save";
102
+ const pageDeleted = Number(row.page_deleted) === 1;
103
+ const actorUserId = requiredString(row.actor_user_id);
104
+ const actorDisplayName = requiredString(row.actor_display_name);
105
+ const savedAt = requiredString(row.saved_at);
106
+ if (!id || !workId || !pageKey || !label || !actorUserId || !actorDisplayName || !savedAt)
107
+ continue;
108
+ changes.push({
109
+ id,
110
+ workId,
111
+ pageKey,
112
+ label,
113
+ action,
114
+ pageDeleted,
115
+ actorUserId,
116
+ actorDisplayName,
117
+ savedAt,
118
+ recipients: parseRecipients(row.recipient_client_ids_json)
119
+ });
120
+ }
121
+ return changes.reverse();
122
+ }
123
+ flush(batch) {
124
+ const hasCleanup = batch.entryExpiryCutoff !== undefined || batch.changeExpiryCutoff !== undefined;
125
+ if (batch.entries.length === 0 && batch.changes.length === 0 && !hasCleanup)
126
+ return;
127
+ this.database.transaction(() => {
128
+ for (const entry of batch.entries) {
129
+ this.database.run(`INSERT INTO presence_entries (
130
+ work_id, client_id, user_id, username, display_name, avatar_url,
131
+ page_kind, page_module, page_resource_id, last_seen_at
132
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
133
+ ON CONFLICT(work_id, client_id) DO UPDATE SET
134
+ user_id = excluded.user_id,
135
+ username = excluded.username,
136
+ display_name = excluded.display_name,
137
+ avatar_url = excluded.avatar_url,
138
+ page_kind = excluded.page_kind,
139
+ page_module = excluded.page_module,
140
+ page_resource_id = excluded.page_resource_id,
141
+ last_seen_at = excluded.last_seen_at
142
+ WHERE excluded.last_seen_at >= presence_entries.last_seen_at`, entry.workId, entry.clientId, entry.userId, entry.username, entry.displayName, entry.avatarUrl, entry.page.kind, entry.page.module ?? null, entry.page.resourceId ?? null, entry.lastSeenAt);
143
+ }
144
+ for (const change of batch.changes) {
145
+ this.database.run(`INSERT INTO presence_changes (
146
+ id, work_id, page_key, label, action, page_deleted, actor_user_id, actor_display_name,
147
+ saved_at, recipient_client_ids_json
148
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
149
+ ON CONFLICT(id) DO NOTHING`, change.id, change.workId, change.pageKey, change.label, change.action, change.pageDeleted ? 1 : 0, change.actorUserId, change.actorDisplayName, change.savedAt, JSON.stringify(uniqueRecipients(change.recipients)));
150
+ }
151
+ if (batch.entryExpiryCutoff !== undefined) {
152
+ this.database.run("DELETE FROM presence_entries WHERE last_seen_at < ?", batch.entryExpiryCutoff);
153
+ }
154
+ if (batch.changeExpiryCutoff !== undefined) {
155
+ this.database.run("DELETE FROM presence_changes WHERE saved_at < ?", batch.changeExpiryCutoff);
156
+ }
157
+ });
158
+ }
159
+ }
160
+ //# sourceMappingURL=presence-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presence-store.js","sourceRoot":"","sources":["../src/presence-store.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,33 @@ export function resolveAiContextUsage(previousUsage, nextUsage) {
17
17
  return nextUsage && typeof nextUsage === "object" ? nextUsage : previousUsage ?? null;
18
18
  }
19
19
 
20
+ const maximumAiContextUsageFields = [
21
+ "inputTokens",
22
+ "contextTokens",
23
+ "conversationTokens",
24
+ "usagePercent",
25
+ "conversationUsagePercent"
26
+ ];
27
+ const minimumAiContextUsageFields = ["remainingTokens"];
28
+
29
+ export function mergeAiContextUsage(previousUsage, nextUsage, allowShrink = false) {
30
+ if (!nextUsage || typeof nextUsage !== "object" || Array.isArray(nextUsage)) return previousUsage ?? null;
31
+ if (!previousUsage || typeof previousUsage !== "object" || Array.isArray(previousUsage) || allowShrink) return nextUsage;
32
+ const mergedUsage = { ...nextUsage };
33
+ for (const field of [...maximumAiContextUsageFields, ...minimumAiContextUsageFields]) {
34
+ const previousValue = Number(previousUsage[field]);
35
+ const nextValue = Number(nextUsage[field]);
36
+ if (Number.isFinite(previousValue) && Number.isFinite(nextValue)) {
37
+ mergedUsage[field] = minimumAiContextUsageFields.includes(field)
38
+ ? Math.min(previousValue, nextValue)
39
+ : Math.max(previousValue, nextValue);
40
+ }
41
+ else if (Number.isFinite(previousValue)) mergedUsage[field] = previousValue;
42
+ else if (Number.isFinite(nextValue)) mergedUsage[field] = nextValue;
43
+ }
44
+ return mergedUsage;
45
+ }
46
+
20
47
  export function formatAiContextUsagePercent(occupiedTokens, contextWindow) {
21
48
  const occupied = tokenCount(occupiedTokens);
22
49
  const window = tokenCount(contextWindow);
@@ -67,3 +67,17 @@ export function mergeAiReferenceScope(scope, references) {
67
67
  if (referenceScope.includeSettingInfo === true) merged.includeSettingInfo = true;
68
68
  return merged;
69
69
  }
70
+
71
+ export function userMessageMentionNames(mentionCharacterIds, characters) {
72
+ if (!Array.isArray(mentionCharacterIds) || !Array.isArray(characters)) return [];
73
+ const characterNames = new Map(characters.map((item) => [String(item?.id ?? ""), String(item?.name ?? "").trim()]));
74
+ const seen = new Set();
75
+ const names = [];
76
+ for (const characterId of mentionCharacterIds) {
77
+ if (typeof characterId !== "string" || seen.has(characterId)) continue;
78
+ seen.add(characterId);
79
+ const name = characterNames.get(characterId);
80
+ if (name) names.push(name);
81
+ }
82
+ return names;
83
+ }