@musnows/scriverse 0.6.8 → 0.6.9
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 +72 -23
- package/dist/ai.js.map +1 -1
- package/dist/app.js +10 -2
- package/dist/app.js.map +1 -1
- package/dist/database.js +132 -1
- package/dist/database.js.map +1 -1
- package/dist/hybrid-search.js +2 -1
- package/dist/hybrid-search.js.map +1 -1
- package/dist/public/app.js +27 -11
- package/dist/public/display-labels.js +2 -1
- package/dist/public/global-search.d.ts +3 -1
- package/dist/public/global-search.js +22 -0
- package/dist/public/index.html +4 -3
- package/dist/public/styles.css +2 -1
- package/dist/store.js +67 -12
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +6 -2
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/public/app.js
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
taskScopeLabel,
|
|
37
37
|
timelineStatusLabel,
|
|
38
38
|
characterStateFieldLabel
|
|
39
|
-
} from "/display-labels.js?v=
|
|
39
|
+
} from "/display-labels.js?v=20260804-agent-history-search-v1";
|
|
40
40
|
import { parsePageRoute, serializePageRoute } from "/page-route.js?v=20260731-work-comments-v2";
|
|
41
41
|
import { splitRelationshipKeywordInput, splitRelationshipKeywords, uniqueRelationshipKeywords } from "/relationship-keywords.js?v=20260720-relationship-keyword-chips";
|
|
42
42
|
import { tokenizeVisibleSpaces } from "/whitespace-visualization.js?v=20260718-visible-whitespace";
|
|
@@ -45,7 +45,7 @@ import { ANALYSIS_TYPES, analysisTypeDescription } from "/analysis-types.js?v=20
|
|
|
45
45
|
import { WORK_PERMISSION_MODULES, canReadPermissionModule, canReadUiModule, canWritePermissionModule, canWriteUiModule, emptyModulePermissions, firstReadableUiModule, normalizeModulePermissions, permissionSummary } from "/work-permissions.js?v=20260731-drafts-to-ideas-v1";
|
|
46
46
|
import { MODULE_LAYOUT_STORAGE_KEY, LEGACY_SETTINGS_LAYOUT_STORAGE_KEY, normalizeModuleLayout } from "/module-layout.js?v=20260723-module-layout-toggle";
|
|
47
47
|
import { isGlobalSearchShortcut } from "/keyboard-shortcuts.js?v=20260723-global-search";
|
|
48
|
-
import { resolveGlobalSearchTarget, splitGlobalSearchHighlight } from "/global-search.js?v=
|
|
48
|
+
import { prioritizeGlobalSearchResults, resolveGlobalSearchTarget, splitGlobalSearchHighlight } from "/global-search.js?v=20260804-agent-history-search-v1";
|
|
49
49
|
import { filterCharacters, paginateCharacters } from "/character-filters.js?v=20260725-character-filters";
|
|
50
50
|
import { filterRelationships } from "/relationship-filters.js?v=20260726-relationship-filters";
|
|
51
51
|
import {
|
|
@@ -1812,8 +1812,10 @@ async function ensureAiConversationsLoaded() {
|
|
|
1812
1812
|
}
|
|
1813
1813
|
}
|
|
1814
1814
|
|
|
1815
|
-
async function openAiConversation(conversationId, hideHistory = true) {
|
|
1816
|
-
const
|
|
1815
|
+
async function openAiConversation(conversationId, hideHistory = true, focusMessageId = null) {
|
|
1816
|
+
const parameters = new URLSearchParams({ page: "1", limit: "100" });
|
|
1817
|
+
if (focusMessageId) parameters.set("messageId", String(focusMessageId));
|
|
1818
|
+
const conversation = await api(`/api/ai-conversations/${conversationId}?${parameters}`);
|
|
1817
1819
|
upsertAiConversationSummary(conversation);
|
|
1818
1820
|
state.aiConversationId = conversation.id;
|
|
1819
1821
|
state.aiPromptSent = conversation.messages.some((message) => message.role === "user");
|
|
@@ -1839,6 +1841,15 @@ async function openAiConversation(conversationId, hideHistory = true) {
|
|
|
1839
1841
|
if (hideHistory) setAiHistoryVisible(false);
|
|
1840
1842
|
}
|
|
1841
1843
|
|
|
1844
|
+
function focusAiConversationMessage(messageId) {
|
|
1845
|
+
const message = [...$("#ai-feed").querySelectorAll("[data-message-id]")]
|
|
1846
|
+
.find((candidate) => candidate.dataset.messageId === String(messageId));
|
|
1847
|
+
if (!message) return;
|
|
1848
|
+
message.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
1849
|
+
message.classList.add("is-search-target");
|
|
1850
|
+
window.setTimeout(() => message.classList.remove("is-search-target"), 1800);
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1842
1853
|
async function createNewAiConversation(taskType = "chat") {
|
|
1843
1854
|
if (!state.work) return;
|
|
1844
1855
|
const conversation = await api(`/api/works/${state.work.id}/ai-conversations`, { method: "POST", body: { taskType } });
|
|
@@ -3965,8 +3976,9 @@ function renderSearchResults(results, query) {
|
|
|
3965
3976
|
$("#search-results").innerHTML = '<p class="search-results-status">未找到相关内容。</p>';
|
|
3966
3977
|
return;
|
|
3967
3978
|
}
|
|
3979
|
+
const orderedResults = prioritizeGlobalSearchResults(results);
|
|
3968
3980
|
const matchKindLabel = { metadata: "资料命中", exact: "精确命中", phonetic: "拼音命中" };
|
|
3969
|
-
$("#search-results").innerHTML = `<p class="search-results-summary">找到 ${
|
|
3981
|
+
$("#search-results").innerHTML = `<p class="search-results-summary">找到 ${orderedResults.length} 条结果,非正文结果优先,正文条目随后展示。</p>${orderedResults.map((item) => {
|
|
3970
3982
|
const matchKinds = Array.isArray(item.matchKinds) ? item.matchKinds : [];
|
|
3971
3983
|
const lineRange = Number.isInteger(item.startLine)
|
|
3972
3984
|
? `<span class="search-result-chip">${item.startLine === item.endLine ? `第 ${item.startLine} 行` : `第 ${item.startLine}-${item.endLine} 行`}</span>`
|
|
@@ -3981,7 +3993,7 @@ function renderSearchResults(results, query) {
|
|
|
3981
3993
|
}).join("")}`;
|
|
3982
3994
|
$("#search-results").querySelectorAll(".search-result").forEach((button, index) => {
|
|
3983
3995
|
button.addEventListener("click", () => {
|
|
3984
|
-
openSearchResult(
|
|
3996
|
+
openSearchResult(orderedResults[index])
|
|
3985
3997
|
.catch((error) => toast(error.message, "error"));
|
|
3986
3998
|
});
|
|
3987
3999
|
});
|
|
@@ -4050,6 +4062,12 @@ async function openSearchResult(result) {
|
|
|
4050
4062
|
|| !$("#platform-usage-view").classList.contains("hidden")
|
|
4051
4063
|
|| !$("#work-audit-view").classList.contains("hidden");
|
|
4052
4064
|
if (inSettings) await returnFromSettings();
|
|
4065
|
+
if (target.kind === "agent-history") {
|
|
4066
|
+
ensureAiPanelExpanded();
|
|
4067
|
+
await openAiConversation(target.conversationId, true, target.messageId);
|
|
4068
|
+
if (target.messageId) focusAiConversationMessage(target.messageId);
|
|
4069
|
+
return;
|
|
4070
|
+
}
|
|
4053
4071
|
if (target.kind === "chapter") {
|
|
4054
4072
|
await selectChapter(target.id);
|
|
4055
4073
|
if (state.chapter?.id === target.id && target.startLine) {
|
|
@@ -6841,11 +6859,9 @@ function renderProviderCards(providers, models) {
|
|
|
6841
6859
|
const modelUnavailable = !isSelectableModel({ ...model, providerStatus: provider.status, providerConnectionStatus: provider.connectionStatus });
|
|
6842
6860
|
const modelStatus = !model.enabled
|
|
6843
6861
|
? `<span class="model-status-badge is-disabled">模型已停用</span>`
|
|
6844
|
-
: provider.
|
|
6845
|
-
? `<span class="model-status-badge is-
|
|
6846
|
-
:
|
|
6847
|
-
? `<span class="model-status-badge is-unavailable">连接不可用</span>`
|
|
6848
|
-
: "";
|
|
6862
|
+
: provider.connectionStatus !== "success"
|
|
6863
|
+
? `<span class="model-status-badge is-unavailable">连接不可用</span>`
|
|
6864
|
+
: "";
|
|
6849
6865
|
const capability = model.multimodalEnabled ? " · 多模态" : "";
|
|
6850
6866
|
const defaultBadge = model.imageToolDefault ? " · 默认读图模型" : "";
|
|
6851
6867
|
return `<div class="provider-model-row${modelUnavailable ? " is-unavailable" : ""}"><button class="pill model-pill" type="button" data-edit-model="${esc(model.id)}" aria-label="编辑模型 ${esc(model.displayName)}">${esc(model.displayName)} · ${model.enabled ? "启用" : "停用"}${capability}${defaultBadge} · 思考模式 ${model.thinkingEnabled ? "开启" : "关闭"} · 上下文 ${Number(model.contextWindow ?? 128000).toLocaleString("zh-CN")} 令牌 · 最大输出 ${Number(model.preset?.max_tokens ?? 32000).toLocaleString("zh-CN")}</button>${modelStatus}</div>`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type GlobalSearchTarget =
|
|
2
2
|
| { kind: "chapter"; type: "chapter"; id: string; module: "editor"; startLine?: number; endLine?: number }
|
|
3
|
+
| { kind: "agent-history"; type: "agent-history"; id: string; module: "editor"; conversationId: string; messageId?: string }
|
|
3
4
|
| {
|
|
4
5
|
kind: "entity";
|
|
5
6
|
type: "setting" | "character" | "race" | "organization" | "timeline-track" | "timeline-event" | "relationship" | "chapter-outline" | "foreshadow" | "review";
|
|
@@ -9,5 +10,6 @@ export type GlobalSearchTarget =
|
|
|
9
10
|
apiPath: string;
|
|
10
11
|
};
|
|
11
12
|
|
|
13
|
+
export function prioritizeGlobalSearchResults<T extends { type?: unknown }>(results: readonly T[]): T[];
|
|
12
14
|
export function splitGlobalSearchHighlight(value: unknown, query: unknown): Array<{ text: string; match: boolean }>;
|
|
13
|
-
export function resolveGlobalSearchTarget(result?: { type?: unknown; id?: unknown; startLine?: unknown; endLine?: unknown }): GlobalSearchTarget | null;
|
|
15
|
+
export function resolveGlobalSearchTarget(result?: { type?: unknown; id?: unknown; startLine?: unknown; endLine?: unknown; conversationId?: unknown; messageId?: unknown }): GlobalSearchTarget | null;
|
|
@@ -11,6 +11,15 @@ const entityTargets = Object.freeze({
|
|
|
11
11
|
review: Object.freeze({ module: "reviews", entity: "review", resource: "reviews" })
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
+
export function prioritizeGlobalSearchResults(results = []) {
|
|
15
|
+
const settingResults = [];
|
|
16
|
+
const proseResults = [];
|
|
17
|
+
for (const result of Array.isArray(results) ? results : []) {
|
|
18
|
+
(String(result?.type ?? "") === "chapter" ? proseResults : settingResults).push(result);
|
|
19
|
+
}
|
|
20
|
+
return [...settingResults, ...proseResults];
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
function positiveLine(value) {
|
|
15
24
|
const line = Number(value);
|
|
16
25
|
return Number.isInteger(line) && line > 0 ? line : null;
|
|
@@ -39,6 +48,19 @@ export function resolveGlobalSearchTarget(result = {}) {
|
|
|
39
48
|
const type = String(result.type ?? "").trim();
|
|
40
49
|
const id = String(result.id ?? "").trim();
|
|
41
50
|
if (!id) return null;
|
|
51
|
+
if (type === "agent-history") {
|
|
52
|
+
const conversationId = String(result.conversationId ?? "").trim();
|
|
53
|
+
if (!conversationId) return null;
|
|
54
|
+
const messageId = String(result.messageId ?? "").trim();
|
|
55
|
+
return {
|
|
56
|
+
kind: "agent-history",
|
|
57
|
+
type,
|
|
58
|
+
id,
|
|
59
|
+
module: "editor",
|
|
60
|
+
conversationId,
|
|
61
|
+
...(messageId ? { messageId } : {})
|
|
62
|
+
};
|
|
63
|
+
}
|
|
42
64
|
if (type === "chapter") {
|
|
43
65
|
const startLine = positiveLine(result.startLine);
|
|
44
66
|
const endLine = positiveLine(result.endLine);
|
package/dist/public/index.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
|
|
11
11
|
<link rel="manifest" href="/site.webmanifest">
|
|
12
12
|
<link rel="stylesheet" href="/vendor/vditor/dist/index.css?v=3.11.2">
|
|
13
|
-
<link rel="stylesheet" href="/styles.css?v=
|
|
13
|
+
<link rel="stylesheet" href="/styles.css?v=20260804-agent-history-search-v1">
|
|
14
14
|
</head>
|
|
15
15
|
<body class="auth-pending">
|
|
16
16
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
@@ -613,7 +613,7 @@
|
|
|
613
613
|
</div>
|
|
614
614
|
<div class="access-dialog-body">
|
|
615
615
|
<form id="search-form" class="search-form">
|
|
616
|
-
<label>关键词<input id="search-query" name="query" type="search" maxlength="500" placeholder="
|
|
616
|
+
<label>关键词<input id="search-query" name="query" type="search" maxlength="500" placeholder="搜索正文、设定、人物、时间线、关系、大纲、伏笔或 Agent 历史" required></label>
|
|
617
617
|
<label>资料类型<select id="search-type" name="type">
|
|
618
618
|
<option value="">全部资料</option>
|
|
619
619
|
<option value="chapter">章节</option>
|
|
@@ -627,6 +627,7 @@
|
|
|
627
627
|
<option value="chapter-outline">章节大纲</option>
|
|
628
628
|
<option value="foreshadow">伏笔</option>
|
|
629
629
|
<option value="review">审核项</option>
|
|
630
|
+
<option value="agent-history">Agent 历史</option>
|
|
630
631
|
</select></label>
|
|
631
632
|
<button class="primary-button" type="submit">搜索</button>
|
|
632
633
|
</form>
|
|
@@ -953,6 +954,6 @@
|
|
|
953
954
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
954
955
|
<script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
|
|
955
956
|
<script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
|
|
956
|
-
<script type="module" src="/app.js?v=
|
|
957
|
+
<script type="module" src="/app.js?v=20260804-agent-history-search-v1"></script>
|
|
957
958
|
</body>
|
|
958
959
|
</html>
|
package/dist/public/styles.css
CHANGED
|
@@ -2193,6 +2193,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2193
2193
|
.ai-context-compaction-divider span { letter-spacing: .08em; white-space: nowrap; }
|
|
2194
2194
|
.assistant-message, .user-message { position: relative; margin-bottom: 12px; padding: 12px 13px; border-radius: 5px; font-size: 12px; line-height: 1.55; }
|
|
2195
2195
|
.assistant-message { background: var(--paper-deep); }
|
|
2196
|
+
.assistant-message.is-search-target, .user-message.is-search-target { outline: 2px solid var(--accent); outline-offset: 3px; }
|
|
2196
2197
|
.assistant-message.is-error { border: 1px solid color-mix(in srgb, var(--accent) 42%, var(--line)); border-left: 3px solid var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--paper-deep)); }
|
|
2197
2198
|
.assistant-message.has-message-actions, .user-message.has-message-actions { margin-bottom: 42px; }
|
|
2198
2199
|
.user-message { margin-left: 24px; background: var(--accent); color: #fff; }
|
|
@@ -3209,7 +3210,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
3209
3210
|
.app-shell.ai-panel-collapsed .ai-panel > :not(.ai-heading) { display: none; }
|
|
3210
3211
|
.app-shell.ai-panel-collapsed .ai-heading { display: flex; justify-content: space-between; padding: 0; }
|
|
3211
3212
|
|
|
3212
|
-
.shelf-view { height:
|
|
3213
|
+
.shelf-view { height: 100%; min-height: 0; padding: 28px var(--mobile-gutter) 42px; }
|
|
3213
3214
|
#shelf-view, #settings-hub-view { padding-bottom: 0; }
|
|
3214
3215
|
#shelf-view .product-footer, #settings-hub-view .product-footer { margin-bottom: 0; padding-bottom: env(safe-area-inset-bottom); }
|
|
3215
3216
|
.shelf-header, .module-header { align-items: stretch; flex-direction: column; gap: 14px; margin-bottom: 20px; }
|
package/dist/store.js
CHANGED
|
@@ -3369,9 +3369,11 @@ export class Store {
|
|
|
3369
3369
|
JOIN characters character ON character.id = search.character_id`;
|
|
3370
3370
|
const rows = [...normalized].length <= 2
|
|
3371
3371
|
? this.db.all(`${columns} JOIN character_profile_section_short_terms term ON term.search_id = search.id
|
|
3372
|
-
WHERE search.work_id = ? AND
|
|
3372
|
+
WHERE search.work_id = ? AND character.merged_into_character_id IS NULL AND term.term = ?
|
|
3373
|
+
ORDER BY character.name, section.sort_order LIMIT ?`, workId, normalized, limit)
|
|
3373
3374
|
: this.db.all(`${columns} JOIN character_profile_section_search_fts fts ON fts.rowid = search.id
|
|
3374
|
-
WHERE search.work_id = ? AND
|
|
3375
|
+
WHERE search.work_id = ? AND character.merged_into_character_id IS NULL
|
|
3376
|
+
AND character_profile_section_search_fts MATCH ?
|
|
3375
3377
|
ORDER BY bm25(character_profile_section_search_fts), character.name, section.sort_order LIMIT ?`, workId, `"${normalized.replaceAll('"', '""')}"`, limit);
|
|
3376
3378
|
return rows.map((row) => ({ ...this.mapCharacterProfileSection(row), characterName: requiredString(row, "character_name") }));
|
|
3377
3379
|
}
|
|
@@ -4403,7 +4405,10 @@ export class Store {
|
|
|
4403
4405
|
const conversationId = id("conversation");
|
|
4404
4406
|
const timestamp = now();
|
|
4405
4407
|
const agentTools = normalizeWorkAgentTools(this.getWorkAiSettings(workId).agentTools);
|
|
4406
|
-
this.db.
|
|
4408
|
+
this.db.transaction(() => {
|
|
4409
|
+
this.db.run("INSERT INTO ai_conversations (id, work_id, task_type, title, agent_tools_json, created_at, updated_at, created_by_user_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", conversationId, workId, taskType, title.trim() || "新对话", JSON.stringify(agentTools), timestamp, timestamp, currentRequestActor()?.userId ?? null);
|
|
4410
|
+
this.syncAiHistorySearchShortTermsForSource("conversation", conversationId);
|
|
4411
|
+
});
|
|
4407
4412
|
return this.getAiConversation(conversationId);
|
|
4408
4413
|
}
|
|
4409
4414
|
listAiConversations(workId) {
|
|
@@ -4444,14 +4449,29 @@ export class Store {
|
|
|
4444
4449
|
const messages = this.db.all("SELECT * FROM ai_conversation_messages WHERE conversation_id = ? ORDER BY created_at, rowid", conversationId).map((message) => this.mapAiConversationMessage(message));
|
|
4445
4450
|
return { ...this.mapAiConversation(row), messageCount: messages.length, messages };
|
|
4446
4451
|
}
|
|
4447
|
-
getAiConversationPage(conversationId, pagination) {
|
|
4452
|
+
getAiConversationPage(conversationId, pagination, focusMessageId) {
|
|
4448
4453
|
const row = this.db.get("SELECT * FROM ai_conversations WHERE id = ?", conversationId);
|
|
4449
4454
|
if (!row)
|
|
4450
4455
|
throw notFound("AI 对话");
|
|
4451
4456
|
const countRow = this.db.get("SELECT COUNT(*) AS count FROM ai_conversation_messages WHERE conversation_id = ?", conversationId);
|
|
4452
|
-
|
|
4457
|
+
let effectivePagination = pagination;
|
|
4458
|
+
if (focusMessageId) {
|
|
4459
|
+
const focused = this.db.get("SELECT rowid, created_at FROM ai_conversation_messages WHERE conversation_id = ? AND id = ?", conversationId, focusMessageId);
|
|
4460
|
+
if (focused) {
|
|
4461
|
+
const newerCount = this.db.get(`SELECT COUNT(*) AS count FROM ai_conversation_messages
|
|
4462
|
+
WHERE conversation_id = ?
|
|
4463
|
+
AND (created_at > ? OR (created_at = ? AND rowid > ?))`, conversationId, String(focused.created_at ?? ""), String(focused.created_at ?? ""), Number(focused.rowid ?? 0));
|
|
4464
|
+
const focusPage = Math.floor(Number(newerCount?.count ?? 0) / pagination.limit) + 1;
|
|
4465
|
+
effectivePagination = {
|
|
4466
|
+
...pagination,
|
|
4467
|
+
page: focusPage,
|
|
4468
|
+
offset: (focusPage - 1) * pagination.limit
|
|
4469
|
+
};
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4472
|
+
const page = paginationSql(effectivePagination);
|
|
4453
4473
|
const rows = this.db.all(`SELECT * FROM ai_conversation_messages WHERE conversation_id = ? ORDER BY created_at DESC, rowid DESC${page.sql}`, conversationId, ...page.params);
|
|
4454
|
-
const messagesPage = paginated(rows.map((message) => this.mapAiConversationMessage(message)),
|
|
4474
|
+
const messagesPage = paginated(rows.map((message) => this.mapAiConversationMessage(message)), effectivePagination);
|
|
4455
4475
|
messagesPage.items.reverse();
|
|
4456
4476
|
return {
|
|
4457
4477
|
...this.mapAiConversation(row),
|
|
@@ -4555,7 +4575,10 @@ export class Store {
|
|
|
4555
4575
|
const conversation = this.db.get("SELECT id FROM ai_conversations WHERE id = ?", conversationId);
|
|
4556
4576
|
if (!conversation)
|
|
4557
4577
|
throw notFound("AI 对话");
|
|
4558
|
-
this.db.
|
|
4578
|
+
this.db.transaction(() => {
|
|
4579
|
+
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);
|
|
4580
|
+
this.syncAiHistorySearchShortTermsForSource("conversation", conversationId);
|
|
4581
|
+
});
|
|
4559
4582
|
return this.getAiConversation(conversationId);
|
|
4560
4583
|
}
|
|
4561
4584
|
setAiConversationTitle(conversationId, title) {
|
|
@@ -4563,7 +4586,10 @@ export class Store {
|
|
|
4563
4586
|
if (!conversation)
|
|
4564
4587
|
throw notFound("AI 对话");
|
|
4565
4588
|
const normalizedTitle = title.replace(/\s+/gu, " ").trim().slice(0, 200) || "新对话";
|
|
4566
|
-
this.db.
|
|
4589
|
+
this.db.transaction(() => {
|
|
4590
|
+
this.db.run("UPDATE ai_conversations SET title = ?, updated_at = ? WHERE id = ?", normalizedTitle, now(), conversationId);
|
|
4591
|
+
this.syncAiHistorySearchShortTermsForSource("conversation", conversationId);
|
|
4592
|
+
});
|
|
4567
4593
|
return this.getAiConversation(conversationId);
|
|
4568
4594
|
}
|
|
4569
4595
|
setAiConversationRoleplayCharacter(conversationId, characterId) {
|
|
@@ -4666,14 +4692,19 @@ export class Store {
|
|
|
4666
4692
|
}
|
|
4667
4693
|
const messageId = id("message");
|
|
4668
4694
|
const timestamp = now();
|
|
4669
|
-
const
|
|
4695
|
+
const previousTitle = requiredString(conversation, "title");
|
|
4696
|
+
const title = previousTitle === "新对话" && input.role === "user"
|
|
4670
4697
|
? defaultAiConversationTitle(input.content)
|
|
4671
|
-
:
|
|
4698
|
+
: previousTitle;
|
|
4672
4699
|
this.db.transaction(() => {
|
|
4673
4700
|
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);
|
|
4674
4701
|
const inserted = this.db.get("SELECT id FROM ai_conversation_messages WHERE id = ?", messageId);
|
|
4675
|
-
if (inserted)
|
|
4702
|
+
if (inserted) {
|
|
4676
4703
|
this.db.run("UPDATE ai_conversations SET title = ?, updated_at = ? WHERE id = ?", title, timestamp, conversationId);
|
|
4704
|
+
if (title !== previousTitle)
|
|
4705
|
+
this.syncAiHistorySearchShortTermsForSource("conversation", conversationId);
|
|
4706
|
+
this.syncAiHistorySearchShortTermsForSource("message", messageId);
|
|
4707
|
+
}
|
|
4677
4708
|
});
|
|
4678
4709
|
const message = requestId
|
|
4679
4710
|
? this.db.get("SELECT * FROM ai_conversation_messages WHERE conversation_id = ? AND request_id = ?", conversationId, requestId)
|
|
@@ -4707,9 +4738,33 @@ export class Store {
|
|
|
4707
4738
|
for (const message of messages.slice(0, targetIndex + 1)) {
|
|
4708
4739
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?)", id("message"), forkId, requiredString(message, "role"), requiredString(message, "content"), requiredString(message, "citations_json"), requiredString(message, "metadata_json"), optionalString(message, "request_id"), requiredString(message, "created_at"), currentRequestActor()?.userId ?? null);
|
|
4709
4740
|
}
|
|
4741
|
+
this.syncAiHistorySearchShortTermsForConversation(forkId);
|
|
4710
4742
|
});
|
|
4711
4743
|
return this.getAiConversation(forkId);
|
|
4712
4744
|
}
|
|
4745
|
+
syncAiHistorySearchShortTermsForSource(sourceType, sourceId) {
|
|
4746
|
+
const row = this.db.get("SELECT id, title, content, search_content FROM ai_history_search WHERE source_type = ? AND source_id = ?", sourceType, sourceId);
|
|
4747
|
+
if (!row)
|
|
4748
|
+
return;
|
|
4749
|
+
const searchId = numberValue(row, "id");
|
|
4750
|
+
const searchContent = sourceType === "message"
|
|
4751
|
+
? normalizeDocumentSearchText(String(row.content ?? ""))
|
|
4752
|
+
: normalizeDocumentSearchText(`${String(row.title ?? "")}\n${String(row.content ?? "")}`);
|
|
4753
|
+
if (String(row.search_content ?? "") !== searchContent) {
|
|
4754
|
+
this.db.run("UPDATE ai_history_search SET search_content = ? WHERE id = ?", searchContent, searchId);
|
|
4755
|
+
}
|
|
4756
|
+
this.db.run("DELETE FROM ai_history_search_short_terms WHERE search_id = ?", searchId);
|
|
4757
|
+
for (const term of documentShortSearchTerms(searchContent)) {
|
|
4758
|
+
this.db.run("INSERT INTO ai_history_search_short_terms (search_id, term) VALUES (?, ?)", searchId, term);
|
|
4759
|
+
}
|
|
4760
|
+
}
|
|
4761
|
+
syncAiHistorySearchShortTermsForConversation(conversationId) {
|
|
4762
|
+
const rows = this.db.all("SELECT source_type, source_id FROM ai_history_search WHERE conversation_id = ?", conversationId);
|
|
4763
|
+
for (const row of rows) {
|
|
4764
|
+
const sourceType = row.source_type === "message" ? "message" : "conversation";
|
|
4765
|
+
this.syncAiHistorySearchShortTermsForSource(sourceType, String(row.source_id));
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4713
4768
|
mapAiConversation(row) {
|
|
4714
4769
|
const roleplayCharacterId = optionalString(row, "roleplay_character_id");
|
|
4715
4770
|
const roleplayCharacter = roleplayCharacterId
|
|
@@ -6085,7 +6140,7 @@ export class Store {
|
|
|
6085
6140
|
SELECT character.id, character.name, character.aliases_json, character.species, character.is_dead,
|
|
6086
6141
|
COALESCE(path.path, character.species) AS race_path
|
|
6087
6142
|
FROM characters character LEFT JOIN character_race_paths path ON path.character_id = character.id
|
|
6088
|
-
WHERE character.work_id = ? AND (
|
|
6143
|
+
WHERE character.work_id = ? AND character.merged_into_character_id IS NULL AND (
|
|
6089
6144
|
character.name LIKE ? ESCAPE '\\' OR character.aliases_json LIKE ? ESCAPE '\\' OR character.species LIKE ? ESCAPE '\\'
|
|
6090
6145
|
OR EXISTS (SELECT 1 FROM character_race_lineage lineage WHERE lineage.character_id = character.id AND lineage.name LIKE ? ESCAPE '\\')
|
|
6091
6146
|
) LIMIT 50`, workId, workId, pattern, pattern, pattern, pattern);
|