🧩 EJS 模板

Since v0.87.0

📖 概述

EJS 模板让你在生成提示词的所有字段——角色卡(描述、性格、场景、开场白等)、预设、世界书、正则——里嵌入 <% %> 语法,写条件判断、循环、读写变量,做宏(Macros)做不到的动态逻辑。

  • 和宏的关系:两者作用于相同的字段。宏({{char}}{{setvar}} 等)擅长简单注入;EJS 擅长逻辑、变量运算、循环。
  • 渲染顺序:先渲染 EJS,再把结果交给 {{}} 宏。也就是说 EJS 的输出里还能包含宏,会被后续宏引擎继续处理。
  • 语法基于 EJS:Tavo 内置了一套 EJS 引擎,支持其中最常用的子集(标签语法 + 变量/逻辑),但不含 include / partial / 自定义分隔符等高级特性。

⚙️ 需要先开启

EJS 默认开启。如未生效,检查 设置 → 聊天设置 → 兼容性 → 启用 EJS 模板(位于该分组第一项)。

🚀 快速上手

在任意提示词字段(最直观的是角色开场白)粘贴:

<% if (getvar("vip", "no") === "no") { %>欢迎,访客。<% } else { %>欢迎回来,尊贵的会员!<% } %>

未设置 vip 变量时渲染为:欢迎,访客。

🏷️ 标签语法

写法作用例子 → 输出
<%- 表达式 %>输出(原样,不转义)<%- "1<2" %>1<2
<%= 表达式 %>输出(HTML 转义)<%= "1<2" %>1&lt;2
<% 语句 %>执行逻辑,不输出<% var n = 1 %> → (无)
<%# 注释 %>注释,不执行不输出a<%# 备注 %>bab
print(x)在逻辑块里输出<% print("hi") %>hi
<%% %%>输出字面量标签<%% raw %%><% raw %>
-%>删除结束标签后紧跟的换行合并上下两行
<#escape-ejs>…</#escape-ejs>范围内的标签当字面文本<#escape-ejs><% x %></#escape-ejs><% x %>

循环示例:

<% for (var i = 1; i <= 3; i++) { %><%= i %><% } %>

输出:123

🔣 变量读写

Tavo 提供一组变量函数,桥接到内置的两层变量存储。

函数说明
getvar(key)读变量,不存在返回空串
getvar(key, 默认值)不存在时返回默认值
getvar(key, {scope, defaults})指定层级读取 / 默认值
setvar(key, value, {scope})写变量(原样存,不解析类型
incvar(key, n=1, {scope})自增(默认 +1)
decvar(key, n=1, {scope})自减(默认 -1)
delvar(key, {scope})删除变量
<% setvar("hp", 100) %>HP:<%- getvar("hp") %>          → HP:100
<% setvar("code", "007") %>编号:<%- getvar("code") %>  → 编号:007(保留前导零)
<% incvar("n") %><% incvar("n") %>n=<%- getvar("n") %>  → n=2
<%- getvar("missing", "缺省值") %>                       → 缺省值

键支持点路径,可直接读写嵌套(无需先取出对象):

<% setvar("o.a.b", 42) %><%- getvar("o.a.b") %>   → 42

作用域 scope

  • chat(默认):当前会话变量,随会话保存。(兼容 local )。
  • global:全局变量,写入即时持久化。
  • 不带 scope 时,getvar 会先找 chat 然后 global
  • getvar 使用 cache 与不带一致;message / initialchat 一致
  • setvar 如果不为 global 统一做 chat 处理。
<% setvar("g", 1, {scope: "global"}) %><%- getvar("g", {scope: "global"}) %>

lodash 风格的 __.get(obj, path, 默认值)_.has(obj, path)_.set(obj, path, value)_.unset(obj, path)_.cloneDeep(obj)。路径支持 a.b[0].ca.b.0.c 两种写法。

<% setvar("o", {a: {b: 42}}) %><%- _.get(getvar("o"), "a.b") %>   → 42

🧩 内置常量

以下常量已注入为全局变量,直接使用即可。

常量含义
charName当前角色名
userName当前 persona 名
lastUserMessage最近一条非隐藏用户消息的原文
lastCharMessage最近一条非隐藏角色消息的原文
characterId当前角色 ID
你好,我是 <%- charName %>。   → 你好,我是 <角色名>。

常见用法

  • characterId:群聊里按角色分支,或做「每角色独立变量」命名空间,如 getvar("affinity_" + characterId)
  • lastUserMessage:依据用户刚说的话做条件注入(检测到关键词就追加提示),或喂给后续逻辑 / 正则。
  • lastCharMessage:基于角色上一条回复做衔接、状态更新、或避免重复。

📍 能用在哪里

EJS 在提示词组装时统一渲染,覆盖:

  • 角色卡:描述 / 性格 / 场景 / 开场白(含备用开场白)/ 对话示例 / 系统提示等
  • 世界书:条目内容、扫描关键词
  • 预设:各 prompt 段
  • 正则:匹配 / 替换 / 裁剪字符串
  • 其它:翻译、图像描述注入、群聊等

⚠️ 注意事项

整模板级兜底

同一字段内任意一个 EJS 标签报错(语法错误、调用了不存在的方法等),整段会原样回退、不渲染,以保证不崩、不丢内容。因此调试时若发现「标签没生效、显示的全是原文」,多半是该字段里某处 EJS 写错了。也因此,不要把「故意写错 / 演示用」的坏标签和正常标签混在同一字段

  • 与宏的顺序:先 EJS、后 {{}} 宏,EJS 的输出可继续包含宏。例如 <%- "{{char}}" %> 先被 EJS 输出为 {{char}},再由宏引擎替换成角色名。
  • 转义显示<%= %> 产出 HTML 实体(如 &lt;);若聊天开启了高级前端渲染,实体可能被二次渲染。要核对原文请查看上下文日志。
  • 与官方 EJS 的差异:仅支持常用子集,不含 include / partial / 自定义分隔符等。

🔗 相关

目录