@musnows/scriverse 0.5.12 → 0.6.1
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-protocol.js +6 -1
- package/dist/ai-protocol.js.map +1 -1
- package/dist/ai-tool-results.js +177 -0
- package/dist/ai-tool-results.js.map +1 -0
- package/dist/ai.js +743 -138
- package/dist/ai.js.map +1 -1
- package/dist/app.js +94 -33
- package/dist/app.js.map +1 -1
- package/dist/cli-contract.js +5 -5
- package/dist/cli-core.js +8 -2
- package/dist/cli-core.js.map +1 -1
- package/dist/collaboration-presence.js +1 -0
- package/dist/collaboration-presence.js.map +1 -1
- package/dist/database.js +17 -0
- package/dist/database.js.map +1 -1
- package/dist/public/ai-context-meter.js +39 -0
- package/dist/public/app.js +619 -228
- package/dist/public/entity-version.js +2 -2
- package/dist/public/index.html +22 -11
- package/dist/public/model-config.d.ts +3 -0
- package/dist/public/model-config.js +14 -0
- package/dist/public/page-route.js +1 -0
- package/dist/public/styles.css +73 -14
- package/dist/public/work-permissions.d.ts +1 -1
- package/dist/public/work-permissions.js +1 -1
- package/dist/store.js +98 -13
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +4 -5
- package/dist/user-auth.js.map +1 -1
- package/dist/utils.js +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/work-permissions.js +1 -1
- package/package.json +1 -1
package/dist/store.js
CHANGED
|
@@ -16,6 +16,7 @@ const defaultPlatformPageSizes = {
|
|
|
16
16
|
timeline: 30,
|
|
17
17
|
outlines: 30,
|
|
18
18
|
relationships: 30,
|
|
19
|
+
comments: 30,
|
|
19
20
|
reviews: 30,
|
|
20
21
|
analysisTasks: 30,
|
|
21
22
|
fileVersions: 30
|
|
@@ -39,6 +40,7 @@ function platformPageSizes(value) {
|
|
|
39
40
|
timeline: pageSize("timeline"),
|
|
40
41
|
outlines: pageSize("outlines"),
|
|
41
42
|
relationships: pageSize("relationships"),
|
|
43
|
+
comments: pageSize("comments"),
|
|
42
44
|
reviews: pageSize("reviews"),
|
|
43
45
|
analysisTasks: pageSize("analysisTasks"),
|
|
44
46
|
fileVersions: pageSize("fileVersions")
|
|
@@ -123,6 +125,10 @@ export const versionedEntityTypes = [
|
|
|
123
125
|
"chapter-outline",
|
|
124
126
|
"foreshadow"
|
|
125
127
|
];
|
|
128
|
+
export function defaultAiConversationTitle(prompt) {
|
|
129
|
+
const normalized = prompt.replace(/\s+/gu, " ").trim();
|
|
130
|
+
return Array.from(normalized).slice(0, 15).join("") || "新对话";
|
|
131
|
+
}
|
|
126
132
|
function isRecord(value) {
|
|
127
133
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
128
134
|
}
|
|
@@ -689,6 +695,9 @@ export class Store {
|
|
|
689
695
|
agentTools: json(String(row?.agent_tools_json ?? '["story_index","read_chapters","search_story_entities","grep","read_character_sections","search_drafts"]'), ["story_index", "read_chapters", "search_story_entities", "grep", "read_character_sections", "search_drafts"])
|
|
690
696
|
.map((tool) => tool === "query_story_knowledge" ? "search_story_entities" : tool)
|
|
691
697
|
.filter((tool, index, tools) => tools.indexOf(tool) === index),
|
|
698
|
+
titleGenerationModelId: row?.title_generation_model_id === null || row?.title_generation_model_id === undefined
|
|
699
|
+
? null
|
|
700
|
+
: String(row.title_generation_model_id),
|
|
692
701
|
updatedAt: String(row?.updated_at ?? "")
|
|
693
702
|
};
|
|
694
703
|
}
|
|
@@ -705,12 +714,15 @@ export class Store {
|
|
|
705
714
|
const nextBookSummaryContextPercent = input.bookSummaryContextPercent ?? Number(current.bookSummaryContextPercent);
|
|
706
715
|
const nextContextCompactThreshold = input.contextCompactThreshold ?? Number(current.contextCompactThreshold);
|
|
707
716
|
const nextAgentTools = input.agentTools ?? current.agentTools;
|
|
717
|
+
const nextTitleGenerationModelId = input.titleGenerationModelId === undefined
|
|
718
|
+
? (current.titleGenerationModelId ? String(current.titleGenerationModelId) : null)
|
|
719
|
+
: input.titleGenerationModelId?.trim() || null;
|
|
708
720
|
this.db.run(`INSERT INTO work_ai_settings (
|
|
709
721
|
work_id, system_prompt, auto_run_enabled, auto_run_concurrency, auto_run_batch_limit,
|
|
710
722
|
auto_run_daily_task_limit, auto_run_failure_threshold, auto_run_paused, auto_run_pause_reason,
|
|
711
723
|
auto_run_resume_at, auto_run_consecutive_failures, book_summary_context_percent,
|
|
712
|
-
context_compact_threshold, agent_tools_json, updated_at
|
|
713
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
724
|
+
context_compact_threshold, agent_tools_json, title_generation_model_id, updated_at
|
|
725
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
714
726
|
ON CONFLICT(work_id) DO UPDATE SET
|
|
715
727
|
system_prompt = excluded.system_prompt,
|
|
716
728
|
auto_run_enabled = excluded.auto_run_enabled,
|
|
@@ -725,7 +737,8 @@ export class Store {
|
|
|
725
737
|
book_summary_context_percent = excluded.book_summary_context_percent,
|
|
726
738
|
context_compact_threshold = excluded.context_compact_threshold,
|
|
727
739
|
agent_tools_json = excluded.agent_tools_json,
|
|
728
|
-
|
|
740
|
+
title_generation_model_id = excluded.title_generation_model_id,
|
|
741
|
+
updated_at = excluded.updated_at`, workId, nextPrompt, nextEnabled ? 1 : 0, Math.min(8, Math.max(1, nextConcurrency)), Math.min(200, Math.max(1, nextBatchLimit)), Math.min(10_000, Math.max(0, nextDailyTaskLimit)), Math.min(10, Math.max(1, nextFailureThreshold)), current.autoRunPaused ? 1 : 0, String(current.autoRunPauseReason ?? ""), current.autoRunResumeAt === null ? null : String(current.autoRunResumeAt), Math.max(0, Number(current.autoRunConsecutiveFailures) || 0), Math.min(90, Math.max(1, nextBookSummaryContextPercent)), Math.min(90, Math.max(50, nextContextCompactThreshold)), JSON.stringify(nextAgentTools), nextTitleGenerationModelId, timestamp);
|
|
729
742
|
this.audit(workId, "work.ai-settings.updated", "work-ai-settings", workId, {
|
|
730
743
|
systemPromptChanged: input.systemPrompt !== undefined,
|
|
731
744
|
autoRunEnabled: nextEnabled,
|
|
@@ -735,7 +748,8 @@ export class Store {
|
|
|
735
748
|
autoRunFailureThreshold: Math.min(10, Math.max(1, nextFailureThreshold)),
|
|
736
749
|
bookSummaryContextPercent: Math.min(90, Math.max(1, nextBookSummaryContextPercent)),
|
|
737
750
|
contextCompactThreshold: Math.min(90, Math.max(50, nextContextCompactThreshold)),
|
|
738
|
-
agentTools: nextAgentTools
|
|
751
|
+
agentTools: nextAgentTools,
|
|
752
|
+
titleGenerationModelId: nextTitleGenerationModelId
|
|
739
753
|
});
|
|
740
754
|
return this.getWorkAiSettings(workId);
|
|
741
755
|
}
|
|
@@ -1561,6 +1575,46 @@ export class Store {
|
|
|
1561
1575
|
WHERE annotation.chapter_id = ? AND annotation.deleted_at IS NULL
|
|
1562
1576
|
ORDER BY CASE annotation.status WHEN 'open' THEN 0 ELSE 1 END, annotation.start_line, annotation.created_at`, chapterId).map((row) => this.mapChapterAnnotation(row));
|
|
1563
1577
|
}
|
|
1578
|
+
listWorkChapterAnnotations(workId) {
|
|
1579
|
+
this.getWork(workId);
|
|
1580
|
+
return this.db.all(`SELECT annotation.*, user.display_name AS actor_display_name, user.username AS actor_username,
|
|
1581
|
+
chapter.title AS chapter_title, volume.title AS volume_title
|
|
1582
|
+
FROM chapter_annotations annotation
|
|
1583
|
+
JOIN chapters chapter ON chapter.id = annotation.chapter_id
|
|
1584
|
+
JOIN volumes volume ON volume.id = chapter.volume_id
|
|
1585
|
+
LEFT JOIN users user ON user.id = annotation.updated_by_user_id
|
|
1586
|
+
WHERE annotation.work_id = ? AND annotation.deleted_at IS NULL AND chapter.deleted_at IS NULL
|
|
1587
|
+
ORDER BY CASE annotation.status WHEN 'open' THEN 0 ELSE 1 END,
|
|
1588
|
+
volume.sort_order, volume.created_at, chapter.sort_order, chapter.created_at,
|
|
1589
|
+
annotation.start_line, annotation.created_at`, workId).map((row) => ({
|
|
1590
|
+
...this.mapChapterAnnotation(row),
|
|
1591
|
+
volumeTitle: requiredString(row, "volume_title"),
|
|
1592
|
+
chapterTitle: requiredString(row, "chapter_title")
|
|
1593
|
+
}));
|
|
1594
|
+
}
|
|
1595
|
+
listWorkChapterAnnotationsPage(workId, pagination) {
|
|
1596
|
+
this.getWork(workId);
|
|
1597
|
+
const page = paginationSql(pagination);
|
|
1598
|
+
const rows = this.db.all(`SELECT annotation.*, user.display_name AS actor_display_name, user.username AS actor_username,
|
|
1599
|
+
chapter.title AS chapter_title, volume.title AS volume_title
|
|
1600
|
+
FROM chapter_annotations annotation
|
|
1601
|
+
JOIN chapters chapter ON chapter.id = annotation.chapter_id
|
|
1602
|
+
JOIN volumes volume ON volume.id = chapter.volume_id
|
|
1603
|
+
LEFT JOIN users user ON user.id = annotation.updated_by_user_id
|
|
1604
|
+
WHERE annotation.work_id = ? AND annotation.deleted_at IS NULL AND chapter.deleted_at IS NULL
|
|
1605
|
+
ORDER BY CASE annotation.status WHEN 'open' THEN 0 ELSE 1 END,
|
|
1606
|
+
volume.sort_order, volume.created_at, chapter.sort_order, chapter.created_at,
|
|
1607
|
+
annotation.start_line, annotation.created_at${page.sql}`, workId, ...page.params);
|
|
1608
|
+
const total = numberValue(this.db.get(`SELECT COUNT(*) AS count
|
|
1609
|
+
FROM chapter_annotations annotation
|
|
1610
|
+
JOIN chapters chapter ON chapter.id = annotation.chapter_id
|
|
1611
|
+
WHERE annotation.work_id = ? AND annotation.deleted_at IS NULL AND chapter.deleted_at IS NULL`, workId) ?? {}, "count");
|
|
1612
|
+
return paginated(rows.map((row) => ({
|
|
1613
|
+
...this.mapChapterAnnotation(row),
|
|
1614
|
+
volumeTitle: requiredString(row, "volume_title"),
|
|
1615
|
+
chapterTitle: requiredString(row, "chapter_title")
|
|
1616
|
+
})), pagination, total);
|
|
1617
|
+
}
|
|
1564
1618
|
createChapterAnnotation(chapterId, input) {
|
|
1565
1619
|
const chapter = this.getChapter(chapterId);
|
|
1566
1620
|
const lines = String(chapter.content).replace(/\r\n?/gu, "\n").split("\n");
|
|
@@ -2226,7 +2280,7 @@ export class Store {
|
|
|
2226
2280
|
this.db.run(`INSERT INTO drafts (id, work_id, draft_type, title, content, created_at, updated_at)
|
|
2227
2281
|
VALUES (?, ?, ?, ?, ?, ?, ?)`, draftId, workId, input.draftType, input.title, input.content, timestamp, timestamp);
|
|
2228
2282
|
this.syncMarkdownAttachmentReferences(workId, "draft", draftId, input.content);
|
|
2229
|
-
this.recordEntityVersion("draft", draftId, source, sourceRef, changeNote || "
|
|
2283
|
+
this.recordEntityVersion("draft", draftId, source, sourceRef, changeNote || "建立创作想法", timestamp);
|
|
2230
2284
|
this.audit(workId, source === "restore" ? "draft.restored" : "draft.created", "draft", draftId, {
|
|
2231
2285
|
draftType: input.draftType,
|
|
2232
2286
|
source,
|
|
@@ -2266,17 +2320,17 @@ export class Store {
|
|
|
2266
2320
|
getDraft(draftId) {
|
|
2267
2321
|
const row = this.db.get("SELECT * FROM drafts WHERE id = ?", draftId);
|
|
2268
2322
|
if (!row)
|
|
2269
|
-
throw notFound("
|
|
2323
|
+
throw notFound("想法");
|
|
2270
2324
|
return this.mapDraft(row, true);
|
|
2271
2325
|
}
|
|
2272
2326
|
updateDraft(draftId, input, source = "manual", sourceRef = null, changeNote = "", expectedVersionNo) {
|
|
2273
2327
|
const current = this.getDraft(draftId);
|
|
2274
2328
|
const content = input.content ?? String(current.content);
|
|
2275
2329
|
this.db.transaction(() => {
|
|
2276
|
-
this.assertExpectedVersion("draft", draftId, expectedVersionNo, "
|
|
2330
|
+
this.assertExpectedVersion("draft", draftId, expectedVersionNo, "想法");
|
|
2277
2331
|
this.db.run("UPDATE drafts SET draft_type = ?, title = ?, content = ?, updated_at = ? WHERE id = ?", input.draftType ?? String(current.draftType), input.title ?? String(current.title), content, now(), draftId);
|
|
2278
2332
|
this.syncMarkdownAttachmentReferences(String(current.workId), "draft", draftId, content);
|
|
2279
|
-
this.recordEntityVersion("draft", draftId, source, sourceRef, changeNote || "
|
|
2333
|
+
this.recordEntityVersion("draft", draftId, source, sourceRef, changeNote || "更新创作想法");
|
|
2280
2334
|
this.audit(String(current.workId), "draft.updated", "draft", draftId, { fields: Object.keys(input), source, sourceRef });
|
|
2281
2335
|
});
|
|
2282
2336
|
return this.getDraft(draftId);
|
|
@@ -2284,8 +2338,8 @@ export class Store {
|
|
|
2284
2338
|
deleteDraft(draftId, expectedVersionNo) {
|
|
2285
2339
|
const current = this.getDraft(draftId);
|
|
2286
2340
|
this.db.transaction(() => {
|
|
2287
|
-
this.assertExpectedVersion("draft", draftId, expectedVersionNo, "
|
|
2288
|
-
this.recordEntityVersion("draft", draftId, "delete", null, "
|
|
2341
|
+
this.assertExpectedVersion("draft", draftId, expectedVersionNo, "想法");
|
|
2342
|
+
this.recordEntityVersion("draft", draftId, "delete", null, "删除创作想法");
|
|
2289
2343
|
this.clearMarkdownAttachmentReferences("draft", draftId);
|
|
2290
2344
|
this.db.run("DELETE FROM drafts WHERE id = ?", draftId);
|
|
2291
2345
|
this.audit(String(current.workId), "draft.deleted", "draft", draftId);
|
|
@@ -4098,6 +4152,16 @@ export class Store {
|
|
|
4098
4152
|
ORDER BY conversation.updated_at DESC, conversation.created_at DESC${page.sql}`, workId, ...page.params);
|
|
4099
4153
|
return paginated(rows.map((row) => this.mapAiConversation(row)), pagination);
|
|
4100
4154
|
}
|
|
4155
|
+
getAiConversationSummary(conversationId) {
|
|
4156
|
+
const row = this.db.get(`SELECT conversation.*,
|
|
4157
|
+
(SELECT COUNT(*) FROM ai_conversation_messages message WHERE message.conversation_id = conversation.id) AS message_count,
|
|
4158
|
+
COALESCE((SELECT content FROM ai_conversation_messages message WHERE message.conversation_id = conversation.id ORDER BY message.created_at DESC, message.rowid DESC LIMIT 1), '') AS preview
|
|
4159
|
+
FROM ai_conversations conversation
|
|
4160
|
+
WHERE conversation.id = ?`, conversationId);
|
|
4161
|
+
if (!row)
|
|
4162
|
+
throw notFound("AI 对话");
|
|
4163
|
+
return this.mapAiConversation(row);
|
|
4164
|
+
}
|
|
4101
4165
|
getAiConversation(conversationId) {
|
|
4102
4166
|
const row = this.db.get("SELECT * FROM ai_conversations WHERE id = ?", conversationId);
|
|
4103
4167
|
if (!row)
|
|
@@ -4127,7 +4191,7 @@ export class Store {
|
|
|
4127
4191
|
throw notFound("AI 对话");
|
|
4128
4192
|
if (requiredString(conversation, "work_id") !== workId)
|
|
4129
4193
|
throw new AppError(400, "CONVERSATION_WORK_MISMATCH", "AI 对话不属于当前作品");
|
|
4130
|
-
const rows = this.db.all("SELECT id, role, content FROM ai_conversation_messages WHERE conversation_id = ? ORDER BY created_at, rowid", conversationId);
|
|
4194
|
+
const rows = this.db.all("SELECT id, role, content, metadata_json FROM ai_conversation_messages WHERE conversation_id = ? ORDER BY created_at, rowid", conversationId);
|
|
4131
4195
|
const compactedMessageCount = Math.min(rows.length, Math.max(0, numberValue(conversation, "compacted_message_count")));
|
|
4132
4196
|
return {
|
|
4133
4197
|
workId,
|
|
@@ -4140,10 +4204,23 @@ export class Store {
|
|
|
4140
4204
|
.map((message) => ({
|
|
4141
4205
|
id: requiredString(message, "id"),
|
|
4142
4206
|
role: requiredString(message, "role") === "assistant" ? "assistant" : "user",
|
|
4143
|
-
content: requiredString(message, "content")
|
|
4207
|
+
content: requiredString(message, "content"),
|
|
4208
|
+
metadata: json(requiredString(message, "metadata_json"), {})
|
|
4144
4209
|
}))
|
|
4145
4210
|
};
|
|
4146
4211
|
}
|
|
4212
|
+
getAiConversationTitleContext(conversationId, workId) {
|
|
4213
|
+
const conversation = this.db.get("SELECT title, work_id FROM ai_conversations WHERE id = ?", conversationId);
|
|
4214
|
+
if (!conversation)
|
|
4215
|
+
throw notFound("AI 对话");
|
|
4216
|
+
if (requiredString(conversation, "work_id") !== workId)
|
|
4217
|
+
throw new AppError(400, "CONVERSATION_WORK_MISMATCH", "AI 对话不属于当前作品");
|
|
4218
|
+
const messages = this.db.all("SELECT role, content FROM ai_conversation_messages WHERE conversation_id = ? ORDER BY created_at, rowid", conversationId).map((message) => ({
|
|
4219
|
+
role: requiredString(message, "role") === "assistant" ? "assistant" : "user",
|
|
4220
|
+
content: requiredString(message, "content")
|
|
4221
|
+
}));
|
|
4222
|
+
return { title: requiredString(conversation, "title"), messages };
|
|
4223
|
+
}
|
|
4147
4224
|
setAiConversationContextWarning(conversationId, pending) {
|
|
4148
4225
|
const conversation = this.db.get("SELECT id FROM ai_conversations WHERE id = ?", conversationId);
|
|
4149
4226
|
if (!conversation)
|
|
@@ -4157,6 +4234,14 @@ export class Store {
|
|
|
4157
4234
|
this.db.run("UPDATE ai_conversations SET compacted_summary = ?, compacted_message_count = ?, context_warning_at = NULL, updated_at = ? WHERE id = ?", summary, Math.max(0, compactedMessageCount), now(), conversationId);
|
|
4158
4235
|
return this.getAiConversation(conversationId);
|
|
4159
4236
|
}
|
|
4237
|
+
setAiConversationTitle(conversationId, title) {
|
|
4238
|
+
const conversation = this.db.get("SELECT id FROM ai_conversations WHERE id = ?", conversationId);
|
|
4239
|
+
if (!conversation)
|
|
4240
|
+
throw notFound("AI 对话");
|
|
4241
|
+
const normalizedTitle = title.replace(/\s+/gu, " ").trim().slice(0, 200) || "新对话";
|
|
4242
|
+
this.db.run("UPDATE ai_conversations SET title = ?, updated_at = ? WHERE id = ?", normalizedTitle, now(), conversationId);
|
|
4243
|
+
return this.getAiConversation(conversationId);
|
|
4244
|
+
}
|
|
4160
4245
|
addAiConversationMessage(conversationId, input) {
|
|
4161
4246
|
const conversation = this.db.get("SELECT * FROM ai_conversations WHERE id = ?", conversationId);
|
|
4162
4247
|
if (!conversation)
|
|
@@ -4170,7 +4255,7 @@ export class Store {
|
|
|
4170
4255
|
const messageId = id("message");
|
|
4171
4256
|
const timestamp = now();
|
|
4172
4257
|
const title = requiredString(conversation, "title") === "新对话" && input.role === "user"
|
|
4173
|
-
? input.content
|
|
4258
|
+
? defaultAiConversationTitle(input.content)
|
|
4174
4259
|
: requiredString(conversation, "title");
|
|
4175
4260
|
this.db.transaction(() => {
|
|
4176
4261
|
this.db.run("INSERT INTO ai_conversation_messages (id, conversation_id, role, content, citations_json, metadata_json, request_id, created_at, created_by_user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(conversation_id, request_id) WHERE request_id IS NOT NULL DO NOTHING", messageId, conversationId, input.role, input.content, JSON.stringify(input.citations ?? []), JSON.stringify(input.metadata ?? {}), requestId, timestamp, currentRequestActor()?.userId ?? null);
|