tavern-harness 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +161 -0
  2. package/README.zh.md +123 -0
  3. package/bin/cli.js +48 -0
  4. package/index.html +35 -0
  5. package/package.json +50 -0
  6. package/public/favicon.png +0 -0
  7. package/public/tavern-harness-logo.png +0 -0
  8. package/sandbox-server.mjs +997 -0
  9. package/src/App.tsx +268 -0
  10. package/src/assets/tavern-handbook.md +39 -0
  11. package/src/components/AchievementModal.tsx +158 -0
  12. package/src/components/CharactersView.tsx +904 -0
  13. package/src/components/ChatView.tsx +1639 -0
  14. package/src/components/ConfirmationDialog.tsx +56 -0
  15. package/src/components/Dashboard.tsx +180 -0
  16. package/src/components/DeleteConfirmDialog.tsx +48 -0
  17. package/src/components/FileDisplayModal.tsx +556 -0
  18. package/src/components/GameplayDialogs.tsx +191 -0
  19. package/src/components/NewSessionMenu.tsx +362 -0
  20. package/src/components/SettingsView.tsx +517 -0
  21. package/src/components/Sidebar.tsx +392 -0
  22. package/src/components/StatsView.tsx +189 -0
  23. package/src/components/Toasts.tsx +20 -0
  24. package/src/components/WorkspaceFileManagerModal.tsx +216 -0
  25. package/src/components/shared.tsx +369 -0
  26. package/src/core/achievements.ts +85 -0
  27. package/src/core/fileDownload.ts +67 -0
  28. package/src/core/gameplay.ts +521 -0
  29. package/src/core/horizontalWheel.ts +93 -0
  30. package/src/core/i18n.ts +1403 -0
  31. package/src/core/linkedScroll.ts +236 -0
  32. package/src/core/markdown.ts +170 -0
  33. package/src/core/openai.ts +451 -0
  34. package/src/core/openingGreeting.ts +41 -0
  35. package/src/core/prompts.ts +254 -0
  36. package/src/core/proxy.ts +58 -0
  37. package/src/core/sillyTavernImporter.ts +317 -0
  38. package/src/core/stats.ts +91 -0
  39. package/src/core/strings.ts +19 -0
  40. package/src/core/toolDefinitions.ts +400 -0
  41. package/src/core/tools/builtinTools.ts +38 -0
  42. package/src/core/tools/generatedSkillExecutor.ts +706 -0
  43. package/src/core/tools/generatedWorkspace.ts +13 -0
  44. package/src/core/tools/toolExecutor.ts +718 -0
  45. package/src/core/turnLoop.ts +211 -0
  46. package/src/db/database.ts +647 -0
  47. package/src/main.tsx +23 -0
  48. package/src/store/store.ts +2419 -0
  49. package/src/theme/chat.css +1227 -0
  50. package/src/theme/global.css +1377 -0
  51. package/src/theme/theme.ts +143 -0
  52. package/src/theme/views.css +1430 -0
  53. package/src/types/models.ts +364 -0
  54. package/src/vite-env.d.ts +1 -0
  55. package/tsconfig.json +21 -0
  56. package/vite.config.ts +273 -0
package/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # Tavern Harness · 酒馆助手
2
+
3
+ MyAgent-Android(Mioo)的 Web 版复刻——一个本地优先的 AI 助手 / Agent 聊天客户端,跑在你自己的浏览器里。
4
+
5
+ **Tavern Harness** 提供与 Android 版一致的功能:任意 OpenAI 兼容 API、多 Provider 管理、角色卡(NPC)、Lorebook、生成式技能、群聊回合制、多模态附件、定时消息、生涯统计等。所有数据通过 IndexedDB 存储在本地,无需自建服务端。
6
+
7
+ > ⚠️ **数据说明**:全部数据(会话、配置、角色等)保存在浏览器 IndexedDB 中,**与浏览器、端口、站点绑定**。换浏览器(如 Chrome → Safari)、开隐私窗口、或换端口访问会看不到旧数据——这是浏览器安全机制,属于正常现象。如需迁移,可在对话页右上角「分享对话」导出会话 JSON(可自定义保存位置与文件名)。
8
+
9
+ ## ✨ 功能
10
+
11
+ ### 对话
12
+ - 任意 OpenAI 兼容 `chat/completions` 接口,**流式输出**(SSE)
13
+ - **多 Provider 管理**:Base URL / API Key,连通性测试与模型列表拉取
14
+ - **思考模式**:独立展示 thinking 内容(`reasoning` / `reasoning_content` / `thinking_content`),支持 Qwen `chat_template_kwargs` 适配
15
+ - **原生工具调用**:OpenAI `tools` 协议,本地真实执行,`role=tool` 结果回灌,最多 4 层 ReAct 链式调用
16
+ - **多模态附件**:图片(粘贴 / 选择)随消息发送为 data URL
17
+ - **消息管理**:右键编辑历史消息、重新生成回复、查看原始请求/响应日志(自动脱敏)
18
+ - **中断生成**:可随时停止流式输出,已生成部分自动持久化
19
+ - **魔法命令**:`/new` 开始新话题(截断上下文)、`/pass` 跳过本轮发言(群聊)
20
+ - **Markdown 渲染**:代码高亮、表格、行内/块级数学公式(KaTeX)
21
+ - 每条消息记录延迟、token 用量、tokens/s、所用模型等指标
22
+
23
+ ### 会话模式
24
+ - **标准对话(STANDARD)**:与助手一对一
25
+ - **NPC 对话**:与单个角色卡对话,独立人设、开场白、技能配置
26
+ - **群聊(GROUP)**:2~5 位 NPC 同场对话
27
+ - 回合制:PRESET(按座位顺序)/ RANDOM(随机)
28
+ - `@角色名` 直接指定下一个发言者
29
+ - 玩家可扮演角色(User Persona)
30
+
31
+ ### 角色工坊(对标 SillyTavern)
32
+ - **角色卡(NPC)**:人设、开场白、彩色首字母头像 / 选图,编辑页内按角色启用技能(内置角色「酒馆老板」默认启用全部内置技能)
33
+ - **SillyTavern PNG 导入**:chara V2 / ccv3 V3 一键导入,解析人设、开场白、内嵌Lorebook
34
+ - **Lorebook**:世界观设定附加在人设后,可绑定到会话
35
+ - **技能表(工具)**:
36
+ - 内置:`run_shell_script`(受控本地命令执行)、`roll_dice`、`create_skill` / `update_skill` / `delete_skill`、`get_tavern_info`、角色与Lorebook CRUD
37
+ - **生成式技能**:`template` / `http_get` / `javascript`(Web Worker 沙箱,支持 async/await,可注入 `$read`/`$write`/`$append`/`$list` 持久化游戏状态)/ `file_read` / `file_write` / `shell`(真实执行,白名单直执 + 其余命令确认)/ `device_action`(通知 / 震动)
38
+ - **确认门控**:更新/删除类操作、以及所有**非白名单 shell 命令**均弹出确认框
39
+
40
+ ## 🚀 使用
41
+
42
+ ### 直接启动(npm)
43
+
44
+ 需要 Node.js 18 或更高版本。无需克隆仓库或全局安装:
45
+
46
+ ```bash
47
+ npx tavern-harness
48
+ ```
49
+
50
+ 首次运行会下载包、启动本地服务,并自动打开浏览器。可选参数:
51
+
52
+ ```bash
53
+ npx tavern-harness --no-open
54
+ npx tavern-harness --port 5273
55
+ ```
56
+
57
+ 服务仅监听本机。使用 shell 技能时,命令会以启动命令的当前用户权限在本机执行;请仅运行来自可信来源的版本,并审阅每次命令确认弹窗。
58
+
59
+ ### 本地开发
60
+
61
+ ```bash
62
+ npm install
63
+ npm run dev # 开发
64
+ npm run build # 构建
65
+ npm run preview # 预览
66
+ ```
67
+
68
+ 然后在「设置 → 模型服务 Provider」中:
69
+
70
+ 1. 添加 Provider:名称、Base URL、API Key,启用后点击「测试连接」拉取模型列表
71
+ 2. 在聊天页顶部点击模型名选择模型
72
+ 3. 回到对话,开聊!
73
+
74
+ ## 生成式技能 `shell` 的受控本地执行
75
+
76
+ `shell` 技能通过独立的本地命令服务执行**真实本地命令**。开发服务器(`npm run dev`)会在首次收到 `/api-v2/exec` 请求时自动拉起该服务(`sandbox-server.mjs`,默认为本机 `127.0.0.1:17891`)。Vite 代理会为每次运行生成彼此独立的服务密钥和批准密钥并注入内部请求;无密钥的本地直连请求会被拒绝,待确认请求也必须先由专用批准端点授权才能执行。
77
+
78
+ > **这不是 OS 沙箱。** 命令以启动服务的当前用户权限执行,能够使用该用户拥有的文件、进程和网络权限。会话工作目录只作为命令的初始 `cwd`,不是文件系统安全边界。安全控制来自命令白名单和执行前的用户许可。
79
+
80
+ ```bash
81
+ npm start # 或 npm run dev —— 本地命令服务按需自动启动
82
+ ```
83
+
84
+ 如需脱离 Vite 调试服务,必须显式提供仅用于该次调试的服务密钥和批准密钥,并分别在请求的 `X-Command-Service-Token`、`X-Command-Approval-Token` 头中携带:
85
+
86
+ ```bash
87
+ COMMAND_SERVICE_TOKEN=临时随机值 COMMAND_APPROVAL_TOKEN=另一临时随机值 node sandbox-server.mjs
88
+ ```
89
+
90
+ 启用后:
91
+
92
+ - 开发服务器(`npm run dev`)会自动把 `/api-v2/exec` 转发给该服务;已编译产物(`npm run preview` / 静态部署)需在同一站点额外部署该服务(或手动代理)。
93
+ - Vite 代理覆盖内部认证头,浏览器不能自行指定服务密钥;服务不开放跨域访问。
94
+ - **权限模型(命令分级)**:
95
+ - **直接执行命令 → 不需确认**:白名单按能力分类维护:
96
+ - 基础信息:`pwd`、`date`、`whoami`、`uname`、`hostname`、`uptime`、`which`
97
+ - 文件与目录:`ls`、`touch`、`mkdir`、`cp`、`basename`、`dirname`、`du`、`stat`、`file`
98
+ - 文本与结构化数据:`echo`、`printf`、`cat`、`head`、`tail`、`wc`、`uniq`、`grep`、`cut`、`tr`、`diff`、`cmp`、`od`、`xxd`、`hexdump`、`strings`、`tee`、`jq`
99
+ - 系统配置 / 查询:`localectl`、`timedatectl`、`ffprobe`
100
+ - 逻辑 / 数学 / 校验:`true`、`false`、`seq`、`factor`、`bc`、`sha256sum`、`md5sum`、`cksum`、`sum`
101
+ - 解释器、命令启动器、带 exec / 外部程序 / 动态插件能力的工具,以及所有压缩工具均不在白名单中;包括 `node`、`npm`、`npx`、`git`、`env`、`xargs`、`awk`、`find`、`sed`、`tar`、`zip`、`sort`、`openssl`、`calc`、`gzip`、`gunzip`、`xz`、`unzip`,执行前必须取得用户许可。
102
+ - **任何非白名单命令 → 弹窗确认后执行**:服务端先签发绑定脚本与会话、60 秒有效且只能使用一次的确认票据;前端展示整段脚本并在用户批准后携票据重发。裸 `confirmed` 标记无效;拒绝返回 `CANCELLED`。
103
+ - 其余规范:单条命令 5s 超时、输出截断、脚本 ≤ 8000 字符 / ≤ 20 条命令;无 shell 解释器,不支持管道、重定向、变量展开或命令替换。
104
+ - 该服务仅监听 `127.0.0.1`,不对外暴露;未启动时返回明确错误提示。
105
+
106
+ ### 会话隔离的工作目录
107
+
108
+ 每个对话(会话)在创建时都会分配一个**以会话 id 命名的专属工作目录**:`sandbox_workspace/session-<会话id>/`(单层目录,直接位于工作区根下,不再嵌套)。文件工具在这个目录内读写;本地命令以它作为初始 `cwd`:
109
+
110
+ - **创建对话时即预建其工作目录**(沙箱服务运行时真实建目录;未启动则静默跳过,首次工具调用时自动补建);
111
+ - 删除对话时会**一并删除其专属工作目录**(磁盘目录 + 沙箱未启动时写入浏览器虚拟工作区 IndexedDB 的数据一并清理);
112
+ - shell 命令以该目录为 `cwd` 执行;读写路径必须留在当前会话工作目录内(普通会话仅允许通过 `public` 软链读取公共目录,写入仍拒绝);
113
+ - 文件读写 / 文件列表只对该目录可见(无法访问其它会话或外部目录);
114
+ - 浏览器虚拟工作区(沙箱未启动时的回退)同样按会话隔离:`generated_skill_workspace/sessions/session-<会话id>/…`;
115
+ - 通过文件工具维护的不同会话数据互不可见、互不影响;本地命令读写也受同一目录边界约束。
116
+
117
+ 旧会话(在本功能上线前创建)如果没有工作目录字段,会自动回退到 `session-<会话id>` 专属目录,避免共享根目录带来的权限扩大。会话头部副标题会显示当前会话的工作目录名(如 `session-12`)。
118
+
119
+ ### 生成式技能 `file_read` / `file_write` 的真实磁盘工作区
120
+
121
+ 当本地工作区服务运行(开发服务器自动拉起)时,技能的 `file_read` / `file_write` 会读写项目根目录下的 **`sandbox_workspace/`** 真实文件夹(首次写入自动创建,已被 `.gitignore` 忽略)。技能可以:
122
+
123
+ - 读取工作区相对路径文件(普通会话可直接读取 `public/...`,单文件 ≤ 100KB,超出截断)
124
+ - 写入 / 追加工作区相对路径文件(单文件 ≤ 400KB);普通会话对 `public/...` 写入会被直接拒绝
125
+ - 通过 `file_list` 端点列出工作区文件(上限 500 个)
126
+
127
+ > **文件 API 权限边界**:`file_read` / `file_write` 仅允许工作区相对路径(拒绝绝对路径与 `..`);普通会话可读 `public` 软链但不可写;`public` 会话(如酒馆老板)可在公共目录正常读写;`file_list` 只枚举当前会话工作区。
128
+
129
+ ### 本地 / 局域网服务(CORS)
130
+
131
+ 浏览器出于安全会拦截跨域请求。若你的 OpenAI 兼容服务(如局域网内 `http://192.168.x.x:8788/`)**未开放 CORS 头**,请求会自动回退到内置代理:Vite 开发服务器在**服务端**把 `/api/<host:port>/` 转发到目标服务,浏览器同源不再受限(`npm run build` 后的静态部署不包含此代理)。
132
+
133
+ - `测试连接` 与对话请求均会自动尝试直连,CORS 被拦时自动改用代理(仅内网/局域网地址)
134
+ - 代理连通后,Provider 的 Base URL 会被自动改写为同源代理形式并保存,无需手动填写
135
+
136
+ ## 🛠 技术栈
137
+
138
+ - React 18 + TypeScript + Vite
139
+ - Zustand(全局状态)
140
+ - Dexie.js(IndexedDB ORM,对应 Room)
141
+ - KaTeX(数学渲染)
142
+ - marked(Markdown 解析)
143
+ - Web Worker 沙箱(JavaScript 技能隔离执行)
144
+
145
+ ## 📁 结构
146
+
147
+ ```
148
+ src/
149
+ ├── core/ # 领域逻辑(提示词组装 / 回合循环 / OpenAI 客户 / 工具引擎 / 统计)
150
+ │ └── tools/ # 内置工具 + 生成式技能执行器 + 虚拟文件系统
151
+ ├── db/ # Dexie 数据库与种子数据
152
+ ├── store/ # Zustand 全局状态(对应 MainViewModel)
153
+ ├── components/ # React UI
154
+ ├── theme/ # 主题系统(4 主题 × 明暗)
155
+ └── types/ # 领域模型
156
+ ```
157
+
158
+ ## 📝 说明
159
+
160
+ - Web 版将设备专属功能映射为等效实现:震动 → `navigator.vibrate`;通知 → Notification API;shell 命令 → 白名单模拟;本地端口转发 → 不适用(浏览器环境)
161
+ - API Key 只存储在浏览器本地 IndexedDB,不上传任何服务器
package/README.zh.md ADDED
@@ -0,0 +1,123 @@
1
+ # Tavern Harness · AI 原生的角色扮演沙盒
2
+
3
+ [GitHub 仓库](https://github.com/Rannichan/Tavern-Harness) · [产品主页](https://rannichan.github.io/Tavern-Harness/) · [高自由度沙盒](https://rannichan.github.io/Tavern-Harness/#world) · [问题反馈](https://github.com/Rannichan/Tavern-Harness/issues) · [讨论区](https://github.com/Rannichan/Tavern-Harness/discussions) · [Discord 交流](https://discord.gg/kPSWGeaHx) · [技能沙箱设计文档](./notebook/skill-sandbox-design.md)
4
+
5
+ <a id="run"></a>
6
+ ## 1 · 如何运行
7
+
8
+ > 需要本机装有 **Node.js ≥ 18**。Tavern Harness 是一个纯前端 Web 应用 + 可选的本地沙箱服务,无需 Docker、无需数据库服务。
9
+
10
+ ### 方式一:一键启动(推荐)
11
+
12
+ ### 方式一:从源码编译
13
+
14
+ ```bash
15
+ git clone https://github.com/Rannichan/Tavern-Harness.git
16
+ cd Tavern-Harness
17
+ npm install
18
+ npm run dev # 开发服务器默认端口 5173,被占用时自动顺延
19
+ ```
20
+
21
+ ### 首次启动配置模型服务
22
+
23
+ 启动后到「**设置 → 模型服务 Provider**」:
24
+
25
+ 1. **添加 Provider**:名称 + Base URL + API Key —— 支持任意 OpenAI 兼容 `chat/completions` 接口(OpenAI / DeepSeek / Qwen / SiliconFlow / Ollama / LM Studio / 自建 vLLM 均可)
26
+ 2. **测试连接**:自动拉取模型列表。若目标服务未开 CORS 头,会自动经内置同源代理转发(仅限内网 / 局域网地址),成功后 Base URL 自动改写为代理形式保存
27
+ 3. **选模型**:聊天页顶部点击模型名即可,回到对话开聊!
28
+
29
+ ### 数据隐私须知
30
+
31
+ - **全部数据存在浏览器 IndexedDB 里**:会话、角色卡、世界书、技能、统计、API Key,均**不上传任何服务器**。
32
+ - 数据与浏览器、端口、站点绑定:换浏览器 / 开隐身窗 / 换端口看不到旧数据——这是浏览器安全机制,属正常现象。请确保每次在同一个端口启动、并在同一个浏览器打开。
33
+
34
+ <a id="features"></a>
35
+ ## 2 · 产品特色
36
+
37
+ ### 🍺 酒馆老板随时为您服务
38
+
39
+ 无论是构建角色卡这种小事儿,还是设计跑团玩法这种累活儿,再或者你只是不知道从何入手,都可以来找酒馆老板!
40
+
41
+ 我们为酒馆进行了全方位的 AI 装修,让游戏的创建和体验都更加简单。
42
+
43
+ ### 🛠️ 技能系统
44
+
45
+ 所有的角色不再是只能对话的角色,而是**具备技能拓展的智能体**:为每一个角色装备需要的技能,实现更高级的玩法。
46
+
47
+ 酒馆已经提供了一些必要的技能(例如掷骰子);同时你也可以拜托酒馆老板,**创建任意你想要的技能**~
48
+
49
+ ### 💬 多角色群聊
50
+
51
+ 每个房间支持添加**至多 6 个角色**,并提供类似于回合制游戏的行动顺序表,轻松管理多人对话。
52
+
53
+ 支持 **@ 角色** 和**跳过**,模拟真实的多人对话场景——无论是从上帝视角观测角色们的行为,还是亲自代入角色参与其中,总能找到你的玩法。
54
+
55
+ ### 🎁 和朋友分享你的游戏
56
+
57
+ 兼容 **Tavern 生态**,一键导入 PNG 角色卡。
58
+
59
+ 同时支持**游戏一键打包分享**:对方直接导入即可游玩,无需手动创建角色、技能、配置对话。
60
+
61
+ ### 🍃 完全本地 / 简洁干净
62
+
63
+ 目前支持 **OpenAI 格式的 API 接口**:你可以使用你的 token plan,也可以自己本地部署模型,一切数据都保存在本地。
64
+
65
+ 项目不依赖任何外部 harness,UI 也经过了精细打磨,提供简洁干净的体验。
66
+
67
+ <a id="gameplay"></a>
68
+ ## 3 · 玩法示例
69
+
70
+ > 「玩法示例」由浅入深:前三例零配置开箱即玩,后四例逐步解锁沙盒能力。所有例子中的「老板」=内置角色「酒馆老板」,首次启动即存在,默认启用全部内置技能。
71
+
72
+ ### 示例一:深夜陪伴(零门槛)
73
+
74
+ 1. 新建对话 → 选「NPC 对话」→ 角色「酒馆老板」(或任意自定义角色)
75
+ 2. 直接开聊,像跟朋友一样对话;开启**思考模式**还能看到老板的内心盘算
76
+ 3. 长按 / 右键任意消息:编辑历史、重新生成、查看原始日志、Fork 分支
77
+
78
+ ### 示例二:跑团掷骰(角色扮演)
79
+
80
+ 1. 新建「NPC 对话」,角色当 GM:人设写上「你是主持本团的 GM,用 roll_dice 决定命运」
81
+ 2. 开场白抛出剧情钩子;老板会自动调用 `roll_dice`,并在消息里呈现「大成功!/ 大失败!」
82
+ 3. 把团规、世界观写进 Lorebook 绑定到该会话,人设即刻变成一个完整世界
83
+
84
+ ### 示例三:群聊剧本(最多 6 人一台戏)
85
+
86
+ 1. 「角色工坊」里建多个 NPC(如侦探、管家、女主)
87
+ 2. 新建「群聊」对话,添加至多 6 个角色:拖拽设定发言顺序,或开启随机洗牌
88
+ 3. 用 `@角色名` 点名下一个发言者;输入 `/new` 开新话题(截断上下文);输入 `/pass` 跳过自己本轮发言
89
+
90
+ ### 示例四:现场造技能(自造能力)
91
+
92
+ 1. 对老板说:「帮我生成一个技能,输入城市名返回天气」,它会调用 `create_skill` 写出 `template` / `http_get` 实现的新技能
93
+ 2. 新技能默认未启用于任何角色,去「角色工坊 → 技能表」为要用的角色勾选生效
94
+ 3. 技能支持 `$read` / `$write` / `$append` / `$list` 桥接会话工作区,无需重启即时生效
95
+
96
+ ### 示例五:持久化小游戏(沙盒高阶)
97
+
98
+ 1. 让老板写一个 `javascript` 技能实现好感度系统:每轮数值经 `$write` 存进工作目录 `state.json`
99
+ 2. 再写一个读取 `$read` 的技能,用 `file_write` 生成 HTML 仪表盘 `dashboard.html`
100
+ 3. `file_display` 直接弹窗图形化展示数值变化(HTML 沙箱渲染、可缩放、可画中画),工作区内文件均可预览
101
+ 4. 删除会话时磁盘目录 `sandbox_workspace/session-<id>/` 自动一并清理
102
+
103
+ ### 示例六:整理文件(shell)
104
+
105
+ 1. 对话里让老板 `ls` 看工作区、`cat` 读文件、`cp` 归档你的创作文稿
106
+ 2. 读写必须留在本会话工作目录内;白名单外命令(如 `node`、`git`)与 `mv` / `rm` 类修改操作都会先弹窗确认再执行
107
+ 3. 普通会话对公共目录 `public/` 只读——只有内置「酒馆老板」的会话能写入公共区,供所有会话共享素材
108
+
109
+ ### 示例七:携带整个世界(导入导出 / Fork)
110
+
111
+ 1. 一局游戏玩到中期,「导出游戏」勾选是否包含对话历史,得到一个自包含 JSON
112
+ 2. 把 JSON 发给朋友,对方「导入游戏」一键还原你的角色与世界(冲突自动建副本不覆盖)
113
+ 3. 或自己「创建分支 / Fork」:同一段历史分岔出多个平行结局
114
+
115
+ <a id="feedback"></a>
116
+ ## 4 · 交流反馈
117
+
118
+ - 🐛 发现 Bug / 提需求 → [GitHub Issues](https://github.com/Rannichan/Tavern-Harness/issues),欢迎附上导出的游戏 JSON 或浏览器控制台日志(务必注意脱敏 API Key 哦)
119
+ - 🟣 用法讨论 / 玩法灵感 / 分享你的角色卡与技能 / 围观开发进展 → 加入 [Discord 社区](https://discord.gg/kPSWGeaHx)
120
+ - 🤝 欢迎提交 Pull Request:我最喜欢写bug了
121
+ - 📣 想第一时间收到更新,点个 Star / Watch 就好
122
+
123
+ > 酒馆24小时营业,老板随时为您服务!
package/bin/cli.js ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ // bin/cli.js — npx tavern-harness 一键启动,默认自动打开浏览器
3
+ // 用法:
4
+ // tavern-harness 启动并按实际端口自动打开浏览器(默认)
5
+ // tavern-harness --no-open 只启动,不打开浏览器
6
+ // tavern-harness --port 5273 指定端口(5173 被占用时 vite 会自动顺延,
7
+ // 会打开顺延后的实际端口)
8
+ import { fileURLToPath } from 'node:url';
9
+ import { dirname, join } from 'node:path';
10
+ import { createServer } from 'vite';
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const root = join(__dirname, '..'); // 包根目录
14
+ const configFile = join(root, 'vite.config.ts'); // 复用现有配置(cors 代理 / sandbox 懒启动都在里面)
15
+
16
+ // ---- 解析命令行参数 ----
17
+ const args = process.argv.slice(2);
18
+ let open = true; // 默认自动打开浏览器
19
+ let port;
20
+ for (let i = 0; i < args.length; i++) {
21
+ const a = args[i];
22
+ if (a === '--no-open') open = false;
23
+ else if (a === '--open') open = true;
24
+ else if (a === '--port') port = Number(args[++i]);
25
+ else if (a.startsWith('--port=')) port = Number(a.slice('--port='.length));
26
+ }
27
+ const serverOptions = { open };
28
+ if (Number.isInteger(port) && port > 0 && port < 65536) serverOptions.port = port;
29
+
30
+ try {
31
+ // inlineConfig 与 vite.config.ts 合并(inline 优先),open 会覆盖配置文件里的 open:false
32
+ const server = await createServer({
33
+ root,
34
+ configFile,
35
+ server: serverOptions,
36
+ });
37
+ await server.listen();
38
+ server.printUrls();
39
+ console.log('\nTavern Harness 已启动,浏览器应已自动打开(未打开可手动访问上面 Local 地址)。按 Ctrl+C 停止。');
40
+
41
+ process.on('SIGINT', async () => {
42
+ await server.close();
43
+ process.exit(0);
44
+ });
45
+ } catch (err) {
46
+ console.error('启动失败:', err);
47
+ process.exit(1);
48
+ }
package/index.html ADDED
@@ -0,0 +1,35 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/png" href="/favicon.png" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <meta name="theme-color" content="#17120A" />
8
+ <script>
9
+ (() => {
10
+ try {
11
+ const mode = localStorage.getItem('tav-theme-mode');
12
+ const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
13
+ const dark = mode === 'dark' || (mode !== 'light' && systemDark);
14
+ const root = document.documentElement;
15
+ const background = dark ? '#20160E' : '#F7F2E9';
16
+ root.dataset.theme = dark ? 'dark' : 'light';
17
+ root.style.colorScheme = dark ? 'dark' : 'light';
18
+ root.style.backgroundColor = background;
19
+ root.style.setProperty('--bg', background);
20
+ root.style.setProperty('--surface', dark ? '#211A10' : '#FDFBF5');
21
+ root.style.setProperty('--text', dark ? '#F2EADC' : '#241c10');
22
+ root.style.setProperty('--text-dim', dark ? '#AA9C86' : '#7a6a50');
23
+ document.querySelector('meta[name="theme-color"]').content = background;
24
+ } catch {
25
+ // Use the CSS fallback when browser storage is unavailable.
26
+ }
27
+ })();
28
+ </script>
29
+ <title>Tavern Harness · 酒馆助手</title>
30
+ </head>
31
+ <body>
32
+ <div id="root"></div>
33
+ <script type="module" src="/src/main.tsx"></script>
34
+ </body>
35
+ </html>
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "tavern-harness",
3
+ "version": "0.1.0",
4
+ "description": "Tavern Harness — 酒馆模式下的本地 AI Agent 助手(MyAgent-Android 的 Web 复刻)",
5
+ "private": false,
6
+ "type": "module",
7
+ "bin": {
8
+ "tavern-harness": "bin/cli.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "src",
13
+ "public",
14
+ "index.html",
15
+ "vite.config.ts",
16
+ "sandbox-server.mjs",
17
+ "tsconfig.json",
18
+ "README.md"
19
+ ],
20
+ "engines": {
21
+ "node": ">=18.0.0"
22
+ },
23
+ "scripts": {
24
+ "start": "vite",
25
+ "dev": "vite",
26
+ "test:command-service": "node sandbox-server.test.mjs",
27
+ "build": "tsc --noEmit && vite build",
28
+ "preview": "vite preview"
29
+ },
30
+ "dependencies": {
31
+ "@dnd-kit/core": "^6.3.1",
32
+ "@dnd-kit/sortable": "^10.0.0",
33
+ "@dnd-kit/utilities": "^3.2.2",
34
+ "@vitejs/plugin-react": "^4.7.0",
35
+ "dexie": "^4.0.8",
36
+ "dompurify": "^3.4.15",
37
+ "katex": "^0.16.11",
38
+ "marked": "^12.0.2",
39
+ "react": "^18.3.1",
40
+ "react-dom": "^18.3.1",
41
+ "vite": "^5.4.21",
42
+ "zustand": "^4.5.5"
43
+ },
44
+ "devDependencies": {
45
+ "@types/katex": "^0.16.7",
46
+ "@types/react": "^18.3.5",
47
+ "@types/react-dom": "^18.3.0",
48
+ "typescript": "^5.5.4"
49
+ }
50
+ }
Binary file
Binary file