@musnows/scriverse 0.6.3 → 0.6.4
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 +22 -9
- package/dist/ai-protocol.js.map +1 -1
- package/dist/ai.js +756 -163
- package/dist/ai.js.map +1 -1
- package/dist/app.js +94 -14
- package/dist/app.js.map +1 -1
- package/dist/database.js +129 -2
- package/dist/database.js.map +1 -1
- package/dist/google-vertex-auth.js +155 -0
- package/dist/google-vertex-auth.js.map +1 -0
- package/dist/public/ai-mentions.js +10 -0
- package/dist/public/app.js +345 -42
- package/dist/public/display-labels.js +2 -1
- package/dist/public/index.html +30 -2
- package/dist/public/styles.css +78 -0
- package/dist/store.js +182 -4
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +21 -2
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/writing-progress-time.js +8 -0
- package/dist/writing-progress-time.js.map +1 -1
- package/package.json +1 -1
package/dist/public/app.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { buildRelationshipGraph, createGalaxyRenderer, renderRelationshipMindMap } from "/relationship-graph.js?v=20260728-galaxy-edge-stars-v3";
|
|
2
2
|
import { collapseExcessBlankLines, formatDateTime, normalizeParagraphSpacing } from "/text-formatting.js?v=20260713-saved-at-seconds";
|
|
3
3
|
import { renderMarkdown } from "/markdown.js?v=20260731-no-external-images-v1";
|
|
4
|
-
import {
|
|
4
|
+
import { findAiMention, listAiMentionOptions, mergeAiReferenceScope } from "/ai-mentions.js?v=20260801-context-lock-v2";
|
|
5
5
|
import { shouldShowAiQuickActions } from "/ai-conversation.js?v=20260713-quick-actions";
|
|
6
6
|
import { calculateLineNumberRowHeight, calculateLineNumberRowTop, calculateLineNumberTextOffset, calculateLineNumberTop } from "/line-number-layout.js?v=20260713-row-box-alignment";
|
|
7
7
|
import { buildVditorLineNumberRows } from "/vditor-line-number-layout.js?v=20260729-vditor-line-numbers-v3";
|
|
@@ -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=20260801-google-vertex";
|
|
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";
|
|
@@ -110,8 +110,11 @@ const state = {
|
|
|
110
110
|
aiCitations: [],
|
|
111
111
|
aiReferences: [],
|
|
112
112
|
aiPromptSent: false,
|
|
113
|
+
aiTaskType: "chat",
|
|
114
|
+
aiContextScope: { type: "none" },
|
|
113
115
|
aiConversationId: null,
|
|
114
116
|
aiConversations: [],
|
|
117
|
+
aiRoleplayCharacter: null,
|
|
115
118
|
aiLastMessageAt: null,
|
|
116
119
|
settings: [],
|
|
117
120
|
dirty: false,
|
|
@@ -336,6 +339,7 @@ function applyWorkAccessMode() {
|
|
|
336
339
|
$("#ai-prompt").readOnly = aiReadOnly;
|
|
337
340
|
$("#ai-prompt").setAttribute("aria-readonly", String(aiReadOnly));
|
|
338
341
|
$("#ai-send").classList.toggle("permission-hidden", aiReadOnly);
|
|
342
|
+
renderAiRoleplayCharacterSelect();
|
|
339
343
|
updateBackgroundTaskCenterVisibility();
|
|
340
344
|
if (proseReadOnly) {
|
|
341
345
|
chapterEditorReadOnly = true;
|
|
@@ -1322,7 +1326,7 @@ function renderAiReferences() {
|
|
|
1322
1326
|
|
|
1323
1327
|
function renderAiQuickActions() {
|
|
1324
1328
|
const quickActions = $(".quick-actions");
|
|
1325
|
-
const visible = shouldShowAiQuickActions(state.aiPromptSent);
|
|
1329
|
+
const visible = !state.aiRoleplayCharacter && shouldShowAiQuickActions(state.aiPromptSent);
|
|
1326
1330
|
quickActions.classList.toggle("hidden", !visible);
|
|
1327
1331
|
quickActions.setAttribute("aria-hidden", String(!visible));
|
|
1328
1332
|
}
|
|
@@ -1357,7 +1361,15 @@ function updateMessageCreatedAt(message, createdAt) {
|
|
|
1357
1361
|
|
|
1358
1362
|
function resetAiFeed() {
|
|
1359
1363
|
state.aiLastMessageAt = null;
|
|
1360
|
-
|
|
1364
|
+
const roleplayName = state.aiRoleplayCharacter?.name;
|
|
1365
|
+
$("#ai-feed").innerHTML = roleplayName
|
|
1366
|
+
? `<div class="assistant-message"><span class="message-heading"><span>${esc(roleplayName)}</span></span><div class="message-body"><p>正在扮演 ${esc(roleplayName)}。我只能通过角色卡和与自己有关的记忆回答。</p></div></div>`
|
|
1367
|
+
: '<div class="assistant-message"><span class="message-heading"><span>助手</span></span><div class="message-body"><p>选择章节和模型后,可以问答、续写或校对。所有引用都基于已保存正文。</p></div></div>';
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
function aiAssistantLabel(suffix = "") {
|
|
1371
|
+
const name = state.aiRoleplayCharacter?.name || "助手";
|
|
1372
|
+
return suffix ? `${name} · ${suffix}` : name;
|
|
1361
1373
|
}
|
|
1362
1374
|
|
|
1363
1375
|
function createAiContextCompactionDivider({ kind = "conversation", ariaLabel = "已压缩上下文", title = "" } = {}) {
|
|
@@ -1462,7 +1474,8 @@ const AI_TOOL_DISPLAY_NAMES = {
|
|
|
1462
1474
|
grep: "查询正文关键字",
|
|
1463
1475
|
search_story_entities: "搜索作品实体",
|
|
1464
1476
|
read_character_sections: "读取人物 Markdown 章节",
|
|
1465
|
-
search_drafts: "搜索想法"
|
|
1477
|
+
search_drafts: "搜索想法",
|
|
1478
|
+
recall_self: "回忆自身"
|
|
1466
1479
|
};
|
|
1467
1480
|
|
|
1468
1481
|
const AI_TOOL_DESCRIPTIONS = {
|
|
@@ -1471,7 +1484,8 @@ const AI_TOOL_DESCRIPTIONS = {
|
|
|
1471
1484
|
grep: "查询正文关键字所在的完整段落及章节信息。",
|
|
1472
1485
|
search_story_entities: "按实体名、拼音或短关键词混合检索设定、人物、组织等结构化记录;非语义问答。",
|
|
1473
1486
|
read_character_sections: "读取指定人物 Markdown 档案章节的摘要或原文。",
|
|
1474
|
-
search_drafts: "搜索可能采用、也可能永远不会进入正文或正式设定的未确认临时想法。"
|
|
1487
|
+
search_drafts: "搜索可能采用、也可能永远不会进入正文或正式设定的未确认临时想法。",
|
|
1488
|
+
recall_self: "读取当前扮演角色自己的角色卡、档案,以及自己参与的关系、时间线和正文记忆。"
|
|
1475
1489
|
};
|
|
1476
1490
|
|
|
1477
1491
|
let aiFeedScrollFrame = null;
|
|
@@ -1690,7 +1704,7 @@ function renderAiConversationHistory() {
|
|
|
1690
1704
|
const title = document.createElement("strong");
|
|
1691
1705
|
title.textContent = conversation.title;
|
|
1692
1706
|
const meta = document.createElement("small");
|
|
1693
|
-
meta.textContent = `${conversation.messageCount}
|
|
1707
|
+
meta.textContent = `${conversation.messageCount} 条${conversation.roleplayCharacter?.name ? ` · 扮演 ${conversation.roleplayCharacter.name}` : ""} · ${formatDateTime(conversation.updatedAt)}`;
|
|
1694
1708
|
button.append(title, meta);
|
|
1695
1709
|
button.addEventListener("click", () => openAiConversation(conversation.id));
|
|
1696
1710
|
host.append(button);
|
|
@@ -1786,6 +1800,9 @@ async function openAiConversation(conversationId, hideHistory = true) {
|
|
|
1786
1800
|
upsertAiConversationSummary(conversation);
|
|
1787
1801
|
state.aiConversationId = conversation.id;
|
|
1788
1802
|
state.aiPromptSent = conversation.messages.some((message) => message.role === "user");
|
|
1803
|
+
applyAiConversationTaskType(conversation.taskType);
|
|
1804
|
+
applyAiConversationContextScope(conversation.contextScope);
|
|
1805
|
+
applyAiRoleplayCharacter(conversation.roleplayCharacter);
|
|
1789
1806
|
resetAiContextMeter();
|
|
1790
1807
|
$("#ai-conversation-title").textContent = conversation.title;
|
|
1791
1808
|
resetAiFeed();
|
|
@@ -1805,12 +1822,15 @@ async function openAiConversation(conversationId, hideHistory = true) {
|
|
|
1805
1822
|
if (hideHistory) setAiHistoryVisible(false);
|
|
1806
1823
|
}
|
|
1807
1824
|
|
|
1808
|
-
async function createNewAiConversation() {
|
|
1825
|
+
async function createNewAiConversation(taskType = "chat") {
|
|
1809
1826
|
if (!state.work) return;
|
|
1810
|
-
const conversation = await api(`/api/works/${state.work.id}/ai-conversations`, { method: "POST", body: {} });
|
|
1827
|
+
const conversation = await api(`/api/works/${state.work.id}/ai-conversations`, { method: "POST", body: { taskType } });
|
|
1811
1828
|
upsertAiConversationSummary(conversation);
|
|
1812
1829
|
state.aiConversationId = conversation.id;
|
|
1813
1830
|
state.aiPromptSent = false;
|
|
1831
|
+
applyAiConversationTaskType(conversation.taskType);
|
|
1832
|
+
applyAiConversationContextScope(conversation.contextScope);
|
|
1833
|
+
applyAiRoleplayCharacter(conversation.roleplayCharacter);
|
|
1814
1834
|
$("#ai-conversation-title").textContent = conversation.title;
|
|
1815
1835
|
resetAiFeed();
|
|
1816
1836
|
hideAiContextWarning();
|
|
@@ -1819,12 +1839,135 @@ async function createNewAiConversation() {
|
|
|
1819
1839
|
setAiHistoryVisible(false);
|
|
1820
1840
|
}
|
|
1821
1841
|
|
|
1842
|
+
function renderAiRoleplayCharacterSelect() {
|
|
1843
|
+
const select = $("#ai-roleplay-character");
|
|
1844
|
+
const selectedId = String(state.aiRoleplayCharacter?.id ?? "");
|
|
1845
|
+
const availableCharacters = state.characters.filter((character) => !character.mergedIntoCharacterId);
|
|
1846
|
+
const options = [{ id: "", name: "选择角色卡" }, ...availableCharacters.map((character) => ({
|
|
1847
|
+
id: String(character.id),
|
|
1848
|
+
name: String(character.name)
|
|
1849
|
+
}))];
|
|
1850
|
+
if (selectedId && !options.some((option) => option.id === selectedId)) {
|
|
1851
|
+
options.push({ id: selectedId, name: String(state.aiRoleplayCharacter.name) });
|
|
1852
|
+
}
|
|
1853
|
+
select.replaceChildren(...options.map((option) => {
|
|
1854
|
+
const element = document.createElement("option");
|
|
1855
|
+
element.value = option.id;
|
|
1856
|
+
element.textContent = option.name;
|
|
1857
|
+
return element;
|
|
1858
|
+
}));
|
|
1859
|
+
select.value = selectedId;
|
|
1860
|
+
const canSelectCharacter = Boolean(state.work)
|
|
1861
|
+
&& canReadModule("characters")
|
|
1862
|
+
&& canWritePermissionModule(state.work, "ai-chat");
|
|
1863
|
+
select.disabled = !canSelectCharacter || state.aiPromptSent;
|
|
1864
|
+
select.title = canSelectCharacter
|
|
1865
|
+
? "为当前对话选择角色卡;角色扮演时 Agent 只能查询与该角色自身有关的记忆"
|
|
1866
|
+
: "当前账户没有角色模块读取权限";
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
function syncAiTaskOptions() {
|
|
1870
|
+
const roleplaySelected = $("#ai-task").value === "roleplay";
|
|
1871
|
+
$("#ai-scope").classList.toggle("hidden", roleplaySelected);
|
|
1872
|
+
$("#ai-scope").setAttribute("aria-hidden", String(roleplaySelected));
|
|
1873
|
+
$("#ai-roleplay-character").classList.toggle("hidden", !roleplaySelected);
|
|
1874
|
+
$("#ai-roleplay-character").setAttribute("aria-hidden", String(!roleplaySelected));
|
|
1875
|
+
$(".ai-include-setting-info").classList.toggle("hidden", roleplaySelected);
|
|
1876
|
+
$(".ai-include-setting-info").setAttribute("aria-hidden", String(roleplaySelected));
|
|
1877
|
+
$("#ai-task").disabled = state.aiPromptSent;
|
|
1878
|
+
$("#ai-task").title = state.aiPromptSent ? "对话开始后不能切换任务类型" : "";
|
|
1879
|
+
$("#ai-scope").disabled = roleplaySelected || state.aiPromptSent;
|
|
1880
|
+
$("#ai-scope").title = state.aiPromptSent
|
|
1881
|
+
? "对话开始后不能切换上下文引用"
|
|
1882
|
+
: roleplaySelected ? "角色扮演模式只使用角色自身的记忆" : "";
|
|
1883
|
+
syncAiIncludeSettingInfoControl();
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
function applyAiConversationTaskType(taskType) {
|
|
1887
|
+
const normalizedTaskType = ["chat", "roleplay", "continue", "polish"].includes(taskType) ? taskType : "chat";
|
|
1888
|
+
state.aiTaskType = normalizedTaskType;
|
|
1889
|
+
$("#ai-task").value = normalizedTaskType;
|
|
1890
|
+
$("#ai-task").dataset.previousValue = normalizedTaskType;
|
|
1891
|
+
syncAiTaskOptions();
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
function applyAiConversationContextScope(scope) {
|
|
1895
|
+
const normalizedScope = scope && typeof scope === "object" ? JSON.parse(JSON.stringify(scope)) : { type: "none" };
|
|
1896
|
+
state.aiContextScope = normalizedScope;
|
|
1897
|
+
$("#ai-scope").value = normalizedScope.type === "book" ? "book"
|
|
1898
|
+
: normalizedScope.type === "volume" ? "volume"
|
|
1899
|
+
: normalizedScope.type === "chapter" && normalizedScope.includeBookSummary ? "chapter-summary"
|
|
1900
|
+
: normalizedScope.type === "chapter" ? "chapter"
|
|
1901
|
+
: "none";
|
|
1902
|
+
$("#ai-include-setting-info").checked = normalizedScope.includeSettingInfo !== false;
|
|
1903
|
+
syncAiTaskOptions();
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
function applyAiRoleplayCharacter(character) {
|
|
1907
|
+
state.aiRoleplayCharacter = character?.id ? character : null;
|
|
1908
|
+
const active = Boolean(state.aiRoleplayCharacter);
|
|
1909
|
+
if (active) $("#ai-scope").value = "none";
|
|
1910
|
+
$(".ai-panel").classList.toggle("is-roleplaying", active);
|
|
1911
|
+
$("#ai-prompt").dataset.placeholder = active
|
|
1912
|
+
? `以 ${String(state.aiRoleplayCharacter.name)} 的身份开始对话……`
|
|
1913
|
+
: "告诉 AI 你想讨论或修改什么……";
|
|
1914
|
+
renderAiRoleplayCharacterSelect();
|
|
1915
|
+
syncAiTaskOptions();
|
|
1916
|
+
renderAiQuickActions();
|
|
1917
|
+
resetAiContextMeter();
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
function refreshAiMessageRoleLabels() {
|
|
1921
|
+
$("#ai-feed").querySelectorAll(".assistant-message .message-heading > span").forEach((label) => {
|
|
1922
|
+
label.textContent = aiAssistantLabel();
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
async function updateAiRoleplayCharacter(characterId) {
|
|
1927
|
+
const conversationId = await ensureAiConversation();
|
|
1928
|
+
const conversation = await api(`/api/ai-conversations/${conversationId}/roleplay`, {
|
|
1929
|
+
method: "PATCH",
|
|
1930
|
+
body: { characterId: characterId || null }
|
|
1931
|
+
});
|
|
1932
|
+
upsertAiConversationSummary(conversation);
|
|
1933
|
+
applyAiConversationTaskType(conversation.taskType);
|
|
1934
|
+
applyAiRoleplayCharacter(conversation.roleplayCharacter);
|
|
1935
|
+
if ($("#ai-feed").querySelector("[data-message-id]")) refreshAiMessageRoleLabels();
|
|
1936
|
+
else resetAiFeed();
|
|
1937
|
+
toast(conversation.roleplayCharacter
|
|
1938
|
+
? `已进入 ${conversation.roleplayCharacter.name} 的角色扮演模式`
|
|
1939
|
+
: "已退出角色扮演模式");
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1822
1942
|
async function ensureAiConversation() {
|
|
1823
1943
|
if (state.aiConversationId) return state.aiConversationId;
|
|
1824
|
-
await createNewAiConversation();
|
|
1944
|
+
await createNewAiConversation($("#ai-task").value);
|
|
1825
1945
|
return state.aiConversationId;
|
|
1826
1946
|
}
|
|
1827
1947
|
|
|
1948
|
+
async function persistAiConversationTaskType(taskType) {
|
|
1949
|
+
const conversationId = await ensureAiConversation();
|
|
1950
|
+
const conversation = await api(`/api/ai-conversations/${conversationId}/task-type`, {
|
|
1951
|
+
method: "PATCH",
|
|
1952
|
+
body: { taskType }
|
|
1953
|
+
});
|
|
1954
|
+
upsertAiConversationSummary(conversation);
|
|
1955
|
+
applyAiConversationTaskType(conversation.taskType);
|
|
1956
|
+
applyAiRoleplayCharacter(conversation.roleplayCharacter);
|
|
1957
|
+
return conversation;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
async function persistAiConversationContextScope(scope) {
|
|
1961
|
+
const conversationId = await ensureAiConversation();
|
|
1962
|
+
const conversation = await api(`/api/ai-conversations/${conversationId}/context-scope`, {
|
|
1963
|
+
method: "PATCH",
|
|
1964
|
+
body: { scope }
|
|
1965
|
+
});
|
|
1966
|
+
upsertAiConversationSummary(conversation);
|
|
1967
|
+
applyAiConversationContextScope(conversation.contextScope);
|
|
1968
|
+
return conversation;
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1828
1971
|
async function persistAiConversationMessage(role, content, citations = [], metadata = {}) {
|
|
1829
1972
|
const conversationId = await ensureAiConversation();
|
|
1830
1973
|
if (!conversationId) throw new Error("无法创建 AI 对话");
|
|
@@ -2128,7 +2271,7 @@ function clearChapterLineSelection() {
|
|
|
2128
2271
|
}
|
|
2129
2272
|
|
|
2130
2273
|
const typographyStorageKey = "ai-novel-typography-v1";
|
|
2131
|
-
const typographyDefaults = Object.freeze({ cjkFont: "system", latinFont: "system", fontSize: 17, density: "balanced" });
|
|
2274
|
+
const typographyDefaults = Object.freeze({ cjkFont: "system", latinFont: "system", fontSize: 17, uiFontSize: 16, aiFontSize: 14, density: "balanced" });
|
|
2132
2275
|
const cjkFontStacks = {
|
|
2133
2276
|
system: '"PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", "Heiti SC"',
|
|
2134
2277
|
pingfang: '"PingFang SC", "Heiti SC", "Microsoft YaHei", "Noto Sans CJK SC"',
|
|
@@ -2144,15 +2287,21 @@ const latinFontStacks = {
|
|
|
2144
2287
|
consolas: 'Consolas, "Liberation Mono", Menlo, Monaco, "SFMono-Regular"'
|
|
2145
2288
|
};
|
|
2146
2289
|
const typographyFontSizes = [15, 16, 17, 18, 20];
|
|
2290
|
+
const typographyUiFontSizes = [14, 15, 16, 17, 18];
|
|
2291
|
+
const typographyAiFontSizes = [12, 13, 14, 15, 16];
|
|
2147
2292
|
const densityLineHeights = { compact: 1.4, balanced: 1.55, relaxed: 1.75 };
|
|
2148
2293
|
|
|
2149
2294
|
function normalizeTypographySettings(input) {
|
|
2150
2295
|
const value = input && typeof input === "object" ? input : {};
|
|
2151
2296
|
const fontSize = Number(value.fontSize);
|
|
2297
|
+
const uiFontSize = Number(value.uiFontSize);
|
|
2298
|
+
const aiFontSize = Number(value.aiFontSize);
|
|
2152
2299
|
return {
|
|
2153
2300
|
cjkFont: Object.hasOwn(cjkFontStacks, value.cjkFont) ? value.cjkFont : typographyDefaults.cjkFont,
|
|
2154
2301
|
latinFont: Object.hasOwn(latinFontStacks, value.latinFont) ? value.latinFont : typographyDefaults.latinFont,
|
|
2155
2302
|
fontSize: typographyFontSizes.includes(fontSize) ? fontSize : typographyDefaults.fontSize,
|
|
2303
|
+
uiFontSize: typographyUiFontSizes.includes(uiFontSize) ? uiFontSize : typographyDefaults.uiFontSize,
|
|
2304
|
+
aiFontSize: typographyAiFontSizes.includes(aiFontSize) ? aiFontSize : typographyDefaults.aiFontSize,
|
|
2156
2305
|
density: Object.hasOwn(densityLineHeights, value.density) ? value.density : typographyDefaults.density
|
|
2157
2306
|
};
|
|
2158
2307
|
}
|
|
@@ -2204,9 +2353,15 @@ function applyTypographySettings(settings) {
|
|
|
2204
2353
|
root.style.setProperty("--font-latin", latinFontStacks[normalized.latinFont]);
|
|
2205
2354
|
root.style.setProperty("--editor-font-size", `${normalized.fontSize}px`);
|
|
2206
2355
|
root.style.setProperty("--editor-line-height", String(densityLineHeights[normalized.density]));
|
|
2356
|
+
root.style.setProperty("--ui-font-size", `${normalized.uiFontSize}px`);
|
|
2357
|
+
root.style.setProperty("--ui-font-scale", String(normalized.uiFontSize / typographyDefaults.uiFontSize));
|
|
2358
|
+
root.style.setProperty("--ai-font-size", `${normalized.aiFontSize}px`);
|
|
2359
|
+
root.style.setProperty("--ai-font-scale", String(normalized.aiFontSize / typographyDefaults.aiFontSize));
|
|
2207
2360
|
root.dataset.cjkFont = normalized.cjkFont;
|
|
2208
2361
|
root.dataset.latinFont = normalized.latinFont;
|
|
2209
2362
|
root.dataset.fontSize = String(normalized.fontSize);
|
|
2363
|
+
root.dataset.uiFontSize = String(normalized.uiFontSize);
|
|
2364
|
+
root.dataset.aiFontSize = String(normalized.aiFontSize);
|
|
2210
2365
|
root.dataset.density = normalized.density;
|
|
2211
2366
|
scheduleChapterLineNumbers();
|
|
2212
2367
|
}
|
|
@@ -2227,6 +2382,8 @@ function fillAppearanceForm(settings) {
|
|
|
2227
2382
|
$("#appearance-cjk-font").value = normalized.cjkFont;
|
|
2228
2383
|
$("#appearance-latin-font").value = normalized.latinFont;
|
|
2229
2384
|
$("#appearance-font-size").value = String(normalized.fontSize);
|
|
2385
|
+
$("#appearance-ui-font-size").value = String(normalized.uiFontSize);
|
|
2386
|
+
$("#appearance-ai-font-size").value = String(normalized.aiFontSize);
|
|
2230
2387
|
$("#appearance-density").value = normalized.density;
|
|
2231
2388
|
}
|
|
2232
2389
|
|
|
@@ -2236,6 +2393,8 @@ function readAppearanceForm() {
|
|
|
2236
2393
|
cjkFont: form.get("cjkFont"),
|
|
2237
2394
|
latinFont: form.get("latinFont"),
|
|
2238
2395
|
fontSize: form.get("fontSize"),
|
|
2396
|
+
uiFontSize: form.get("uiFontSize"),
|
|
2397
|
+
aiFontSize: form.get("aiFontSize"),
|
|
2239
2398
|
density: form.get("density")
|
|
2240
2399
|
});
|
|
2241
2400
|
}
|
|
@@ -2246,6 +2405,7 @@ function renderTypographyPreview() {
|
|
|
2246
2405
|
preview.style.fontFamily = `${latinFontStacks[settings.latinFont]}, ${cjkFontStacks[settings.cjkFont]}, monospace, sans-serif`;
|
|
2247
2406
|
preview.style.fontSize = `${settings.fontSize}px`;
|
|
2248
2407
|
preview.style.lineHeight = String(densityLineHeights[settings.density]);
|
|
2408
|
+
$("#font-size-preview").textContent = `界面 ${settings.uiFontSize} px · Agent 对话 ${settings.aiFontSize} px`;
|
|
2249
2409
|
}
|
|
2250
2410
|
|
|
2251
2411
|
function openAppearanceDialog() {
|
|
@@ -3976,10 +4136,14 @@ function resetWorkScopedUiCaches() {
|
|
|
3976
4136
|
state.aiPromptSent = false;
|
|
3977
4137
|
state.aiConversationId = null;
|
|
3978
4138
|
state.aiConversations = [];
|
|
4139
|
+
state.aiRoleplayCharacter = null;
|
|
3979
4140
|
renderAiCitations();
|
|
3980
4141
|
renderAiReferences();
|
|
3981
4142
|
renderAiQuickActions();
|
|
3982
4143
|
resetAiFeed();
|
|
4144
|
+
applyAiConversationTaskType("chat");
|
|
4145
|
+
applyAiConversationContextScope({ type: "none" });
|
|
4146
|
+
applyAiRoleplayCharacter(null);
|
|
3983
4147
|
$("#ai-conversation-title").textContent = "新对话";
|
|
3984
4148
|
$("#ai-model").innerHTML = '<option value="">使用创作助手时加载模型</option>';
|
|
3985
4149
|
resetAiContextMeter();
|
|
@@ -6467,6 +6631,7 @@ function openTaskDetailDialog(task, trace) {
|
|
|
6467
6631
|
</li>`;
|
|
6468
6632
|
}
|
|
6469
6633
|
if (item.type === "book") return "<li>全书</li>";
|
|
6634
|
+
if (item.type === "settings-catalog") return "<li>设定库</li>";
|
|
6470
6635
|
if (item.type === "selection") return item.restricted
|
|
6471
6636
|
? "<li>选定内容(正文读取权限受限)</li>"
|
|
6472
6637
|
: `<li>选定内容:${esc(item.selection || "未提供")}</li>`;
|
|
@@ -6548,7 +6713,7 @@ function renderProviderCards(providers, models) {
|
|
|
6548
6713
|
}).join("")}</div>
|
|
6549
6714
|
<div class="card-actions"><button data-edit-provider="${esc(provider.id)}">编辑配置</button>${provider.status === "enabled" ? `<button data-test-provider="${esc(provider.id)}" ${providerModels.length ? "" : "disabled aria-disabled=\"true\" title=\"请先添加模型\""}>测试连接</button>` : ""}<button data-add-model="${esc(provider.id)}">添加模型</button></div></article>`;
|
|
6550
6715
|
}).join("")}</div>`
|
|
6551
|
-
: emptyModule("尚未配置 AI 供应商", "添加 OpenAI 或
|
|
6716
|
+
: emptyModule("尚未配置 AI 供应商", "添加 OpenAI、Anthropic 或 Google Vertex 接口地址和凭据,测试成功后再添加模型。");
|
|
6552
6717
|
}
|
|
6553
6718
|
|
|
6554
6719
|
function bindPlatformProviderActions(host, providers, models) {
|
|
@@ -7307,23 +7472,51 @@ async function ensureAiModelsLoaded() {
|
|
|
7307
7472
|
}
|
|
7308
7473
|
}
|
|
7309
7474
|
|
|
7475
|
+
function syncAiIncludeSettingInfoControl() {
|
|
7476
|
+
const scopeType = $("#ai-scope").value;
|
|
7477
|
+
const checkbox = $("#ai-include-setting-info");
|
|
7478
|
+
const proseScopes = new Set(["chapter", "chapter-summary", "volume", "book"]);
|
|
7479
|
+
const enabled = proseScopes.has(scopeType);
|
|
7480
|
+
const roleplaySelected = $("#ai-task").value === "roleplay";
|
|
7481
|
+
checkbox.disabled = roleplaySelected || state.aiPromptSent || !enabled;
|
|
7482
|
+
checkbox.title = state.aiPromptSent
|
|
7483
|
+
? "对话开始后不能切换上下文选项"
|
|
7484
|
+
: enabled
|
|
7485
|
+
? "在正文上下文中注入锁定设定、组织/种族简表等"
|
|
7486
|
+
: scopeType === "settings-catalog"
|
|
7487
|
+
? "设定库范围会直接注入设定目录,无需此选项"
|
|
7488
|
+
: "仅在选择正文类上下文范围时可用";
|
|
7489
|
+
}
|
|
7490
|
+
|
|
7310
7491
|
function currentAiRequestScope() {
|
|
7311
7492
|
if (!state.work) return null;
|
|
7312
|
-
const
|
|
7313
|
-
const
|
|
7314
|
-
const
|
|
7493
|
+
const selectedTaskType = $("#ai-task").value;
|
|
7494
|
+
const roleplaySelected = selectedTaskType === "roleplay";
|
|
7495
|
+
const taskType = roleplaySelected ? "chat" : selectedTaskType;
|
|
7496
|
+
if (state.aiPromptSent) {
|
|
7497
|
+
const conversationScope = JSON.parse(JSON.stringify(state.aiContextScope ?? { type: "none" }));
|
|
7498
|
+
const scope = mergeAiReferenceScope(conversationScope, state.aiReferences);
|
|
7499
|
+
return { taskType, scope, conversationScope, selection: typeof conversationScope.selection === "string" ? conversationScope.selection : "" };
|
|
7500
|
+
}
|
|
7501
|
+
const scopeType = roleplaySelected ? "none" : $("#ai-scope").value;
|
|
7502
|
+
const requiresChapter = taskType === "polish" || taskType === "continue" || (scopeType !== "none" && scopeType !== "settings-catalog");
|
|
7315
7503
|
if (requiresChapter && !state.chapter) return null;
|
|
7316
7504
|
const selection = state.chapter ? $("#chapter-content").value.slice($("#chapter-content").selectionStart, $("#chapter-content").selectionEnd) : "";
|
|
7317
7505
|
const volume = state.chapter ? state.work.volumes.find((item) => item.id === state.chapter.volumeId) : null;
|
|
7318
7506
|
const includeBookSummary = scopeType === "chapter-summary";
|
|
7319
|
-
const
|
|
7507
|
+
const conversationScope = taskType === "polish" ? { type: "chapter", chapterId: state.chapter?.id, selection }
|
|
7320
7508
|
: scopeType === "none" ? { type: "none", ...(taskType === "continue" && state.chapter ? { chapterId: state.chapter.id } : {}) }
|
|
7321
7509
|
: scopeType === "book" ? { type: "book" }
|
|
7322
7510
|
: scopeType === "volume" ? { type: "volume", volumeId: volume?.id }
|
|
7511
|
+
: scopeType === "settings-catalog" ? { type: "settings-catalog" }
|
|
7323
7512
|
: { type: "chapter", chapterId: state.chapter?.id };
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7513
|
+
if (includeBookSummary) conversationScope.includeBookSummary = true;
|
|
7514
|
+
const proseScopes = new Set(["chapter", "chapter-summary", "volume", "book"]);
|
|
7515
|
+
if (proseScopes.has(scopeType) || taskType === "polish") {
|
|
7516
|
+
conversationScope.includeSettingInfo = $("#ai-include-setting-info").checked;
|
|
7517
|
+
}
|
|
7518
|
+
const scope = mergeAiReferenceScope(conversationScope, state.aiReferences);
|
|
7519
|
+
return { taskType, scope, conversationScope, selection };
|
|
7327
7520
|
}
|
|
7328
7521
|
|
|
7329
7522
|
function renderAiContextDistribution(usage) {
|
|
@@ -7429,6 +7622,7 @@ async function loadAiReferences() {
|
|
|
7429
7622
|
if (state.work?.id !== workId || generation !== workScopedUiGeneration) return;
|
|
7430
7623
|
state.characters = characters;
|
|
7431
7624
|
state.settings = settings;
|
|
7625
|
+
renderAiRoleplayCharacterSelect();
|
|
7432
7626
|
loadedAiReferencesWorkId = workId;
|
|
7433
7627
|
}
|
|
7434
7628
|
|
|
@@ -9707,23 +9901,66 @@ async function openTaskDialog() {
|
|
|
9707
9901
|
|
|
9708
9902
|
function openProviderDialog(item) {
|
|
9709
9903
|
const protocol = item?.protocol ?? "openai-chat-completions";
|
|
9710
|
-
const
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
|
|
9720
|
-
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
|
|
9725
|
-
|
|
9726
|
-
|
|
9904
|
+
const providerProtocolOptions = [
|
|
9905
|
+
["openai-chat-completions", "OpenAI Chat Completions"],
|
|
9906
|
+
["anthropic-messages", "Anthropic Messages"],
|
|
9907
|
+
["google-vertex", "Google Vertex"]
|
|
9908
|
+
];
|
|
9909
|
+
const defaultBaseUrlForProtocol = (value) => {
|
|
9910
|
+
if (value === "anthropic-messages") return "https://api.anthropic.com";
|
|
9911
|
+
if (value === "google-vertex") {
|
|
9912
|
+
return "https://aiplatform.googleapis.com/v1/projects/PROJECT_ID/locations/global/endpoints/openapi";
|
|
9913
|
+
}
|
|
9914
|
+
return "https://api.openai.com/v1";
|
|
9915
|
+
};
|
|
9916
|
+
const credentialFieldForProtocol = (value) => {
|
|
9917
|
+
if (value === "google-vertex") {
|
|
9918
|
+
return field(
|
|
9919
|
+
"apiKey",
|
|
9920
|
+
item ? "替换服务账号 JSON(留空则不变)" : "服务账号 JSON",
|
|
9921
|
+
"textarea",
|
|
9922
|
+
""
|
|
9923
|
+
);
|
|
9924
|
+
}
|
|
9925
|
+
return field("apiKey", item ? "替换 API 密钥(留空则不变)" : "API 密钥", "password");
|
|
9926
|
+
};
|
|
9927
|
+
const defaultBaseUrl = item?.baseUrl ?? defaultBaseUrlForProtocol(protocol);
|
|
9928
|
+
openDialog(
|
|
9929
|
+
item ? "编辑 AI 供应商" : "新建 AI 供应商",
|
|
9930
|
+
field("name", "显示名称", "text", item?.name)
|
|
9931
|
+
+ field("protocol", "接口协议", "select", protocol, providerProtocolOptions)
|
|
9932
|
+
+ field("baseUrl", "API 基础地址", "url", defaultBaseUrl)
|
|
9933
|
+
+ `<div data-provider-credential-field>${credentialFieldForProtocol(protocol)}</div>`
|
|
9934
|
+
+ field("concurrencyLimit", "最大并发请求数", "number", item?.concurrencyLimit ?? 10)
|
|
9935
|
+
+ field("rpmLimit", "每分钟请求上限", "number", item?.rpmLimit ?? 10)
|
|
9936
|
+
+ field("note", "用途备注", "textarea", item?.note)
|
|
9937
|
+
+ field("enabled", item ? "启用供应商" : "立即启用", "checkbox", item ? item.status === "enabled" : true),
|
|
9938
|
+
async (form) => {
|
|
9939
|
+
const body = {
|
|
9940
|
+
name: form.get("name"),
|
|
9941
|
+
protocol: form.get("protocol"),
|
|
9942
|
+
baseUrl: form.get("baseUrl"),
|
|
9943
|
+
concurrencyLimit: Number(form.get("concurrencyLimit")),
|
|
9944
|
+
rpmLimit: Number(form.get("rpmLimit")),
|
|
9945
|
+
note: form.get("note"),
|
|
9946
|
+
status: form.get("enabled") === "on" ? "enabled" : "disabled"
|
|
9947
|
+
};
|
|
9948
|
+
if (!item || String(form.get("apiKey") ?? "").trim()) body.apiKey = form.get("apiKey");
|
|
9949
|
+
await api(item ? `/api/providers/${item.id}` : "/api/platform/ai/providers", { method: item ? "PATCH" : "POST", body });
|
|
9950
|
+
await renderPlatformAiConfig();
|
|
9951
|
+
await loadModels();
|
|
9952
|
+
},
|
|
9953
|
+
item ? "协议、限流与凭据" : "OpenAI / Anthropic / Google Vertex"
|
|
9954
|
+
);
|
|
9955
|
+
const protocolSelect = $("#dialog-fields select[name='protocol']");
|
|
9956
|
+
const baseUrlInput = $("#dialog-fields input[name='baseUrl']");
|
|
9957
|
+
const credentialHost = $("#dialog-fields [data-provider-credential-field]");
|
|
9958
|
+
const syncProviderCredentialField = () => {
|
|
9959
|
+
const nextProtocol = protocolSelect.value;
|
|
9960
|
+
credentialHost.innerHTML = credentialFieldForProtocol(nextProtocol);
|
|
9961
|
+
if (!item) baseUrlInput.value = defaultBaseUrlForProtocol(nextProtocol);
|
|
9962
|
+
};
|
|
9963
|
+
protocolSelect.addEventListener("change", syncProviderCredentialField);
|
|
9727
9964
|
}
|
|
9728
9965
|
|
|
9729
9966
|
function openModelDialog(providerId, item = null) {
|
|
@@ -9793,10 +10030,17 @@ async function sendAi() {
|
|
|
9793
10030
|
if (!modelId) return toast("请先在 AI 管理中配置并选择模型", "error");
|
|
9794
10031
|
const instruction = aiPromptText().trim();
|
|
9795
10032
|
if (!instruction) return toast("请输入指令", "error");
|
|
10033
|
+
if ($("#ai-task").value === "roleplay" && !state.aiRoleplayCharacter) return toast("请先选择角色卡", "error");
|
|
9796
10034
|
const requestScope = currentAiRequestScope();
|
|
9797
10035
|
if (!requestScope) return toast("请先选择章节", "error");
|
|
9798
10036
|
const { taskType, scope, selection } = requestScope;
|
|
9799
10037
|
if (taskType === "polish" && !selection) return toast("请先在正文中选中一段文本", "error");
|
|
10038
|
+
try {
|
|
10039
|
+
await persistAiConversationTaskType($("#ai-task").value);
|
|
10040
|
+
await persistAiConversationContextScope(requestScope.conversationScope);
|
|
10041
|
+
} catch (error) {
|
|
10042
|
+
return toast(`对话配置锁定失败:${error.message}`, "error");
|
|
10043
|
+
}
|
|
9800
10044
|
setAiAssistantStatus("ready");
|
|
9801
10045
|
const citations = state.aiCitations.map(({ chapterId, chapterTitle, startLine, endLine, text }) => ({ chapterId, chapterTitle, startLine, endLine, text }));
|
|
9802
10046
|
let persistedUserMessage = null;
|
|
@@ -9808,12 +10052,15 @@ async function sendAi() {
|
|
|
9808
10052
|
return toast(`对话记录创建失败:${error.message}`, "error");
|
|
9809
10053
|
}
|
|
9810
10054
|
state.aiPromptSent = true;
|
|
10055
|
+
syncAiTaskOptions();
|
|
10056
|
+
renderAiRoleplayCharacterSelect();
|
|
9811
10057
|
renderAiQuickActions();
|
|
9812
10058
|
appendMessage("user", instruction, citations, persistedUserMessage.createdAt, {}, persistedUserMessage.id);
|
|
9813
10059
|
clearAiPromptComposer();
|
|
9814
10060
|
}
|
|
9815
10061
|
$("#ai-send").disabled = true;
|
|
9816
10062
|
$("#ai-send").textContent = "发送中";
|
|
10063
|
+
$("#ai-roleplay-character").disabled = true;
|
|
9817
10064
|
try {
|
|
9818
10065
|
let assistantContent = "";
|
|
9819
10066
|
let assistantMessage;
|
|
@@ -9869,6 +10116,7 @@ async function sendAi() {
|
|
|
9869
10116
|
} finally {
|
|
9870
10117
|
$("#ai-send").disabled = false;
|
|
9871
10118
|
$("#ai-send").textContent = "发送";
|
|
10119
|
+
renderAiRoleplayCharacterSelect();
|
|
9872
10120
|
}
|
|
9873
10121
|
}
|
|
9874
10122
|
|
|
@@ -9882,7 +10130,7 @@ async function streamChat(body) {
|
|
|
9882
10130
|
let messageMounted = false;
|
|
9883
10131
|
const mountAssistantMessage = () => {
|
|
9884
10132
|
if (messageMounted) return;
|
|
9885
|
-
attachMessageHeading(message, "
|
|
10133
|
+
attachMessageHeading(message, aiAssistantLabel("正在生成"));
|
|
9886
10134
|
$("#ai-feed").append(message);
|
|
9887
10135
|
messageMounted = true;
|
|
9888
10136
|
scrollAiFeedToBottom();
|
|
@@ -9955,6 +10203,8 @@ async function streamChat(body) {
|
|
|
9955
10203
|
state.aiConversationId = persistedUserMessage.conversationId;
|
|
9956
10204
|
updateAiConversationSummaryFromMessage(persistedUserMessage);
|
|
9957
10205
|
state.aiPromptSent = true;
|
|
10206
|
+
syncAiTaskOptions();
|
|
10207
|
+
renderAiRoleplayCharacterSelect();
|
|
9958
10208
|
renderAiQuickActions();
|
|
9959
10209
|
appendMessage("user", persistedUserMessage.content, persistedUserMessage.citations, persistedUserMessage.createdAt, {}, persistedUserMessage.id);
|
|
9960
10210
|
clearAiPromptComposer();
|
|
@@ -10038,7 +10288,7 @@ async function streamChat(body) {
|
|
|
10038
10288
|
typewriter.reveal();
|
|
10039
10289
|
message.classList.remove("is-streaming");
|
|
10040
10290
|
content.setAttribute("aria-busy", "false");
|
|
10041
|
-
message.querySelector(".message-heading > span").textContent = "
|
|
10291
|
+
message.querySelector(".message-heading > span").textContent = aiAssistantLabel("生成中断");
|
|
10042
10292
|
renderAiProcessSteps(message, processSteps, true, elapsedProcessTime());
|
|
10043
10293
|
meta.textContent = "生成中断";
|
|
10044
10294
|
scrollAiFeedToBottom();
|
|
@@ -10054,7 +10304,7 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
10054
10304
|
? `<p class="ai-error-text">${esc(text)}</p>`
|
|
10055
10305
|
: renderMarkdown(text);
|
|
10056
10306
|
message.innerHTML = `<div class="message-body">${messageBody}</div>`;
|
|
10057
|
-
const heading = attachMessageHeading(message, role === "user" ? "作者" :
|
|
10307
|
+
const heading = attachMessageHeading(message, role === "user" ? "作者" : aiAssistantLabel(), createdAt ?? undefined);
|
|
10058
10308
|
if (isFailure) {
|
|
10059
10309
|
message.dataset.status = "failed";
|
|
10060
10310
|
const failureBadge = document.createElement("strong");
|
|
@@ -11318,8 +11568,61 @@ $("#ai-model").addEventListener("focus", () => {
|
|
|
11318
11568
|
ensureAiModelsLoaded().catch((error) => toast(`模型加载失败:${error.message}`, "error"));
|
|
11319
11569
|
});
|
|
11320
11570
|
$("#ai-model").addEventListener("change", () => setAiContextMeter(null));
|
|
11321
|
-
$("#ai-
|
|
11322
|
-
|
|
11571
|
+
$("#ai-roleplay-character").addEventListener("focus", async () => {
|
|
11572
|
+
try {
|
|
11573
|
+
await ensureAiReferencesLoaded();
|
|
11574
|
+
renderAiRoleplayCharacterSelect();
|
|
11575
|
+
} catch (error) {
|
|
11576
|
+
toast(`角色卡加载失败:${error.message}`, "error");
|
|
11577
|
+
}
|
|
11578
|
+
});
|
|
11579
|
+
$("#ai-roleplay-character").addEventListener("change", async (event) => {
|
|
11580
|
+
const select = event.currentTarget;
|
|
11581
|
+
const characterId = select.value;
|
|
11582
|
+
select.disabled = true;
|
|
11583
|
+
try {
|
|
11584
|
+
await updateAiRoleplayCharacter(characterId);
|
|
11585
|
+
} catch (error) {
|
|
11586
|
+
renderAiRoleplayCharacterSelect();
|
|
11587
|
+
toast(`角色扮演模式切换失败:${error.message}`, "error");
|
|
11588
|
+
} finally {
|
|
11589
|
+
renderAiRoleplayCharacterSelect();
|
|
11590
|
+
}
|
|
11591
|
+
});
|
|
11592
|
+
$("#ai-task").addEventListener("change", async (event) => {
|
|
11593
|
+
const select = event.currentTarget;
|
|
11594
|
+
const previousTaskType = select.dataset.previousValue || state.aiTaskType || "chat";
|
|
11595
|
+
const nextTaskType = select.value;
|
|
11596
|
+
if (state.aiPromptSent) {
|
|
11597
|
+
applyAiConversationTaskType(previousTaskType);
|
|
11598
|
+
return toast("当前对话已经开始,请新建对话后再切换任务类型", "error");
|
|
11599
|
+
}
|
|
11600
|
+
applyAiConversationTaskType(nextTaskType);
|
|
11601
|
+
if (state.aiConversationId) {
|
|
11602
|
+
select.disabled = true;
|
|
11603
|
+
try {
|
|
11604
|
+
await persistAiConversationTaskType(nextTaskType);
|
|
11605
|
+
} catch (error) {
|
|
11606
|
+
applyAiConversationTaskType(previousTaskType);
|
|
11607
|
+
toast(`任务类型切换失败:${error.message}`, "error");
|
|
11608
|
+
} finally {
|
|
11609
|
+
syncAiTaskOptions();
|
|
11610
|
+
renderAiRoleplayCharacterSelect();
|
|
11611
|
+
}
|
|
11612
|
+
}
|
|
11613
|
+
setAiContextMeter(null);
|
|
11614
|
+
});
|
|
11615
|
+
$("#ai-scope").addEventListener("change", (event) => {
|
|
11616
|
+
if (state.aiPromptSent) {
|
|
11617
|
+
applyAiConversationContextScope(state.aiContextScope);
|
|
11618
|
+
return toast("当前对话已经开始,请新建对话后再切换上下文引用", "error");
|
|
11619
|
+
}
|
|
11620
|
+
event.currentTarget.title = "";
|
|
11621
|
+
syncAiIncludeSettingInfoControl();
|
|
11622
|
+
setAiContextMeter(null);
|
|
11623
|
+
});
|
|
11624
|
+
$("#ai-include-setting-info").addEventListener("change", () => setAiContextMeter(null));
|
|
11625
|
+
syncAiIncludeSettingInfoControl();
|
|
11323
11626
|
$("#ai-mention-menu").addEventListener("click", (event) => {
|
|
11324
11627
|
const button = event.target.closest("[data-ai-reference-id]");
|
|
11325
11628
|
if (button) selectAiMention(button);
|
|
@@ -11602,7 +11905,7 @@ $("#ai-prompt").addEventListener("keydown", (event) => {
|
|
|
11602
11905
|
$(".quick-actions").addEventListener("click", (event) => {
|
|
11603
11906
|
const button = event.target.closest("[data-task]");
|
|
11604
11907
|
if (!button) return;
|
|
11605
|
-
|
|
11908
|
+
applyAiConversationTaskType(button.dataset.task);
|
|
11606
11909
|
setAiPromptText(button.dataset.prompt);
|
|
11607
11910
|
$("#ai-prompt").focus();
|
|
11608
11911
|
});
|