Communitygithub.com

MidMeantime/novel-ai-scripting

Guide complet et référence pour les User Scripts NovelAI, les règles de script TypeScript, la référence API v1, les extensions d'interface utilisateur, les hooks de génération et la configuration de génération de texte. À utiliser lors de l'écriture, du débogage, du refactoring ou de la révision de scripts NovelAI, de l'intégration de Lorebook, de la construction de contexte ou de code TypeScript NovelAI.

Qu'est-ce que novel-ai-scripting ?

Ce guide est une référence exhaustive pour le développement de scripts utilisateur dans l'environnement NovelAI. Il couvre l'architecture d'exécution en Web Worker isolé, les règles strictes de typage TypeScript nécessaires pour éviter les erreurs, et l'ensemble des API `api.v1` (stockage, UI, hooks, document, génération, lorebook, etc.). Le guide détaille également les bonnes pratiques pour la gestion de l'état persistant via `api.v1.storage`, l'enregistrement des hooks de cycle de vie, et la création d'interfaces utilisateur à l'aide de composants déclaratifs. Il est compatible avec tous les scripts NovelAI (`.naiscript`) et inclut des références vers des fichiers de documentation détaillés, des exemples officiels et des définitions de types. En suivant ces instructions, les développeurs peuvent écrire des scripts robustes, maintenables et conformes aux contraintes de l'environnement NovelAI.

Compatible avec~Claude Code✓Codex CLI~Cursor
npx skills add MidMeantime/nai-scripting-skill

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Guide de compétence NovelAI Scripting & TypeScript

Cette compétence fournit des instructions complètes, des références API, des règles de typage et des directives architecturales pour développer des User Scripts NovelAI et interagir avec le moteur de génération de texte NovelAI.


1. Référence rapide et fichiers de documentation

Toute la documentation détaillée et les types se trouvent dans le répertoire references/ :

  • Directives TypeScript — Règles critiques pour éviter les plantages d'exécution dans l'interpréteur Web Worker, les modèles de typage, la gestion des événements et le casting.
  • Documentation complète de l'API Scripting — Référence complète pour tous les espaces de noms api.v1 (Storage, UI, Document, Hooks, Generation, Lorebook, TTS, etc.).
  • Documentation de génération de texte — Contexte sur les fonctionnalités de l'éditeur et du site Web NovelAI (modèles, paramètres de contexte, échantillonneurs, interface utilisateur du lorebook, ATTG, Text Adventure). Rarement nécessaire pour le code de script ; consultez-le lorsqu'un script doit raisonner sur la façon dont l'éditeur construit le contexte ou sur la signification d'un paramètre.
  • Définition des types de script (.d.ts) — Les définitions TypeScript officielles (provenant de https://novelai.net/scripting/types/script-types.d.ts). Ceci est la source de vérité pour les noms de propriétés et les valeurs autorisées. En cas de désaccord avec la documentation en prose, faites confiance aux types.
  • Guide JSX + Types JSX — Parties UI jsx basées sur Preact pour des visuels personnalisés (tableaux, graphiques SVG/canvas, widgets en direct) : globaux (h, render, hooks, createStore), la liste blanche des éléments/attributs, les événements, le canvas et comment effectuer la vérification de type. Sur option uniquement : n'utilisez JSX que lorsque l'utilisateur le demande explicitement. Sinon, construisez l'interface utilisateur à partir de parties standard, même pour des mises en page complexes, et mentionnez tout au plus que JSX pourrait aider.
  • Types expérimentaux — api.v1.editor.block() et api.v1.document.transaction(), protégés par le paramètre experimentalScriptingApi de l'utilisateur (voir section 18 de la documentation de l'API). Ne les utilisez pas à moins que l'utilisateur ne dise qu'il les a activés.
  • Système de composants UI — Comment créer une UI réutilisable et évolutive : tokens, préréglages de style, fabriques de composants typés, champs liés au stockage, stratégie de rendu/rafraîchissement, boîtes de dialogue réutilisables. Utilisez ceci comme structure par défaut pour tout script avec UI.
  • Exemple d'en-tête et de configuration de script — L'en-tête de script /*--- ... ---*/ avec chaque type d'option de configuration (chaîne, chaîne multiligne, nombre, booléen, sélection).
  • Exemples officiels : https://github.com/NovelAI/novelai-script-examples. Le scénario Scripting Demo et les scripts comme Find & Replace, Notes et Smart Summarizer montrent des modèles concrets.

2. Architecture de base et environnement d'exécution

  1. Exécution Web Worker isolée :
    • Les scripts s'exécutent dans un interpréteur JavaScript isolé au sein d'un Web Worker.
    • Les scripts ne peuvent pas accéder à des éléments DOM arbitraires ni effectuer de requêtes réseau brutes.
    • Toutes les interactions avec NovelAI se font via l'interface asynchrone versionnée api.v1.
    • Les setTimeout/clearTimeout globaux ne sont pas disponibles. Utilisez await api.v1.timers.setTimeout(...), api.v1.timers.clearTimeout(...) et api.v1.timers.sleep(ms).
  2. Les scripts sont transitoires :
    • Les scripts peuvent être rechargés ou déchargés lorsque les histoires changent, lorsque la modale User Scripts se ferme ou lorsqu'ils sont activés/désactivés.
    • Ne stockez jamais d'état persistant dans des variables mémoire globales. Persistez toujours l'état à l'aide de api.v1.storage.
  3. Rejets de promesses silencieux :
    • Les promesses rejetées non gérées dans le worker ne s'affichent pas dans la console ou l'interface utilisateur.
    • Enveloppez toujours les appels asynchrones avec try/catch ou attachez des gestionnaires .catch((err) => ...).
  4. Récupération d'urgence :
    • Si un script provoque une instabilité, ajoutez ?noscript=true à l'URL de NovelAI pour désactiver l'exécution du script au chargement.

3. Directives TypeScript critiques pour les scripts NovelAI

L'interpréteur Web Worker nécessite un code TypeScript propre et strictement annoté pour éviter les erreurs de compilation/d'exécution :

A. Annotations de paramètres explicites

Tapez toujours tous les paramètres dans les fonctions standard et les callbacks fléchés :

// ❌ FAUX
function processEntry(id, data) { ... }
items.filter(item => item.active);

// ✅ CORRECT
function processEntry(id: string, data: any): void { ... }
items.filter((item: any) => item.active);

B. Casting d'itération d'objets

Object.entries() et Object.values() doivent être explicitement castés :

// ✅ CORRECT
for (const [key, value] of Object.entries(myObj) as [string, any][]) { ... }
for (const val of Object.values(myObj) as any[]) { ... }

C. Vérifications d'undefined sur les shifts de tableau

Array.prototype.shift() retourne T | undefined :

// ✅ CORRECT
const item = queue.shift();
if (!item) return;

D. Await uniquement dans les contextes asynchrones

Les fonctions de construction UI et les fonctions d'assistance qui invoquent des méthodes API asynchrones doivent être async :

// ✅ CORRECT
async function loadUserData(): Promise<void> {
    const hasData = await api.v1.storage.has('userData');
    if (hasData) {
        const data = await api.v1.storage.get('userData');
    }
}

4. Espaces de noms API clés (api.v1)

Espace de noms / MéthodeObjectif
api.v1.storageStockage persistant clé-valeur : get, getOrDefault, set, setIfAbsent, has, remove, list (ainsi que storyStorage, historyStorage, tempStorage).
api.v1.hooksEnregistrer des hooks de cycle de vie d'événements (onGenerationRequested, onBeforeContextBuild, onContextBuilt, onResponse, onGenerationEnd, onScriptsLoaded, onLorebookEntrySelected, onTextAdventureInput, onHistoryNavigated, onDocumentConvertedToText). Un callback par hook par script ; un nouvel enregistrement remplace le précédent.
api.v1.uiCréer des extensions UI (scriptPanel, sidebarPanel, lorebookPanel, toolbarButton, contextMenuButton, toolboxOption), des modales, des fenêtres, des toasts ; updateParts() pour modifier des parties individuelles par id.
api.v1.documentLire et éditer le document de l'histoire par ID de section (paragraphe) : scan(), sectionIds(), textFromSelection(), append(), appendParagraph(s)(), insertParagraph(s)After(), updateParagraph(s)(), removeParagraph(s)(), ainsi que document.history.
api.v1.editorgenerate(), isBlocked(), selection.get(), et editor.decorations (surlignements, marqueurs, widgets).
api.v1.memory / systemPrompt / prefillObtenir ou définir la Mémoire, le Prompt Système et le Prefill de l'histoire (la définition nécessite storyEdit).
api.v1.generate()Exécuter une génération de texte directe avec des paramètres personnalisés et un contexte.
api.v1.generateWithStory()Exécuter la génération en utilisant le contexte et les paramètres de l'histoire actuelle.
api.v1.lorebookInterroger, mettre à jour, créer ou supprimer des entrées et catégories de Lorebook.
api.v1.anObtenir ou définir le texte de la Note de l'Auteur.
api.v1.buildContext()Accéder et inspecter l'assemblage du contexte avant qu'il ne soit envoyé à l'IA.
api.v1.configawait api.v1.config.get('name') lit une valeur configurable par l'utilisateur déclarée dans l'en-tête du script (voir ci-dessous).
api.v1.permissionshas() / request() / list(). Demandez documentEdit, storyEdit, lorebookEdit, editorDecorations, clipboardWrite, fileInput, fileDownload (et presetEdit) avant d'utiliser les API verrouillées.

En-tête de script et configuration

Chaque .naiscript commence par un en-tête YAML dans un commentaire /*--- ... ---*/ (compatibilityVersion: naiscript-1.0, id, name, version, author, description, memoryLimit, config optionnel). Les entrées config (name, prettyName, type, default, description) apparaissent dans l'onglet Configuration de la modale User Scripts, permettant aux utilisateurs de les modifier sans éditer le code. Les types sont string (éventuellement multiline: true), number (min/max/decimal optionnels), boolean et select (options: [{ label, value }] ; la valeur par défaut est la première option). Voir script_config_examples.ts. Lisez les valeurs à l'exécution avec une valeur de repli :

const openOnCreate: boolean = (await api.v1.config.get('open_on_create')) ?? true;

5. Modèle de script standard

Lors de la création d'un nouveau script NovelAI, suivez cette structure :

(async () => {
    try {
        const STORAGE_KEY = "my_custom_script_data";

        // 1. Initialiser l'état persistant
        const state = await api.v1.storage.getOrDefault(STORAGE_KEY, { enabled: true, count: 0 });
        await api.v1.storage.setIfAbsent(STORAGE_KEY, state);

        // 2. Enregistrer les hooks de cycle de vie
        // onGenerationRequested s'exécute avant la construction du contexte ; il peut seulement arrêter la génération ou préparer l'état.
        api.v1.hooks.register('onGenerationRequested', async (params: any) => {
            if (!state.enabled) return;
            // Préparez l'état pour les hooks ultérieurs ici, ou retournez { stopGeneration: true } pour le bloquer
        });

        // Pour modifier ce que le modèle voit, utilisez onBeforeContextBuild (mémoire, lorebook, AN...)
        // ou onContextBuilt (retournez { messages } avec le tableau de messages édité).
        api.v1.hooks.register('onContextBuilt', async (params: any) => {
            if (!state.enabled || params.dryRun) return;
            return { messages: params.messages };
        });

        // 3. Enregistrer l'interface utilisateur — utilisez toujours update(), PAS register()
        api.v1.hooks.register('onScriptsLoaded', async () => {
            await api.v1.ui.update([{
                type: 'sidebarPanel' as const,
                id: 'my-script-panel',
                name: 'Outil personnalisé',
                content: [
                    { type: 'text' as const, text: `Statut : ${state.enabled ? 'Actif' : 'Désactivé'}` } as any
                ]
            }]);
        });

    } catch (err: any) {
        api.v1.error("Erreur d'initialisation du script :", err);
    }
})();

6. Style, tokens de thème et fonctions de fabrique

Les scripts NovelAI s'exécutent dans un Web Worker isolé sans accès au DOM. N'utilisez jamais document.createElement, document.head, cloneNode ou toute API navigateur qui touche directement la page. Toute l'interface utilisateur est construite à partir d'objets Partie UI déclaratifs passés à api.v1.ui.update().

Architecture par défaut pour tout script avec UI : suivez NovelAI_UI_Component_System.md. Il propose une couche réutilisable et évolutive qui effectue une vérification de type par rapport aux types officiels :

  1. Espace de noms et tokens : NS + id(), COLOR carte de tokens de thème, constantes SPACE/RADIUS
  2. Préréglages de style combinés avec sx()
  3. Fabriques de composants basées sur api.v1.ui.part.* avec des objets d'options et des variantes
  4. Liaisons field() qui maintiennent les préfixes storageKey et les API de stockage synchronisés
  5. Un render(), avec updateParts/{{templates}} pour les petits rafraîchissements
  6. Boîtes de dialogue confirm()/notify() réutilisables

Les extraits ci-dessous sont la version minimale de la même idée. Si l'utilisateur demande JSX, consultez NovelAI_JSX_Guide.md. Les styles JSX utilisent du CSS normal et var(--theme-*), pas des noms de tokens. Ne passez pas à JSX de votre propre initiative.

Préréglages d'objets de style

Définissez des objets de style typés en haut de votre script et étalez-les sur les parties UI :

const CARD_STYLE: Record<string, string> = {
    backgroundColor: 'bg1',      // token de thème
    border: '1px solid bg2',     // token de thème
    borderRadius: '6px',
    padding: '10px',
    margin: '6px 0',
};

const DANGER_STYLE: Record<string, string> = {
    ...CARD_STYLE,
    borderColor: 'warning',
};

// Application via spread dans une Partie UI :
{ type: 'container' as const, style: CARD_STYLE, content: [...] } as any

Tokens de thème

Au lieu de coder en dur des couleurs hexadécimales, utilisez les chaînes de tokens de thème NovelAI directement comme valeurs dans les objets style. Au moment du rendu, chaque token est remplacé par la valeur du thème actif de l'utilisateur sous forme de valeur en ligne littérale (par exemple 'bg1' devient un code rgb(...)). Ce ne sont pas des variables CSS : il n'y a pas de propriétés personnalisées --theme-* à utiliser avec var(), et le CSS personnalisé ailleurs sur le site ne peut pas les référencer. Pour les valeurs brutes du thème à l'intérieur d'un script, utilisez api.v1.theme.get(). (Exception : selon les types officiels, les parties UI JSX obtiennent des propriétés personnalisées --theme-* dans leur propre shadow DOM.)

TokenRôle
'bg0'Arrière-plan le plus profond / le plus extérieur
'bg1'Arrière-plan de panneau par défaut
'bg2'Surface surélevée / bordure / arrière-plan de bouton
'bg3'Surface surlignée / sélectionnée
'textHeadings'Titres et texte de titre à contraste élevé
'textMain'Texte de corps par défaut
'textDisabled'Texte désactivé / atténué
'textPlaceholder'Texte d'espace réservé d'entrée
'textHighlight'Couleur de recherche / surlignage
'warning'État d'avertissement / d'alerte
'textPrompt', 'textUser', 'textAI', 'textEdit'Couleurs du texte de l'histoire
'lowIntensityColor', 'midIntensityColor', 'highIntensityColor'Couleurs de probabilité basse/moyenne/élevée (telles qu'utilisées par les probabilités de token)
'default'Famille de police du corps (à utiliser dans fontFamily)
'headings'Famille de police des titres (à utiliser dans fontFamily)
'code'Famille de police de code monospace
'field'Famille de police du champ de formulaire / d'entrée

⚠️ Note sur 'primary' : NovelAI n'a PAS de token de thème 'primary' ou 'error'. L'utilisation de color: 'primary' produit un CSS invalide que les navigateurs ignorent. Pour les surfaces actives/surlignées, utilisez 'bg3', pour les accents à contraste élevé, utilisez 'textHeadings', ou fournissez des chaînes de couleur hexadécimales/rgba explicites (par exemple '#90caf9').

Fonctions de fabrique (remplace cloneNode / Templates)

Écrivez des fonctions de fabrique en haut de votre script qui retournent des objets Partie UI pré-stylés et pré-configurés. Cela évite de répéter les objets de style en ligne dans tout le script.

// Fabrique de boutons stylisés réutilisable
function makeButton(
    text: string,
    onClick: () => Promise<void>

...

Skills associés