Skip to Content

功能简述

小白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来获得详细的酒馆指令。


应用实践-变量状态栏前端

说明文档并非作者编纂,有可能出现谬误。

前置设置

  1. 思考自己需要的状态栏,如:
{"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>

设置-与酒馆交互

  1. 设计完状态栏后,我需要让它与酒馆交互,获得存储在酒馆内的变量状态,再对应更新状态栏。

注意,await STscript(‘/getvar 状态栏’) 返回的是对象~

用 await STscript('/getvar 状态栏') 读取本地变量。 返回的结构对象示例: { "profile": { "hp": 85, "max_hp": 100, "mood": "警惕" }, "game_time": "清晨", "story_log": "你走进了一片寂静的森林,阳光透过树叶的缝隙洒下斑驳的光影。远处传来一声狼嚎,让你不禁握紧了手中的剑。" } --- 然后更新状态栏