功能简述
小白X的代码渲染功能可将聊天中包含HTML标签(完整的<html>, <!DOCTYPE>或单独的<script>)的代码块将其渲染为可交互iframe,iframe高度为自适应。
- HTML 内容需要在 ``` 包裹的代码块中,并包含
<html>、<!DOCTYPE>或<script>标签才会被渲染。
启用功能
在酒馆中打开 扩展设置 → 小白X扩展 → 小白X模板 → 勾选开启总开关及渲染;
STscript函数
我们可以使用STscript()异步函数来调用SillyTavern的Slash命令:
STscript()是一个异步函数,调用await STscript('/echo Hello World');将可以在你的可交互介面中使用酒馆命令与酒馆进行互动。- 除非启用封装函数功能,否则小白X提供的自定义函数就只有
STscript() - 插件将自动解析iframe内,STscript命令的管道输出(pipe值),请不要再JSON.parse
基本用法
<!DOCTYPE html>
<html>
<head>
<title>交互式界面</title>
<style>
body { font-family: Arial; padding: 10px; color: white; background: black;}
button { margin: 5px; }
</style>
</head>
<body>
<h3>简单的示例</h3>
<button onclick="check()">点击我</button>
<script>
async function check() {
await STscript('/echo HelloWorld!');
}
</script>
</body>
</html>Slash Command的参考
在酒馆里的聊天框输入/help slash和/help macros来获得详细的酒馆指令。
应用实践-变量状态栏前端
说明文档并非作者编纂,有可能出现谬误。
前置设置
- 思考自己需要的状态栏,如:
{"profile":{"hp":45,"max_hp":100,"mood":"疲惫"},"game_time":"深夜","story_log":"激战过后,你拖着伤痕累累的身体来到一处废弃的神庙。月光下,石柱的影子如同巨兽般扭曲,空气中弥漫着不祥的气息。你需要尽快找到地方休息。"}那样的话,我就需要先输入指令到酒馆输入框里面,或者在循环任务里设置角色初始化的任务。
/setvar key=状态栏 {"profile":{"hp":45,"max_hp":100,"mood":"疲惫"},"game_time":"深夜","story_log":"激战过后,你拖着伤痕累累的身体来到一处废弃的神庙。月光下,石柱的影子如同巨兽般扭曲,空气中弥漫着不祥的气息。你需要尽快找到地方休息。"}设计前端
想好状态栏包含什么之后,我需要设计一个状态栏的前端。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>现代轻色状态栏</title>
<style>
:root{
--bg: #f7f9fc;
--card: #ffffff;
--text: #2b2f36;
--muted: #8a93a3;
--line: #e9eef5;
--accent: #74b9ff;
--accent-2: #a5d8ff;
--hp: #ff7675;
--hp-soft: #ffd7d6;
--good: #2ecc71;
--warn: #f1c40f;
}
*{ box-sizing: border-box; }
body{
margin:0; padding:24px;
background: radial-gradient(1200px 800px at 10% -10%, #f0f6ff 0%, var(--bg) 35%, var(--bg) 100%);
font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Noto Sans SC", "Microsoft Yahei", sans-serif;
color: var(--text);
}
.hud{
max-width: 920px;
margin: 0 auto;
background: var(--card);
border-radius: 16px;
box-shadow: 0 10px 30px rgba(16, 24, 40, 0.08);
border: 1px solid var(--line);
overflow: hidden;
}
.hud__top{
display: grid;
grid-template-columns: 1.2fr 1fr 1fr;
gap: 16px;
padding: 18px 18px 14px;
align-items: center;
}
@media (max-width: 720px){
.hud__top{
grid-template-columns: 1fr;
}
}
.meta{
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
}
.meta__icon{
width: 28px; height: 28px;
display: grid; place-items: center;
border-radius: 9px;
background: linear-gradient(180deg, #ffffff, #f4f7fb);
border: 1px solid var(--line);
box-shadow: 0 2px 8px rgba(16, 24, 40, 0.06) inset;
}
.meta__icon svg{ width: 16px; height: 16px; }
.meta__text{
display: grid;
gap: 4px;
min-width: 0;
}
.meta__label{
font-size: 12px; line-height: 1; color: var(--muted); letter-spacing: .2px;
}
.meta__value{
font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* HP block */
.hp{
display: grid;
gap: 8px;
width: 100%;
}
.hp__row{
display: flex; align-items: center; gap: 12px; min-width: 0;
}
.hpbar{
position: relative;
width: 100%;
height: 10px;
background: linear-gradient(180deg, #f5f7fb, #eef2f7);
border: 1px solid var(--line);
border-radius: 999px;
overflow: hidden;
}
.hpbar__fill{
position: absolute; inset: 0;
width: 0%; /* JS sets */
background: linear-gradient(90deg, var(--hp) 0%, #ff9a8c 100%);
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.03);
transition: width .4s ease;
}
.hpbar.indeterminate .hpbar__fill{
width: 40%;
background: repeating-linear-gradient(
135deg,
var(--hp-soft) 0 10px,
#ffe9e8 10px 20px
);
animation: slide 1.2s linear infinite;
box-shadow: none;
}
@keyframes slide{
from{ transform: translateX(-40%); }
to{ transform: translateX(120%); }
}
.divider{
height: 1px; background: var(--line);
margin: 0 18px;
}
.story{
padding: 14px 18px 18px;
display: grid; gap: 8px;
background: linear-gradient(180deg, #ffffff 0%, #fbfdff 100%);
}
.story__label{ font-size: 12px; color: var(--muted); letter-spacing: .2px; }
.story__text{
font-size: 14px; line-height: 1.6;
color: #3a3f47;
}
.pill{
display: inline-flex; align-items: center; gap: 6px;
padding: 6px 10px; border-radius: 999px;
background: #f5f8ff; border: 1px solid #e6eefc;
color: #2b4c7e; font-weight: 600; font-size: 12px;
}
/* Icon colors */
.icon-heart{ color: var(--hp); }
.icon-sun{ color: #f39c12; }
.icon-eye{ color: #6c7fd1; }
</style>
</head>
<body>
<div className="hud" id="hud">
<div className="hud__top">
<!-- HP -->
<div className="hp">
<div className="hp__row">
<div className="meta__icon" aria-hidden="true">
<svg className="icon-heart" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 21s-6.716-4.332-9.428-7.043C.858 12.244 1 9.5 3.172 7.758 5.343 6.016 8.1 6.6 9.6 8.4L12 11l2.4-2.6c1.5-1.8 4.257-2.384 6.428-.642C23 9.5 23.142 12.244 21.428 13.957 18.716 16.668 12 21 12 21z"/>
</svg>
</div>
<div className="meta__text">
<div className="meta__label">体力</div>
<div className="meta__value" id="hpText">-- / --</div>
</div>
</div>
<div className="hpbar" id="hpBar" role="progressbar" aria-label="体力">
<div className="hpbar__fill"></div>
</div>
</div>
<!-- 时间 -->
<div className="meta">
<div className="meta__icon" aria-hidden="true">
<svg className="icon-sun" viewBox="0 0 24 24" fill="currentColor">
<path d="M6.76 4.84l-1.8-1.79-1.41 1.41 1.79 1.8 1.42-1.42zm10.45 14.32l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4zM12 4V1h-1v3h1zm0 19v-3h-1v3h1zm8-8h3v-1h-3v1zM1 12h3v-1H1v1zm16.24-7.16l1.42 1.42 1.79-1.8-1.41-1.41-1.8 1.79zM4.84 17.24l-1.79 1.8 1.41 1.41 1.8-1.79-1.42-1.42zM12 7a5 5 0 100 10 5 5 0 000-10z"/>
</svg>
</div>
<div className="meta__text">
<div className="meta__label">时间</div>
<div className="meta__value"><span className="pill" id="timeText">--</span></div>
</div>
</div>
<!-- 心情 -->
<div className="meta">
<div className="meta__icon" aria-hidden="true">
<svg className="icon-eye" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 5C7 5 2.73 8.11 1 12c1.73 3.89 6 7 11 7s9.27-3.11 11-7c-1.73-3.89-6-7-11-7zm0 12a5 5 0 110-10 5 5 0 010 10zm0-3a2 2 0 100-4 2 2 0 000 4z"/>
</svg>
</div>
<div className="meta__text">
<div className="meta__label">心情</div>
<div className="meta__value" id="moodText">--</div>
</div>
</div>
</div>
<div className="divider"></div>
<div className="story">
<div className="story__label">故事记录</div>
<div className="story__text" id="storyText">...</div>
</div>
</div>
<script>
// 数据
const state = {
profile: {
hp: 85,
max_hp: 00,
mood: "警惕"
},
game_time: "清晨",
story_log: "你走进了一片寂静的森林,阳光透过树叶的缝隙洒下斑驳的光影。远处传来一声狼嚎,让你不禁握紧了手中的剑。"
};
// 绑定
const hpText = document.getElementById('hpText');
const hpBar = document.getElementById('hpBar');
const hpFill = hpBar.querySelector('.hpbar__fill');
const timeText = document.getElementById('timeText');
const moodText = document.getElementById('moodText');
const storyText = document.getElementById('storyText');
function renderHUD(data){
const { profile, game_time, story_log } = data;
const hp = Number(profile.hp ?? 0);
const max = Number(profile.max_hp ?? 0);
// 文本
const maxDisplay = Number.isFinite(max) ? String(max).padStart(2, '0') : '--';
hpText.textContent = `${hp} / ${maxDisplay}`;
timeText.textContent = game_time || '--';
moodText.textContent = profile.mood || '--';
storyText.textContent = story_log || '';
// 进度条
if (max > 0){
const pct = Math.max(0, Math.min(100, (hp / max) * 100));
hpBar.classList.remove('indeterminate');
hpFill.style.width = pct + '%';
hpBar.setAttribute('aria-valuemin', '0');
hpBar.setAttribute('aria-valuemax', String(max));
hpBar.setAttribute('aria-valuenow', String(Math.max(0, Math.min(hp, max))));
hpBar.removeAttribute('aria-busy');
} else {
// 未知/无上限 -> 不确定动画
hpBar.classList.add('indeterminate');
hpFill.style.width = '40%';
hpBar.removeAttribute('aria-valuemin');
hpBar.removeAttribute('aria-valuemax');
hpBar.removeAttribute('aria-valuenow');
hpBar.setAttribute('aria-busy', 'true');
}
}
renderHUD(state);
</script>
</body>
</html>设置-与酒馆交互
- 设计完状态栏后,我需要让它与酒馆交互,获得存储在酒馆内的变量状态,再对应更新状态栏。
提示词
注意,await STscript(‘/getvar 状态栏’) 返回的是对象~
用 await STscript('/getvar 状态栏') 读取本地变量。
返回的结构对象示例:
{
"profile": {
"hp": 85,
"max_hp": 100,
"mood": "警惕"
},
"game_time": "清晨",
"story_log": "你走进了一片寂静的森林,阳光透过树叶的缝隙洒下斑驳的光影。远处传来一声狼嚎,让你不禁握紧了手中的剑。"
}
---
然后更新状态栏