SillyTavern Development Expert
Expert guidance for developing frontend interfaces and background scripts for SillyTavern using the Tavern Helper framework.
When to Use This Skill
Use this skill when users request:
- ✅ Frontend interfaces - Interactive UI in message floors (status bars, game systems, multimedia displays)
- ✅ Background scripts - Automation, chat enhancement, external service integration
- ✅ Character card features - Enhanced gameplay, visual effects, data visualization
- ✅ MVU variable framework - Message floor variable management and display
- ✅ SillyTavern UX improvements - UI tweaks, workflow automation, custom tools
Core Concepts
Project Types
Projects live in src/项目名/ folders:
Frontend Interface (index.ts + index.html):
- Runs as sandboxless iframe in message floor (foreground)
- Has visual UI with styles and interactions
- Preferred stack: Vue 3 + Pinia + Vue Router
Script (index.ts only):
- Runs as sandboxless iframe in background
- No visual UI, code-only
- jQuery operates on parent SillyTavern page:
window.$ = window.parent.$
Data Persistence: Tavern Variables
Use getVariables() / replaceVariables() for persistent storage:
global- Shared across entire taverncharacter- Bound to specific character cardchat- Bound to specific chat filemessage- Bound to specific message floorscript- Bound to specific scriptextension- Bound to specific extension
MVU Variable Framework
Separate Tavern Helper script enhancing message floor variables:
- Set variables in world info
- Auto-update variables from AI output
- Provides
display_dataanddelta_datafor visualization - Must initialize:
await waitGlobalInitialized('Mvu')
See references/mvu_framework.md for detailed API.
Quick Start
1. Choose Project Type
Determine frontend interface or script based on whether UI is needed.
2. Select Tech Stack
For frontend interfaces:
- Simple UI: Vue 3 + Tailwind CSS
- Complex game: PixiJS + @pixi/react
- React preference: React + hooks
For scripts:
- DOM manipulation: jQuery + jQueryUI
- State management: Pinia + Zod
- Animations: GSAP
See references/tech_stack.md for full dependency list and usage patterns.
3. Development Workflow
# Install dependencies (pnpm only)
pnpm install
# Development build
pnpm run build:dev
# Watch mode (auto-rebuild)
pnpm run watch
# Production build
pnpm run build
4. Live Preview
Option 1: Soft link (recommended)
ln -s /path/to/dist/项目名/index.html /root/web-dev/preview.html
Option 2: Copy
cp /path/to/dist/项目名/index.html /root/web-dev/
Access: https://dev.piepia.space/preview.html
Use browsermcp to test in real browser environment.
Development Best Practices
TypeScript Required
// ✅ Correct: TypeScript with Zod validation
import { z } from 'zod';
const Settings = z.object({
volume: z.number().min(0).max(1).default(0.5),
autoPlay: z.boolean().default(true),
}).prefault({});
type Settings = z.infer<typeof Settings>;
Use Third-Party Libraries
// ✅ jQuery for DOM
$('#button').on('click', handler);
// ✅ GSAP for animations (including typewriter)
gsap.to('#text', {
duration: 2,
text: { value: "New text", delimiter: "" },
ease: "none"
});
// ✅ Zod for validation
const result = Schema.safeParse(data);
if (!result.success) {
console.error(z.prettifyError(result.error));
}
Correct Loading/Unloading Timing
// ✅ Correct: jQuery ready handler
$(() => {
toastr.success('Loaded');
// Initialization logic...
});
// ✅ Correct: Cleanup on unload
$(window).on('pagehide', () => {
toastr.info('Unloaded');
// Cleanup: remove listeners, stop timers...
});
// ❌ Wrong: DOMContentLoaded doesn't fire on network load
document.addEventListener('DOMContentLoaded', () => {
// Won't execute when loaded via $('body').load(url)!
});
Reload on Chat Change
let chat_id = SillyTavern.getCurrentChatId();
eventOn(tavern_events.CHAT_CHANGED, new_chat_id => {
if (chat_id !== new_chat_id) {
chat_id = new_chat_id;
window.location.reload();
}
});
Frontend Interface Development
index.html Rules
<head>
<!-- Keep empty - webpack auto-injects styles/scripts -->
</head>
<body>
<!-- Static content only -->
<div id="app"></div>
<!-- ❌ NO <link rel="stylesheet" href="./index.css"> -->
<!-- ❌ NO <script src="./index.ts"> -->
<!-- ❌ NO <img src=""> empty placeholders -->
</body>
Styling Approaches
// Method 1: Tailwind CSS (simple styles)
import './tailwind.css'; // Content: @import 'tailwindcss';
// Method 2: Vue component styles (recommended)
// Component.vue
<template>...</template>
<style lang="scss" scoped>
.my-class { color: red; }
</style>
// Method 3: Global SCSS
import './index.scss';
iframe Adaptation
// ✅ Use width + aspect-ratio (NOT vh)
.container {
width: 100%;
aspect-ratio: 16 / 9;
}
// ❌ Avoid forcing parent height
.bad {
min-height: 500px; // Not recommended
overflow: auto; // May cause scrollbars
}
// ✅ Fit container width, no horizontal scroll
body {
max-width: 100%;
overflow-x: hidden;
}
// ✅ Card style: transparent background (unless explicitly requested)
.card {
background: transparent;
}
Vue Development Template
See references/vue_template.md for complete Vue 3 + Pinia + Vue Router setup.
PixiJS Multimedia Interface
See references/pixijs_usage.md for game-like interfaces with heavy multimedia assets.
Script Development
jQuery Scope
// ⚠️ $ operates on SillyTavern page, not iframe
$('body'); // Selects SillyTavern <body>
// Select iframe elements
$('body', document); // document = script iframe
Using Vue in Scripts
// 1. Create mount point with jQuery
const $app = $('<div>').attr('id', 'my-app').appendTo('body');
// 2. Create Vue app
const app = createApp(App);
app.use(createPinia());
// 3. Mount to jQuery element
app.mount($app[0]); // Note [0] to get DOM element
// 4. Cleanup
$(window).on('pagehide', () => {
app.unmount();
$app.remove();
});
Teleporting Styles to SillyTavern Page
// Script styles only apply to iframe - manually teleport to parent
export function teleport_style() {
$(`<div>`)
.attr('script_id', getScriptId())
.append($(`head > style`, document).clone())
.appendTo('head'); // Appends to SillyTavern <head>
}
export function deteleport_style() {
$(`head > div[script_id="${getScriptId()}"]`).remove();
}
$(() => teleport_style());
$(window).on('pagehide', () => deteleport_style());
Script Settings UI
See references/script_patterns.md for Pinia + Zod settings management pattern.
Registering Script Buttons
// Triggered when user clicks button in Tavern Helper script library
eventOn(getButtonEvent('Reload'), () => {
window.location.reload();
});
eventOn(getButtonEvent('Clear Data'), () => {
if (confirm('Clear all data?')) {
replaceVariables({}, { type: 'script', script_id: getScriptId() });
toastr.success('Data cleared');
}
});
API Priority
Priority order: Tavern Helper API > STScript Commands > SillyTavern Native API
// ❌ Don't: STScript
await triggerSlash('/setvar key=score 100');
const score = await triggerSlash('/getvar score');
// ✅ Do: Tavern Helper API
replaceVariables({ score: 100 }, { type: 'chat' });
const score = getVariables({ type: 'chat' }).score;
// ✅ Do: toastr instead of /echo
toastr.success('Hello');
// ✅ Do: Tavern Helper functions
const messages = getChatMessages(); // Instead of SillyTavern.chat
replaceWorldbook(data); // Instead of /setentryfield
Reason: Tavern Helper API provides:
- ✅ Type safety (full TypeScript support)
- ✅ Higher abstraction (semantic operations)
- ✅ Better error handling
- ✅ Better performance (less string parsing)
Resources
references/
Detailed documentation loaded as needed:
tech_stack.md- Complete dependency list and usage patternsapi_reference.md- Tavern Helper API documentationstscript_commands.md- All 264 STScript commands with examplestype_definitions.md- TypeScript type system overviewvue_template.md- Vue 3 + Pinia + Router setupscript_patterns.md- Common script development patternspixijs_usage.md- PixiJS multimedia interface guidemvu_framework.md- MVU variable framework APIbest_practices.md- Development do's and don'ts
assets/
Template files for quick project initialization:
frontend-template/- Vue 3 frontend interface boilerplatescript-template/- Background script boilerplatetailwind.css- Tailwind CSS import file
Common Patterns
Reactive Data + Tavern Variable Sync
const data = ref({ score: 0, hp: 100 });
// Load initial values
data.value = getVariables({ type: 'chat' });
// Auto-save (remove Vue Proxy with klona)
watchEffect(() => {
replaceVariables(klona(data.value), { type: 'chat' });
});
Listening to Tavern Events
import { tavern_events, eventOn } from '@types/iframe/event';
// AI message received
eventOn(tavern_events.MESSAGE_RECEIVED, (message_id) => {
console.log('New message', message_id);
});
// Message updated
eventOn(tavern_events.MESSAGE_UPDATED, (message_id) => {
console.log('Message updated', message_id);
});
Calling LLM
// Simple generation (with chat history + character card)
const result = await generate({
prompt: 'Summarize the conversation',
quietPrompt: 'Summarize in 50 words or less',
});
// Raw generation (no chat history)
const result = await triggerSlash(`
/genraw instruct=off Why is the sky blue?
`);
Error Handling with Zod
const UserInput = z.object({
name: z.string().min(1),
age: z.number().int().positive(),
});
const result = UserInput.safeParse(data);
if (!result.success) {
toastr.error(z.prettifyError(result.error));
return;
}
const { name, age } = result.data; // Type-safe
Forbidden Practices
❌ Node.js libraries (fs, path, http, etc.)
❌ <link> / <script> tags in index.html for local files
❌ DOMContentLoaded for load timing
❌ vh units in frontend interface styles (use aspect-ratio)
❌ Direct code execution in global scope (use $(() => {}))
❌ Saving Vue reactive data directly (use klona() first)
Output Requirements
When developing features for users:
- Confirm project type - Frontend interface or script?
- Choose tech stack - Vue (recommended) / React / PixiJS?
- Data persistence plan - Which variable type?
- Provide complete code:
- Clear TypeScript types
- Use recommended libraries
- Follow best practices
- Include error handling
- Test with preview - Build and preview on dev.piepia.space via browsermcp
- Code documentation - Comment key logic, explain special usage
Working Directory
Current directory: /root/tavern_helper_template
Scan to join WeChat group