NovelAIスクリプティング&TypeScriptスキルガイド は何をしますか?
このスキルは、NovelAIユーザースクリプトの開発とNovelAIテキスト生成エンジンとの連携に関する包括的なインストラクション、APIリファレンス、型付けルール、アーキテクチャガイドラインを提供します。
1. クイックリファレンス&ドキュメントファイル
詳細なドキュメントと型定義は references/ ディレクトリにあります。
- TypeScriptガイドライン — Web Workerインタープリターでのランタイムクラッシュを避けるための重要なルール、型付けパターン、イベント処理、キャストについて。
- スクリプティングAPI完全ドキュメント — すべての
api.v1名前空間(Storage、UI、Document、Hooks、Generation、Lorebook、TTSなど)の完全なリファレンス。 - テキスト生成ドキュメント — NovelAIエディターとWebサイト機能(モデル、コンテキスト設定、サンプラー、Lorebook UI、ATTG、テキストアドベンチャー)の背景情報。スクリプトがエディターのコンテキスト構築方法や設定の意味を理解する必要がある場合に参照しますが、スクリプトコード自体にはほとんど必要ありません。
- スクリプト型定義 (
.d.ts) — 公式TypeScript定義(https://novelai.net/scripting/types/script-types.d.tsから取得)。プロパティ名と許容値の真実の情報源です。 散文のドキュメントと矛盾する場合は、型定義を優先してください。 - JSXガイド + JSX型定義 — Preactベースの
jsxUIパーツ(テーブル、SVG/キャンバスチャート、ライブウィジェット)のカスタムビジュアル作成用:グローバル(h、render、フック、createStore)、許可される要素/属性、イベント、キャンバス、型チェック方法。オプトイン専用:ユーザーが明示的に要求した場合のみJSXを使用してください。 それ以外の場合は、複雑なレイアウトでも標準のパーツからUIを構築し、JSXの可能性について言及するのは控えてください。 - 実験的型定義 —
api.v1.editor.block()とapi.v1.document.transaction()。ユーザーのexperimentalScriptingApi設定によって制御されます(APIドキュメントのセクション18参照)。ユーザーが有効にしたと明言しない限り使用しないでください。 - UIコンポーネントシステム — 再利用可能でスケーラブルなUIの構築方法:トークン、スタイルプリセット、型付きコンポーネントファクトリ、ストレージバインドフィールド、レンダリング/リフレッシュ戦略、再利用可能なダイアログ。UIを持つスクリプトのデフォルト構造としてこれを使用してください。
- スクリプトヘッダー&設定例 — すべての設定オプション型(string、multiline string、number、boolean、select)を含む
/*--- ... ---*/スクリプトヘッダー。 - 公式サンプル: https://github.com/NovelAI/novelai-script-examples。*Scripting Demo* シナリオや Find & Replace、Notes、Smart Summarizer などのスクリプトは、実際のパターンを示しています。
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)。フックごとにスクリプトあたり1コールバック。再度登録すると置き換えられます。 |
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 | ストーリーのMemory、System Prompt、Prefillの取得または設定(設定には storyEdit 権限が必要)。 |
api.v1.generate() | カスタムパラメーターとコンテキストで直接テキスト生成を実行します。 |
api.v1.generateWithStory() | 現在のストーリーコンテキストと設定を使用して生成を実行します。 |
api.v1.lorebook | Lorebookエントリとカテゴリのクエリ、更新、作成、削除。 |
api.v1.an | Author's Noteテキストの取得または設定。 |
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(memory、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. スタイリング、テーマトークンとファクトリ関数
NovelAIスクリプトは隔離されたWeb Worker内で実行され、DOMにアクセスできません。document.createElement、document.head、cloneNode、またはページに直接触れるブラウザAPIは絶対に使用しないでください。すべてのUIは、api.v1.ui.update() に渡される宣言型のUIパーツオブジェクトから構築されます。
UIを持つスクリプトのデフォルトアーキテクチャ: NovelAI_UI_Component_System.md に従ってください。 これには、公式の型に対して型チェックを行う再利用可能でスケーラブルな階層が含まれています。
- 名前空間とトークン:
NS+id()、COLORテーマトークンマップ、SPACE/RADIUS定数sx()と組み合わせるスタイルプリセット- オプションオブジェクトとバリアントを持つ
api.v1.ui.part.*に基づくコンポーネントファクトリーstorageKeyプレフィックスとストレージAPIを同期させるfield()バインディング- 1つの
render()と、小さなリフレッシュ用のupdateParts/{{templates}}- 再利用可能な
confirm()/notify()ダイアログ以下のスニペットは、同じ考え方をミニマムにしたものです。ユーザーがJSXを要求した場合は、NovelAI_JSX_Guide.md を参照してください。JSXスタイルは通常のCSSと
var(--theme-*)を使用し、トークン名は使用しません。自分の判断でJSXに切り替えないでください。
スタイルオブジェクトプリセット
スクリプトの先頭で型付けされたスタイルオブジェクトを定義し、UIパーツにスプレッドします。
const CARD_STYLE: Record<string, string> = {
backgroundColor: 'bg1', // テーマトークン
border: '1px solid bg2', // テーマトークン
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
テーマトークン
ハードコードされた16進数カラーの代わりに、style オブジェクトの値としてNovelAIテーマトークン文字列を直接使用してください。レンダリング時に各トークンは、ユーザーのアクティブなテーマ値にリテラルのインライン値(例:'bg1' は rgb(...) コードになる)として置き換えられます。これらはCSS変数ではありません:var() で使用できる --theme-* カスタムプロパティはなく、サイト上の他のカスタムCSSから参照することもできません。スクリプト内で生のテーマ値が必要な場合は、api.v1.theme.get() を使用してください。(例外:公式の型によると、JSX UIパーツは独自のシャドウDOM内で --theme-* カスタムプロパティを取得します。)
| トークン | 役割 |
|---|---|
'bg0' | 最も深い/最外部の背景 |
'bg1' | デフォルトのパネル背景 |
'bg2' | 浮き上がったサーフェス/境界線/ボタン背景 |
'bg3' | ハイライト/選択されたサーフェス |
'textHeadings' | 高コントラストの見出しとタイトルテキスト |
'textMain' | デフォルトの本文テキスト |
'textDisabled' | 無効化/ミュートされたテキスト |
'textPlaceholder' | 入力プレースホルダーテキスト |
'textHighlight' | 検索/ハイライト色 |
'warning' | 警告/アラート状態 |
'textPrompt'、'textUser'、'textAI'、'textEdit' | ストーリーテキストの色 |
'lowIntensityColor'、'midIntensityColor'、'highIntensityColor' | 低/中/高確率の色(トークン確率で使用) |
'default' | 本文フォントファミリー(fontFamily で使用) |
'headings' | 見出しフォントファミリー(fontFamily で使用) |
'code' | 等幅コードフォントファミリー |
'field' | フォーム/入力フィールドフォントファミリー |
⚠️
'primary'に関する注意: NovelAIには'primary'や'error'テーマトークンはありません。color: 'primary'を使用すると、ブラウザが無視する無効なCSSが生成されます。アクティブ/ハイライトサーフェスには'bg3'を、高コントラストのアクセントには'textHeadings'を使用するか、明示的な16進数/rgbaカラー文字列(例:'#90caf9')を指定してください。
ファクトリ関数(cloneNode / テンプレートの代替)
スクリプトの先頭に、プリスタイルおよびプリ設定されたUIパーツオブジェクトを返すファクトリ関数を記述します。これにより、スクリプト全体でインラインスタイルオブジェクトを繰り返す必要がなくなります。
// 再利用可能なスタイル付きボタンファクトリー
function makeButton(
text: string,
onClick: () => Promise<void>,
variant: 'primary' | 'secondary' = 'secondary'
) {
return {
type: 'button' as const,
label: text,
style: {
backgroundColor: variant === 'primary' ? 'bg3' : 'bg2',
color: 'textMain',
border: '1px solid bg2',
borderRadius: '6px',
padding: '8px 16px',
cursor: 'pointer',
margin: '4px',
},
onClick: onClick,
};
}