Storvia

小组件

编写可嵌入到深度模式里的辅助小组件

什么是小组件

小组件是一种特殊的沉浸创作模式:它可以嵌入到某个深度模式的游戏(下面称之为主线)里运行。

典型用途:

  • 群聊
  • 私聊
  • 热搜
  • ...

一句话理解边界:

  • 共享主线(读写都作用在主线那一份):角色、物品、关系、世界、玩家、永久记忆、人设。小组件改了它们,等于改了主游戏。
  • 小组件私有沙盒(主线和其他小组件都看不到):剧情设定、场景设定、自定义(custom)属性、存档。
  • 消息比较特殊:所有 topic 的消息存在同一张表里,但按 topic 隔离——小组件默认只读到自己 topic 的消息,需要时可显式去读主线(见下文「读取消息历史」)。

「共享」意味着没有回收站:小组件里创建 / 修改 / 删除角色、编辑人设,都是直接写主线,会立刻影响主游戏,且不会自动回滚。真正私有、互不干扰的只有 custom 属性和存档。

创作流程

  1. 在创作台「创作模式」面板选择「沉浸创作」
  2. 勾选「可作为小组件」开关
  3. 你可以编辑的区块:
    • 世界观及剧情设定:小组件本身的 LLM 设定(人设、行为规则、回复风格等)
    • 场景设定:小组件自己的场景设定,类似于沉浸模式(不同场景下不同行为)
    • 自定义属性:小组件的私有沙盒字段
    • 封面、标题、标签、简介、开幕小剧场等基础信息

小组件能做什么

资源归属能读能写
消息同表、按 topic 隔离传自己的 topic 只看自己;传 storvia_chat_main 读主线;不传 = 全部混返写入自己 topic;显式传 storvia_chat_main 可写入 / 编辑 / 删除主线消息(见下文)
永久记忆共享主线✅✅ 与主线走同一条生成链,同等待遇
人设共享主线✅✅ 写回主线(会改动主游戏)
角色 / 物品 / 关系 / 世界 / 玩家共享主线✅✅ AI 自动更新,写回主线
自定义(custom) 属性小组件私有沙盒✅✅ 落自己沙盒,主线 / 其他组件都看不到
预设世界书(作者写,玩家按条开;每条可选常驻 / 关键词触发)完全分开:小组件只有自己的—创作台里写;玩家在小组件的悬浮球 → 记忆 → 世界书里开关,开启的注入你的生成(常驻的每轮注入、关键词的提到才注入)。主线作者的预设对小组件不可见也不注入
玩家世界书(基础 + 高级)共享主线那一份;基础和每条高级条目各有「小手机生效」勾选,勾了才进小组件—玩家在主线或小组件的记忆面板里都能看到、改到同一份;勾了的按它自己的模式注入你的生成

关键点:

  • 世界书两边默认分开:主线作者写的预设只在主线生效,小组件作者写的只在小组件生效;玩家自己写的世界书(基础 + 高级)只有一份,主线和小组件的记忆面板里看到的是同一份;基础和每条高级条目只有玩家勾了「小手机生效」才进小组件。玩家在哪一边开的预设,只影响那一边的生成。这是刻意的——玩家为主线剧情写的规则默认不该串进你的小组件,要串就让玩家自己勾。
  • 共享主线的属性(角色 / 物品 / 关系 / 世界 / 玩家 / 记忆 / 人设)用的都是主线那一份,小组件里读到的、写回去的都是主线的数据——小组件不能为它们另起一套平行 schema(创作台发布时会自动剔除)。
  • custom 属性是小组件独有的私有沙盒:只有该小组件自己运行时才会注入进 LLM,主线和其他小组件既读不到、也无法覆盖。
  • AI 自动更新指:LLM 生成的正文里夹带的结构化状态变更,会被系统自动解析并落库——共享属性写回主线,custom 写回自己的沙盒。
  • 深度模式主线本身没有 custom(custom 是自由模式专属),所以在深度模式下,custom 就是纯粹属于小组件的字段。

沙盒数据

每次小组件被安装到某主线对话,会给你的小组件开一份私有沙盒,物理位置在 conversations.metadata.widget_sandbox[你的 game_id]。沙盒里存三样东西:

  • custom:自定义属性的当前值
  • custom_inject:各 custom 字段是否注入进 LLM 的开关
  • save_data:小组件的存档数据
  • enabled_presets:玩家开启了你哪几条预设世界书(平台维护,SDK 不读写)

沙盒的生命周期:

  • 用户卸载小组件:沙盒数据保留(重新安装即恢复,不会丢失数据,也不会自动清理数据)
  • 多次安装同一小组件:使用同一份沙盒
  • 沙盒按 game_id 隔离:主线看不到它,其他小组件也各有各的沙盒,互不可见

Topic 命名约定

小组件通过 SDK 调用聊天能力时必须传一个 topic,让自己的消息流与主线(storvia_chat_main)以及其他小组件区分开。

强制格式:<游戏 id>:<组件内部 topic>

  • 前缀 = 游戏 id(game_id),即你这个小组件被发布后由平台分配的唯一标识。平台保证全局唯一,永远不会与别人的小组件撞。
    • 还没发布拿不到 id?可以先把小组件以私有可见性发布一次,发布成功后即可在游戏详情页拿到 game_id,再回来填到 topic 里使用
  • 后缀 = 你小组件内部自己用的 topic 名(自由命名,用来区分自己内部的多条对话流,如不同房间、不同会话)
// 推荐:用 game_id 作为前缀
const topic = `${gameId}:main`;            // 组件主聊天流
const topic = `${gameId}:room-${roomId}`;  // 多房间场景
const topic = `${gameId}:user-${userId}`;  // 多对一私聊

// 不推荐
const topic = "chat";              // 没前缀,必撞主线 / 其他组件
const topic = "widget:chat";       // 前缀不带游戏 id,多个小组件同名会撞

平台不会强制校验 topic 格式,但不按这个规范走,发生跨组件消息污染时责任自负。

读取消息历史(自己 / 主线 / 全部)

小组件通过 chat.messages({ topic, ... }) 拉消息时,topic 参数决定看到的范围:

传值拿到的范围典型用途
${gameId}:xxx(自己的 topic)仅自己这个 topic 的消息小组件正常聊天 / 翻看历史
"storvia_chat_main"主线对话的消息想让小组件「读懂主角现在在干嘛」
其他小组件的 topic该组件的消息一般用不到,不建议跨组件读
不传 / null当前会话下所有 topic 的消息(主线 + 全部组件)极少需要;通常是 bug
// 小组件自己的对话历史
await sdk.chat.messages({ topic: `${gameId}:main`, order: 'asc', limit: 20 });

// 主线对话历史(让群聊 / 私聊 widget 知道主角刚发生了什么)
await sdk.chat.messages({
  topic: 'storvia_chat_main',
  order: 'desc',
  limit: 10,
});

// 全部消息(不传 topic)—— 几乎用不到,注意会拿到所有 widget 的消息流
await sdk.chat.messages({ order: 'desc', limit: 20 });

不传 topic 会把当前 conversation 下所有 topic 的消息混在一起返回(主线 + 你自己 + 其他小组件)。除非确实需要"全景视角",否则永远显式传 topic,避免拿到不该读的内容、上下文错乱。

往主线写入 / 编辑消息(SDK 0.10.6 起)

小组件和主线跑在同一个对话里,只靠 topic 区分,所以小组件不止能读主线,也能直接往主线写消息、改主线上已有的消息。

主线的 topic 是固定值 storvia_chat_main,SDK 导出了同名常量 MAIN_CHAT_TOPIC,不用手写字符串:

import { MAIN_CHAT_TOPIC } from "@storvia/sdk";
// UMD 引入时:window.StorviaSDK.MAIN_CHAT_TOPIC

// 1. 往主线插一条消息(不调 AI、不扣费、不计互动数)
const { id } = await storvia.chat.save({
    role: "assistant",
    content: "📮 一封来自远方的信被塞进了门缝……",
    topic: MAIN_CHAT_TOPIC,
});

// 2. 改主线上已有的一条消息:先拿 uuid,再 edit
const { data } = await storvia.chat.messages({ topic: MAIN_CHAT_TOPIC, order: "desc", limit: 1 });
await storvia.chat.edit({ uuid: data[0].uuid, content: data[0].content.replace("的地", "的得") });

// 3. 也可以删:storvia.chat.delete({ uuid })

// 4. 让主线聊天页刷出来(否则玩家要重进对话才看得到)
storvia.host.refreshMessages();

规则与坑:

  • topic 必须显式传 MAIN_CHAT_TOPIC。不传 topic 的 save() 落的是一个「无 topic」的消息,主线和你的小组件都看不到它;不传 topic 的 send() / generate() 同理,而且它们的历史上下文会把主线 + 所有小组件的消息混在一起。
  • edit() / delete() 按消息 uuid 定位,不看 topic——平台只校验这条消息属于当前玩家的这个对话,不会拦你改主线消息。这是能力也是责任:只改玩家明确预期你会改的内容。
  • 写进主线的消息会进入主线下一轮 AI 的历史上下文。这意味着小组件能「往主角的记忆里塞事实」——用来做剧情联动很强,但别塞与主线设定冲突的内容。
  • save() 进主线的消息不带游戏状态快照,不能作为「回溯」的落点;主线 send() 产生的消息才带。
  • 主线聊天页不会自动刷新。写 / 改 / 删完调一次 storvia.host.refreshMessages()(见下方「通知主线」一节),玩家在聊天页才能立刻看到;不调的话要等重进对话。

只有 topic: MAIN_CHAT_TOPIC 的消息会出现在主线聊天页。往自己 topic 写完再调 refreshMessages(),聊天页当然什么都不会变——这是这组 API 最容易踩的坑。

SDK 调用

聊天、属性(创建、修改、删除)、玩家人设编辑、角色人设(创建、修改、删除)等功能和沉浸模式完全一样调用 SDK 即可。读写 custom 属性会自动落到你自己的沙盒,不需要做任何额外处理。

注意区分写入落点:只有 custom 属性写进小组件私有沙盒。角色、物品、关系、世界、玩家、人设的读写全部作用在主线——在小组件里通过 SDK 建角色 / 改人设 / 删物品,改的是主游戏本身,不是组件私有副本,务必谨慎。

通知主线 / 刷新主线面板 / 刷新主线消息(SDK 0.9.8 起)

小组件跑在主线聊天页「里面」,玩家可以把它最小化回聊天页,让它在后台继续运行。在此之前这条回路是断的:小组件里发生了什么(AI 回完了、道具发下去了)主线毫无反馈,面板也要等下一轮主线剧情才更新。storvia.host 的三个方法补上这条回路:

// 1. 给主线弹一条 toast(仅小组件在后台运行时展示)
storvia.host.notify("📮 你的信件已送达");

// 2. 让主线重新拉取游戏状态、刷新聊天页的游戏面板
storvia.host.refreshPanel(["inventory", "player"]);

// 3. 让主线重新拉取消息列表(往主线写 / 改 / 删了消息之后调,SDK 0.10.6 起)
storvia.host.refreshMessages();

三个方法都是同步的(不用 await),返回 boolean 表示这次是否真的发出去了——被节流拦下、或文本清洗后为空时返回 false。都不消耗花园币。

storvia.host.notify(text)

在主线聊天页弹一条 toast,展示格式为 小组件名 · 你的文本。

  • 只在小组件后台运行时弹:玩家正开着你的小组件时不弹——他就在现场看着,再弹一条只是噪音
  • 没有成功 / 失败图标:这是「消息通知」而不是「结果通知」,给「你的信件已送达」配一个成功 ✓ 反而误导。想表达语气就在文本里放 emoji
  • 文本上限 60 字,超出部分截断;换行与连续空白会被折叠成单个空格(防止多行刷屏)
  • 1 秒最多一条,期间的调用直接丢弃并返回 false

storvia.host.refreshPanel(scope?)

让主线重新拉取一次游戏状态,并刷新聊天页的游戏面板。

scope 可选,取 player / characters / inventory / relations / world 的任意组合,只用来亮对应 tab 的小红点、提示玩家哪里变了(主线恒全量重拉,不传就只刷新、不亮红点)。

  • 10 秒最多一次

storvia.host.refreshMessages()(SDK 0.10.6 起)

让主线聊天页重新拉取一次消息列表。配合往主线写入 / 编辑消息使用:save() 进主线的新消息会追加到聊天页末尾,edit() 过的消息就地换成新内容,delete() 掉的消息从列表里消失。

  • 10 秒最多一次,与 refreshPanel 分开计时——写状态和写消息经常一起做,两个各有各的窗口
  • 主线正在生成回复时这次刷新会被跳过(不打断正在流式输出的气泡),需要的话稍后再调一次
  • 只刷聊天页当前加载的最新一页;更早的历史消息不受影响

面板刷出来的是主线数据。 只有写共享主线的属性(角色 / 玩家 / 物品 / 关系 / 世界)才会体现在主线面板上——它们本来写的就是主线那一份。custom 属性和存档在你的私有沙盒里,永远不会出现在主线面板:先写 custom 再 refreshPanel、然后奇怪面板没变,是这个 API 最常见的踩坑。

典型用法——小组件里发一件道具,玩家不用等下一轮剧情就能在主线面板看到:

await storvia.inventory.add({ name: "青铜钥匙", groupName: "道具" });
storvia.host.notify("🔑 你获得了「青铜钥匙」");
storvia.host.refreshPanel(["inventory"]);

创作台预览里:notify 会降级成小组件内部的 SDK 自带 toast(方便你看文案效果),refreshPanel / refreshMessages 无效果并返回 false——预览没有主线可刷。真实效果要在正式安装到某个游戏后、把小组件最小化才能看到。

notify / refreshPanel 需要 SDK 0.9.8 及以上,refreshMessages 需要 0.10.6 及以上。玩家的网页 / App 版本过老时,这些信号会被静默忽略(不报错,但也不弹、不刷新),所以别把关键玩法建立在「通知一定送达」上——通知只做锦上添花的提示,真正的状态变更该写还是要写进属性里。

共享设置

以下设置不属于小组件,跟着当前主线对话走:

  • 模型选择
  • 温度
  • 回复长度
  • 回复语言

本页目录