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 dehttps://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
jsxbasé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()etapi.v1.document.transaction(), protégés par le paramètreexperimentalScriptingApide 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
- 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/clearTimeoutglobaux ne sont pas disponibles. Utilisezawait api.v1.timers.setTimeout(...),api.v1.timers.clearTimeout(...)etapi.v1.timers.sleep(ms).
- 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.
- 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/catchou attachez des gestionnaires.catch((err) => ...).
- 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.
- Si un script provoque une instabilité, ajoutez
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éthode | Objectif |
|---|---|
api.v1.storage | Stockage persistant clé-valeur : get, getOrDefault, set, setIfAbsent, has, remove, list (ainsi que storyStorage, historyStorage, tempStorage). |
api.v1.hooks | Enregistrer 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.ui | Cré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.document | Lire 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.editor | generate(), isBlocked(), selection.get(), et editor.decorations (surlignements, marqueurs, widgets). |
api.v1.memory / systemPrompt / prefill | Obtenir 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.lorebook | Interroger, mettre à jour, créer ou supprimer des entrées et catégories de Lorebook. |
api.v1.an | Obtenir 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.config | await 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.permissions | has() / 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 :
- Espace de noms et tokens :
NS+id(),COLORcarte de tokens de thème, constantesSPACE/RADIUS- Préréglages de style combinés avec
sx()- Fabriques de composants basées sur
api.v1.ui.part.*avec des objets d'options et des variantes- Liaisons
field()qui maintiennent les préfixesstorageKeyet les API de stockage synchronisés- Un
render(), avecupdateParts/{{templates}}pour les petits rafraîchissements- Boîtes de dialogue
confirm()/notify()réutilisablesLes 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.)
| Token | Rô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 decolor: '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>
...