Communitygithub.com

MidMeantime/novel-ai-scripting

NovelAI用户脚本的全面指南与参考,涵盖TypeScript脚本编写规则、API v1参考、UI扩展、生成钩子及文本生成配置。用于编写、调试、重构或审查NovelAI脚本、Lorebook集成、上下文构建或NovelAI TypeScript代码。

novel-ai-scripting 是什么?

该技能是为开发NovelAI用户脚本提供的全面技术指南和参考,支持在Claude Code、Cursor和Codex等AI编码助手中使用。它详细介绍了基于Web Worker的隔离运行时环境、TypeScript编写规范(如显式参数注解、对象迭代转换、异步处理)、声明式UI组件系统(包括主题令牌、样式预设和工厂函数),以及api.v1命名空间中所有关键API(存储、钩子、UI、文档、生成、Lorebook等)。技能还涵盖了脚本配置头部、状态持久化、错误处理(静默Promise拒绝)和紧急恢复机制。通过遵循这些指南,开发者可以避免常见的运行时崩溃,构建可靠、可扩展的NovelAI脚本,并高效地集成Lorebook、上下文构建和文本生成功能。该技能的所有详细文档和类型定义均位于references/目录下,并引用了官方类型定义文件(.d.ts)作为真实来源。

兼容平台~Claude Code✓Codex CLI~Cursor
npx skills add MidMeantime/nai-scripting-skill

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

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 的 jsx UI 组件,用于自定义视觉元素(表格、SVG/Canvas 图表、实时小部件):全局变量(h、render、钩子、createStore)、元素/属性白名单、事件、Canvas 以及如何类型检查。仅可选:仅在用户明确要求时使用 JSX。 否则,即使对于复杂布局,也应使用标准组件构建 UI,最多可以提及 JSX 可能有所帮助。
  • 实验性类型 — api.v1.editor.block() 和 api.v1.document.transaction(),受用户 experimentalScriptingApi 设置控制(参见 API 文档第 18 节)。除非用户表示已启用,否则不要使用这些。
  • UI 组件系统 — 如何构建可重用、可扩展的 UI:令牌、样式预设、类型化组件工厂、与存储绑定的字段、渲染/刷新策略、可重用对话框。将此作为任何带 UI 的脚本的默认结构。
  • 脚本头部与配置示例 — 包含每种配置选项类型(字符串、多行字符串、数字、布尔值、选择器)的 /*--- ... ---*/ 脚本头部。
  • 官方示例:https://github.com/NovelAI/novelai-script-examples。*脚本演示* 场景以及类似 查找与替换、笔记 和 智能摘要 的脚本展示了实际模式。

2. 核心架构与运行时环境

  1. 隔离的 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)。
  2. 脚本是暂时的:
    • 当故事切换、用户脚本模态框关闭或脚本被禁用时,脚本可能会被重新加载或卸载。
    • 切勿将持久状态存储在全局内存变量中。 始终使用 api.v1.storage 持久化状态。
  3. 静默的 Promise 拒绝:
    • Worker 中未处理的拒绝 Promise 不会在控制台或 UI 中打印。
    • 始终使用 try/catch 包装异步调用,或附加 .catch((err) => ...) 处理程序。
  4. 紧急恢复:
    • 如果脚本导致不稳定,请在 NovelAI URL 后追加 ?noscript=true 以在加载时禁用脚本执行。

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. 数组 shift 的 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.editorgenerate()、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获取或设置作者注释文本。
api.v1.buildContext()在上下文发送到 AI 之前访问和检查上下文组装。
api.v1.configawait api.v1.config.get('name') 读取脚本头部中声明的用户可配置值(见下文)。
api.v1.permissionshas() / 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 Part 对象 构建。

任何包含 UI 的脚本的默认架构:遵循 NovelAI_UI_Component_System.md。 它提供了一个可重用、可扩展的分层结构,可对照官方类型进行类型检查:

  1. 命名空间和令牌:NS + id()、COLOR 主题令牌映射、SPACE/RADIUS 常量
  2. 使用 sx() 组合的样式预设
  3. 基于 api.v1.ui.part.* 构建的组件工厂,带有选项对象和变体
  4. 保持 storageKey 前缀和存储 API 同步的 field() 绑定
  5. 一个 render(),使用 updateParts/{{templates}} 进行小的刷新
  6. 可重用的 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 Part 中通过展开应用:
{ type: 'container' as const, style: CARD_STYLE, content: [...] } as any

主题令牌

不要硬编码十六进制颜色,而是直接在 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',或提供明确的十六进制/ rgba 颜色字符串(例如 '#90caf9')。

工厂函数(替代 cloneNode / 模板)

在脚本顶部编写返回预先设置样式和配置的 UI Part 对象的工厂函数。这可以避免在整个脚本中重复内联样式对象。

// 可重复使用的带样式按钮工厂
function makeButton(
    text: string,
    onClick: () => Promise<void>,
    options?: { disabled?: boolean; variant?: 'default' | 'danger' }
): any {
    const baseStyle: Record<string, string> = {
        backgroundColor: options?.variant === 'danger' ? 'warning' : 'bg2',
        color: options?.variant === 'danger' ? 'textHeadings' : 'textMain',
        border: '1px solid bg2',
        borderRadius: '4px',
        padding: '6px 12px',
        cursor: options?.disabled ? 'not-allowed' : 'pointer',
    };
    return {
        type: 'button' as const,
        style: baseStyle,
        label: text,
        onClick: async () => {
            if (!options?.disabled) await onClick();
        },
        disabled: options?.disabled ?? false,
    };
}

// 用法:
const myButton = makeButton('点击我', async () => {
    await api.v1.storage.set('clicked', true);
});

7. 错误处理与调试

  • 静默的 Promise 拒绝:始终捕获 Promise 拒绝。
  • 日志记录:使用 api.v1.log(msg) 进行常规日志记录,api.v1.warn(msg) 用于警告,api.v1.error(msg, error) 用于错误。这些会出现在浏览器的开发者工具控制台中。
  • 脚本恢复:如果脚本导致问题,使用 ?noscript=true URL 参数禁用脚本。

8. 补充说明

  • 始终优先使用 api.v1.storage 进行持久化,而不是全局变量。
  • 当两个文档冲突时,.d.ts 类型定义是真实来源。
  • 除非用户明确要求,否则不要使用 JSX。
  • 在调用受限制的 API 之前请求必要的权限。
  • 有关 UI 组件、JSX 或实验性 API 的更多详细信息,请参阅 references/ 目录中的文档。

相关技能