@musnows/scriverse 0.8.6 → 0.8.8
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.en.md +3 -1
- package/README.md +3 -1
- package/dist/ai-model-pricing.js +297 -62
- package/dist/ai-model-pricing.js.map +1 -1
- package/dist/ai.js +543 -133
- package/dist/ai.js.map +1 -1
- package/dist/app.js +77 -11
- package/dist/app.js.map +1 -1
- package/dist/database.js +151 -3
- package/dist/database.js.map +1 -1
- package/dist/public/ai-connectivity-test.js +5 -2
- package/dist/public/app.js +601 -74
- package/dist/public/index.html +23 -10
- package/dist/public/model-config.d.ts +3 -3
- package/dist/public/model-config.js +2 -1
- package/dist/public/roleplay-turn.js +100 -0
- package/dist/public/styles.css +109 -12
- package/dist/public/toast-layer.d.ts +4 -0
- package/dist/public/toast-layer.js +5 -0
- package/dist/roleplay-turn.js +98 -0
- package/dist/roleplay-turn.js.map +1 -0
- package/dist/security.js +33 -7
- package/dist/security.js.map +1 -1
- package/dist/server-runtime.js +2 -1
- package/dist/server-runtime.js.map +1 -1
- package/dist/store.js +95 -12
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +36 -6
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/store.js
CHANGED
|
@@ -13,6 +13,7 @@ import { countWords, documentShortSearchTerms, escapeSqlLikePattern, id, json, n
|
|
|
13
13
|
import { buildWritingCalendar, writingDateKey } from "./writing-progress-time.js";
|
|
14
14
|
import { resolveMaxAgentToolCallLimit } from "./ai-tool-results.js";
|
|
15
15
|
import { DEFAULT_AI_STREAM_IDLE_TIMEOUT_SECONDS, normalizeAiStreamIdleTimeoutSeconds } from "./ai-stream-timeout.js";
|
|
16
|
+
import { normalizeRoleplayScenePin, roleplayUserTurnDisplayText, roleplayUserTurnTitleSource } from "./roleplay-turn.js";
|
|
16
17
|
const WORK_LIST_BATCH_SIZE = 500;
|
|
17
18
|
const ENTITY_LIST_BATCH_SIZE = 400;
|
|
18
19
|
export const RECYCLE_BIN_RETENTION_DAYS = 30;
|
|
@@ -211,7 +212,7 @@ export const versionedEntityTypes = [
|
|
|
211
212
|
export const AI_CONVERSATION_STREAM_REQUEST_LEASE_MS = 3 * 60_000;
|
|
212
213
|
export const aiConversationTaskTypes = ["chat", "roleplay", "continue", "polish"];
|
|
213
214
|
export function defaultAiConversationTitle(prompt) {
|
|
214
|
-
const normalized = prompt.replace(/\s+/gu, " ").trim();
|
|
215
|
+
const normalized = roleplayUserTurnTitleSource(prompt).replace(/\s+/gu, " ").trim();
|
|
215
216
|
return Array.from(normalized).slice(0, 15).join("") || "新对话";
|
|
216
217
|
}
|
|
217
218
|
function isRecord(value) {
|
|
@@ -3706,7 +3707,7 @@ export class Store {
|
|
|
3706
3707
|
return this.db.all(`SELECT draft.*, volume.title AS volume_title FROM drafts draft
|
|
3707
3708
|
LEFT JOIN volumes volume ON volume.id = draft.volume_id
|
|
3708
3709
|
WHERE draft.work_id = ? AND (? IS NULL OR draft.draft_type = ?)
|
|
3709
|
-
ORDER BY draft.updated_at DESC, draft.title`, workId, draftType ?? null, draftType ?? null).map((row) => this.mapDraft(row, includeContent));
|
|
3710
|
+
ORDER BY draft.is_favorite DESC, draft.updated_at DESC, draft.title`, workId, draftType ?? null, draftType ?? null).map((row) => this.mapDraft(row, includeContent));
|
|
3710
3711
|
}
|
|
3711
3712
|
listDraftsPage(workId, pagination, draftType, includeContent = false) {
|
|
3712
3713
|
this.getWork(workId);
|
|
@@ -3714,7 +3715,7 @@ export class Store {
|
|
|
3714
3715
|
const rows = this.db.all(`SELECT draft.*, volume.title AS volume_title FROM drafts draft
|
|
3715
3716
|
LEFT JOIN volumes volume ON volume.id = draft.volume_id
|
|
3716
3717
|
WHERE draft.work_id = ? AND (? IS NULL OR draft.draft_type = ?)
|
|
3717
|
-
ORDER BY draft.updated_at DESC, draft.title${page.sql}`, workId, draftType ?? null, draftType ?? null, ...page.params);
|
|
3718
|
+
ORDER BY draft.is_favorite DESC, draft.updated_at DESC, draft.title${page.sql}`, workId, draftType ?? null, draftType ?? null, ...page.params);
|
|
3718
3719
|
return paginated(rows.map((row) => this.mapDraft(row, includeContent)), pagination);
|
|
3719
3720
|
}
|
|
3720
3721
|
searchDrafts(workId, query, draftType, limit = 20) {
|
|
@@ -3743,6 +3744,21 @@ export class Store {
|
|
|
3743
3744
|
throw notFound("想法");
|
|
3744
3745
|
return this.mapDraft(row, true);
|
|
3745
3746
|
}
|
|
3747
|
+
setDraftFavorite(draftId, isFavorite) {
|
|
3748
|
+
const draft = this.db.get("SELECT id, work_id, is_favorite FROM drafts WHERE id = ?", draftId);
|
|
3749
|
+
if (!draft)
|
|
3750
|
+
throw notFound("想法");
|
|
3751
|
+
const workId = requiredString(draft, "work_id");
|
|
3752
|
+
const previousFavorite = booleanValue(draft, "is_favorite");
|
|
3753
|
+
this.db.transaction(() => {
|
|
3754
|
+
this.db.run("UPDATE drafts SET is_favorite = ? WHERE id = ?", isFavorite ? 1 : 0, draftId);
|
|
3755
|
+
this.audit(workId, "draft.favorite-updated", "draft", draftId, {
|
|
3756
|
+
previousFavorite,
|
|
3757
|
+
isFavorite
|
|
3758
|
+
});
|
|
3759
|
+
});
|
|
3760
|
+
return this.getDraft(draftId);
|
|
3761
|
+
}
|
|
3746
3762
|
updateDraft(draftId, input, source = "manual", sourceRef = null, changeNote = "", expectedVersionNo) {
|
|
3747
3763
|
const current = this.getDraft(draftId);
|
|
3748
3764
|
const content = input.content ?? String(current.content);
|
|
@@ -3781,6 +3797,7 @@ export class Store {
|
|
|
3781
3797
|
volumeTitle: optionalString(row, "volume_title"),
|
|
3782
3798
|
settingModule: optionalString(row, "setting_module"),
|
|
3783
3799
|
title: requiredString(row, "title"),
|
|
3800
|
+
isFavorite: booleanValue(row, "is_favorite"),
|
|
3784
3801
|
...(includeContent ? { content } : { contentPreview: content.replace(/\s+/gu, " ").trim().slice(0, 320) }),
|
|
3785
3802
|
versionNo: this.currentEntityVersionNo("draft", requiredString(row, "id")),
|
|
3786
3803
|
createdAt: requiredString(row, "created_at"),
|
|
@@ -3844,12 +3861,12 @@ export class Store {
|
|
|
3844
3861
|
}
|
|
3845
3862
|
listSettings(workId, includeContent = true) {
|
|
3846
3863
|
this.getWork(workId);
|
|
3847
|
-
return this.db.all("SELECT * FROM settings WHERE work_id = ? ORDER BY locked DESC, category, title", workId).map((row) => this.mapSetting(row, includeContent));
|
|
3864
|
+
return this.db.all("SELECT * FROM settings WHERE work_id = ? ORDER BY is_favorite DESC, locked DESC, category, title", workId).map((row) => this.mapSetting(row, includeContent));
|
|
3848
3865
|
}
|
|
3849
3866
|
listSettingsPage(workId, pagination, includeContent = true) {
|
|
3850
3867
|
this.getWork(workId);
|
|
3851
3868
|
const page = paginationSql(pagination);
|
|
3852
|
-
const rows = this.db.all(`SELECT * FROM settings WHERE work_id = ? ORDER BY locked DESC, category, title${page.sql}`, workId, ...page.params);
|
|
3869
|
+
const rows = this.db.all(`SELECT * FROM settings WHERE work_id = ? ORDER BY is_favorite DESC, locked DESC, category, title${page.sql}`, workId, ...page.params);
|
|
3853
3870
|
return paginated(rows.map((row) => this.mapSetting(row, includeContent)), pagination);
|
|
3854
3871
|
}
|
|
3855
3872
|
getSetting(settingId) {
|
|
@@ -3858,6 +3875,21 @@ export class Store {
|
|
|
3858
3875
|
throw notFound("设定");
|
|
3859
3876
|
return this.mapSetting(row);
|
|
3860
3877
|
}
|
|
3878
|
+
setSettingFavorite(settingId, isFavorite) {
|
|
3879
|
+
const setting = this.db.get("SELECT id, work_id, is_favorite FROM settings WHERE id = ?", settingId);
|
|
3880
|
+
if (!setting)
|
|
3881
|
+
throw notFound("设定");
|
|
3882
|
+
const workId = requiredString(setting, "work_id");
|
|
3883
|
+
const previousFavorite = booleanValue(setting, "is_favorite");
|
|
3884
|
+
this.db.transaction(() => {
|
|
3885
|
+
this.db.run("UPDATE settings SET is_favorite = ? WHERE id = ?", isFavorite ? 1 : 0, settingId);
|
|
3886
|
+
this.audit(workId, "setting.favorite-updated", "setting", settingId, {
|
|
3887
|
+
previousFavorite,
|
|
3888
|
+
isFavorite
|
|
3889
|
+
});
|
|
3890
|
+
});
|
|
3891
|
+
return this.getSetting(settingId);
|
|
3892
|
+
}
|
|
3861
3893
|
updateSetting(settingId, input, source = "manual", sourceRef = null, changeNote = "", expectedVersionNo) {
|
|
3862
3894
|
const current = this.getSetting(settingId);
|
|
3863
3895
|
const content = input.content ?? String(current.content);
|
|
@@ -3892,6 +3924,7 @@ export class Store {
|
|
|
3892
3924
|
tags: json(requiredString(row, "tags_json"), []),
|
|
3893
3925
|
status: requiredString(row, "status"),
|
|
3894
3926
|
locked: booleanValue(row, "locked"),
|
|
3927
|
+
isFavorite: booleanValue(row, "is_favorite"),
|
|
3895
3928
|
evidence: json(requiredString(row, "evidence_json"), []),
|
|
3896
3929
|
scope: json(requiredString(row, "scope_json"), {}),
|
|
3897
3930
|
authorNote: requiredString(row, "author_note"),
|
|
@@ -4205,14 +4238,14 @@ export class Store {
|
|
|
4205
4238
|
}
|
|
4206
4239
|
listOrganizations(workId, includeMarkdown = true) {
|
|
4207
4240
|
this.getWork(workId);
|
|
4208
|
-
const rows = this.db.all("SELECT * FROM organizations WHERE work_id = ? ORDER BY name", workId);
|
|
4241
|
+
const rows = this.db.all("SELECT * FROM organizations WHERE work_id = ? ORDER BY is_favorite DESC, name", workId);
|
|
4209
4242
|
const batch = this.organizationListBatch(rows);
|
|
4210
4243
|
return rows.map((row) => this.mapOrganization(row, includeMarkdown, batch));
|
|
4211
4244
|
}
|
|
4212
4245
|
listOrganizationsPage(workId, pagination, includeMarkdown = true) {
|
|
4213
4246
|
this.getWork(workId);
|
|
4214
4247
|
const page = paginationSql(pagination);
|
|
4215
|
-
const rows = this.db.all(`SELECT * FROM organizations WHERE work_id = ? ORDER BY name${page.sql}`, workId, ...page.params);
|
|
4248
|
+
const rows = this.db.all(`SELECT * FROM organizations WHERE work_id = ? ORDER BY is_favorite DESC, name${page.sql}`, workId, ...page.params);
|
|
4216
4249
|
const batch = this.organizationListBatch(rows);
|
|
4217
4250
|
return paginated(rows.map((row) => this.mapOrganization(row, includeMarkdown, batch)), pagination);
|
|
4218
4251
|
}
|
|
@@ -4222,6 +4255,21 @@ export class Store {
|
|
|
4222
4255
|
throw notFound("组织");
|
|
4223
4256
|
return this.mapOrganization(row);
|
|
4224
4257
|
}
|
|
4258
|
+
setOrganizationFavorite(organizationId, isFavorite) {
|
|
4259
|
+
const organization = this.db.get("SELECT id, work_id, is_favorite FROM organizations WHERE id = ?", organizationId);
|
|
4260
|
+
if (!organization)
|
|
4261
|
+
throw notFound("组织");
|
|
4262
|
+
const workId = requiredString(organization, "work_id");
|
|
4263
|
+
const previousFavorite = booleanValue(organization, "is_favorite");
|
|
4264
|
+
this.db.transaction(() => {
|
|
4265
|
+
this.db.run("UPDATE organizations SET is_favorite = ? WHERE id = ?", isFavorite ? 1 : 0, organizationId);
|
|
4266
|
+
this.audit(workId, "organization.favorite-updated", "organization", organizationId, {
|
|
4267
|
+
previousFavorite,
|
|
4268
|
+
isFavorite
|
|
4269
|
+
});
|
|
4270
|
+
});
|
|
4271
|
+
return this.getOrganization(organizationId);
|
|
4272
|
+
}
|
|
4225
4273
|
updateOrganization(organizationId, input, source = "manual", sourceRef = null, changeNote = "", expectedVersionNo) {
|
|
4226
4274
|
const current = this.getOrganization(organizationId);
|
|
4227
4275
|
const workId = String(current.workId);
|
|
@@ -4349,6 +4397,7 @@ export class Store {
|
|
|
4349
4397
|
name: requiredString(row, "name"),
|
|
4350
4398
|
description: requiredString(row, "description"),
|
|
4351
4399
|
isDissolved: booleanValue(row, "is_dissolved"),
|
|
4400
|
+
isFavorite: booleanValue(row, "is_favorite"),
|
|
4352
4401
|
...(includeMarkdown
|
|
4353
4402
|
? { settings, settingsMarkdown: settingsMarkdownFromList(settings), settingsSections }
|
|
4354
4403
|
: { settings: [], settingsCount: settingsSections.length }),
|
|
@@ -4467,14 +4516,14 @@ export class Store {
|
|
|
4467
4516
|
}
|
|
4468
4517
|
listCharacters(workId, includeProfileSections = false, includeMerged = false, includeRaceMarkdown = true) {
|
|
4469
4518
|
this.getWork(workId);
|
|
4470
|
-
return this.db.all(`SELECT * FROM characters WHERE work_id = ?${includeMerged ? "" : " AND merged_into_character_id IS NULL"} ORDER BY name`, workId)
|
|
4519
|
+
return this.db.all(`SELECT * FROM characters WHERE work_id = ?${includeMerged ? "" : " AND merged_into_character_id IS NULL"} ORDER BY is_favorite DESC, name`, workId)
|
|
4471
4520
|
.map((row) => this.mapCharacter(row, includeProfileSections, includeRaceMarkdown));
|
|
4472
4521
|
}
|
|
4473
4522
|
listCharactersPage(workId, pagination, includeProfileSections = false, includeMerged = false, includeRaceMarkdown = true) {
|
|
4474
4523
|
this.getWork(workId);
|
|
4475
4524
|
const page = paginationSql(pagination);
|
|
4476
4525
|
const count = this.db.get(`SELECT COUNT(*) AS count FROM characters WHERE work_id = ?${includeMerged ? "" : " AND merged_into_character_id IS NULL"}`, workId);
|
|
4477
|
-
const rows = this.db.all(`SELECT * FROM characters WHERE work_id = ?${includeMerged ? "" : " AND merged_into_character_id IS NULL"} ORDER BY name${page.sql}`, workId, ...page.params);
|
|
4526
|
+
const rows = this.db.all(`SELECT * FROM characters WHERE work_id = ?${includeMerged ? "" : " AND merged_into_character_id IS NULL"} ORDER BY is_favorite DESC, name${page.sql}`, workId, ...page.params);
|
|
4478
4527
|
return paginated(rows.map((row) => this.mapCharacter(row, includeProfileSections, includeRaceMarkdown)), pagination, Number(count?.count ?? 0));
|
|
4479
4528
|
}
|
|
4480
4529
|
mapCharacterProfileSection(row) {
|
|
@@ -4955,6 +5004,26 @@ export class Store {
|
|
|
4955
5004
|
throw notFound("角色");
|
|
4956
5005
|
return this.mapCharacter(row);
|
|
4957
5006
|
}
|
|
5007
|
+
setCharacterFavorite(characterId, isFavorite) {
|
|
5008
|
+
const character = this.db.get("SELECT id, work_id, is_favorite, merged_into_character_id FROM characters WHERE id = ?", characterId);
|
|
5009
|
+
if (!character)
|
|
5010
|
+
throw notFound("角色");
|
|
5011
|
+
if (optionalString(character, "merged_into_character_id")) {
|
|
5012
|
+
throw new AppError(409, "CHARACTER_ALREADY_MERGED", "已合并角色不能收藏");
|
|
5013
|
+
}
|
|
5014
|
+
const workId = requiredString(character, "work_id");
|
|
5015
|
+
const previousFavorite = booleanValue(character, "is_favorite");
|
|
5016
|
+
if (previousFavorite === isFavorite)
|
|
5017
|
+
return this.getCharacter(characterId);
|
|
5018
|
+
this.db.transaction(() => {
|
|
5019
|
+
this.db.run("UPDATE characters SET is_favorite = ? WHERE id = ?", isFavorite ? 1 : 0, characterId);
|
|
5020
|
+
this.audit(workId, "character.favorite-updated", "character", characterId, {
|
|
5021
|
+
previousFavorite,
|
|
5022
|
+
isFavorite
|
|
5023
|
+
});
|
|
5024
|
+
});
|
|
5025
|
+
return this.getCharacter(characterId);
|
|
5026
|
+
}
|
|
4958
5027
|
getCharacterAvatar(characterId) {
|
|
4959
5028
|
const character = this.db.get("SELECT id FROM characters WHERE id = ?", characterId);
|
|
4960
5029
|
if (!character)
|
|
@@ -5229,6 +5298,7 @@ export class Store {
|
|
|
5229
5298
|
profileSectionCount,
|
|
5230
5299
|
currentState: json(requiredString(row, "current_state_json"), {}),
|
|
5231
5300
|
isDead: booleanValue(row, "is_dead"),
|
|
5301
|
+
isFavorite: booleanValue(row, "is_favorite"),
|
|
5232
5302
|
avatarUrl: avatarSha256
|
|
5233
5303
|
? `/api/characters/${encodeURIComponent(characterId)}/avatar?v=${encodeURIComponent(avatarSha256)}`
|
|
5234
5304
|
: null,
|
|
@@ -6053,6 +6123,7 @@ export class Store {
|
|
|
6053
6123
|
totalMessageCount,
|
|
6054
6124
|
warningPending: Boolean(optionalString(conversation, "context_warning_at")),
|
|
6055
6125
|
injectedEntities: parseAiInjectedEntities(optionalString(conversation, "injected_entities_json") ?? EMPTY_AI_INJECTED_ENTITIES),
|
|
6126
|
+
scenePin: normalizeRoleplayScenePin(json(optionalString(conversation, "scene_pin_json") ?? "{}", {})),
|
|
6056
6127
|
messages: rows.filter((message) => requiredString(message, "id") !== excludeMessageId)
|
|
6057
6128
|
.map((message) => ({
|
|
6058
6129
|
id: requiredString(message, "id"),
|
|
@@ -6134,6 +6205,16 @@ export class Store {
|
|
|
6134
6205
|
const refreshed = this.db.get("SELECT system_clock_text FROM ai_conversations WHERE id = ?", conversationId);
|
|
6135
6206
|
return (optionalString(refreshed ?? {}, "system_clock_text") ?? clock).trim() || clock;
|
|
6136
6207
|
}
|
|
6208
|
+
setAiConversationScenePin(conversationId, workId, pin) {
|
|
6209
|
+
const conversation = this.db.get("SELECT work_id FROM ai_conversations WHERE id = ?", conversationId);
|
|
6210
|
+
if (!conversation)
|
|
6211
|
+
throw notFound("AI 对话");
|
|
6212
|
+
if (requiredString(conversation, "work_id") !== workId)
|
|
6213
|
+
throw new AppError(400, "CONVERSATION_WORK_MISMATCH", "AI 对话不属于当前作品");
|
|
6214
|
+
const normalized = normalizeRoleplayScenePin(pin);
|
|
6215
|
+
this.db.run("UPDATE ai_conversations SET scene_pin_json = ?, updated_at = ? WHERE id = ?", JSON.stringify(normalized), now(), conversationId);
|
|
6216
|
+
return normalized;
|
|
6217
|
+
}
|
|
6137
6218
|
listCharacterNameEntries(workId) {
|
|
6138
6219
|
this.getWork(workId);
|
|
6139
6220
|
return this.db.all(`SELECT character_id, normalized_name, display_name, kind FROM character_names
|
|
@@ -6577,10 +6658,11 @@ export class Store {
|
|
|
6577
6658
|
const injectedEntitiesJson = optionalString(conversation, "injected_entities_json")
|
|
6578
6659
|
?? JSON.stringify(EMPTY_AI_INJECTED_ENTITIES);
|
|
6579
6660
|
const systemClockText = optionalString(conversation, "system_clock_text") ?? "";
|
|
6661
|
+
const scenePinJson = optionalString(conversation, "scene_pin_json") ?? "{}";
|
|
6580
6662
|
this.db.transaction(() => {
|
|
6581
|
-
this.db.run("INSERT INTO ai_conversations (id, work_id, roleplay_character_id, roleplay_user_character_id, task_type, context_scope_json, title, compacted_summary, compacted_message_count, agent_tools_json, injected_entities_json, system_clock_text, created_at, updated_at, created_by_user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", forkId, workId, optionalString(conversation, "roleplay_character_id"), optionalString(conversation, "roleplay_user_character_id"), optionalString(conversation, "task_type"), optionalString(conversation, "context_scope_json"), title.slice(0, 200), forkSummary, forkCompactedCount, conversation.agent_tools_json == null
|
|
6663
|
+
this.db.run("INSERT INTO ai_conversations (id, work_id, roleplay_character_id, roleplay_user_character_id, task_type, context_scope_json, title, compacted_summary, compacted_message_count, agent_tools_json, injected_entities_json, system_clock_text, scene_pin_json, created_at, updated_at, created_by_user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", forkId, workId, optionalString(conversation, "roleplay_character_id"), optionalString(conversation, "roleplay_user_character_id"), optionalString(conversation, "task_type"), optionalString(conversation, "context_scope_json"), title.slice(0, 200), forkSummary, forkCompactedCount, conversation.agent_tools_json == null
|
|
6582
6664
|
? JSON.stringify(normalizeWorkAgentTools(this.getWorkAiSettings(workId).agentTools))
|
|
6583
|
-
: String(conversation.agent_tools_json), injectedEntitiesJson, systemClockText, timestamp, timestamp, currentRequestActor()?.userId ?? null);
|
|
6665
|
+
: String(conversation.agent_tools_json), injectedEntitiesJson, systemClockText, scenePinJson, timestamp, timestamp, currentRequestActor()?.userId ?? null);
|
|
6584
6666
|
for (const message of messages.slice(0, targetIndex + 1)) {
|
|
6585
6667
|
const role = requiredString(message, "role");
|
|
6586
6668
|
const inheritedMetadata = json(requiredString(message, "metadata_json"), {});
|
|
@@ -6647,7 +6729,7 @@ export class Store {
|
|
|
6647
6729
|
title: requiredString(row, "title"),
|
|
6648
6730
|
isFavorite: booleanValue(row, "is_favorite"),
|
|
6649
6731
|
messageCount: numberValue(row, "message_count"),
|
|
6650
|
-
preview: requiredString(row, "preview"),
|
|
6732
|
+
preview: roleplayUserTurnDisplayText(requiredString(row, "preview")),
|
|
6651
6733
|
compactedMessageCount: numberValue(row, "compacted_message_count"),
|
|
6652
6734
|
hasCompactedSummary: Boolean(requiredString(row, "compacted_summary")),
|
|
6653
6735
|
contextWarningPending: Boolean(optionalString(row, "context_warning_at")),
|
|
@@ -6655,6 +6737,7 @@ export class Store {
|
|
|
6655
6737
|
...(lockedModelId ? { modelId: lockedModelId } : {}),
|
|
6656
6738
|
...(hasImageAttachments ? { hasImageAttachments: true, modelLockedByImage: true } : {}),
|
|
6657
6739
|
contextScope: json(optionalString(row, "context_scope_json") ?? "", { type: "none" }),
|
|
6740
|
+
scenePin: normalizeRoleplayScenePin(json(optionalString(row, "scene_pin_json") ?? "{}", {})),
|
|
6658
6741
|
roleplayCharacter: roleplayCharacter ? {
|
|
6659
6742
|
id: requiredString(roleplayCharacter, "id"),
|
|
6660
6743
|
name: requiredString(roleplayCharacter, "name"),
|