Skip to content

角色卡开发者指南

本指南面向角色卡创作者,介绍如何利用 Luker 的扩展能力创建更丰富、更智能的角色卡。Luker 在保持与 SillyTavern 角色卡格式完全兼容的基础上,提供了多项增强功能。

角色卡扩展字段

Luker 使用角色卡 data.extensions 中的多个命名空间存储扩展数据。这些字段不会影响角色卡在 SillyTavern 中的正常使用——不识别的字段会被忽略。

data.extensions 扩展字段结构

json
{
  "data": {
    "extensions": {
      "luker": {
        "memoryGraphSchema": {
          "nodeTypes": [
            { "name": "string", "label": "string", "color": "#hex" }
          ]
        }
      },
      "card_app": {
        "enabled": true,
        "entry": "index.js"
      }
    }
  }
}
字段路径类型说明
luker.memoryGraphSchemaobject记忆图的自定义节点类型 schema
luker.memoryGraphSchema.nodeTypesarray节点类型定义列表
luker.memoryGraphSchema.nodeTypes[].namestring节点类型标识符(英文,用于内部引用)
luker.memoryGraphSchema.nodeTypes[].labelstring节点类型显示名称
luker.memoryGraphSchema.nodeTypes[].colorstring节点颜色(十六进制色值)
card_app.enabledboolean是否启用 CardApp
card_app.entrystringCardApp 文件夹下的入口模块文件名,默认 index.js。入口文件必须导出 init(ctx) 函数。同目录下的 style.css 会按约定自动加载。

NOTE

绑定预设和人设的数据存储在角色卡的状态文件中,不在 data.extensions 内。这样设计是为了避免修改角色卡本身的 JSON 数据。

绑定预设和人设

Luker 支持在角色卡中绑定推荐的生成预设和用户人设。当用户加载角色卡时,可以一键应用创作者推荐的配置,确保最佳的角色扮演体验。

绑定信息存储在角色卡的状态文件中:

  • 推荐预设:指定最适合该角色的生成预设(温度、采样参数等)
  • 推荐人设:指定与角色互动时建议使用的用户人设

这些绑定通过角色卡编辑助手的「绑定预设」面板进行配置。

配置编排工作流

编排器允许角色卡创作者定义复杂的提示词编排工作流。通过编排器,你可以:

  • 定义多步骤的提示词处理管线
  • 在生成前后插入自定义逻辑
  • 根据对话状态动态调整提示词结构

编排工作流可以绑定到角色卡,当用户加载角色卡时自动激活。

自定义记忆图 Schema

记忆图支持角色卡级别的节点类型 schema 自定义。通过在角色卡的扩展数据中定义 luker.memoryGraphSchema,你可以为角色定制专属的记忆结构。

例如,一个奇幻世界的角色卡可以定义如下 schema:

json
{
  "data": {
    "extensions": {
      "luker": {
        "memoryGraphSchema": {
          "nodeTypes": [
            { "name": "character", "label": "角色", "color": "#4A90D9" },
            { "name": "location", "label": "地点", "color": "#7ED321" },
            { "name": "quest", "label": "任务", "color": "#F5A623" },
            { "name": "magic", "label": "魔法", "color": "#BD10E0" },
            { "name": "faction", "label": "阵营", "color": "#D0021B" }
          ]
        }
      }
    }
  }
}

自定义 schema 会覆盖默认的节点类型集合,让记忆图的提取和组织更贴合角色的世界观。

CardApp 开发

CardApp 是 Luker 的角色卡内嵌应用系统,允许你在角色卡中嵌入交互式 JavaScript 应用。

适用场景

  • 状态追踪:好感度、体力值、库存等游戏化元素
  • 互动元素:骰子、卡牌、小游戏
  • 可视化:关系图、时间线、地图
  • 自定义 UI:角色专属的界面组件

应用定义结构

CardApp 定义在角色卡的 data.extensions.card_app 字段中。角色卡里只存开关和入口文件名,实际代码以文件形式存放在 CardApp 的角色级目录(由 Studio 管理):

json
{
  "data": {
    "extensions": {
      "card_app": {
        "enabled": true,
        "entry": "index.js"
      }
    }
  }
}

运行时把 index.js(或 entry 指向的文件)以 ES 模块方式从 /api/card-app/<charId>/<entry> 加载,同目录下若有 style.css 也会自动注入。推荐用 Studio 编辑/保存/版本管理这些文件。

入口函数

CardApp 的入口模块必须导出 init(ctx) 函数,接收上下文对象:

javascript
export async function init(ctx) {
  const container = ctx.container;

  // 读取持久化状态 — getChatState 是异步的(server-backed 状态)
  const state = await ctx.getChatState('my_app');
  render(state);

  // 渲染 UI
  function render(state) {
    const fav = state?.favorability ?? 0;
    container.innerHTML = `
      <div class="fav-panel">
        <h3>好感度</h3>
        <div class="fav-bar">
          <div class="fav-fill" style="width: ${Math.min(fav, 100)}%"></div>
        </div>
        <span>${fav} / 100</span>
      </div>
    `;
  }
}

上下文 API(ctx)

CardApp 的上下文对象提供以下 API:

消息与生成

API说明
ctx.container应用的DOM容器元素
ctx.charId当前角色ID
ctx.sendMessage(text, options?)发送消息(内部调用sendTextareaMessage
ctx.stopGeneration()停止当前生成
ctx.continueGeneration()继续生成
ctx.getHistory(limit?, offset?)获取聊天历史记录,支持限制条数和偏移
ctx.editMessage(messageId, newText)编辑指定消息
ctx.deleteMessage(messageId)删除指定消息
ctx.deleteLastMessage()删除最后一条消息
ctx.swipe()切换到下一个回复变体
ctx.regenerate()重新生成最后一条AI消息(调用Generate('regenerate')

数据与状态

API说明
ctx.getCharacterData()获取当前角色数据(只读)
ctx.updateCharacterFields(fields)更新角色字段并保存。支持name、description、personality、scenario、first_mes、mes_example、system_prompt、post_history_instructions、creator_notes、creator、character_version、tags(逗号分隔字符串)、talkativeness(数字)、depth_prompt相关字段
ctx.getChatState(namespace, options?)异步 读取聊天绑定的状态(server-backed,/api/chats/state/)。HP / 金币 / 好感度 / 物品 / 任务标志这类用聊天变量。
ctx.updateChatState(namespace, updater, options?)异步 reducer 风格写入聊天状态,返回 { ok, state, updated }
ctx.patchChatState(namespace, operations, options?)异步 对聊天状态应用 JSON-patch 操作。
ctx.deleteChatState(namespace, options?)异步 删除一个聊天状态命名空间。
ctx.getCharacterState(namespace)异步 读取角色绑定的状态(avatar 自动绑定),跨该角色的所有聊天保留。
ctx.setCharacterState(namespace, data)异步 写入角色绑定的状态(avatar 自动绑定),传 null 表示删除。
ctx.getVariable(key)读取聊天变量(来自 chat_metadata.variables,即 {{getvar::key}} 读的同一个桶)。
ctx.setVariable(key, value, options?)异步 设置聊天变量。默认写入 chat_metadata.variables(会话级,贯穿整个 chat)。传 { floor: <消息序号> } 则改走变量 op-log,把这次写入绑定到该楼的当前 swipe——切 swipe / 切回 / 删楼 / 创建分支都会经过 rebuilder 重放,效果跟 AI 在消息里直接写 {{setvar}} 一致。绑定楼层的路径会把 value 强转成字符串(op-log 的存储格式只承载字符串)。如果需要"结构化的逐楼状态 + 独立命名空间 + 自己的 commit log",改用 ctx.lukerContext.createFloorState({ namespace })

聊天管理

API说明
ctx.getChatList()获取当前角色的聊天列表
ctx.switchChat(chatName)切换到指定聊天
ctx.newChat()创建新聊天
ctx.closeChat()关闭当前聊天

世界书操作

API说明
ctx.getWorldBooks()获取当前角色可见的世界书名称列表(角色主世界书 + 角色附加世界书 + 聊天绑定 + 全局激活,已去重)。传 { withSource: true } 可拿到带 source: 'character' | 'character_aux' | 'chat' | 'global' 的标注列表
ctx.getCharacterAuxWorldBooks()获取当前角色绑定的附加世界书(非主世界书)。这些与主世界书一同参与提示词组装,但通过 Luker 的世界书编辑器单独管理
ctx.getWorldBookEntries(bookName)获取指定世界书的所有条目
ctx.createWorldBookEntry(bookName, fields?)创建世界书条目,返回新条目对象(含 uid)
ctx.updateWorldBookEntry(bookName, uid, patch)更新世界书条目(浅合并)
ctx.deleteWorldBookEntry(bookName, uid)删除世界书条目

事件与生命周期

API说明
ctx.eventSourceLuker 的内部事件总线。订阅用 ctx.eventSource.on(eventName, handler),取消订阅用 ctx.eventSource.off(eventName, handler)。事件名在 ctx.lukerContext.eventTypes 上(CHAT_CHANGEDMESSAGE_DELETEDMESSAGE_SWIPED 等)。每次 .on() 都搭配 ctx.onDispose(() => ctx.eventSource.off(eventName, handler)),CardApp 卸载时监听器才会被移除干净。
ctx.addEventListener(target, event, handler, options?)订阅 DOM 元素上的事件。target 通常是 ctx.containerquerySelector 的返回值;用于容器内的 UI 事件如 click、keydown、scroll。CardApp 卸载时监听器会自动移除。
ctx.setInterval(fn, ms)setInterval 的封装,卸载时句柄自动清理。
ctx.setTimeout(fn, ms)setTimeout 的封装,卸载时句柄自动清理。
ctx.onDispose(fn)注册清理回调,CardApp 卸载(切换聊天、热重载、切换角色)时执行。

示例 —— 聊天切换或当前 swipe 改变时刷新面板:

javascript
export async function init(ctx) {
    const { eventTypes } = ctx.lukerContext;
    const refresh = () => render(ctx);

    ctx.eventSource.on(eventTypes.CHAT_CHANGED, refresh);
    ctx.eventSource.on(eventTypes.MESSAGE_SWIPED, refresh);
    ctx.onDispose(() => {
        ctx.eventSource.off(eventTypes.CHAT_CHANGED, refresh);
        ctx.eventSource.off(eventTypes.MESSAGE_SWIPED, refresh);
    });

    refresh();
}

安全限制

CardApp 运行在受限环境中:

  • ✅ 可以操作 ctx.container 内的 DOM
  • ✅ 可以通过 ctx API 读写聊天状态
  • ✅ 可以发送消息和控制生成
  • ❌ 不应直接操作沙箱外的 DOM
  • ❌ 不应直接发起网络请求
  • ❌ 不应直接访问其他扩展的数据

CardApp Studio

CardApp Studio 是开发 CardApp 的完整环境,提供基于 CodeMirror 6 的代码编辑器、实时预览、AI 辅助和 Git 版本历史。建议通过 Studio 开发 CardApp,而非手动编辑 JSON。

世界书最佳实践

世界书(World Info / Lorebook)是角色卡的重要组成部分。以下是在 Luker 中使用世界书的最佳实践:

1. 合理组织条目

  • 按主题分组:角色背景、世界设定、重要事件等
  • 使用清晰的关键词触发条件
  • 避免条目之间的内容重叠

2. 利用预设绑定世界书

Luker 支持将世界书绑定到预设。当用户切换预设时,关联的世界书会自动激活。这适用于需要不同世界观设定的场景。

3. 搜索工具与世界书集成

搜索工具的搜索代理可以将搜索结果自动写入世界书条目,实现实时信息注入。创作者可以利用这一机制为角色提供实时知识更新能力。

4. 控制注入深度和顺序

合理设置世界书条目的注入深度(depth)和排序(order),确保关键设定在提示词中的位置合理。Luker 的搜索工具提供了 lorebookDepthlorebookRolelorebookEntryOrder 等配置项来精细控制。

5. 与记忆图配合

世界书提供静态的世界设定,记忆图提供动态的对话记忆。两者互补:

  • 世界书:不变的背景设定、角色关系、世界规则
  • 记忆图:对话中产生的事件、情感变化、新发现

角色卡分发注意事项

兼容性

  • data.extensions.lukerdata.extensions.card_app 中的字段在 SillyTavern 中会被忽略,不影响角色卡的正常使用
  • 绑定预设和人设存储在状态文件中,不包含在角色卡文件内,分发时不会携带
  • 编排工作流需要用户手动导入或通过其他方式分发

建议

  • 在角色卡描述中注明推荐使用 Luker 以获得完整体验
  • 如果角色卡依赖 CardApp,说明所需的 Luker 版本
  • 提供不依赖 Luker 扩展功能的基础体验,将 Luker 特性作为增强

相关页面

基于 SillyTavern 构建