NovelAI 腳本與 TypeScript 技能指南
此技能提供開發 NovelAI 使用者腳本及與 NovelAI 文字生成引擎互動的完整說明、API 參考、型別規則與架構指南。
1. 快速參考與文檔檔案
所有深入文檔與型別定義位於 references/ 目錄中:
- TypeScript 指南 — 避免在 Web Worker 解譯器中發生執行時崩潰的關鍵規則、型別模式、事件處理與型別轉換。
- 腳本 API 完整文檔 — 所有
api.v1命名空間(Storage、UI、Document、Hooks、Generation、Lorebook、TTS 等)的完整參考。 - 文字生成文檔 — NovelAI 編輯器與網站功能的背景知識(模型、上下文設定、取樣器、Lorebook UI、ATTG、文字冒險)。腳本程式碼通常不需要;當腳本需要推理編輯器如何構建上下文或某個設定的含義時查閱。
- 腳本型別定義(
.d.ts) — 官方 TypeScript 定義(來自https://novelai.net/scripting/types/script-types.d.ts)。這是屬性名稱與允許值的權威來源。 當與文字文檔衝突時,請信任型別定義。 - JSX 指南 + JSX 型別 — 基於 Preact 的
jsxUI 部分,用於自訂視覺元素(表格、SVG/Canvas 圖表、即時小工具):全域變數(h、render、鉤子、createStore)、元素/屬性白名單、事件、Canvas 以及如何進行型別檢查。僅選擇性使用:僅在使用者明確要求時才使用 JSX。 否則使用標準部分構建 UI,即使是複雜佈局也至少提及 JSX 可能有所幫助。 - 實驗型別 —
api.v1.editor.block()和api.v1.document.transaction(),受使用者experimentalScriptingApi設定限制(請參閱 API 文檔第 18 節)。除非使用者表示已啟用,否則不要使用。 - UI 組件系統 — 如何構建可重複使用、可擴展的 UI:Token、樣式預設、型別化組件工廠、儲存綁定欄位、渲染/刷新策略、可重複使用的對話框。對於任何包含 UI 的腳本,請將其用作預設結構。
- 腳本標頭與配置範例 —
/*--- ... ---*/腳本標頭,包含所有配置選項型別(字串、多行字串、數字、布林值、選取)。 - 官方範例:https://github.com/NovelAI/novelai-script-examples。*腳本演示*場景以及如*尋找與取代*、*筆記*和*智慧摘要器*等腳本展示了真實世界模式。
2. 核心架構與執行環境
- 隔離的 Web Worker 執行:
- 腳本在 Web Worker 內部的隔離 JavaScript 解譯器中執行。
- 腳本無法存取任意的 DOM 元素或發出原始網路請求。
- 所有與 NovelAI 的互動都通過版本化的
api.v1非同步介面進行。 - 全域
setTimeout/clearTimeout不可用。請使用await api.v1.timers.setTimeout(...)、api.v1.timers.clearTimeout(...)和api.v1.timers.sleep(ms)。
- 腳本是暫時的:
- 當故事切換、使用者腳本模態框關閉或切換時,腳本可能會被重新載入或卸載。
- 切勿在記憶體全域變數中儲存持久化狀態。 始終使用
api.v1.storage持久化狀態。
- 靜默 Promise 拒絕:
- Worker 中未處理的拒絕 Promise 不會列印到控制台或 UI。
- 始終使用
try/catch包裝非同步呼叫,或附加.catch((err) => ...)處理器。
- 緊急恢復:
- 如果腳本導致不穩定,請在 NovelAI URL 附加
?noscript=true以在載入時停用腳本執行。
- 如果腳本導致不穩定,請在 NovelAI URL 附加
3. NovelAI 腳本的關鍵 TypeScript 指南
Web Worker 解譯器需要乾淨、嚴格註解的 TypeScript 以避免編譯/執行時錯誤:
A. 明確的參數註解
始終為標準函數和箭頭回呼中的參數註明型別:
// ❌ 錯誤
function processEntry(id, data) { ... }
items.filter(item => item.active);
// ✅ 正確
function processEntry(id: string, data: any): void { ... }
items.filter((item: any) => item.active);
B. 物件迭代型別轉換
Object.entries() 和 Object.values() 必須明確進行型別轉換:
// ✅ 正確
for (const [key, value] of Object.entries(myObj) as [string, any][]) { ... }
for (const val of Object.values(myObj) as any[]) { ... }
C. 陣列位移的 undefined 檢查
Array.prototype.shift() 返回 T | undefined:
// ✅ 正確
const item = queue.shift();
if (!item) return;
D. 僅在非同步上下文中使用 Await
呼叫非同步 API 方法的 UI 建構器和輔助函數必須是 async:
// ✅ 正確
async function loadUserData(): Promise<void> {
const hasData = await api.v1.storage.has('userData');
if (hasData) {
const data = await api.v1.storage.get('userData');
}
}
4. 關鍵 API 命名空間 (api.v1)
| 命名空間 / 方法 | 用途 |
|---|---|
api.v1.storage | 持久化鍵值儲存:get、getOrDefault、set、setIfAbsent、has、remove、list(也有 storyStorage、historyStorage、tempStorage)。 |
api.v1.hooks | 註冊事件生命週期鉤子(onGenerationRequested、onBeforeContextBuild、onContextBuilt、onResponse、onGenerationEnd、onScriptsLoaded、onLorebookEntrySelected、onTextAdventureInput、onHistoryNavigated、onDocumentConvertedToText)。每個腳本每個鉤子只能有一個回呼;再次註冊會取代它。 |
api.v1.ui | 建立 UI 擴充(scriptPanel、sidebarPanel、lorebookPanel、toolbarButton、contextMenuButton、toolboxOption)、模態框、視窗、提示訊息;使用 updateParts() 按 id 變更單個部分。 |
api.v1.document | 按章節(段落)ID 讀取和編輯故事文件:scan()、sectionIds()、textFromSelection()、append()、appendParagraph(s)()、insertParagraph(s)After()、updateParagraph(s)()、removeParagraph(s)(),以及 document.history。 |
api.v1.editor | generate()、isBlocked()、selection.get(),以及 editor.decorations(高亮、標記、小工具)。 |
api.v1.memory / systemPrompt / prefill | 取得或設定故事的記憶、系統提示和預填文字(設定需要 storyEdit)。 |
api.v1.generate() | 使用自訂參數和上下文執行直接文字生成。 |
api.v1.generateWithStory() | 使用目前故事上下文和設定執行生成。 |
api.v1.lorebook | 查詢、更新、建立或刪除 Lorebook 條目和類別。 |
api.v1.an | 取得或設定作者註記文字。 |
api.v1.buildContext() | 在上下文發送到 AI 之前存取和檢查其組合。 |
api.v1.config | await api.v1.config.get('name') 讀取在腳本標頭中聲明的使用者可配置值(見下文)。 |
api.v1.permissions | has() / request() / list()。在使用受限 API 之前,請求 documentEdit、storyEdit、lorebookEdit、editorDecorations、clipboardWrite、fileInput、fileDownload(以及 presetEdit)。 |
腳本標頭與配置
每個 .naiscript 以 /*--- ... ---*/ 註解中的 YAML 標頭開頭(compatibilityVersion: naiscript-1.0、id、name、version、author、description、memoryLimit、可選的 config)。config 條目(name、prettyName、type、default、description)會顯示在使用者腳本模態框的配置標籤中,因此使用者無需編輯程式碼即可變更它們。型別為 string(可選 multiline: true)、number(可選 min/max/decimal)、boolean 和 select(options: [{ label, value }];預設為第一個選項)。請參閱 script_config_examples.ts。在執行時使用回退讀取值:
const openOnCreate: boolean = (await api.v1.config.get('open_on_create')) ?? true;
5. 標準腳本模板
建立新的 NovelAI 腳本時,請遵循此結構:
(async () => {
try {
const STORAGE_KEY = "my_custom_script_data";
// 1. 初始化持久化狀態
const state = await api.v1.storage.getOrDefault(STORAGE_KEY, { enabled: true, count: 0 });
await api.v1.storage.setIfAbsent(STORAGE_KEY, state);
// 2. 註冊生命週期鉤子
// onGenerationRequested 在上下文構建之前執行;它只能停止生成或準備狀態。
api.v1.hooks.register('onGenerationRequested', async (params: any) => {
if (!state.enabled) return;
// 在此為後續鉤子準備狀態,或返回 { stopGeneration: true } 以阻止它
});
// 若要修改模型看到的內容,請使用 onBeforeContextBuild(記憶、lorebook、AN...)
// 或 onContextBuilt(返回 { messages } 與編輯後的訊息陣列)。
api.v1.hooks.register('onContextBuilt', async (params: any) => {
if (!state.enabled || params.dryRun) return;
return { messages: params.messages };
});
// 3. 註冊 UI — 始終使用 update(),而非 register()
api.v1.hooks.register('onScriptsLoaded', async () => {
await api.v1.ui.update([{
type: 'sidebarPanel' as const,
id: 'my-script-panel',
name: '自訂工具',
content: [
{ type: 'text' as const, text: `狀態:${state.enabled ? '啟用' : '停用'}` } as any
]
}]);
});
} catch (err: any) {
api.v1.error("腳本初始化錯誤:", err);
}
})();
6. 樣式、主題 Token 與工廠函數
NovelAI 腳本在隔離的 Web Worker 中執行,無法存取 DOM。切勿使用 document.createElement、document.head、cloneNode 或任何直接操作頁面的瀏覽器 API。所有 UI 都通過傳遞給 api.v1.ui.update() 的宣告式 UI 部分物件構建。
任何包含 UI 的腳本的預設架構:遵循 NovelAI_UI_Component_System.md。 它提供一個可重複使用、可擴展的分層結構,並對官方型別進行型別檢查:
- 命名空間與 Token:
NS+id()、COLOR主題 Token 對應、SPACE/RADIUS常數- 結合
sx()的樣式預設- 基於
api.v1.ui.part.*的組件工廠,帶有選項物件和變體field()綁定,保持storageKey前綴與儲存 API 同步- 一個
render(),搭配updateParts/{{templates}}進行小幅刷新- 可重複使用的
confirm()/notify()對話框下面的程式碼片段是相同概念的簡化版本。如果使用者要求 JSX,請參閱 NovelAI_JSX_Guide.md。JSX 樣式使用普通 CSS 和
var(--theme-*),而非 Token 名稱。不要自行切換到 JSX。
樣式物件預設
在腳本頂部定義型別化的樣式物件,並將其展開到 UI 部分上:
const CARD_STYLE: Record<string, string> = {
backgroundColor: 'bg1', // 主題 Token
border: '1px solid bg2', // 主題 Token
borderRadius: '6px',
padding: '10px',
margin: '6px 0',
};
const DANGER_STYLE: Record<string, string> = {
...CARD_STYLE,
borderColor: 'warning',
};
// 透過展開應用於 UI 部分:
{ type: 'container' as const, style: CARD_STYLE, content: [...] } as any
主題 Token
請勿硬編碼十六進位顏色,直接在 style 物件中使用 NovelAI 主題 Token 字串作為值。在渲染時,每個 Token 都會被替換為使用者活躍主題的值,作為行內字面值(例如 'bg1' 變成 rgb(...) 代碼)。這些不是 CSS 變數:沒有 --theme-* 自訂屬性可與 var() 一起使用,而且網站上其他地方的自訂 CSS 也無法引用它們。若要在腳本內取得原始主題值,請使用 api.v1.theme.get()。(例外:根據官方型別,JSX UI 部分在其自身的 Shadow DOM 內會獲得 --theme-* 自訂屬性。)
| Token | 角色 |
|---|---|
'bg0' | 最深/最外層背景 |
'bg1' | 預設面板背景 |
'bg2' | 凸起表面/邊框/按鈕背景 |
'bg3' | 高亮/選中的表面 |
'textHeadings' | 高對比度的標題和標題文字 |
'textMain' | 預設正文文字 |
'textDisabled' | 停用/減弱文字 |
'textPlaceholder' | 輸入框預留位置文字 |
'textHighlight' | 搜尋/高亮顏色 |
'warning' | 警告/警示狀態 |
'textPrompt'、'textUser'、'textAI'、'textEdit' | 故事文字顏色 |
'lowIntensityColor'、'midIntensityColor'、'highIntensityColor' | 低/中/高機率顏色(如 Token 機率所使用的) |
'default' | 正文字型(在 fontFamily 中使用) |
'headings' | 標題字型(在 fontFamily 中使用) |
'code' | 等寬程式碼字型 |
'field' | 表單/輸入欄位字型 |
⚠️ 關於
'primary'的注意事項:NovelAI 沒有'primary'或'error'主題 Token。使用color: 'primary'會產生無效的 CSS,瀏覽器會忽略它。對於活動/高亮表面,請使用'bg3';對於高對比度強調,請使用'textHeadings';或提供明確的十六進位/rgba 顏色字串(例如'#90caf9')。
工廠函數(取代 cloneNode / 模板)
在腳本頂部編寫工廠函數,返回預先設定樣式和配置的 UI 部分物件。這可以避免在整個腳本中重複行內樣式物件。
// 可重複使用的樣式化按鈕工廠
function makeButton(
text: string,
onClick: () => Promise<void>
…
[為翻譯而截斷]