进阶技巧#

请选择你感兴趣的部分进行阅读.

自定义项目配置#

在 figma 中用预制组件像做 PPT 一样设计界面

  • .cursor/rules 中, 我预先为项目配置了一些编程助手编写规则 (相当于给编程助手添加了一个全局世界书). 你完全可以自行编写更多规则

  • .cursor/mcp.json 中, 我预先设置了 Browser MCP 来让 AI 能够查看酒馆网页. 你完全可以为 AI 找更多好用的 MCP, 如通过 figma MCP, 你可以用大量预制好的组件像做 PPT 一样设计好界面, 然后让 AI 生成代码结果.

  • package.json 中, 我预先为项目代码添加了 jquery、zod 等方便的第三方库 (具体请查看 dependencies 部分). 你可以让 AI 或自己用 pnpm add 第三方库 添加更多需要的第三方库, 它们一般添加上就能直接使用

用 figma 设计界面#

除了上面提到 figma 允许你用大量预制好的组件像做 PPT 一样进行界面设计外, figma 也自带 AI 功能. 它是专业的前端原型设计软件, 因此用它的 AI 设计出的界面一般比其他 AI 好看许多. 具体配置方法请自行研究.

常用资源网站#

免费字体#

你可以从 ZeoSeven Fonts 中找到很多免费字体.

Sarasa Gothic 更纱黑体 Mono 为例, 我们搜索到它, 进入它的页面, 点击右上角的嵌入到 Web 项目即会跳转到对应内容, 选择常规 CSS 然后点击复制即可.

../../../../_images/%E5%85%8D%E8%B4%B9%E5%AD%97%E4%BD%93.png

免费图标#

你可以从 FontAwesome 中找到很多免费图标. 它们在前端界面中直接可用.

免费 CDN#

jsDelivr 为所有 npm 库和 github 文件提供了 CDN 支持, 你可以直接在脚本或界面中引用它们.

需要注意的是, 你应该用 https://testingcf.jsdelivr.net 这个国内也能访问的镜像, 而不是直接用 https://cdn.jsdelivr.net.

提示

如果是需要 npm 第三方库, 推荐直接通过 pnpm add 第三方库 来添加它们, 模板文件夹已经配置好会在打包时将第三方库转换为 jsDelivr 链接, 从而避免在多个脚本或界面中重复打包它们.

部分第三方库用 jsDelivr 的 cdn 链接导入可能无法使用, 则可以尝试在 webpack.config.ts 最后的 cdn 变量中为它指定可用的 cdn 链接.

前端界面正则的推荐写法#

提高正则容错率#

你的前端界面可能对应于 AI 很复杂的输出格式, 但这些格式应该由前端界面的代码通过 getChatMessages 获取楼层消息内容后处理, 而不应该由酒馆正则处理.

前端界面正则唯一要做的是定位前端界面应该在的位置, 而不是处理输出数据.

例如, 如果你的输出格式是: (并不是说我推荐这种格式)

<status>
心情:→(平静通话)
衣着:居家长T恤
角色阶段:暗中观察
位置:自己房间
行动:结束与user通话
下一步行动:准备就寝
身体:及腰银发散落,深紫色的眼眸中带着一丝遗憾,白皙的肌肤在月光下泛着珍珠般的光泽,散发着淡淡的茶香
内心:心爱找他有什么事呢...不知道他会不会陪我过生日...
求签问卦:月下逢君意/花开并蒂时/缘定三生后
</status>

前端界面正则只需要定位这一段文本即可:

脚本名称: [界面]状态栏
查找正则表达式: <status>.*</status> # 里面具体是什么不由正则在意
替换为: 前端界面的 html 代码块
作用范围:
  - [x] 用户输入
  - [x] AI输出
短暂:
  - [x] 仅格式显示
  - [ ] 仅格式提示词

避免渲染卡顿#

酒馆助手需要酒馆正则先将内容替换成代码块再进行渲染. 当代码内容过多时, 代码块本身的显示就会很卡.

为此像下文 "发布会自动更新的前端界面或脚本" 那样, 将 html 转换为链接而正则代码块里只加载链接. 这样酒馆需要渲染的代码块内容没多少, 自然不会卡顿.

使用 Vue 编写前端界面#

酒馆助手前端界面和脚本可以直接使用 Vue, 它会让数据显示变得更为简单. src/界面示例 就是这么做的.

除了 vue 之外, 我还配置了 gsappiniavue-routervueuse 等库, 让你无论是实现打字机效果、做可调整元素顺序并及时保存结果的列表、给设置做浏览器缓存、让界面全屏、发送系统通知等等都异常简单. AI 很会, 拷打 AI.

你可以安装 Vue.js devtools, 从而在 F12 所打开的开发者工具中新增 vue 选项卡, 得到 vue 更多调试功能.

与外部应用程序通信#

酒馆助手脚本可以安装和使用 socket.io-client 乃至所有浏览器环境支持的第三方库, 从而和外部应用程序进行通信. 实时编写角色卡、世界书或预设 (StageDog/tavern_sync) 就是如此实现的.

如果你要传输数据, 请注意调整服务器端的 maxHttpBufferSize 参数, 它默认仅为 1mb.

执行酒馆的 /STScript 命令#

你可以通过 triggerSlashSillyTavern.executeSlashCommandsWithOptions 来执行酒馆的 STScript 命令, 如触发 AI 回复 (/trigger)、刷新网页 (/reload-page) 等. 编写模板已经在 slash_command.txt 中提供了命令列表给 AI; 如果需要人工查询, 请使用命令手册

在脚本中用 jquery 修改页面元素#

在脚本中, 你可以用 jquery 来修改页面元素. 酒馆助手内置库中的预设条目更多按钮预设防误触等脚本都是通过这种方式来实现的:

预设防误触#
function lock_inputs(enable: boolean) {
  // 用 jquery 访问酒馆页面元素, 让它们不能被修改
  $('#range_block_openai :input').prop('disabled', enable);
  $('#openai_settings > div:first-child :input').prop('disabled', enable);
  $('#stream_toggle').prop('disabled', false);
  $('#openai_show_thoughts').prop('disabled', false);
}

$(() => {
  // 启用脚本时锁定预设设置, 防止误触预设
  lock_inputs(true);
});

$(window).on('pagehide', () => {
  // 卸载脚本时解锁预设设置, 允许修改预设
  lock_inputs(false)
});

甚至, 你可以不使用酒馆助手的前端界面渲染方式, 而是自己用 jquery 将代码块替换为要渲染的 jquery/vue 界面. 输入助手、文生图、行动选择框、压缩相邻消息等脚本就是如此:

替换第 0 楼中含 <galgame> 的代码块为 galgame 界面#
const $galgame = extract_galgame_element();

const $mes_text = retrieveDisplayedMessage(0);
$mes_text.find('pre:contains("<galgame>")').replaceWith($galgame);

在脚本中对发送出的提示词进行修改#

除了通过 jquery 修改页面元素, 脚本也可以监听酒馆的提示词发送事件, 进而自行修改提示词.

要做到这一点, 我们可以监听的事件有很多, 如 (按请求生成时事件发生先后顺序) tavern_events.GENERATE_AFTER_COMBINE_PROMPTStavern_events.GENERATE_AFTER_DATA (提示词模板、酒馆助手宏发生在这里)、tavern_events.CHAT_COMPLETION_PROMPT_READYtavern_events.CHAT_COMPLETION_SETTINGS_READY.

此处以 tavern_events.CHAT_COMPLETION_PROMPT_READY 为例:

eventOn(
  tavern_events.CHAT_COMPLETION_PROMPT_READY,
  async (event_data: Parameters<ListenerType['chat_completion_prompt_ready']>[0]) => {
    // 移除非 user 提示词
    assignInplace(
      event_data.messages,
      event_data.messages.filter(message => message.role === 'user'),
    );
  },
);

function assignInplace<T>(destination: T[], new_array: T[]): T[] {
  destination.length = 0;
  destination.push(...new_array);
  return destination;
}

noass 等压缩相邻消息、合并消息的功能就是这么做的, 例如压缩相邻消息.

注意修改方式

不要用 event_data.chat = event_data.chat.map(message => ...) 之类的方法, 这是让 event_data.messages 指向一个新数组, 而不是修改 event_data.messages 原本指向的数组.

你应该使用:

  • lodash 中的某些原地修改函数

  • 数组的 splicepush 等函数

  • 上面代码中给出的 assignInplace 函数

在脚本中控制世界书条目的激活#

见于酒馆如何处理世界书.

流式传输、同层前端界面#

简单方案#

要简单做流式传输, 我们使用酒馆助手的前端界面, 在其中设计发送按钮来触发酒馆助手的generate函数.

也就是说:

  • 玩家只在一个前端界面内进行游玩

  • 对于玩家的输入, 我们通过前端助手的 generategenerateRaw 命令自行要求 ai 回复, 并监听 iframe_events.STREAM_TOKEN_RECEIVED_FULLYiframe_events.STREAM_TOKEN_RECEIVED_INCREMENTALLY 事件来获取流式传输文本

  • 为了记录剧情, 我个人建议将前端界面正则调成最大深度 0, 然后用 setChatMessagescreateChatMessagesdeleteChatMessages 等函数的 { refresh: 'none' } 参数来修改消息楼层但不刷新显示. 由于不会刷新显示, 玩家可以继续游玩你的界面, 但剧情记录等又能直接利用酒馆的消息楼层功能, 你不需要写一堆额外的代码来制作记录剧情功能.

更自由的办法#

前面提到我们可以在酒馆助手脚本中用 jquery 修改页面元素, 那么我们完全可以把酒馆的楼层显示隐藏起来, 自己在它原本的位置制作一个楼层显示:

  • 监听 tavern_events.MESSAGE_SEND -> message_id, 我们可以知道玩家要求酒馆调用 ai 生成;

  • 监听 tavern_events.STREAM_TOKEN_RECEIVED -> text, 我们可以获取流式传输文本;

  • 监听 tavern_events.MESSAGE_RECEIVED -> message_id, 我们可以知道酒馆结束了回复.

这样一来, 我们是自己获取流式传输文本, 自己控制该怎么显示界面.

例如, 假设我们让 AI 以 YAML 的形式发送对话, 流式过程中可能得到:

- 角色: 络络
  对话: 杂鱼喵
- 角色: 青空莉
  对话: 杂鱼哦
- 角色: 络络 
  

我们可以在还没收到消息时就显示 Galgame 对话界面, 而只将已经完全发送的消息添加到界面中, 允许玩家翻页阅读.

@hakoyukaya 的流式 Galgame 界面

发布会自动更新的前端界面、脚本或美化样式#

还记得我们是如何实现实时修改前端界面或脚本的吗? 我们利用 Go Live 将本地文件夹转换为了网络链接, 而正则或脚本通过网络链接来加载最新打包内容.

既然这样, 我们完全可以发布一个网络链接给玩家, 从而让玩家永远加载到最新的前端界面或脚本!

提示

如果使用自己的服务器部署链接, 请注意设置 https, 否则使用 https 的云酒馆玩家将不可访问.

最简单的方式是利用 jsDelivr 为 github 文件提供的 CDN 功能. 对于上传在 github.com/组织名/仓库名路径 下的文件, 你可以直接用 https://testingcf.jsdelivr.net/gh/组织名/仓库名/路径 来访问它们.

酒馆助手内置库即采用了这种方法. 例如, 如果你从酒馆助手 ‣ 脚本库 ‣ 内置库中导入标签化, 编辑它就会发现它的代码里仅有一行:

import 'https://testingcf.jsdelivr.net/gh/StageDog/tavern_resource/dist/酒馆助手/标签化/index.js'

同理, 你也可以用 jsDelivr 发布会自动更新的酒馆美化:

@import url("https://testingcf.jsdelivr.net/gh/lolo-desu/lolocard/src/思维链美化/酒馆自带推理块版/暗色.css");

然而, jsDelivr 服务器、jsDelivr 镜像服务器 (为了国内直连) 和玩家的浏览器都会缓存文件, 因此并不是 github 上文件更新后, 这个链接就会立即得到最新的打包结果.

除了等待服务器自行刷新缓存, 你可以通过以下方式来加快缓存刷新:

  • StageDog/tavern_helper_template Use this template 来创建新仓库而不是仅在本地使用模板文件夹, 这个仓库配置了自动工作流, 会自动打包结果并在每次更新时都修改版本号,可以做到 12h 刷新服务器缓存

  • 更新版本号后,在 https://www.testingcf.com/tools/purge 中输入链接, 将 testingcf.jsdelivr 改成 cdn.jsdelivr, 然后点击确认, 能立即刷新 jsDelivr 服务器缓存, 但镜像服务器缓存不会刷新

  • 玩家主动清除浏览器缓存

或者, 你可以暂时换个镜像来访问, 如果这个镜像没有缓存:

testingcf.jsdelivr.net
fastly.jsdelivr.net
gcore.jsdelivr.net

导入文件文本内容#

你可能需要在代码里获取本地 json 等文件内容, 那么可以直接用 import string from './文件?raw' 来将文件内容作为字符串导入.

混淆代码#

无论前端界面还是脚本, 你都可以在 index.ts 中添加一行 // @obfuscate 来指示打包时应该混淆代码.

混淆后的代码依旧能正常使用, 只是代码本身很难辨认. (当然我更建议你将源代码分享出来供人学习, 我添加这个功能只是因为我将预设及破限塞进了代码里, 如果在 github 仓库里直接发布可能会被注意到)

../../../../_images/%E6%B7%B7%E6%B7%86%E4%BB%A3%E7%A0%81.png

混淆后的代码#

不自动打包代码#

如果你是从模板新建仓库的形式使用编写模板, 那么可以在 index.ts 中添加一行 // @no-ci 来指示 CI 工作流不自动打包某个前端界面或脚本.

允许代码分块#

默认情况下, 前端界面或脚本会打包成单个文件, 但如果你以网络链接的形式发布前端界面或脚本, 则可以打包成多个文件块让玩家在游玩中按需加载.

为了允许代码分块, 你需要删去 webpack.config.ts 中的 new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }) 这一句.

自定义 tailwindcss 配置#

tailwindcss 提供了许多组件类, 允许你不总是自定义类然后设置样式:

<p class="content-text">Hello, world!</p>
.content-text {
  font-size: 30px;
  line-height: 36px;
  --tw-font-weight: 500;
  font-weight: 500;
  color: #030712;
}

模板并不强制你用 tailwindcss, 因此没有开启 tailwindcss 很多检查. 如果需要, 你可以自行去 eslint.config.mjs 调整它.

{
  files: ['src/**/*.{html,vue,js,ts}'],  // 调整检查范围
  plugins: {
    'better-tailwindcss': eslintPluginBetterTailwindcss,
  },
  rules: {
    ...eslintPluginBetterTailwindcss.configs['recommended-warn'].rules,
    ...eslintPluginBetterTailwindcss.configs['recommended-error'].rules,

    // class 过长时自动换行
    //   这和 Ctrl + S 所触发的自动格式化软件 Prettier 相冲突, 需要时必须在 html 标签上一行加一句 <!-- prettier-ignore-attribute -->
    'better-tailwindcss/enforce-consistent-line-wrapping': [
      'off',  // 改为 `warn` 或 `error` 则开启
      { printWidth: 120 }
    ],

    // 禁止在 html 中使用未经 tailwindcss 注册的类
    //   由此, 如果写了 tailwindcss 不支持的类, 将会报错
    'better-tailwindcss/no-unregistered-classes': [
      'off',
      { ignore: ['fa-*'] }  // FontAwesome 图标类没有被 tailwindcss 注册, 我们应该让检查忽略它避免报错
    ],
  },
  settings: {
    'better-tailwindcss': {
      entryPoint: 'src/global.css',
      tailwindConfig: 'tailwind.config.js',
    },
  },
},
// 你可以复制上面的 {} 块, 通过改变 `files` 为不同范围设置不同检查